PrashanthB461 commited on
Commit
34bd28d
Β·
verified Β·
1 Parent(s): 7c9e188

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -57
app.py CHANGED
@@ -87,7 +87,7 @@ class AttendanceSystem:
87
  self.recognition_cooldown = 10 # Cooldown to prevent duplicates
88
  self.session_log: List[str] = []
89
  self.session_marked_present = set() # Track who's already marked present in this session
90
- self.session_registered = set() # Track who's already auto-registered in this session
91
  self.face_recognition_buffer = {} # Buffer for multiple detections before confirming
92
  self.buffer_threshold = 2 # Reduced threshold for faster recognition
93
  self.frame_skip_counter = 0 # Skip frames for better performance
@@ -160,40 +160,51 @@ class AttendanceSystem:
160
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
161
  try:
162
  image_array = np.array(image)
 
 
 
 
 
163
  DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
164
- embedding = DeepFace.represent(img_path=image_array, model_name='Facenet')[0]['embedding']
165
- if self._is_duplicate_face(embedding):
 
 
166
  return f"❌ Face matches an existing worker!", self.get_registered_workers_info()
167
 
168
  worker_id = f"W{self.next_worker_id:04d}"
169
  name = name.strip().title()
170
  self._add_worker_to_system(worker_id, name, embedding, image_array)
171
  self.save_local_worker_data()
172
- self.load_worker_data()
173
  return f"βœ… {name} registered with ID: {worker_id}!", self.get_registered_workers_info()
174
- except ValueError:
175
- return "❌ No face detected in the image!", self.get_registered_workers_info()
176
  except Exception as e:
 
177
  return f"❌ Registration error: {e}", self.get_registered_workers_info()
178
 
179
  def _register_worker_auto(self, face_image: np.ndarray, face_embedding: List[float]) -> Optional[Tuple[str, str]]:
180
  try:
181
- # Check for duplicates with more lenient threshold for auto-registration
182
- if self._is_duplicate_face(face_embedding, threshold=12.0):
 
 
 
183
  return None
184
-
185
- worker_id = f"W{self.next_worker_id:04d}"
186
 
187
- # Check if already auto-registered in this session
188
- if worker_id in self.session_registered:
189
  return None
190
 
 
191
  worker_name = f"Unknown Worker {self.next_worker_id}"
 
192
  self._add_worker_to_system(worker_id, worker_name, face_embedding, face_image)
193
  self.save_local_worker_data()
194
 
195
- # Mark as registered in this session
196
- self.session_registered.add(worker_id)
197
 
198
  log_msg = f"πŸ†• [{datetime.now().strftime('%H:%M:%S')}] Auto-registered: {worker_name} ({worker_id})"
199
  self.session_log.append(log_msg)
@@ -208,8 +219,19 @@ class AttendanceSystem:
208
  self.known_face_names.append(name)
209
  self.known_face_ids.append(worker_id)
210
  self.next_worker_id += 1
211
- face_pil = Image.fromarray(cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB))
 
 
 
 
 
 
 
 
 
 
212
  face_pil.save(f"data/faces/{worker_id}.jpg")
 
213
  caption = self._get_image_caption(face_pil)
214
  if self.sf:
215
  try:
@@ -227,12 +249,14 @@ class AttendanceSystem:
227
 
228
  embedding_array = np.array(embedding)
229
  for known_embedding in self.known_face_embeddings:
230
- # Use both euclidean distance and cosine similarity
231
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
 
 
232
  cosine_sim = np.dot(embedding_array, known_embedding) / (np.linalg.norm(embedding_array) * np.linalg.norm(known_embedding))
233
 
234
- # If either similarity is high or distance is low, consider it duplicate
235
- if cosine_sim > 0.80 or euclidean_dist < threshold:
236
  return True
237
 
238
  return False
@@ -285,25 +309,17 @@ class AttendanceSystem:
285
  return -1, float('inf')
286
 
287
  best_match_idx = -1
288
- best_score = float('inf')
289
 
290
  for i, known_embedding in enumerate(self.known_face_embeddings):
291
- # Euclidean distance
292
  euclidean_dist = np.linalg.norm(target_embedding - known_embedding)
293
 
294
- # Cosine similarity
295
- cosine_sim = np.dot(target_embedding, known_embedding) / (
296
- np.linalg.norm(target_embedding) * np.linalg.norm(known_embedding)
297
- )
298
-
299
- # Combined score (lower is better)
300
- combined_score = euclidean_dist * (1 - cosine_sim)
301
-
302
- if combined_score < best_score:
303
- best_score = combined_score
304
  best_match_idx = i
305
 
306
- return best_match_idx, best_score
307
 
308
  # --- Video Processing ---
309
  def process_frame(self, frame: np.ndarray) -> np.ndarray:
@@ -334,7 +350,7 @@ class AttendanceSystem:
334
  print(f" Face #{i+1}: Confidence Score = {confidence:.2f}")
335
 
336
  # More lenient confidence threshold
337
- if confidence < 0.85:
338
  print(" -> Confidence too low, skipping.")
339
  continue
340
 
@@ -348,7 +364,7 @@ class AttendanceSystem:
348
  continue
349
 
350
  # More lenient minimum face size check
351
- if w < 50 or h < 50:
352
  print(" -> Face too small, skipping.")
353
  continue
354
 
@@ -363,26 +379,15 @@ class AttendanceSystem:
363
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
364
 
365
  if self.known_face_embeddings:
366
- # Enhanced matching
367
- match_index, match_score = self._find_best_match(embedding_array)
368
 
369
- # Also try simple euclidean distance for backup
370
- distances = [np.linalg.norm(embedding_array - known) for known in self.known_face_embeddings]
371
- min_dist = min(distances) if distances else float('inf')
372
- simple_match_index = distances.index(min_dist) if min_dist < 12.0 else -1
373
-
374
- print(f" -> Comparing to DB... Combined Score: {match_score:.4f}, Simple Distance: {min_dist:.4f}")
375
-
376
- # Use more lenient thresholds for recognition
377
- if (match_index != -1 and match_score < 15.0) or (simple_match_index != -1 and min_dist < 12.0):
378
- # Use the better match
379
- if match_index != -1 and match_score < 15.0:
380
- final_match_index = match_index
381
- else:
382
- final_match_index = simple_match_index
383
-
384
- worker_id = self.known_face_ids[final_match_index]
385
- worker_name = self.known_face_names[final_match_index]
386
  color = (0, 255, 0) # Green
387
  print(f" βœ“ MATCH! Recognized as {worker_name}")
388
 
@@ -402,26 +407,32 @@ class AttendanceSystem:
402
  del self.face_recognition_buffer[buffer_key]
403
 
404
  else:
405
- # Check if this should be auto-registered
406
- if min_dist > 15.0: # Only register if very different from existing faces
407
  color = (0, 165, 255) # Orange for potential new worker
408
- print(f" βœ— NO MATCH. Attempting to register as new worker...")
409
  new_worker = self._register_worker_auto(face_image, embedding)
410
  if new_worker:
411
  worker_id, worker_name = new_worker[0], new_worker[1]
 
412
  if self.mark_attendance(worker_id, worker_name):
413
  self.last_recognition_time[worker_id] = time.time()
 
 
414
  else:
415
  print(" βœ— Uncertain match, skipping registration.")
416
  else:
417
- # No known faces, auto-register
418
  color = (0, 165, 255) # Orange for new worker
419
  print(" -> No known faces in database. Auto-registering...")
420
  new_worker = self._register_worker_auto(face_image, embedding)
421
  if new_worker:
422
  worker_id, worker_name = new_worker[0], new_worker[1]
 
423
  if self.mark_attendance(worker_id, worker_name):
424
  self.last_recognition_time[worker_id] = time.time()
 
 
425
 
426
  # Clean old buffer entries
427
  current_time = time.time()
@@ -463,7 +474,7 @@ class AttendanceSystem:
463
  self.session_log.clear()
464
  self.last_recognition_time.clear()
465
  self.session_marked_present.clear() # Reset session attendance tracking
466
- self.session_registered.clear() # Reset session registration tracking
467
  self.face_recognition_buffer.clear() # Reset recognition buffer
468
  self.error_message = None
469
  self.last_processed_frame = None
 
87
  self.recognition_cooldown = 10 # Cooldown to prevent duplicates
88
  self.session_log: List[str] = []
89
  self.session_marked_present = set() # Track who's already marked present in this session
90
+ self.session_auto_registered = set() # Track embeddings that were auto-registered in this session
91
  self.face_recognition_buffer = {} # Buffer for multiple detections before confirming
92
  self.buffer_threshold = 2 # Reduced threshold for faster recognition
93
  self.frame_skip_counter = 0 # Skip frames for better performance
 
160
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
161
  try:
162
  image_array = np.array(image)
163
+ # Convert RGB to BGR for DeepFace
164
+ if len(image_array.shape) == 3 and image_array.shape[2] == 3:
165
+ image_array = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
166
+
167
+ # Verify face detection first
168
  DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
169
+ embedding = DeepFace.represent(img_path=image_array, model_name='Facenet', enforce_detection=True)[0]['embedding']
170
+
171
+ # Check for duplicates with reasonable threshold for manual registration
172
+ if self._is_duplicate_face(embedding, threshold=8.0):
173
  return f"❌ Face matches an existing worker!", self.get_registered_workers_info()
174
 
175
  worker_id = f"W{self.next_worker_id:04d}"
176
  name = name.strip().title()
177
  self._add_worker_to_system(worker_id, name, embedding, image_array)
178
  self.save_local_worker_data()
179
+
180
  return f"βœ… {name} registered with ID: {worker_id}!", self.get_registered_workers_info()
181
+ except ValueError as e:
182
+ return f"❌ No face detected in the image! Error: {str(e)}", self.get_registered_workers_info()
183
  except Exception as e:
184
+ logger.error(f"Registration error: {e}")
185
  return f"❌ Registration error: {e}", self.get_registered_workers_info()
186
 
187
  def _register_worker_auto(self, face_image: np.ndarray, face_embedding: List[float]) -> Optional[Tuple[str, str]]:
188
  try:
189
+ # Create a unique identifier for this embedding to track session registrations
190
+ embedding_hash = hash(tuple(np.round(face_embedding, 3)))
191
+
192
+ # Check if this specific face was already auto-registered in this session
193
+ if embedding_hash in self.session_auto_registered:
194
  return None
 
 
195
 
196
+ # Check for duplicates with more lenient threshold for auto-registration
197
+ if self._is_duplicate_face(face_embedding, threshold=10.0):
198
  return None
199
 
200
+ worker_id = f"W{self.next_worker_id:04d}"
201
  worker_name = f"Unknown Worker {self.next_worker_id}"
202
+
203
  self._add_worker_to_system(worker_id, worker_name, face_embedding, face_image)
204
  self.save_local_worker_data()
205
 
206
+ # Mark this embedding as registered in this session
207
+ self.session_auto_registered.add(embedding_hash)
208
 
209
  log_msg = f"πŸ†• [{datetime.now().strftime('%H:%M:%S')}] Auto-registered: {worker_name} ({worker_id})"
210
  self.session_log.append(log_msg)
 
219
  self.known_face_names.append(name)
220
  self.known_face_ids.append(worker_id)
221
  self.next_worker_id += 1
222
+
223
+ # Ensure image is in RGB format for saving
224
+ if len(image_array.shape) == 3:
225
+ if image_array.shape[2] == 3:
226
+ # Check if BGR (OpenCV format) and convert to RGB
227
+ face_pil = Image.fromarray(cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB))
228
+ else:
229
+ face_pil = Image.fromarray(image_array)
230
+ else:
231
+ face_pil = Image.fromarray(image_array)
232
+
233
  face_pil.save(f"data/faces/{worker_id}.jpg")
234
+
235
  caption = self._get_image_caption(face_pil)
236
  if self.sf:
237
  try:
 
249
 
250
  embedding_array = np.array(embedding)
251
  for known_embedding in self.known_face_embeddings:
252
+ # Use euclidean distance as primary method
253
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
254
+
255
+ # Also check cosine similarity for additional validation
256
  cosine_sim = np.dot(embedding_array, known_embedding) / (np.linalg.norm(embedding_array) * np.linalg.norm(known_embedding))
257
 
258
+ # Consider it duplicate if distance is small OR similarity is very high
259
+ if euclidean_dist < threshold or cosine_sim > 0.85:
260
  return True
261
 
262
  return False
 
309
  return -1, float('inf')
310
 
311
  best_match_idx = -1
312
+ best_distance = float('inf')
313
 
314
  for i, known_embedding in enumerate(self.known_face_embeddings):
315
+ # Primary: Euclidean distance
316
  euclidean_dist = np.linalg.norm(target_embedding - known_embedding)
317
 
318
+ if euclidean_dist < best_distance:
319
+ best_distance = euclidean_dist
 
 
 
 
 
 
 
 
320
  best_match_idx = i
321
 
322
+ return best_match_idx, best_distance
323
 
324
  # --- Video Processing ---
325
  def process_frame(self, frame: np.ndarray) -> np.ndarray:
 
350
  print(f" Face #{i+1}: Confidence Score = {confidence:.2f}")
351
 
352
  # More lenient confidence threshold
353
+ if confidence < 0.80:
354
  print(" -> Confidence too low, skipping.")
355
  continue
356
 
 
364
  continue
365
 
366
  # More lenient minimum face size check
367
+ if w < 40 or h < 40:
368
  print(" -> Face too small, skipping.")
369
  continue
370
 
 
379
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
380
 
381
  if self.known_face_embeddings:
382
+ # Find best match
383
+ match_index, match_distance = self._find_best_match(embedding_array)
384
 
385
+ print(f" -> Comparing to DB... Best Match Distance: {match_distance:.4f}")
386
+
387
+ # Use reasonable threshold for recognition (12.0 works well with Facenet)
388
+ if match_index != -1 and match_distance < 12.0:
389
+ worker_id = self.known_face_ids[match_index]
390
+ worker_name = self.known_face_names[match_index]
 
 
 
 
 
 
 
 
 
 
 
391
  color = (0, 255, 0) # Green
392
  print(f" βœ“ MATCH! Recognized as {worker_name}")
393
 
 
407
  del self.face_recognition_buffer[buffer_key]
408
 
409
  else:
410
+ # No match found - attempt auto-registration for new faces
411
+ if match_distance > 12.0: # Clearly different from existing faces
412
  color = (0, 165, 255) # Orange for potential new worker
413
+ print(f" βœ— NO MATCH (Distance: {match_distance:.4f}). Attempting auto-registration...")
414
  new_worker = self._register_worker_auto(face_image, embedding)
415
  if new_worker:
416
  worker_id, worker_name = new_worker[0], new_worker[1]
417
+ color = (0, 255, 0) # Change to green after successful registration
418
  if self.mark_attendance(worker_id, worker_name):
419
  self.last_recognition_time[worker_id] = time.time()
420
+ else:
421
+ print(" -> Auto-registration skipped (duplicate or session limit)")
422
  else:
423
  print(" βœ— Uncertain match, skipping registration.")
424
  else:
425
+ # No known faces, auto-register the first face
426
  color = (0, 165, 255) # Orange for new worker
427
  print(" -> No known faces in database. Auto-registering...")
428
  new_worker = self._register_worker_auto(face_image, embedding)
429
  if new_worker:
430
  worker_id, worker_name = new_worker[0], new_worker[1]
431
+ color = (0, 255, 0) # Change to green after successful registration
432
  if self.mark_attendance(worker_id, worker_name):
433
  self.last_recognition_time[worker_id] = time.time()
434
+ else:
435
+ print(" -> Auto-registration failed")
436
 
437
  # Clean old buffer entries
438
  current_time = time.time()
 
474
  self.session_log.clear()
475
  self.last_recognition_time.clear()
476
  self.session_marked_present.clear() # Reset session attendance tracking
477
+ self.session_auto_registered.clear() # Reset session registration tracking
478
  self.face_recognition_buffer.clear() # Reset recognition buffer
479
  self.error_message = None
480
  self.last_processed_frame = None