Abhishek Gola commited on
Commit
c741d15
·
1 Parent(s): fafc849

Added face image quality assessment to opencv spaces

Browse files
Files changed (5) hide show
  1. README.md +7 -0
  2. app.py +92 -0
  3. ediffiqa.py +45 -0
  4. requirements.txt +4 -0
  5. yunet.py +55 -0
README.md CHANGED
@@ -7,6 +7,13 @@ sdk: gradio
7
  sdk_version: 5.34.2
8
  app_file: app.py
9
  pinned: false
 
 
 
 
 
 
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
7
  sdk_version: 5.34.2
8
  app_file: app.py
9
  pinned: false
10
+ short_description: Face image quality assessment with ediffiqa using OpenCV
11
+ tags:
12
+ - opencv
13
+ - face-image-quality
14
+ - image-quality-assessment
15
+ - ediffiqa
16
+ - yunet
17
  ---
18
 
19
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2 as cv
2
+ import numpy as np
3
+ import gradio as gr
4
+ from huggingface_hub import hf_hub_download
5
+ from yunet import YuNet
6
+ from ediffiqa import eDifFIQA
7
+
8
+ # Download face detection model (YuNet)
9
+ model_path_yunet = hf_hub_download(
10
+ repo_id="opencv/face_detection_yunet",
11
+ filename="face_detection_yunet_2023mar.onnx"
12
+ )
13
+
14
+ # Download face quality assessment model (eDifFIQA Tiny)
15
+ model_path_quality = hf_hub_download(
16
+ repo_id="opencv/face_image_quality_assessment_ediffiqa",
17
+ filename="ediffiqa_tiny_jun2024.onnx"
18
+ )
19
+
20
+ # Backend and target
21
+ backend_id = cv.dnn.DNN_BACKEND_OPENCV
22
+ target_id = cv.dnn.DNN_TARGET_CPU
23
+
24
+ # Initialize YuNet for face detection
25
+ face_detector = YuNet(
26
+ modelPath=model_path_yunet,
27
+ inputSize=[320, 320],
28
+ confThreshold=0.9,
29
+ nmsThreshold=0.3,
30
+ topK=5000,
31
+ backendId=backend_id,
32
+ targetId=target_id
33
+ )
34
+
35
+ # Initialize eDifFIQA for quality assessment
36
+ quality_model = eDifFIQA(
37
+ modelPath=model_path_quality,
38
+ inputSize=[112, 112]
39
+ )
40
+ quality_model.setBackendAndTarget(
41
+ backendId=backend_id,
42
+ targetId=target_id
43
+ )
44
+
45
+ REFERENCE_FACIAL_POINTS = np.array([
46
+ [38.2946 , 51.6963 ],
47
+ [73.5318 , 51.5014 ],
48
+ [56.0252 , 71.7366 ],
49
+ [41.5493 , 92.3655 ],
50
+ [70.729904, 92.2041 ]
51
+ ], dtype=np.float32)
52
+
53
+ def align_image(image, detection_data):
54
+ src_pts = np.float32(detection_data[0][4:-1]).reshape(5, 2)
55
+ tfm, _ = cv.estimateAffinePartial2D(src_pts, REFERENCE_FACIAL_POINTS, method=cv.LMEDS)
56
+ face_img = cv.warpAffine(image, tfm, (112, 112))
57
+ return face_img
58
+
59
+ def assess_face_quality(input_image):
60
+ bgr_image = cv.cvtColor(input_image, cv.COLOR_RGB2BGR)
61
+ h, w, _ = bgr_image.shape
62
+
63
+ face_detector.setInputSize([w, h])
64
+ detections = face_detector.infer(bgr_image)
65
+
66
+ if detections is None or len(detections) == 0:
67
+ return "No face detected.", input_image
68
+
69
+ aligned_face = align_image(bgr_image, detections)
70
+ score = np.squeeze(quality_model.infer(aligned_face)).item()
71
+
72
+ output_image = aligned_face.copy()
73
+ cv.putText(output_image, f"{score:.3f}", (0, 20), cv.FONT_HERSHEY_DUPLEX, 0.8, (0, 0, 255), 2)
74
+ output_image = cv.cvtColor(output_image, cv.COLOR_BGR2RGB)
75
+
76
+ return f"Quality Score: {score:.3f}", output_image
77
+
78
+ # Gradio Interface
79
+ demo = gr.Interface(
80
+ fn=assess_face_quality,
81
+ inputs=gr.Image(type="numpy", label="Upload Face Image"),
82
+ outputs=[
83
+ gr.Text(label="Quality Score"),
84
+ gr.Image(type="numpy", label="Aligned Face with Score")
85
+ ],
86
+ title="Face Image Quality Assessment (eDifFIQA + YuNet)",
87
+ allow_flagging="never",
88
+ description="Upload a face image. The app detects and aligns the face, then evaluates image quality using the eDifFIQA model."
89
+ )
90
+
91
+ if __name__ == "__main__":
92
+ demo.launch()
ediffiqa.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is part of OpenCV Zoo project.
2
+ # It is subject to the license terms in the LICENSE file found in the same directory.
3
+
4
+ import numpy as np
5
+ import cv2 as cv
6
+
7
+
8
+ class eDifFIQA:
9
+
10
+ def __init__(self, modelPath, inputSize=[112, 112]):
11
+ self.modelPath = modelPath
12
+ self.inputSize = tuple(inputSize) # [w, h]
13
+
14
+ self.model = cv.dnn.readNetFromONNX(self.modelPath)
15
+
16
+ @property
17
+ def name(self):
18
+ return self.__class__.__name__
19
+
20
+ def setBackendAndTarget(self, backendId, targetId):
21
+ self._backendId = backendId
22
+ self._targetId = targetId
23
+ self.model.setPreferableBackend(self._backendId)
24
+ self.model.setPreferableTarget(self._targetId)
25
+
26
+ def infer(self, image):
27
+ # Preprocess image
28
+ image = self._preprocess(image)
29
+ # Forward
30
+ self.model.setInput(image)
31
+ quality_score = self.model.forward()
32
+
33
+ return quality_score
34
+
35
+ def _preprocess(self, image: cv.Mat):
36
+ # Change image from BGR to RGB
37
+ image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
38
+ # Resize to (112, 112)
39
+ image = cv.resize(image, self.inputSize)
40
+ # Scale to [0, 1] and normalize by mean=0.5, std=0.5
41
+ image = ((image / 255) - 0.5) / 0.5
42
+ # Move channel axis
43
+ image = np.moveaxis(image[None, ...], -1, 1)
44
+
45
+ return image
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ opencv-python
2
+ gradio
3
+ numpy
4
+ huggingface_hub
yunet.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is part of OpenCV Zoo project.
2
+ # It is subject to the license terms in the LICENSE file found in the same directory.
3
+ #
4
+ # Copyright (C) 2021, Shenzhen Institute of Artificial Intelligence and Robotics for Society, all rights reserved.
5
+ # Third party copyrights are property of their respective owners.
6
+
7
+ from itertools import product
8
+
9
+ import numpy as np
10
+ import cv2 as cv
11
+
12
+ class YuNet:
13
+ def __init__(self, modelPath, inputSize=[320, 320], confThreshold=0.6, nmsThreshold=0.3, topK=5000, backendId=0, targetId=0):
14
+ self._modelPath = modelPath
15
+ self._inputSize = tuple(inputSize) # [w, h]
16
+ self._confThreshold = confThreshold
17
+ self._nmsThreshold = nmsThreshold
18
+ self._topK = topK
19
+ self._backendId = backendId
20
+ self._targetId = targetId
21
+
22
+ self._model = cv.FaceDetectorYN.create(
23
+ model=self._modelPath,
24
+ config="",
25
+ input_size=self._inputSize,
26
+ score_threshold=self._confThreshold,
27
+ nms_threshold=self._nmsThreshold,
28
+ top_k=self._topK,
29
+ backend_id=self._backendId,
30
+ target_id=self._targetId)
31
+
32
+ @property
33
+ def name(self):
34
+ return self.__class__.__name__
35
+
36
+ def setBackendAndTarget(self, backendId, targetId):
37
+ self._backendId = backendId
38
+ self._targetId = targetId
39
+ self._model = cv.FaceDetectorYN.create(
40
+ model=self._modelPath,
41
+ config="",
42
+ input_size=self._inputSize,
43
+ score_threshold=self._confThreshold,
44
+ nms_threshold=self._nmsThreshold,
45
+ top_k=self._topK,
46
+ backend_id=self._backendId,
47
+ target_id=self._targetId)
48
+
49
+ def setInputSize(self, input_size):
50
+ self._model.setInputSize(tuple(input_size))
51
+
52
+ def infer(self, image):
53
+ # Forward
54
+ faces = self._model.detect(image)
55
+ return np.empty(shape=(0, 5)) if faces[1] is None else faces[1]