jarondon82 commited on
Commit
bc2a24c
1 Parent(s): 3911742

Add face comparison functionality

Browse files
Files changed (2) hide show
  1. face_comparison.py +252 -0
  2. streamlit_app.py +8 -0
face_comparison.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import streamlit as st
4
+ from sklearn.metrics.pairwise import cosine_similarity
5
+
6
+ def compare_faces(image1, bboxes1, image2, bboxes2):
7
+ """
8
+ Compare faces using HOG features
9
+ """
10
+ # Convert images to grayscale
11
+ gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
12
+ gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
13
+
14
+ # Initialize list to store comparison results
15
+ comparison_results = []
16
+
17
+ # Calculate HOG parameters based on face size
18
+ win_size = (64, 64)
19
+ block_size = (16, 16)
20
+ block_stride = (8, 8)
21
+ cell_size = (8, 8)
22
+ nbins = 9
23
+
24
+ # Iterate over each face in the first image
25
+ for bbox1 in bboxes1:
26
+ x1_1, y1_1, x2_1, y2_1, _ = bbox1
27
+
28
+ # Check if the face region is valid
29
+ if x1_1 >= x2_1 or y1_1 >= y2_1:
30
+ continue
31
+
32
+ # Resize face to a standard size for HOG
33
+ face1_roi = image1[y1_1:y2_1, x1_1:x2_1]
34
+ face1_resized = cv2.resize(face1_roi, win_size)
35
+ face1_gray = cv2.cvtColor(face1_resized, cv2.COLOR_BGR2GRAY)
36
+
37
+ # Calculate HOG features
38
+ hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, nbins)
39
+ h1 = hog.compute(face1_gray)
40
+
41
+ # Normalize the feature vector
42
+ h1_norm = h1 / np.linalg.norm(h1)
43
+
44
+ # Store results for this face
45
+ face_comparisons = []
46
+
47
+ # Compare with each face in the second image
48
+ for bbox2 in bboxes2:
49
+ x1_2, y1_2, x2_2, y2_2, _ = bbox2
50
+
51
+ # Check if the face region is valid
52
+ if x1_2 >= x2_2 or y1_2 >= y2_2:
53
+ continue
54
+
55
+ # Resize face to a standard size for HOG
56
+ face2_roi = image2[y1_2:y2_2, x1_2:x2_2]
57
+ face2_resized = cv2.resize(face2_roi, win_size)
58
+ face2_gray = cv2.cvtColor(face2_resized, cv2.COLOR_BGR2GRAY)
59
+
60
+ # Calculate HOG features
61
+ h2 = hog.compute(face2_gray)
62
+
63
+ # Normalize the feature vector
64
+ h2_norm = h2 / np.linalg.norm(h2)
65
+
66
+ # Calculate cosine similarity
67
+ similarity = np.dot(h1_norm.flatten(), h2_norm.flatten()) * 100
68
+
69
+ # Add result
70
+ face_comparisons.append({
71
+ "similarity": similarity
72
+ })
73
+
74
+ comparison_results.append(face_comparisons)
75
+
76
+ return comparison_results
77
+
78
+ def compare_faces_embeddings(image1, bboxes1, image2, bboxes2, model_name="VGG-Face"):
79
+ """
80
+ Compare faces using facial embeddings from DeepFace
81
+ """
82
+ try:
83
+ from deepface import DeepFace
84
+ import numpy as np
85
+ except ImportError:
86
+ # Fallback to HOG if DeepFace is not available
87
+ return compare_faces(image1, bboxes1, image2, bboxes2)
88
+
89
+ # Initialize list to store comparison results
90
+ comparison_results = []
91
+
92
+ # Iterate over each face in the first image
93
+ for bbox1 in bboxes1:
94
+ x1_1, y1_1, x2_1, y2_1, _ = bbox1
95
+
96
+ # Check if the face region is valid
97
+ if x1_1 >= x2_1 or y1_1 >= y2_1:
98
+ continue
99
+
100
+ # Extract face region
101
+ face1_roi = image1[y1_1:y2_1, x1_1:x2_1]
102
+
103
+ # Get embedding for the face
104
+ try:
105
+ embedding1 = DeepFace.represent(face1_roi, model_name=model_name, enforce_detection=False)[0]["embedding"]
106
+ except Exception as e:
107
+ st.warning(f"Error extracting embedding from face 1: {str(e)}")
108
+ # Try with a fallback model
109
+ try:
110
+ embedding1 = DeepFace.represent(face1_roi, model_name="OpenFace", enforce_detection=False)[0]["embedding"]
111
+ except:
112
+ # If still fails, use HOG
113
+ face_comparisons = []
114
+ for bbox2 in bboxes2:
115
+ face_comparisons.append({"similarity": 0})
116
+ comparison_results.append(face_comparisons)
117
+ continue
118
+
119
+ # Store results for this face
120
+ face_comparisons = []
121
+
122
+ # Compare with each face in the second image
123
+ for bbox2 in bboxes2:
124
+ x1_2, y1_2, x2_2, y2_2, _ = bbox2
125
+
126
+ # Check if the face region is valid
127
+ if x1_2 >= x2_2 or y1_2 >= y2_2:
128
+ continue
129
+
130
+ # Extract face region
131
+ face2_roi = image2[y1_2:y2_2, x1_2:x2_2]
132
+
133
+ # Get embedding for the face
134
+ try:
135
+ embedding2 = DeepFace.represent(face2_roi, model_name=model_name, enforce_detection=False)[0]["embedding"]
136
+ except Exception as e:
137
+ st.warning(f"Error extracting embedding from face 2: {str(e)}")
138
+ # Try with a fallback model
139
+ try:
140
+ embedding2 = DeepFace.represent(face2_roi, model_name="OpenFace", enforce_detection=False)[0]["embedding"]
141
+ except:
142
+ # If still fails, add a 0 similarity
143
+ face_comparisons.append({"similarity": 0})
144
+ continue
145
+
146
+ # Calculate cosine similarity between embeddings
147
+ embedding1_array = np.array(embedding1).reshape(1, -1)
148
+ embedding2_array = np.array(embedding2).reshape(1, -1)
149
+ similarity = cosine_similarity(embedding1_array, embedding2_array)[0][0] * 100
150
+
151
+ # Add result
152
+ face_comparisons.append({
153
+ "similarity": similarity
154
+ })
155
+
156
+ comparison_results.append(face_comparisons)
157
+
158
+ return comparison_results
159
+
160
+ def generate_comparison_report_english(comparison_results, bboxes1, bboxes2, threshold=50.0):
161
+ """
162
+ Generate a text report of the face comparison results
163
+ """
164
+ report = []
165
+
166
+ # Skip if no comparison results
167
+ if not comparison_results:
168
+ return ["No face comparisons were performed."]
169
+
170
+ # Add header
171
+ report.append(f"Face Comparison Report:")
172
+
173
+ # Add comparison results
174
+ for i, face_comparisons in enumerate(comparison_results):
175
+ report.append(f"\nFace {i+1} from Image 1:")
176
+
177
+ # Skip if no comparisons for this face
178
+ if not face_comparisons:
179
+ report.append(" No comparisons available for this face.")
180
+ continue
181
+
182
+ # Find best match
183
+ best_match_idx = max(range(len(face_comparisons)), key=lambda j: face_comparisons[j]["similarity"])
184
+ best_match_similarity = face_comparisons[best_match_idx]["similarity"]
185
+
186
+ # Add best match info
187
+ if best_match_similarity >= threshold:
188
+ report.append(f" Best match: Face {best_match_idx+1} from Image 2 (Similarity: {best_match_similarity:.2f}%)")
189
+ else:
190
+ report.append(f" No strong matches found. Best similarity is with Face {best_match_idx+1} ({best_match_similarity:.2f}%)")
191
+
192
+ # Add all comparisons
193
+ report.append(" All comparisons:")
194
+ for j, comp in enumerate(face_comparisons):
195
+ report.append(f" Face {j+1}: Similarity {comp['similarity']:.2f}%")
196
+
197
+ return report
198
+
199
+ def draw_face_matches(image1, bboxes1, image2, bboxes2, comparison_results, threshold=50.0):
200
+ """
201
+ Create a combined image showing the two input images side by side with lines connecting matching faces
202
+ """
203
+ # Get dimensions
204
+ h1, w1 = image1.shape[:2]
205
+ h2, w2 = image2.shape[:2]
206
+
207
+ # Create a combined image
208
+ combined_h = max(h1, h2)
209
+ combined_w = w1 + w2
210
+ combined_img = np.zeros((combined_h, combined_w, 3), dtype=np.uint8)
211
+
212
+ # Copy images
213
+ combined_img[:h1, :w1] = image1
214
+ combined_img[:h2, w1:w1+w2] = image2
215
+
216
+ # Draw lines between matching faces
217
+ for i, face_comparisons in enumerate(comparison_results):
218
+ # Skip if no comparisons for this face
219
+ if not face_comparisons:
220
+ continue
221
+
222
+ # Get bbox for this face
223
+ x1_1, y1_1, x2_1, y2_1, _ = bboxes1[i]
224
+ center1_x = (x1_1 + x2_1) // 2
225
+ center1_y = (y1_1 + y2_1) // 2
226
+
227
+ # For each comparison
228
+ for j, comp in enumerate(face_comparisons):
229
+ similarity = comp["similarity"]
230
+
231
+ # Only draw lines for matches above threshold
232
+ if similarity >= threshold:
233
+ # Get bbox for the other face
234
+ x1_2, y1_2, x2_2, y2_2, _ = bboxes2[j]
235
+ center2_x = (x1_2 + x2_2) // 2 + w1 # Adjust for offset
236
+ center2_y = (y1_2 + y2_2) // 2
237
+
238
+ # Calculate color based on similarity (green for high, red for low)
239
+ # Map 50-100% to color scale
240
+ color_val = min(255, max(0, int((similarity - threshold) * 255 / (100 - threshold))))
241
+ line_color = (0, 0, 255) # Red for all matches
242
+
243
+ # Draw line
244
+ cv2.line(combined_img, (center1_x, center1_y), (center2_x, center2_y), line_color, 2)
245
+
246
+ # Add similarity text
247
+ text_x = (center1_x + center2_x) // 2 - 20
248
+ text_y = (center1_y + center2_y) // 2 - 10
249
+ cv2.putText(combined_img, f"{similarity:.1f}%", (text_x, text_y),
250
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
251
+
252
+ return combined_img
streamlit_app.py CHANGED
@@ -20,6 +20,14 @@ try:
20
  except ImportError:
21
  DEEPFACE_AVAILABLE = False
22
 
 
 
 
 
 
 
 
 
23
  # Funci贸n principal que encapsula toda la aplicaci贸n
24
  def main():
25
  # Set page config with custom title and layout
 
20
  except ImportError:
21
  DEEPFACE_AVAILABLE = False
22
 
23
+ # Import functions for face comparison
24
+ try:
25
+ from face_comparison import compare_faces, compare_faces_embeddings, generate_comparison_report_english, draw_face_matches
26
+ FACE_COMPARISON_AVAILABLE = True
27
+ except ImportError:
28
+ FACE_COMPARISON_AVAILABLE = False
29
+ st.warning("Face comparison functions are not available. Please check your installation.")
30
+
31
  # Funci贸n principal que encapsula toda la aplicaci贸n
32
  def main():
33
  # Set page config with custom title and layout