paresh95 commited on
Commit
59ece0e
1 Parent(s): 3334bb8

PS|Modularise symmetry code

Browse files
Files changed (5) hide show
  1. app.py +9 -5
  2. parameters.yml +3 -0
  3. requirements.txt +1 -1
  4. utils/face_symmetry.py +142 -5
  5. utils/face_texture.py +30 -21
app.py CHANGED
@@ -1,13 +1,17 @@
1
  import gradio as gr
2
  from utils.face_texture import GetFaceTexture
 
3
 
4
  iface = gr.Interface(
5
  fn=GetFaceTexture().main,
6
  inputs=gr.inputs.Image(type="pil"),
7
- outputs=[gr.outputs.Image(type="pil"),
8
- gr.outputs.Image(type="pil"),
9
- "text"
10
- ]
11
  )
12
 
13
- iface.launch()
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from utils.face_texture import GetFaceTexture
3
+ from utils.face_symmetry import GetFaceSymmetry
4
 
5
  iface = gr.Interface(
6
  fn=GetFaceTexture().main,
7
  inputs=gr.inputs.Image(type="pil"),
8
+ outputs=[gr.outputs.Image(type="pil"), gr.outputs.Image(type="pil"), "text"],
 
 
 
9
  )
10
 
11
+ iface = gr.Interface(
12
+ fn=GetFaceSymmetry().main,
13
+ inputs=gr.inputs.Image(type="pil"),
14
+ outputs=[gr.outputs.Image(type="pil"), "text"],
15
+ )
16
+
17
+ iface.launch()
parameters.yml CHANGED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ face_detection:
2
+ prototxt: "models/face_detection/deploy.prototxt.txt"
3
+ model: "models/face_detection/res10_300x300_ssd_iter_140000.caffemodel"
requirements.txt CHANGED
@@ -4,4 +4,4 @@ scikit-image==0.21.0
4
  dlib==19.24.2
5
  imutils==0.5.4
6
  pillow==9.4.0
7
- pyyaml==6.0
 
4
  dlib==19.24.2
5
  imutils==0.5.4
6
  pillow==9.4.0
7
+ pyyaml==6.0
utils/face_symmetry.py CHANGED
@@ -1,5 +1,142 @@
1
- #TODO: create YAML file to point towards static parameters
2
- #TODO: Test main output and app
3
- #TODO: Consider using other method for face detector - this one not as reliable
4
- #TODO: Text output showing other examples - celeb, child, gender
5
- #TODO: Move notebooks here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from utils.cv_utils import get_image
4
+ from typing import Tuple, List, Union
5
+ from skimage.metrics import structural_similarity as ssim
6
+ from scipy.spatial import distance
7
+ from sklearn.metrics import mean_squared_error, mean_absolute_error
8
+ from PIL import Image as PILImage
9
+ import yaml
10
+
11
+ with open("parameters.yml", "r") as stream:
12
+ try:
13
+ parameters = yaml.safe_load(stream)
14
+ except yaml.YAMLError as exc:
15
+ print(exc)
16
+
17
+
18
+ class GetFaceSymmetry:
19
+ def __init__(self):
20
+ pass
21
+
22
+ def get_faces(self, image: np.array) -> np.array:
23
+ self.h, self.w = image.shape[:2]
24
+ blob = cv2.dnn.blobFromImage(image=image, scalefactor=1.0, size=(300, 300))
25
+ net = cv2.dnn.readNetFromCaffe(
26
+ parameters["face_detection"]["prototxt"],
27
+ parameters["face_detection"]["model"],
28
+ )
29
+ net.setInput(blob)
30
+ detections = net.forward()
31
+ return detections
32
+
33
+ @staticmethod
34
+ def postprocess_face(face: np.array) -> np.array:
35
+ face = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
36
+ face = cv2.equalizeHist(face) # remove illumination
37
+ face = cv2.GaussianBlur(face, (5, 5), 0) # remove noise
38
+ return face
39
+
40
+ @staticmethod
41
+ def get_face_halves(face: np.array) -> Tuple:
42
+ mid = face.shape[1] // 2
43
+ left_half = face[:, :mid]
44
+ right_half = face[:, mid:]
45
+ right_half = cv2.resize(right_half, (left_half.shape[1], left_half.shape[0]))
46
+ right_half = cv2.flip(right_half, 1)
47
+ return left_half, right_half
48
+
49
+ @staticmethod
50
+ def histogram_performance(
51
+ left_half: np.array, right_half: np.array
52
+ ) -> List[Union[float, float, float, float]]:
53
+ hist_left = cv2.calcHist([left_half], [0], None, [256], [0, 256])
54
+ hist_right = cv2.calcHist([right_half], [0], None, [256], [0, 256])
55
+
56
+ # Normalize histograms
57
+ hist_left /= hist_left.sum()
58
+ hist_right /= hist_right.sum()
59
+ correlation = cv2.compareHist(hist_left, hist_right, cv2.HISTCMP_CORREL)
60
+ chi_square = cv2.compareHist(hist_left, hist_right, cv2.HISTCMP_CHISQR)
61
+ intersection = cv2.compareHist(hist_left, hist_right, cv2.HISTCMP_INTERSECT)
62
+ bhattacharyya = cv2.compareHist(
63
+ hist_left, hist_right, cv2.HISTCMP_BHATTACHARYYA
64
+ )
65
+
66
+ return correlation, chi_square, intersection, bhattacharyya
67
+
68
+ @staticmethod
69
+ def orb_detector(left_half: np.array, right_half: np.array) -> int:
70
+ """The fewer the matches (or the greater the average distance), the more dissimilar the images"""
71
+
72
+ orb = cv2.ORB_create()
73
+ keypoints_left, descriptors_left = orb.detectAndCompute(left_half, None)
74
+ keypoints_right, descriptors_right = orb.detectAndCompute(right_half, None)
75
+ bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
76
+ matches = bf.match(descriptors_left, descriptors_right)
77
+ matches = sorted(matches, key=lambda x: x.distance)
78
+ return len(matches)
79
+
80
+ def get_face_similarity_results(
81
+ self, left_half: np.array, right_half: np.array
82
+ ) -> dict:
83
+ structural_similarity, _ = ssim(left_half, right_half, full=True)
84
+ cosine_distance = distance.cosine(left_half.ravel(), right_half.ravel())
85
+ mse = mean_squared_error(left_half, right_half)
86
+ mae = mean_absolute_error(left_half, right_half)
87
+ (
88
+ correlation,
89
+ chi_square,
90
+ intersection,
91
+ bhattacharyya,
92
+ ) = self.histogram_performance(left_half, right_half)
93
+ matches = self.orb_detector(left_half, right_half)
94
+ pixel_difference = np.sum((left_half - right_half) ** 2)
95
+
96
+ d = {
97
+ "structural_similarity": structural_similarity,
98
+ "cosine_distance": cosine_distance,
99
+ "mse": mse,
100
+ "mae": mae,
101
+ "histogram_correlation": correlation,
102
+ "histogram_intersection": intersection,
103
+ "orb_detector_matches": matches,
104
+ "pixel_difference": pixel_difference,
105
+ }
106
+ return d
107
+
108
+ def main(self, image_input):
109
+ image = get_image(image_input)
110
+ detections = self.get_faces(image)
111
+ lowest_mse = float("inf")
112
+ best_face_data, best_left_half, best_right_half = None, None, None
113
+ for i in range(0, detections.shape[2]):
114
+ confidence = detections[0, 0, i, 2]
115
+ if confidence > 0.99:
116
+ box = detections[0, 0, i, 3:7] * np.array(
117
+ [self.w, self.h, self.w, self.h]
118
+ )
119
+ (startX, startY, endX, endY) = box.astype("int")
120
+ face = image[startY:endY, startX:endX]
121
+ face = self.postprocess_face(face)
122
+ left_half, right_half = self.get_face_halves(face)
123
+ d = self.get_face_similarity_results(left_half, right_half)
124
+
125
+ if d["mse"] < lowest_mse:
126
+ best_face_data, best_left_half, best_right_half = (
127
+ d,
128
+ left_half,
129
+ right_half,
130
+ )
131
+ lowest_mse = d["mse"]
132
+
133
+ full_face = np.hstack((best_left_half, best_right_half))
134
+ full_face = PILImage.fromarray(full_face)
135
+
136
+ return full_face, best_face_data
137
+
138
+
139
+ if __name__ == "__main__":
140
+ image_path = "data/images_symmetry/gigi_hadid.webp"
141
+ results = GetFaceSymmetry().main(image_path)
142
+ print(results)
utils/face_texture.py CHANGED
@@ -1,48 +1,57 @@
1
  import cv2
2
  import numpy as np
3
  from skimage.feature import local_binary_pattern
4
- import matplotlib.pyplot as plt
5
  import dlib
6
  import imutils
7
- import os
8
- from PIL import Image
9
  from utils.cv_utils import get_image
10
- from typing import Tuple
11
 
12
 
13
  class GetFaceTexture:
14
  def __init__(self) -> None:
15
  pass
16
-
17
- def preprocess_image(self, image) -> np.array:
18
- image = imutils.resize(image, width=400)
 
19
  gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
20
  return gray_image
21
-
22
- def get_face(self, gray_image: np.array) -> np.array:
 
23
  detector = dlib.get_frontal_face_detector()
24
  faces = detector(gray_image, 1)
25
  if len(faces) == 0:
26
  return "No face detected."
27
 
28
- x, y, w, h = (faces[0].left(), faces[0].top(), faces[0].width(), faces[0].height())
29
- face_image = gray_image[y:y+h, x:x+w]
 
 
 
 
 
30
  return face_image
31
-
32
- def get_face_texture(self, face_image: np.array) -> Tuple[np.array, float]:
 
33
  radius = 1
34
  n_points = 8 * radius
35
  lbp = local_binary_pattern(face_image, n_points, radius, method="uniform")
36
- hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, n_points + 3), range=(0, n_points + 2))
 
 
37
  variance = np.var(hist)
38
  std = np.sqrt(variance)
39
  return lbp, std
40
-
41
- def postprocess_image(self, lbp: np.array) -> Image:
 
42
  lbp = (lbp * 255).astype(np.uint8)
43
- return Image.fromarray(lbp)
44
-
45
- def main(self, image_input) -> Image:
46
  image = get_image(image_input)
47
  gray_image = self.preprocess_image(image)
48
  face_image = self.get_face(gray_image)
@@ -51,6 +60,6 @@ class GetFaceTexture:
51
  return face_texture_image, face_image, std
52
 
53
 
54
- if __name__ == "__main__":
55
- image_path = 'data/images_symmetry/gigi_hadid.webp'
56
  print(GetFaceTexture().main(image_path))
 
1
  import cv2
2
  import numpy as np
3
  from skimage.feature import local_binary_pattern
 
4
  import dlib
5
  import imutils
6
+ from PIL import Image as PILImage
 
7
  from utils.cv_utils import get_image
8
+ from typing import Tuple, List, Union
9
 
10
 
11
  class GetFaceTexture:
12
  def __init__(self) -> None:
13
  pass
14
+
15
+ @staticmethod
16
+ def preprocess_image(image) -> np.array:
17
+ image = imutils.resize(image, width=300)
18
  gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
19
  return gray_image
20
+
21
+ @staticmethod
22
+ def get_face(gray_image: np.array) -> np.array:
23
  detector = dlib.get_frontal_face_detector()
24
  faces = detector(gray_image, 1)
25
  if len(faces) == 0:
26
  return "No face detected."
27
 
28
+ x, y, w, h = (
29
+ faces[0].left(),
30
+ faces[0].top(),
31
+ faces[0].width(),
32
+ faces[0].height(),
33
+ )
34
+ face_image = gray_image[y: y + h, x: x + w]
35
  return face_image
36
+
37
+ @staticmethod
38
+ def get_face_texture(face_image: np.array) -> Tuple[np.array, float]:
39
  radius = 1
40
  n_points = 8 * radius
41
  lbp = local_binary_pattern(face_image, n_points, radius, method="uniform")
42
+ hist, _ = np.histogram(
43
+ lbp.ravel(), bins=np.arange(0, n_points + 3), range=(0, n_points + 2)
44
+ )
45
  variance = np.var(hist)
46
  std = np.sqrt(variance)
47
  return lbp, std
48
+
49
+ @staticmethod
50
+ def postprocess_image(lbp: np.array) -> PILImage:
51
  lbp = (lbp * 255).astype(np.uint8)
52
+ return PILImage.fromarray(lbp)
53
+
54
+ def main(self, image_input) -> List[Union[PILImage.Image, np.array, float]]:
55
  image = get_image(image_input)
56
  gray_image = self.preprocess_image(image)
57
  face_image = self.get_face(gray_image)
 
60
  return face_texture_image, face_image, std
61
 
62
 
63
+ if __name__ == "__main__":
64
+ image_path = "data/images_symmetry/gigi_hadid.webp"
65
  print(GetFaceTexture().main(image_path))