PrashanthB461 commited on
Commit
618cada
Β·
verified Β·
1 Parent(s): 6c18796

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -69
app.py CHANGED
@@ -73,9 +73,8 @@ class AttendanceSystem:
73
  self.is_processing = threading.Event()
74
  self.frame_queue = queue.Queue(maxsize=10)
75
  self.error_message = None
76
- self.last_processed_frame = None
77
- self.final_log = None
78
- self.processed_workers = set() # Track workers processed in this session
79
 
80
  # Data Storage
81
  self.known_face_embeddings: List[np.ndarray] = []
@@ -87,11 +86,11 @@ class AttendanceSystem:
87
  self.last_recognition_time: Dict[str, float] = {}
88
  self.recognition_cooldown = 5 # seconds
89
  self.session_log: List[str] = []
90
- self.today_attendance: Dict[str, bool] = {}
91
- self.last_detected_faces: Dict[str, float] = {}
92
 
93
  # Performance optimization
94
- self.frame_skip = 1 # Process every 2nd frame
95
  self.frame_counter = 0
96
 
97
  # Initialize
@@ -121,7 +120,7 @@ class AttendanceSystem:
121
  worker_num = int(worker['Worker_ID__c'][1:])
122
  if worker_num > max_id:
123
  max_id = worker_num
124
- except (ValueError, TypeError):
125
  continue
126
 
127
  self.known_face_embeddings = temp_embeddings
@@ -162,6 +161,7 @@ class AttendanceSystem:
162
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
163
  try:
164
  image_array = np.array(image)
 
165
  analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
166
  if analysis[0]['face_confidence'] < 0.95:
167
  return "❌ Face not clear enough for registration!", self.get_registered_workers_info()
@@ -183,6 +183,7 @@ class AttendanceSystem:
183
 
184
  def _register_worker_auto(self, face_image: np.ndarray) -> Optional[Tuple[str, str]]:
185
  try:
 
186
  analysis = DeepFace.analyze(img_path=face_image, actions=['emotion'], enforce_detection=False)
187
  if analysis[0]['face_confidence'] < 0.95:
188
  return None
@@ -226,24 +227,26 @@ class AttendanceSystem:
226
  return min(distances) < threshold
227
 
228
  def mark_attendance(self, worker_id: str, worker_name: str) -> bool:
229
- if worker_id in self.processed_workers:
230
- return False
231
-
232
  today_str = date.today().isoformat()
 
 
233
  if worker_id in self.today_attendance:
234
  return False
235
 
 
236
  current_time = time.time()
237
  if worker_id in self.last_recognition_time and (current_time - self.last_recognition_time[worker_id] < self.recognition_cooldown):
238
  return False
239
 
 
 
240
  if self.sf:
241
  try:
242
  self.sf.Attendance__c.create({
243
  'Worker_ID__c': worker_id,
244
  'Name__c': worker_name,
245
  'Date__c': today_str,
246
- 'Timestamp__c': datetime.now().isoformat(),
247
  'Status__c': "Present"
248
  })
249
  except Exception as e:
@@ -251,10 +254,8 @@ class AttendanceSystem:
251
 
252
  self.today_attendance[worker_id] = True
253
  self.last_recognition_time[worker_id] = current_time
254
- self.processed_workers.add(worker_id)
255
- log_msg = f"βœ… [{datetime.now().strftime('%H:%M:%S')}] Marked Present: {worker_name} ({worker_id})"
256
  self.session_log.append(log_msg)
257
- logger.info(log_msg)
258
  return True
259
 
260
  # --- Video Processing ---
@@ -263,46 +264,43 @@ class AttendanceSystem:
263
  Process a single video frame with optimizations for speed and accuracy.
264
  """
265
  try:
 
266
  self.frame_counter += 1
267
  if self.frame_counter % (self.frame_skip + 1) != 0:
268
  return frame
269
 
270
- # Resize frame to a smaller size for faster processing
271
  height, width = frame.shape[:2]
272
- new_width = 480
273
  new_height = int((new_width / width) * height)
274
  small_frame = cv2.resize(frame, (new_width, new_height))
275
 
276
- # Use OpenCV detector for better reliability
277
  face_objs = DeepFace.extract_faces(
278
- img_path=small_frame,
279
- detector_backend='opencv',
280
  enforce_detection=False,
281
  align=True
282
  )
283
 
284
- if not face_objs:
285
- logger.warning("No faces detected in this frame.")
286
- return frame
287
-
288
  for face_obj in face_objs:
289
- confidence = face_obj.get('confidence', 0.0)
290
  if confidence < 0.90: # Lowered threshold for detection
291
  continue
292
 
293
  facial_area = face_obj['facial_area']
294
  x, y, w, h = facial_area['x'], facial_area['y'], facial_area['w'], facial_area['h']
295
 
 
296
  x = int(x * width / new_width)
297
  y = int(y * height / new_height)
298
  w = int(w * width / new_width)
299
  h = int(h * height / new_height)
300
 
301
  face_image = frame[y:y+h, x:x+w]
302
- if face_image.size == 0:
303
  continue
304
 
305
- # Skip recently detected faces
306
  current_time = time.time()
307
  face_key = f"{x}_{y}_{w}_{h}"
308
  if face_key in self.last_detected_faces and (current_time - self.last_detected_faces[face_key] < 2.0):
@@ -310,39 +308,36 @@ class AttendanceSystem:
310
  self.last_detected_faces[face_key] = current_time
311
 
312
  embedding = DeepFace.represent(
313
- img_path=face_image,
314
- model_name='Facenet512',
315
  enforce_detection=False,
316
  align=True
317
  )[0]['embedding']
318
 
319
  if not self.known_face_embeddings:
320
- logger.info("No known face embeddings loaded.")
321
  continue
322
 
323
  distances = [np.linalg.norm(np.array(embedding) - known) for known in self.known_face_embeddings]
324
  min_dist = min(distances) if distances else float('inf')
325
- match_index = distances.index(min_dist) if min_dist < 8.0 else -1
326
 
327
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
328
 
329
  if match_index != -1:
330
  worker_id = self.known_face_ids[match_index]
331
  worker_name = self.known_face_names[match_index]
332
- if worker_id not in self.processed_workers:
333
- color = (0, 255, 0)
334
- if self.mark_attendance(worker_id, worker_name):
335
- pass # Log already handled in mark_attendance
336
  else:
 
337
  analysis = DeepFace.analyze(img_path=face_image, actions=['emotion'], enforce_detection=False)
338
- if analysis[0].get('face_confidence', 0.0) >= 0.90:
339
- color = (0, 165, 255)
340
  new_worker = self._register_worker_auto(face_image)
341
  if new_worker:
342
  worker_id, worker_name = new_worker
343
- if self.mark_attendance(worker_id, worker_name):
344
- pass # Log already handled in _register_worker_auto
345
-
346
  # Draw rectangle and label
347
  label = f"{worker_name}" + (f" ({worker_id})" if worker_id else "")
348
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
@@ -356,31 +351,34 @@ class AttendanceSystem:
356
  def _processing_loop(self, source):
357
  video_capture = cv2.VideoCapture(source)
358
  if not video_capture.isOpened():
359
- err_msg = f"❌ **Error:** Could not open video source."
360
  self.error_message = err_msg
361
  self.is_processing.clear()
362
  return
363
 
 
364
  if isinstance(source, int):
365
  video_capture.set(cv2.CAP_PROP_FPS, 30)
366
 
367
  while self.is_processing.is_set():
368
  ret, frame = video_capture.read()
369
- if not ret:
370
  break
371
 
372
  processed_frame = self.process_frame(frame)
373
- if processed_frame is not None and not self.frame_queue.full():
 
374
  self.frame_queue.put(processed_frame)
375
 
376
  self.last_processed_frame = processed_frame
 
377
 
378
  self.final_log = self.session_log.copy()
379
  video_capture.release()
380
  self.is_processing.clear()
381
 
382
  def start_processing(self, source) -> str:
383
- if self.is_processing.is_set():
384
  return "⚠️ Processing is already active."
385
 
386
  # Reset states for new session
@@ -388,7 +386,6 @@ class AttendanceSystem:
388
  self.last_recognition_time.clear()
389
  self.today_attendance.clear()
390
  self.last_detected_faces.clear()
391
- self.processed_workers.clear()
392
  self.error_message = None
393
  self.last_processed_frame = None
394
  self.final_log = None
@@ -408,12 +405,11 @@ class AttendanceSystem:
408
  self.error_message = None
409
  self.last_processed_frame = None
410
  self.final_log = None
411
- self.processed_workers.clear()
412
  return "βœ… Processing stopped by user."
413
 
414
  # --- Helper & Reporting ---
415
  def _get_image_caption(self, image: Image.Image) -> str:
416
- if not HF_API_TOKEN:
417
  return "Hugging Face API token not configured."
418
  try:
419
  buffered = BytesIO()
@@ -429,7 +425,7 @@ class AttendanceSystem:
429
  return "Caption generation failed."
430
 
431
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
432
- if not self.sf:
433
  return None
434
  try:
435
  buffered = BytesIO()
@@ -441,22 +437,22 @@ class AttendanceSystem:
441
  'VersionData': encoded_image,
442
  'FirstPublishLocationId': record_id
443
  })
444
- return f"/{cv['id']}"
445
  except Exception as e:
446
  logger.error(f"Salesforce image upload error: {e}")
447
  return None
448
 
449
  def get_registered_workers_info(self) -> str:
450
- if not self.sf:
451
  return "❌ Salesforce not connected."
452
  try:
453
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
454
- if not records:
455
  return "No workers registered."
456
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join(
457
  [f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records]
458
  )
459
- except Exception as e:
460
  return f"Error: {e}"
461
 
462
  # --- GRADIO UI ---
@@ -489,7 +485,6 @@ def create_interface():
489
  video_output = gr.Image(label="Recognition Output", interactive=False)
490
  with gr.Column(scale=1):
491
  session_log_display = gr.Markdown(label="πŸ“‹ Session Log", value="System is ready.")
492
- workers_summary = gr.Markdown(label="πŸ“‹ Workers Processed", value="No workers processed yet.")
493
 
494
  with gr.Tab("πŸ‘€ Worker Management"):
495
  with gr.Row():
@@ -538,38 +533,31 @@ def create_interface():
538
  def update_ui_generator():
539
  while True:
540
  if attendance_system.error_message:
541
- yield None, attendance_system.error_message, "No workers processed yet."
542
  time.sleep(2)
543
  attendance_system.error_message = None
544
  continue
545
 
546
  if attendance_system.is_processing.is_set():
547
- frame = None
548
  try:
549
  if not attendance_system.frame_queue.empty():
550
  frame = attendance_system.frame_queue.get_nowait()
551
- if frame is not None:
552
  frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
553
- except queue.Empty:
554
  pass
555
- log_md = "\n".join(reversed(attendance_system.session_log[-20:])) or "Processing..."
556
- workers_md = f"**Workers Processed ({len(attendance_system.processed_workers)})**\n" + "\n".join(
557
- [f"- {log.split('] ')[1]}" for log in reversed(attendance_system.session_log[-20:]) if 'Marked Present' in log or 'Auto-registered' in log]
558
- ) if attendance_system.session_log else "No workers processed yet."
559
- yield frame, log_md, workers_md
560
  else:
561
  if attendance_system.last_processed_frame is not None:
562
  final_frame = cv2.cvtColor(attendance_system.last_processed_frame, cv2.COLOR_BGR2RGB)
563
- final_log_md = "\n".join(reversed(attendance_system.final_log[-20:])) or "Processing complete."
564
- workers_md = f"**Workers Processed ({len(attendance_system.processed_workers)})**\n" + "\n".join(
565
- [f"- {log.split('] ')[1]}" for log in reversed(attendance_system.final_log[-20:]) if 'Marked Present' in log or 'Auto-registered' in log]
566
- ) if attendance_system.final_log else "No workers processed."
567
- yield final_frame, final_log_md, workers_md
568
  else:
569
- yield None, "System stopped. Go to 'Controls & Status' to start.", "No workers processed yet."
570
- time.sleep(0.03)
571
 
572
- demo.load(fn=update_ui_generator, outputs=[video_output, session_log_display, workers_summary])
573
  return demo
574
 
575
  if __name__ == "__main__":
 
73
  self.is_processing = threading.Event()
74
  self.frame_queue = queue.Queue(maxsize=10)
75
  self.error_message = None
76
+ self.last_processed_frame = None # Holds the final frame after processing
77
+ self.final_log = None # Holds the final log after processing
 
78
 
79
  # Data Storage
80
  self.known_face_embeddings: List[np.ndarray] = []
 
86
  self.last_recognition_time: Dict[str, float] = {}
87
  self.recognition_cooldown = 5 # seconds
88
  self.session_log: List[str] = []
89
+ self.today_attendance: Dict[str, bool] = {} # Track attendance for today
90
+ self.last_detected_faces: Dict[str, float] = {} # Track last detection time per worker
91
 
92
  # Performance optimization
93
+ self.frame_skip = 2 # Process every 3rd frame for faster processing
94
  self.frame_counter = 0
95
 
96
  # Initialize
 
120
  worker_num = int(worker['Worker_ID__c'][1:])
121
  if worker_num > max_id:
122
  max_id = worker_num
123
+ except (ValueError, TypeTypeError):
124
  continue
125
 
126
  self.known_face_embeddings = temp_embeddings
 
161
  return "❌ Please provide both image and name!", self.get_registered_workers_info()
162
  try:
163
  image_array = np.array(image)
164
+ # Analyze face quality before registration
165
  analysis = DeepFace.analyze(img_path=image_array, actions=['emotion'], enforce_detection=True)
166
  if analysis[0]['face_confidence'] < 0.95:
167
  return "❌ Face not clear enough for registration!", self.get_registered_workers_info()
 
183
 
184
  def _register_worker_auto(self, face_image: np.ndarray) -> Optional[Tuple[str, str]]:
185
  try:
186
+ # Check face quality before auto-registration
187
  analysis = DeepFace.analyze(img_path=face_image, actions=['emotion'], enforce_detection=False)
188
  if analysis[0]['face_confidence'] < 0.95:
189
  return None
 
227
  return min(distances) < threshold
228
 
229
  def mark_attendance(self, worker_id: str, worker_name: str) -> bool:
 
 
 
230
  today_str = date.today().isoformat()
231
+
232
+ # Check if already marked today
233
  if worker_id in self.today_attendance:
234
  return False
235
 
236
+ # Check cooldown period
237
  current_time = time.time()
238
  if worker_id in self.last_recognition_time and (current_time - self.last_recognition_time[worker_id] < self.recognition_cooldown):
239
  return False
240
 
241
+ # Mark attendance
242
+ current_time_dt = datetime.now()
243
  if self.sf:
244
  try:
245
  self.sf.Attendance__c.create({
246
  'Worker_ID__c': worker_id,
247
  'Name__c': worker_name,
248
  'Date__c': today_str,
249
+ 'Timestamp__c': current_time_dt.isoformat(),
250
  'Status__c': "Present"
251
  })
252
  except Exception as e:
 
254
 
255
  self.today_attendance[worker_id] = True
256
  self.last_recognition_time[worker_id] = current_time
257
+ log_msg = f"βœ… [{current_time_dt.strftime('%H:%M:%S')}] Marked Present: {worker_name} ({worker_id})"
 
258
  self.session_log.append(log_msg)
 
259
  return True
260
 
261
  # --- Video Processing ---
 
264
  Process a single video frame with optimizations for speed and accuracy.
265
  """
266
  try:
267
+ # Skip frames for faster processing
268
  self.frame_counter += 1
269
  if self.frame_counter % (self.frame_skip + 1) != 0:
270
  return frame
271
 
272
+ # Resize frame for faster processing (keeping aspect ratio)
273
  height, width = frame.shape[:2]
274
+ new_width = 360 # Reduced from 640 for speed
275
  new_height = int((new_width / width) * height)
276
  small_frame = cv2.resize(frame, (new_width, new_height))
277
 
 
278
  face_objs = DeepFace.extract_faces(
279
+ img_path=small_frame,
280
+ detector_backend='opencv', # More reliable detector
281
  enforce_detection=False,
282
  align=True
283
  )
284
 
 
 
 
 
285
  for face_obj in face_objs:
286
+ confidence = face_obj['confidence']
287
  if confidence < 0.90: # Lowered threshold for detection
288
  continue
289
 
290
  facial_area = face_obj['facial_area']
291
  x, y, w, h = facial_area['x'], facial_area['y'], facial_area['w'], facial_area['h']
292
 
293
+ # Scale coordinates back to original frame size
294
  x = int(x * width / new_width)
295
  y = int(y * height / new_height)
296
  w = int(w * width / new_width)
297
  h = int(h * height / new_height)
298
 
299
  face_image = frame[y:y+h, x:x+w]
300
+ if face_image.size == 0:
301
  continue
302
 
303
+ # Only process faces that haven't been detected recently
304
  current_time = time.time()
305
  face_key = f"{x}_{y}_{w}_{h}"
306
  if face_key in self.last_detected_faces and (current_time - self.last_detected_faces[face_key] < 2.0):
 
308
  self.last_detected_faces[face_key] = current_time
309
 
310
  embedding = DeepFace.represent(
311
+ img_path=face_image,
312
+ model_name='Facenet',
313
  enforce_detection=False,
314
  align=True
315
  )[0]['embedding']
316
 
317
  if not self.known_face_embeddings:
 
318
  continue
319
 
320
  distances = [np.linalg.norm(np.array(embedding) - known) for known in self.known_face_embeddings]
321
  min_dist = min(distances) if distances else float('inf')
322
+ match_index = distances.index(min_dist) if min_dist < 10.0 else -1
323
 
324
  color, worker_id, worker_name = (0, 0, 255), None, "Unknown"
325
 
326
  if match_index != -1:
327
  worker_id = self.known_face_ids[match_index]
328
  worker_name = self.known_face_names[match_index]
329
+ color = (0, 255, 0) # Green for known workers
330
+ self.mark_attendance(worker_id, worker_name)
 
 
331
  else:
332
+ # Only attempt auto-registration for high-quality faces
333
  analysis = DeepFace.analyze(img_path=face_image, actions=['emotion'], enforce_detection=False)
334
+ if analysis[0]['face_confidence'] >= 0.90: # Lowered threshold
335
+ color = (0, 165, 255) # Orange for potential new worker
336
  new_worker = self._register_worker_auto(face_image)
337
  if new_worker:
338
  worker_id, worker_name = new_worker
339
+ self.mark_attendance(worker_id, worker_name)
340
+
 
341
  # Draw rectangle and label
342
  label = f"{worker_name}" + (f" ({worker_id})" if worker_id else "")
343
  cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
 
351
  def _processing_loop(self, source):
352
  video_capture = cv2.VideoCapture(source)
353
  if not video_capture.isOpened():
354
+ err_msg = f"❌ **Error:** Could not open video source. The file may be corrupt or in an unsupported format. Please try converting it to a standard MP4."
355
  self.error_message = err_msg
356
  self.is_processing.clear()
357
  return
358
 
359
+ # Set higher FPS if possible (for live camera)
360
  if isinstance(source, int):
361
  video_capture.set(cv2.CAP_PROP_FPS, 30)
362
 
363
  while self.is_processing.is_set():
364
  ret, frame = video_capture.read()
365
+ if not ret:
366
  break
367
 
368
  processed_frame = self.process_frame(frame)
369
+
370
+ if not self.frame_queue.full():
371
  self.frame_queue.put(processed_frame)
372
 
373
  self.last_processed_frame = processed_frame
374
+ time.sleep(0.01) # Reduced sleep for faster processing
375
 
376
  self.final_log = self.session_log.copy()
377
  video_capture.release()
378
  self.is_processing.clear()
379
 
380
  def start_processing(self, source) -> str:
381
+ if self.is_processing.is_set():
382
  return "⚠️ Processing is already active."
383
 
384
  # Reset states for new session
 
386
  self.last_recognition_time.clear()
387
  self.today_attendance.clear()
388
  self.last_detected_faces.clear()
 
389
  self.error_message = None
390
  self.last_processed_frame = None
391
  self.final_log = None
 
405
  self.error_message = None
406
  self.last_processed_frame = None
407
  self.final_log = None
 
408
  return "βœ… Processing stopped by user."
409
 
410
  # --- Helper & Reporting ---
411
  def _get_image_caption(self, image: Image.Image) -> str:
412
+ if not HF_API_TOKEN:
413
  return "Hugging Face API token not configured."
414
  try:
415
  buffered = BytesIO()
 
425
  return "Caption generation failed."
426
 
427
  def _upload_image_to_salesforce(self, image: Image.Image, record_id: str, worker_id: str) -> Optional[str]:
428
+ if not self.sf:
429
  return None
430
  try:
431
  buffered = BytesIO()
 
437
  'VersionData': encoded_image,
438
  'FirstPublishLocationId': record_id
439
  })
440
+ return f"/{cv['id']}" # Relative URL
441
  except Exception as e:
442
  logger.error(f"Salesforce image upload error: {e}")
443
  return None
444
 
445
  def get_registered_workers_info(self) -> str:
446
+ if not self.sf:
447
  return "❌ Salesforce not connected."
448
  try:
449
  records = self.sf.query_all("SELECT Name, Worker_ID__c FROM Worker__c ORDER BY Name")['records']
450
+ if not records:
451
  return "No workers registered."
452
  return f"**πŸ‘₯ Registered Workers ({len(records)})**\n" + "\n".join(
453
  [f"- **{w['Name']}** (ID: {w['Worker_ID__c']})" for w in records]
454
  )
455
+ except Exception as e:
456
  return f"Error: {e}"
457
 
458
  # --- GRADIO UI ---
 
485
  video_output = gr.Image(label="Recognition Output", interactive=False)
486
  with gr.Column(scale=1):
487
  session_log_display = gr.Markdown(label="πŸ“‹ Session Log", value="System is ready.")
 
488
 
489
  with gr.Tab("πŸ‘€ Worker Management"):
490
  with gr.Row():
 
533
  def update_ui_generator():
534
  while True:
535
  if attendance_system.error_message:
536
+ yield None, attendance_system.error_message
537
  time.sleep(2)
538
  attendance_system.error_message = None
539
  continue
540
 
541
  if attendance_system.is_processing.is_set():
542
+ frame, log_md = None, "\n".join(reversed(attendance_system.session_log[-20:])) or "Processing..."
543
  try:
544
  if not attendance_system.frame_queue.empty():
545
  frame = attendance_system.frame_queue.get_nowait()
546
+ if frame is not None:
547
  frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
548
+ except queue.Empty:
549
  pass
550
+ yield frame, log_md
 
 
 
 
551
  else:
552
  if attendance_system.last_processed_frame is not None:
553
  final_frame = cv2.cvtColor(attendance_system.last_processed_frame, cv2.COLOR_BGR2RGB)
554
+ final_log_md = "\n".join(reversed(attendance_system.final_log[-20:])) or "Processing complete. No log entries."
555
+ yield final_frame, final_log_md
 
 
 
556
  else:
557
+ yield None, "System stopped. Go to 'Controls & Status' to start."
558
+ time.sleep(0.05) # Faster UI updates
559
 
560
+ demo.load(fn=update_ui_generator, outputs=[video_output, session_log_display])
561
  return demo
562
 
563
  if __name__ == "__main__":