PrashanthB461 commited on
Commit
7c9e188
·
verified ·
1 Parent(s): 9abc8cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -88
app.py CHANGED
@@ -84,12 +84,13 @@ class AttendanceSystem:
84
 
85
  # Session Tracking - Enhanced for better accuracy
86
  self.last_recognition_time = {}
87
- self.recognition_cooldown = 10 # Increased 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 = 3 # Number of consistent detections needed
 
93
 
94
  # Initialize
95
  self.sf = connect_to_salesforce()
@@ -177,8 +178,8 @@ class AttendanceSystem:
177
 
178
  def _register_worker_auto(self, face_image: np.ndarray, face_embedding: List[float]) -> Optional[Tuple[str, str]]:
179
  try:
180
- # Enhanced duplicate check with multiple embeddings if possible
181
- if self._is_duplicate_face(face_embedding, threshold=8.0): # Stricter threshold
182
  return None
183
 
184
  worker_id = f"W{self.next_worker_id:04d}"
@@ -219,19 +220,19 @@ class AttendanceSystem:
219
  except Exception as e:
220
  logger.error(f"❌ Salesforce sync error for {worker_id}: {e}")
221
 
222
- def _is_duplicate_face(self, embedding: List[float], threshold: float = 8.0) -> bool:
223
- """Enhanced duplicate detection with stricter threshold"""
224
  if not self.known_face_embeddings:
225
  return False
226
 
227
  embedding_array = np.array(embedding)
228
  for known_embedding in self.known_face_embeddings:
229
- # Use cosine similarity as well as euclidean distance
230
- cosine_sim = np.dot(embedding_array, known_embedding) / (np.linalg.norm(embedding_array) * np.linalg.norm(known_embedding))
231
  euclidean_dist = np.linalg.norm(embedding_array - known_embedding)
 
232
 
233
  # If either similarity is high or distance is low, consider it duplicate
234
- if cosine_sim > 0.85 or euclidean_dist < threshold:
235
  return True
236
 
237
  return False
@@ -278,38 +279,6 @@ class AttendanceSystem:
278
  pass
279
  return False
280
 
281
- def _enhance_face_image(self, face_image: np.ndarray) -> np.ndarray:
282
- """Enhance face image quality for better recognition"""
283
- try:
284
- # Convert to grayscale and back to improve contrast
285
- gray = cv2.cvtColor(face_image, cv2.COLOR_BGR2GRAY)
286
- # Apply histogram equalization
287
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
288
- enhanced = clahe.apply(gray)
289
- # Convert back to BGR
290
- enhanced_bgr = cv2.cvtColor(enhanced, cv2.COLOR_GRAY2BGR)
291
- return enhanced_bgr
292
- except:
293
- return face_image
294
-
295
- def _get_multiple_embeddings(self, face_image: np.ndarray) -> List[np.ndarray]:
296
- """Get multiple embeddings from slightly modified versions of the face"""
297
- embeddings = []
298
- try:
299
- # Original embedding
300
- original_embedding = DeepFace.represent(img_path=face_image, model_name='Facenet', enforce_detection=False)[0]['embedding']
301
- embeddings.append(np.array(original_embedding))
302
-
303
- # Enhanced version embedding
304
- enhanced_face = self._enhance_face_image(face_image)
305
- enhanced_embedding = DeepFace.represent(img_path=enhanced_face, model_name='Facenet', enforce_detection=False)[0]['embedding']
306
- embeddings.append(np.array(enhanced_embedding))
307
-
308
- except Exception as e:
309
- logger.error(f"Error getting multiple embeddings: {e}")
310
-
311
- return embeddings
312
-
313
  def _find_best_match(self, target_embedding: np.ndarray) -> Tuple[int, float]:
314
  """Find best match using multiple comparison methods"""
315
  if not self.known_face_embeddings:
@@ -342,17 +311,30 @@ class AttendanceSystem:
342
  Enhanced frame processing with better accuracy and duplicate prevention
343
  """
344
  try:
345
- face_objs = DeepFace.extract_faces(img_path=frame, detector_backend='opencv', enforce_detection=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  if face_objs:
348
  print(f"\n--- Frame Processed: Found {len(face_objs)} faces. ---")
349
 
350
  for i, face_obj in enumerate(face_objs):
351
- confidence = face_obj['confidence']
352
  print(f" Face #{i+1}: Confidence Score = {confidence:.2f}")
353
 
354
- # Stricter confidence threshold for better accuracy
355
- if confidence < 0.98:
356
  print(" -> Confidence too low, skipping.")
357
  continue
358
 
@@ -365,64 +347,81 @@ class AttendanceSystem:
365
  if face_image.size == 0:
366
  continue
367
 
368
- # Minimum face size check
369
- if w < 80 or h < 80:
370
  print(" -> Face too small, skipping.")
371
  continue
372
 
373
- # Get enhanced embedding
374
  try:
375
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet', enforce_detection=False)[0]['embedding']
376
  embedding_array = np.array(embedding)
377
- except:
378
- print(" -> Could not generate embedding, skipping.")
379
  continue
380
 
381
- if not self.known_face_embeddings:
382
- print(" -> No known faces in database to compare against.")
383
- continue
384
-
385
- # Enhanced matching
386
- match_index, match_score = self._find_best_match(embedding_array)
387
-
388
- print(f" -> Comparing to DB... Best Match Score: {match_score:.4f}")
389
-
390
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
391
 
392
- # Stricter threshold for matches
393
- if match_index != -1 and match_score < 8.0: # Adjusted threshold
394
- worker_id = self.known_face_ids[match_index]
395
- worker_name = self.known_face_names[match_index]
396
- color = (0, 255, 0) # Green
397
- print(f" ✓ MATCH! Recognized as {worker_name}")
398
 
399
- # Use buffering for consistent recognition
400
- buffer_key = f"{worker_id}_{x}_{y}"
401
- if buffer_key not in self.face_recognition_buffer:
402
- self.face_recognition_buffer[buffer_key] = {'count': 1, 'last_time': time.time()}
403
- else:
404
- self.face_recognition_buffer[buffer_key]['count'] += 1
405
- self.face_recognition_buffer[buffer_key]['last_time'] = time.time()
406
-
407
- # Only mark attendance after consistent detections
408
- if self.face_recognition_buffer[buffer_key]['count'] >= self.buffer_threshold:
409
- if self.mark_attendance(worker_id, worker_name):
410
- self.last_recognition_time[worker_id] = time.time()
411
- # Reset buffer after marking
412
- del self.face_recognition_buffer[buffer_key]
 
 
 
 
 
413
 
414
- else:
415
- # More stringent check before auto-registration
416
- if match_score > 12.0: # Only register if very different from existing faces
417
- color = (0, 165, 255) # Orange for potential new worker
418
- print(f" ✗ NO MATCH. Attempting to register as new worker...")
419
- new_worker = self._register_worker_auto(face_image, embedding)
420
- if new_worker:
421
- worker_id, worker_name = new_worker[0], new_worker[1]
 
 
422
  if self.mark_attendance(worker_id, worker_name):
423
  self.last_recognition_time[worker_id] = time.time()
 
 
 
424
  else:
425
- print(" ✗ Uncertain match, skipping registration.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
  # Clean old buffer entries
428
  current_time = time.time()
@@ -469,6 +468,7 @@ class AttendanceSystem:
469
  self.error_message = None
470
  self.last_processed_frame = None
471
  self.final_log = None
 
472
  self.is_processing.set()
473
  self.processing_thread = threading.Thread(target=self._processing_loop, args=(source,))
474
  self.processing_thread.daemon = True
 
84
 
85
  # Session Tracking - Enhanced for better accuracy
86
  self.last_recognition_time = {}
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
94
 
95
  # Initialize
96
  self.sf = connect_to_salesforce()
 
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}"
 
220
  except Exception as e:
221
  logger.error(f"❌ Salesforce sync error for {worker_id}: {e}")
222
 
223
+ def _is_duplicate_face(self, embedding: List[float], threshold: float = 10.0) -> bool:
224
+ """Enhanced duplicate detection"""
225
  if not self.known_face_embeddings:
226
  return False
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
 
279
  pass
280
  return False
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  def _find_best_match(self, target_embedding: np.ndarray) -> Tuple[int, float]:
283
  """Find best match using multiple comparison methods"""
284
  if not self.known_face_embeddings:
 
311
  Enhanced frame processing with better accuracy and duplicate prevention
312
  """
313
  try:
314
+ # Skip frames for better performance
315
+ self.frame_skip_counter += 1
316
+ if self.frame_skip_counter % 3 != 0: # Process every 3rd frame
317
+ return frame
318
+
319
+ # Use multiple detection backends for better results
320
+ face_objs = []
321
+ try:
322
+ face_objs = DeepFace.extract_faces(img_path=frame, detector_backend='opencv', enforce_detection=False)
323
+ except:
324
+ try:
325
+ face_objs = DeepFace.extract_faces(img_path=frame, detector_backend='mtcnn', enforce_detection=False)
326
+ except:
327
+ pass
328
 
329
  if face_objs:
330
  print(f"\n--- Frame Processed: Found {len(face_objs)} faces. ---")
331
 
332
  for i, face_obj in enumerate(face_objs):
333
+ confidence = face_obj.get('confidence', 0.0)
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
 
 
347
  if face_image.size == 0:
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
 
355
+ # Get embedding
356
  try:
357
  embedding = DeepFace.represent(img_path=face_image, model_name='Facenet', enforce_detection=False)[0]['embedding']
358
  embedding_array = np.array(embedding)
359
+ except Exception as e:
360
+ print(f" -> Could not generate embedding: {e}, skipping.")
361
  continue
362
 
 
 
 
 
 
 
 
 
 
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
 
389
+ # Use buffering for consistent recognition
390
+ buffer_key = f"{worker_id}"
391
+ if buffer_key not in self.face_recognition_buffer:
392
+ self.face_recognition_buffer[buffer_key] = {'count': 1, 'last_time': time.time()}
393
+ else:
394
+ self.face_recognition_buffer[buffer_key]['count'] += 1
395
+ self.face_recognition_buffer[buffer_key]['last_time'] = time.time()
396
+
397
+ # Mark attendance after consistent detections
398
+ if self.face_recognition_buffer[buffer_key]['count'] >= self.buffer_threshold:
399
  if self.mark_attendance(worker_id, worker_name):
400
  self.last_recognition_time[worker_id] = time.time()
401
+ # Reset buffer after marking
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()
 
468
  self.error_message = None
469
  self.last_processed_frame = None
470
  self.final_log = None
471
+ self.frame_skip_counter = 0
472
  self.is_processing.set()
473
  self.processing_thread = threading.Thread(target=self._processing_loop, args=(source,))
474
  self.processing_thread.daemon = True