abhilash88 commited on
Commit
7e03307
Β·
verified Β·
1 Parent(s): b9341c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +298 -294
app.py CHANGED
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
15
  # Model configuration
16
  MODEL_NAME = "abhilash88/face-emotion-detection"
17
 
18
- # Emotion labels mapping - update this based on your model's actual labels
19
  EMOTION_LABELS = {
20
  'LABEL_0': 'angry',
21
  'LABEL_1': 'disgust',
@@ -76,11 +76,6 @@ def load_models():
76
 
77
  logger.info("Emotion detection model loaded successfully")
78
 
79
- # Test model to get actual label mapping
80
- test_image = Image.new('RGB', (224, 224), color='white')
81
- test_results = emotion_classifier(test_image)
82
- logger.info(f"Model test result format: {test_results}")
83
-
84
  # Load OpenCV face cascade
85
  face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
86
 
@@ -96,21 +91,110 @@ def load_models():
96
  logger.error(f"Error loading models: {e}")
97
  return False
98
 
99
- def detect_faces(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
100
- """Detect faces in the image using OpenCV"""
 
 
 
101
  try:
102
  gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
 
 
103
  faces = face_cascade.detectMultiScale(
104
  gray,
105
- scaleFactor=1.1,
106
- minNeighbors=5,
107
- minSize=(30, 30)
 
 
108
  )
109
- return faces.tolist()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  except Exception as e:
111
  logger.error(f"Error detecting faces: {e}")
112
  return []
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  def predict_emotion(face_image: Image.Image) -> List[Dict]:
115
  """Predict emotion for a single face"""
116
  try:
@@ -118,9 +202,8 @@ def predict_emotion(face_image: Image.Image) -> List[Dict]:
118
  logger.warning("Emotion classifier not loaded, returning neutral")
119
  return [{"label": "neutral", "score": 1.0}]
120
 
121
- # Resize image if too large for better performance
122
- if face_image.size[0] > 224 or face_image.size[1] > 224:
123
- face_image = face_image.resize((224, 224))
124
 
125
  # The pipeline returns results in different formats depending on configuration
126
  results = emotion_classifier(face_image)
@@ -154,54 +237,66 @@ def predict_emotion(face_image: Image.Image) -> List[Dict]:
154
  logger.error(f"Error predicting emotion: {e}")
155
  return [{"label": "neutral", "score": 1.0}]
156
 
157
- def draw_emotion_results(image: Image.Image, faces: List, emotions: List) -> Image.Image:
158
  """Draw bounding boxes and emotion labels on the image"""
159
  try:
160
  draw = ImageDraw.Draw(image)
161
 
162
  # Try to load a font, fallback to default if not available
163
  try:
164
- font = ImageFont.truetype("arial.ttf", 16)
165
  except:
166
- font = ImageFont.load_default()
 
 
 
167
 
168
  for i, (x, y, w, h) in enumerate(faces):
169
  if i < len(emotions):
170
- # Get top emotion
171
- top_emotion = max(emotions[i], key=lambda x: x['score'])
 
 
 
 
172
  emotion_label = top_emotion['label']
173
  confidence = top_emotion['score']
174
 
175
  # Get color for this emotion
176
  color = EMOTION_COLORS.get(emotion_label, '#FFFFFF')
177
 
178
- # Draw bounding box
179
- draw.rectangle([(x, y), (x + w, y + h)], outline=color, width=3)
180
 
181
- # Draw emotion label
182
- label_text = f"{emotion_label}: {confidence:.2f}"
 
183
 
184
  # Calculate text size for background
185
- bbox = draw.textbbox((0, 0), label_text, font=font)
186
- text_width = bbox[2] - bbox[0]
187
- text_height = bbox[3] - bbox[1]
 
188
 
189
  # Draw background for text
190
  draw.rectangle(
191
- [(x, y - text_height - 5), (x + text_width + 10, y)],
192
  fill=color
193
  )
194
 
195
- # Draw text
196
- draw.text((x + 5, y - text_height - 2), label_text, fill='white', font=font)
 
 
 
197
 
198
  return image
199
  except Exception as e:
200
  logger.error(f"Error drawing results: {e}")
201
  return image
202
 
203
- def process_single_image(image: Image.Image) -> Tuple[Image.Image, str]:
204
- """Process a single image for emotion detection"""
205
  try:
206
  if image is None:
207
  return None, "No image provided"
@@ -209,80 +304,44 @@ def process_single_image(image: Image.Image) -> Tuple[Image.Image, str]:
209
  # Convert PIL to numpy array
210
  image_np = np.array(image)
211
 
212
- # Detect faces
213
- faces = detect_faces(image_np)
214
 
215
  if not faces:
216
- return image, "No faces detected in the image"
217
-
218
- # Process each face
219
- emotions_list = []
220
- for (x, y, w, h) in faces:
221
- # Extract face region
222
- face_region = image.crop((x, y, x + w, y + h))
223
-
224
- # Predict emotion
225
- emotions = predict_emotion(face_region)
226
- emotions_list.append(emotions)
227
-
228
- # Draw results
229
- result_image = draw_emotion_results(image.copy(), faces, emotions_list)
230
-
231
- # Create summary text
232
- summary_lines = [f"Detected {len(faces)} face(s):"]
233
- for i, emotions in enumerate(emotions_list):
234
- top_emotion = max(emotions, key=lambda x: x['score'])
235
- summary_lines.append(f"Face {i+1}: {top_emotion['label']} ({top_emotion['score']:.2f})")
236
-
237
- summary = "\n".join(summary_lines)
238
-
239
- return result_image, summary
240
-
241
- except Exception as e:
242
- logger.error(f"Error processing image: {e}")
243
- return image, f"Error processing image: {str(e)}"
244
-
245
- def process_webcam_frame(image: Image.Image, confidence_threshold: float = 0.5) -> Tuple[Image.Image, str]:
246
- """Process webcam frame for emotion detection with confidence threshold"""
247
- try:
248
- if image is None:
249
- return None, "πŸ“· No image from camera"
250
-
251
- # Convert PIL to numpy array
252
- image_np = np.array(image)
253
-
254
- # Detect faces
255
- faces = detect_faces(image_np)
256
-
257
- if not faces:
258
- return image, "πŸ‘€ No faces detected in the camera feed"
259
 
260
  # Process each face
261
  emotions_list = []
262
  valid_faces = []
263
 
264
  for (x, y, w, h) in faces:
265
- # Extract face region
266
- face_region = image.crop((x, y, x + w, y + h))
 
 
 
 
 
 
267
 
268
  # Predict emotion
269
  emotions = predict_emotion(face_region)
270
 
271
- # Filter by confidence threshold
272
- filtered_emotions = [e for e in emotions if e['score'] >= confidence_threshold]
273
 
274
- if filtered_emotions:
275
  emotions_list.append(emotions)
276
  valid_faces.append((x, y, w, h))
277
 
278
  if not valid_faces:
279
- return image, f"🎯 Faces detected but no emotions above {confidence_threshold:.1f} confidence threshold"
280
 
281
  # Draw results
282
- result_image = draw_emotion_results(image.copy(), valid_faces, emotions_list)
283
 
284
- # Create detailed summary text
285
- summary_lines = [f"🎯 **Detected {len(valid_faces)} face(s) with high confidence:**\n"]
286
 
287
  for i, emotions in enumerate(emotions_list):
288
  # Sort emotions by confidence
@@ -295,14 +354,18 @@ def process_webcam_frame(image: Image.Image, confidence_threshold: float = 0.5)
295
  'happy': '😊', 'sad': '😒', 'surprise': '😲', 'neutral': '😐'
296
  }.get(top_emotion['label'], '😐')
297
 
298
- summary_lines.append(f"**Face {i+1}:** {emotion_emoji} {top_emotion['label'].title()} ({top_emotion['score']:.3f})")
299
 
300
  # Add top 3 emotions for detailed analysis
301
  if len(sorted_emotions) > 1:
302
- summary_lines.append(" πŸ“Š Other emotions:")
303
  for emotion in sorted_emotions[1:4]: # Top 3 others
304
  if emotion['score'] >= confidence_threshold:
305
- summary_lines.append(f" β€’ {emotion['label'].title()}: {emotion['score']:.3f}")
 
 
 
 
306
  summary_lines.append("")
307
 
308
  summary = "\n".join(summary_lines)
@@ -310,8 +373,8 @@ def process_webcam_frame(image: Image.Image, confidence_threshold: float = 0.5)
310
  return result_image, summary
311
 
312
  except Exception as e:
313
- logger.error(f"Error processing webcam frame: {e}")
314
- return image, f"❌ Error processing frame: {str(e)}"
315
 
316
  def analyze_emotions_batch(files) -> str:
317
  """Analyze emotions in multiple uploaded files"""
@@ -329,11 +392,11 @@ def analyze_emotions_batch(files) -> str:
329
  # Convert PIL to numpy array
330
  image_np = np.array(image)
331
 
332
- # Detect faces
333
- faces = detect_faces(image_np)
334
 
335
  if not faces:
336
- all_results.append(f"File {idx+1} ({file.name}): No faces detected")
337
  continue
338
 
339
  # Process each face
@@ -345,12 +408,12 @@ def analyze_emotions_batch(files) -> str:
345
  # Predict emotion
346
  emotions = predict_emotion(face_region)
347
  top_emotion = max(emotions, key=lambda x: x['score'])
348
- image_emotions.append(f"{top_emotion['label']} ({top_emotion['score']:.2f})")
349
 
350
- all_results.append(f"File {idx+1} ({file.name}): {', '.join(image_emotions)}")
351
 
352
  except Exception as e:
353
- all_results.append(f"File {idx+1}: Error processing - {str(e)}")
354
 
355
  return "\n".join(all_results)
356
 
@@ -367,22 +430,31 @@ def get_emotion_statistics(image: Image.Image) -> str:
367
  # Convert PIL to numpy array
368
  image_np = np.array(image)
369
 
370
- # Detect faces
371
- faces = detect_faces(image_np)
372
 
373
  if not faces:
374
- return "No faces detected"
375
 
376
  # Collect all emotions
377
  all_emotions = {}
 
378
 
379
- for (x, y, w, h) in faces:
380
  # Extract face region
381
  face_region = image.crop((x, y, x + w, y + h))
382
 
383
  # Predict emotion
384
  emotions = predict_emotion(face_region)
385
 
 
 
 
 
 
 
 
 
386
  for emotion_data in emotions:
387
  emotion = emotion_data['label']
388
  score = emotion_data['score']
@@ -392,51 +464,57 @@ def get_emotion_statistics(image: Image.Image) -> str:
392
  all_emotions[emotion].append(score)
393
 
394
  # Calculate statistics
395
- stats_lines = [f"**Emotion Analysis for {len(faces)} face(s):**\n"]
396
 
397
- for emotion, scores in all_emotions.items():
398
- avg_score = np.mean(scores)
399
- max_score = np.max(scores)
400
- count = len(scores)
 
401
 
402
- stats_lines.append(f"**{emotion.title()}:**")
403
- stats_lines.append(f" - Average confidence: {avg_score:.3f}")
404
- stats_lines.append(f" - Maximum confidence: {max_score:.3f}")
405
- stats_lines.append(f" - Detections: {count}")
 
406
  stats_lines.append("")
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  return "\n".join(stats_lines)
409
 
410
  except Exception as e:
411
  logger.error(f"Error calculating statistics: {e}")
412
- return f"Error calculating statistics: {str(e)}"
413
 
414
- # Create Gradio interface
415
  def create_interface():
416
- # Custom CSS for modern styling
417
  custom_css = """
418
  .main-header {
419
  text-align: center;
420
  color: #2563eb;
421
  margin-bottom: 2rem;
422
  }
423
- .emotion-box {
424
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
425
- color: white;
426
- padding: 1rem;
427
- border-radius: 0.5rem;
428
- margin: 1rem 0;
429
- }
430
- .stats-box {
431
- background: #f8fafc;
432
- border: 1px solid #e2e8f0;
433
- border-radius: 0.5rem;
434
- padding: 1rem;
435
  }
436
  """
437
 
438
  with gr.Blocks(
439
- title="Live Face Emotion Detection",
440
  theme=gr.themes.Soft(),
441
  css=custom_css
442
  ) as iface:
@@ -444,103 +522,70 @@ def create_interface():
444
  # Header
445
  gr.Markdown(
446
  """
447
- # 😊 Live Face Emotion Detection
448
 
449
- ### Real-time emotion recognition powered by deep learning
450
 
451
- This tool uses a fine-tuned model to detect and classify emotions in faces. It can identify
452
- 7 different emotions: **angry**, **disgust**, **fear**, **happy**, **sad**, **surprise**, and **neutral**.
453
  """,
454
  elem_classes=["main-header"]
455
  )
456
 
457
- with gr.Tab("πŸ“· Single Image Analysis"):
458
  with gr.Row():
459
  with gr.Column(scale=1):
460
- single_image_input = gr.Image(
461
  label="Upload Image",
462
  type="pil",
463
  height=400
464
  )
465
- analyze_single_btn = gr.Button("Analyze Emotions", variant="primary", size="lg")
466
-
467
- with gr.Column(scale=1):
468
- single_image_output = gr.Image(
469
- label="Emotion Detection Results",
470
- height=400
471
- )
472
- single_result_text = gr.Textbox(
473
- label="Detection Summary",
474
- lines=5,
475
- show_copy_button=True
476
- )
477
-
478
- with gr.Tab("πŸŽ₯ Live Webcam Detection"):
479
- gr.Markdown(
480
- """
481
- ### Real-time Emotion Detection
482
- Use your camera for live emotion detection! The system will automatically process frames as you capture them.
483
- """
484
- )
485
-
486
- with gr.Row():
487
- with gr.Column(scale=1):
488
- # Use Image component with webcam source for live detection
489
- webcam_input = gr.Image(
490
- label="πŸ“Ή Live Camera Feed",
491
- type="pil",
492
- height=400,
493
- sources=["webcam"],
494
- streaming=False # We'll handle updates manually
495
- )
496
 
497
  with gr.Row():
498
- start_detection_btn = gr.Button("πŸ”΄ Start Live Detection", variant="primary", size="lg")
499
- stop_detection_btn = gr.Button("⏹️ Stop Detection", variant="secondary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
 
501
- # Auto-detection toggle
502
- auto_detect_checkbox = gr.Checkbox(
503
- label="πŸ”„ Auto-detect on camera change",
504
- value=True,
505
- info="Automatically analyze when camera input changes"
506
- )
507
 
508
  with gr.Column(scale=1):
509
- webcam_output = gr.Image(
510
- label="🎯 Emotion Detection Results",
511
  height=400
512
  )
513
-
514
- webcam_result_text = gr.Textbox(
515
- label="πŸ“Š Live Detection Results",
516
- lines=6,
517
- show_copy_button=True,
518
- placeholder="Start detection to see emotion analysis..."
519
- )
520
-
521
- # Confidence threshold slider
522
- confidence_threshold = gr.Slider(
523
- minimum=0.1,
524
- maximum=1.0,
525
- value=0.5,
526
- step=0.1,
527
- label="🎚️ Confidence Threshold",
528
- info="Minimum confidence to display emotions"
529
  )
530
 
531
  with gr.Tab("πŸ“Š Detailed Statistics"):
532
  with gr.Row():
533
  with gr.Column(scale=1):
534
  stats_image_input = gr.Image(
535
- label="Upload Image for Analysis",
536
  type="pil",
537
  height=400
538
  )
539
- analyze_stats_btn = gr.Button("Generate Statistics", variant="primary", size="lg")
540
 
541
  with gr.Column(scale=1):
542
  stats_output = gr.Markdown(
543
- value="Upload an image and click 'Generate Statistics' to see detailed emotion analysis...",
544
  label="Emotion Statistics"
545
  )
546
 
@@ -551,169 +596,128 @@ def create_interface():
551
  file_count="multiple",
552
  file_types=["image"]
553
  )
554
- batch_process_btn = gr.Button("Process Batch", variant="primary", size="lg")
555
  batch_results_output = gr.Textbox(
556
  label="Batch Processing Results",
557
- lines=10,
558
  show_copy_button=True
559
  )
560
 
561
- with gr.Tab("πŸ“š About & Model Info"):
562
  gr.Markdown(
563
  """
564
- ## About This Model
565
-
566
- This face emotion detection system uses a fine-tuned deep learning model specifically trained
567
- for emotion recognition. The model can detect 7 different emotional states with high accuracy.
568
 
569
- ### Supported Emotions
 
 
 
 
570
 
571
- - 😠 **Angry** - Expressions of anger, frustration, or annoyance
572
- - 🀒 **Disgust** - Expressions of revulsion or distaste
573
- - 😨 **Fear** - Expressions of fear, anxiety, or worry
574
- - 😊 **Happy** - Expressions of joy, contentment, or pleasure
575
- - 😒 **Sad** - Expressions of sadness, sorrow, or melancholy
576
- - 😲 **Surprise** - Expressions of surprise, shock, or amazement
577
- - 😐 **Neutral** - Calm, neutral expressions with no strong emotion
578
 
579
- ### Technical Details
 
 
 
580
 
581
- - **Model:** Fine-tuned emotion classification model
582
- - **Architecture:** Deep convolutional neural network
583
- - **Training:** Specialized dataset for facial emotion recognition
584
- - **Face Detection:** OpenCV Haar Cascade classifier
585
- - **Real-time Processing:** Optimized for live webcam inference
586
 
587
- ### Use Cases
588
-
589
- - **Human-Computer Interaction:** Emotion-aware interfaces
590
- - **Market Research:** Analyze customer emotional responses
591
- - **Healthcare:** Monitor patient emotional states
592
- - **Education:** Assess student engagement and understanding
593
- - **Entertainment:** Emotion-responsive gaming and media
594
- - **Security:** Detect emotional distress or suspicious behavior
595
 
596
- ### Privacy & Ethics
597
 
598
- - All processing is done locally in your browser
599
- - No images are stored or transmitted to external servers
600
- - Use responsibly and respect privacy in all applications
601
- - Consider bias and fairness in emotion detection systems
 
602
 
603
- ### Performance Tips
604
 
605
- - Ensure good lighting for best results
606
- - Face should be clearly visible and unobstructed
607
- - Works best with frontal face views
608
- - Multiple faces in one image are supported
609
 
610
  ---
611
 
612
- **Model Repository:** [abhilash88/face-emotion-detection](https://huggingface.co/abhilash88/face-emotion-detection)
613
-
614
- Made with ❀️ for emotion AI research and applications
615
  """
616
  )
617
 
618
  # Event handlers
619
- analyze_single_btn.click(
620
- fn=process_single_image,
621
- inputs=single_image_input,
622
- outputs=[single_image_output, single_result_text],
623
- api_name="analyze_single_image"
624
- )
625
-
626
- # Live webcam detection handlers
627
- start_detection_btn.click(
628
- fn=process_webcam_frame,
629
- inputs=[webcam_input, confidence_threshold],
630
- outputs=[webcam_output, webcam_result_text],
631
- api_name="start_detection"
632
- )
633
-
634
- # Auto-detection when camera input changes
635
- webcam_input.change(
636
- fn=lambda img, auto, threshold: process_webcam_frame(img, threshold) if auto and img is not None else (None, "Auto-detection disabled or no image"),
637
- inputs=[webcam_input, auto_detect_checkbox, confidence_threshold],
638
- outputs=[webcam_output, webcam_result_text]
639
- )
640
-
641
- stop_detection_btn.click(
642
- fn=lambda: (None, "πŸ›‘ Detection stopped"),
643
- outputs=[webcam_output, webcam_result_text]
644
  )
645
 
646
  analyze_stats_btn.click(
647
  fn=get_emotion_statistics,
648
  inputs=stats_image_input,
649
  outputs=stats_output,
650
- api_name="get_emotion_statistics"
 
 
 
 
 
 
 
651
  )
652
 
653
- # Example images - Add more diverse examples
654
  gr.Examples(
655
  examples=[
656
- ["https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face"], # Happy person
657
- ["https://images.unsplash.com/photo-1554151228-14d9def656e4?w=400&h=400&fit=crop&crop=face"], # Smiling woman
658
- ["https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=400&fit=crop&crop=face"], # Serious man
659
- ["https://images.unsplash.com/photo-1552374196-c4e7ffc6e126?w=400&h=400&fit=crop&crop=face"], # Surprised expression
660
- ["https://images.unsplash.com/photo-1619895862022-09114b41f16f?w=400&h=400&fit=crop&crop=face"], # Neutral expression
661
- ["https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400&h=400&fit=crop&crop=face"], # Confident look
662
- ["https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop&crop=face"], # Friendly smile
663
- ["https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop&crop=face"], # Professional headshot
664
  ],
665
- inputs=single_image_input,
666
- label="πŸ–ΌοΈ Try these example images to test different emotions",
667
- cache_examples=False
668
  )
669
 
670
  return iface
671
 
672
  # Initialize and launch
673
  if __name__ == "__main__":
674
- # Load models
675
- logger.info("Initializing Face Emotion Detection System...")
676
 
677
  if load_models():
678
  logger.info("Models loaded successfully!")
679
 
680
- # Test the model with a simple image
681
- try:
682
- # Create a test image
683
- test_image = Image.new('RGB', (224, 224), color='white')
684
- test_result = predict_emotion(test_image)
685
- logger.info(f"Model test successful. Result format: {type(test_result)}")
686
- logger.info(f"Test result: {test_result}")
687
- except Exception as e:
688
- logger.error(f"Model test failed: {e}")
689
-
690
- # Create interface
691
  iface = create_interface()
692
 
693
- # Launch
694
  iface.launch(
695
  share=False,
696
  show_error=True,
697
  server_name="0.0.0.0",
698
  server_port=7860,
699
- favicon_path=None,
700
  show_api=True
701
  )
702
  else:
703
  logger.error("Failed to load models. Please check your model configuration.")
704
- # Create a simple interface to show the error
705
  with gr.Blocks() as error_iface:
706
  gr.Markdown(
707
  """
708
  # ⚠️ Model Loading Error
709
 
710
- The emotion detection model failed to load. This could be due to:
711
-
712
- 1. Network connectivity issues
713
- 2. Model compatibility problems
714
- 3. Missing dependencies
715
 
716
- Please check the logs for more details.
 
 
717
  """
718
  )
719
 
 
15
  # Model configuration
16
  MODEL_NAME = "abhilash88/face-emotion-detection"
17
 
18
+ # Emotion labels mapping
19
  EMOTION_LABELS = {
20
  'LABEL_0': 'angry',
21
  'LABEL_1': 'disgust',
 
76
 
77
  logger.info("Emotion detection model loaded successfully")
78
 
 
 
 
 
 
79
  # Load OpenCV face cascade
80
  face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
81
 
 
91
  logger.error(f"Error loading models: {e}")
92
  return False
93
 
94
+ def detect_faces_improved(image: np.ndarray, min_face_size: int = 80) -> List[Tuple[int, int, int, int]]:
95
+ """
96
+ Improved face detection with better parameters to reduce false positives
97
+ and merge overlapping detections
98
+ """
99
  try:
100
  gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
101
+
102
+ # Use more strict parameters to reduce false positives
103
  faces = face_cascade.detectMultiScale(
104
  gray,
105
+ scaleFactor=1.05, # Smaller scale factor for more careful detection
106
+ minNeighbors=8, # Higher min neighbors to be more strict
107
+ minSize=(min_face_size, min_face_size), # Larger minimum size
108
+ maxSize=(int(min(image.shape[:2]) * 0.8), int(min(image.shape[:2]) * 0.8)), # Maximum size
109
+ flags=cv2.CASCADE_SCALE_IMAGE | cv2.CASCADE_DO_CANNY_PRUNING
110
  )
111
+
112
+ if len(faces) == 0:
113
+ return []
114
+
115
+ # Convert to list and merge overlapping detections
116
+ faces_list = faces.tolist()
117
+ merged_faces = merge_overlapping_faces(faces_list)
118
+
119
+ # Filter faces that are too small relative to image size
120
+ image_area = image.shape[0] * image.shape[1]
121
+ filtered_faces = []
122
+
123
+ for (x, y, w, h) in merged_faces:
124
+ face_area = w * h
125
+ # Face should be at least 0.5% of image area but not more than 80%
126
+ if 0.005 < (face_area / image_area) < 0.8:
127
+ # Additional validation: check aspect ratio (faces are roughly square)
128
+ aspect_ratio = w / h
129
+ if 0.7 <= aspect_ratio <= 1.4: # Allow some variance but not extreme rectangles
130
+ filtered_faces.append((x, y, w, h))
131
+
132
+ return filtered_faces
133
+
134
  except Exception as e:
135
  logger.error(f"Error detecting faces: {e}")
136
  return []
137
 
138
+ def merge_overlapping_faces(faces: List[Tuple[int, int, int, int]], overlap_threshold: float = 0.3) -> List[Tuple[int, int, int, int]]:
139
+ """
140
+ Merge overlapping face detections to avoid duplicates
141
+ """
142
+ if len(faces) <= 1:
143
+ return faces
144
+
145
+ # Calculate IoU (Intersection over Union) for all pairs
146
+ merged = []
147
+ used = [False] * len(faces)
148
+
149
+ for i in range(len(faces)):
150
+ if used[i]:
151
+ continue
152
+
153
+ current_face = faces[i]
154
+ merged_face = list(current_face)
155
+ count = 1
156
+ used[i] = True
157
+
158
+ for j in range(i + 1, len(faces)):
159
+ if used[j]:
160
+ continue
161
+
162
+ if calculate_iou(current_face, faces[j]) > overlap_threshold:
163
+ # Merge by averaging coordinates
164
+ merged_face[0] = (merged_face[0] * count + faces[j][0]) // (count + 1)
165
+ merged_face[1] = (merged_face[1] * count + faces[j][1]) // (count + 1)
166
+ merged_face[2] = (merged_face[2] * count + faces[j][2]) // (count + 1)
167
+ merged_face[3] = (merged_face[3] * count + faces[j][3]) // (count + 1)
168
+ count += 1
169
+ used[j] = True
170
+
171
+ merged.append(tuple(merged_face))
172
+
173
+ return merged
174
+
175
+ def calculate_iou(box1: Tuple[int, int, int, int], box2: Tuple[int, int, int, int]) -> float:
176
+ """Calculate Intersection over Union of two bounding boxes"""
177
+ x1, y1, w1, h1 = box1
178
+ x2, y2, w2, h2 = box2
179
+
180
+ # Calculate intersection
181
+ x_left = max(x1, x2)
182
+ y_top = max(y1, y2)
183
+ x_right = min(x1 + w1, x2 + w2)
184
+ y_bottom = min(y1 + h1, y2 + h2)
185
+
186
+ if x_right < x_left or y_bottom < y_top:
187
+ return 0.0
188
+
189
+ intersection = (x_right - x_left) * (y_bottom - y_top)
190
+
191
+ # Calculate union
192
+ area1 = w1 * h1
193
+ area2 = w2 * h2
194
+ union = area1 + area2 - intersection
195
+
196
+ return intersection / union if union > 0 else 0.0
197
+
198
  def predict_emotion(face_image: Image.Image) -> List[Dict]:
199
  """Predict emotion for a single face"""
200
  try:
 
202
  logger.warning("Emotion classifier not loaded, returning neutral")
203
  return [{"label": "neutral", "score": 1.0}]
204
 
205
+ # Resize image for better performance and consistency
206
+ face_image = face_image.resize((224, 224))
 
207
 
208
  # The pipeline returns results in different formats depending on configuration
209
  results = emotion_classifier(face_image)
 
237
  logger.error(f"Error predicting emotion: {e}")
238
  return [{"label": "neutral", "score": 1.0}]
239
 
240
+ def draw_emotion_results(image: Image.Image, faces: List, emotions: List, confidence_threshold: float = 0.5) -> Image.Image:
241
  """Draw bounding boxes and emotion labels on the image"""
242
  try:
243
  draw = ImageDraw.Draw(image)
244
 
245
  # Try to load a font, fallback to default if not available
246
  try:
247
+ font = ImageFont.truetype("arial.ttf", 20)
248
  except:
249
+ try:
250
+ font = ImageFont.truetype("DejaVuSans.ttf", 20)
251
+ except:
252
+ font = ImageFont.load_default()
253
 
254
  for i, (x, y, w, h) in enumerate(faces):
255
  if i < len(emotions):
256
+ # Get top emotion above threshold
257
+ valid_emotions = [e for e in emotions[i] if e['score'] >= confidence_threshold]
258
+ if not valid_emotions:
259
+ continue
260
+
261
+ top_emotion = max(valid_emotions, key=lambda x: x['score'])
262
  emotion_label = top_emotion['label']
263
  confidence = top_emotion['score']
264
 
265
  # Get color for this emotion
266
  color = EMOTION_COLORS.get(emotion_label, '#FFFFFF')
267
 
268
+ # Draw bounding box with thicker line
269
+ draw.rectangle([(x, y), (x + w, y + h)], outline=color, width=4)
270
 
271
+ # Draw emotion label with better formatting
272
+ label_text = f"{emotion_label.upper()}"
273
+ confidence_text = f"{confidence:.1%}"
274
 
275
  # Calculate text size for background
276
+ bbox1 = draw.textbbox((0, 0), label_text, font=font)
277
+ bbox2 = draw.textbbox((0, 0), confidence_text, font=font)
278
+ text_width = max(bbox1[2] - bbox1[0], bbox2[2] - bbox2[0]) + 20
279
+ text_height = (bbox1[3] - bbox1[1]) + (bbox2[3] - bbox2[1]) + 15
280
 
281
  # Draw background for text
282
  draw.rectangle(
283
+ [(x, y - text_height - 10), (x + text_width, y)],
284
  fill=color
285
  )
286
 
287
+ # Draw emotion label
288
+ draw.text((x + 10, y - text_height - 5), label_text, fill='white', font=font)
289
+
290
+ # Draw confidence
291
+ draw.text((x + 10, y - text_height + 20), confidence_text, fill='white', font=font)
292
 
293
  return image
294
  except Exception as e:
295
  logger.error(f"Error drawing results: {e}")
296
  return image
297
 
298
+ def process_image(image: Image.Image, confidence_threshold: float = 0.5, min_face_size: int = 80) -> Tuple[Image.Image, str]:
299
+ """Process an image for emotion detection with improved face detection"""
300
  try:
301
  if image is None:
302
  return None, "No image provided"
 
304
  # Convert PIL to numpy array
305
  image_np = np.array(image)
306
 
307
+ # Detect faces with improved method
308
+ faces = detect_faces_improved(image_np, min_face_size)
309
 
310
  if not faces:
311
+ return image, "❌ No faces detected in the image. Try adjusting the minimum face size or use an image with clearer faces."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
  # Process each face
314
  emotions_list = []
315
  valid_faces = []
316
 
317
  for (x, y, w, h) in faces:
318
+ # Extract face region with some padding
319
+ padding = max(10, min(w, h) // 10)
320
+ x_pad = max(0, x - padding)
321
+ y_pad = max(0, y - padding)
322
+ w_pad = min(image.width - x_pad, w + 2 * padding)
323
+ h_pad = min(image.height - y_pad, h + 2 * padding)
324
+
325
+ face_region = image.crop((x_pad, y_pad, x_pad + w_pad, y_pad + h_pad))
326
 
327
  # Predict emotion
328
  emotions = predict_emotion(face_region)
329
 
330
+ # Check if any emotion meets the confidence threshold
331
+ valid_emotions = [e for e in emotions if e['score'] >= confidence_threshold]
332
 
333
+ if valid_emotions:
334
  emotions_list.append(emotions)
335
  valid_faces.append((x, y, w, h))
336
 
337
  if not valid_faces:
338
+ return image, f"⚠️ {len(faces)} face(s) detected but no emotions above {confidence_threshold:.1f} confidence threshold. Try lowering the threshold."
339
 
340
  # Draw results
341
+ result_image = draw_emotion_results(image.copy(), valid_faces, emotions_list, confidence_threshold)
342
 
343
+ # Create summary text
344
+ summary_lines = [f"βœ… **Successfully detected {len(valid_faces)} face(s) with confident emotion predictions:**\n"]
345
 
346
  for i, emotions in enumerate(emotions_list):
347
  # Sort emotions by confidence
 
354
  'happy': '😊', 'sad': '😒', 'surprise': '😲', 'neutral': '😐'
355
  }.get(top_emotion['label'], '😐')
356
 
357
+ summary_lines.append(f"**Face {i+1}:** {emotion_emoji} **{top_emotion['label'].title()}** ({top_emotion['score']:.1%} confidence)")
358
 
359
  # Add top 3 emotions for detailed analysis
360
  if len(sorted_emotions) > 1:
361
+ summary_lines.append(" πŸ“Š Other detected emotions:")
362
  for emotion in sorted_emotions[1:4]: # Top 3 others
363
  if emotion['score'] >= confidence_threshold:
364
+ emoji = {
365
+ 'angry': '😠', 'disgust': '🀒', 'fear': '😨',
366
+ 'happy': '😊', 'sad': '😒', 'surprise': '😲', 'neutral': '😐'
367
+ }.get(emotion['label'], '😐')
368
+ summary_lines.append(f" β€’ {emoji} {emotion['label'].title()}: {emotion['score']:.1%}")
369
  summary_lines.append("")
370
 
371
  summary = "\n".join(summary_lines)
 
373
  return result_image, summary
374
 
375
  except Exception as e:
376
+ logger.error(f"Error processing image: {e}")
377
+ return image, f"❌ Error processing image: {str(e)}"
378
 
379
  def analyze_emotions_batch(files) -> str:
380
  """Analyze emotions in multiple uploaded files"""
 
392
  # Convert PIL to numpy array
393
  image_np = np.array(image)
394
 
395
+ # Detect faces with improved method
396
+ faces = detect_faces_improved(image_np)
397
 
398
  if not faces:
399
+ all_results.append(f"πŸ“ File {idx+1} ({file.name}): No faces detected")
400
  continue
401
 
402
  # Process each face
 
408
  # Predict emotion
409
  emotions = predict_emotion(face_region)
410
  top_emotion = max(emotions, key=lambda x: x['score'])
411
+ image_emotions.append(f"{top_emotion['label']} ({top_emotion['score']:.1%})")
412
 
413
+ all_results.append(f"πŸ“ File {idx+1} ({file.name}): {len(faces)} face(s) - {', '.join(image_emotions)}")
414
 
415
  except Exception as e:
416
+ all_results.append(f"πŸ“ File {idx+1}: Error processing - {str(e)}")
417
 
418
  return "\n".join(all_results)
419
 
 
430
  # Convert PIL to numpy array
431
  image_np = np.array(image)
432
 
433
+ # Detect faces with improved method
434
+ faces = detect_faces_improved(image_np)
435
 
436
  if not faces:
437
+ return "❌ No faces detected in the image"
438
 
439
  # Collect all emotions
440
  all_emotions = {}
441
+ face_details = []
442
 
443
+ for i, (x, y, w, h) in enumerate(faces):
444
  # Extract face region
445
  face_region = image.crop((x, y, x + w, y + h))
446
 
447
  # Predict emotion
448
  emotions = predict_emotion(face_region)
449
 
450
+ # Store face details
451
+ sorted_emotions = sorted(emotions, key=lambda x: x['score'], reverse=True)
452
+ face_details.append({
453
+ 'face_num': i + 1,
454
+ 'position': (x, y, w, h),
455
+ 'emotions': sorted_emotions
456
+ })
457
+
458
  for emotion_data in emotions:
459
  emotion = emotion_data['label']
460
  score = emotion_data['score']
 
464
  all_emotions[emotion].append(score)
465
 
466
  # Calculate statistics
467
+ stats_lines = [f"πŸ“Š **Detailed Emotion Analysis for {len(faces)} face(s):**\n"]
468
 
469
+ # Per-face breakdown
470
+ for face_detail in face_details:
471
+ stats_lines.append(f"### πŸ‘€ Face {face_detail['face_num']}:")
472
+ top_emotion = face_detail['emotions'][0]
473
+ stats_lines.append(f"**Primary emotion:** {top_emotion['label'].title()} ({top_emotion['score']:.1%})")
474
 
475
+ stats_lines.append("**All emotions detected:**")
476
+ for emotion in face_detail['emotions']:
477
+ bar_length = int(emotion['score'] * 20) # Scale to 20 chars
478
+ bar = "β–ˆ" * bar_length + "β–‘" * (20 - bar_length)
479
+ stats_lines.append(f" {emotion['label'].title()}: {bar} {emotion['score']:.1%}")
480
  stats_lines.append("")
481
 
482
+ # Overall statistics
483
+ if len(faces) > 1:
484
+ stats_lines.append("### πŸ“ˆ Overall Statistics:")
485
+ for emotion, scores in all_emotions.items():
486
+ avg_score = np.mean(scores)
487
+ max_score = np.max(scores)
488
+ count = len(scores)
489
+
490
+ stats_lines.append(f"**{emotion.title()}:**")
491
+ stats_lines.append(f" - Average confidence: {avg_score:.1%}")
492
+ stats_lines.append(f" - Maximum confidence: {max_score:.1%}")
493
+ stats_lines.append(f" - Faces showing this emotion: {count}/{len(faces)}")
494
+ stats_lines.append("")
495
+
496
  return "\n".join(stats_lines)
497
 
498
  except Exception as e:
499
  logger.error(f"Error calculating statistics: {e}")
500
+ return f"❌ Error calculating statistics: {str(e)}"
501
 
502
+ # Create simplified Gradio interface
503
  def create_interface():
 
504
  custom_css = """
505
  .main-header {
506
  text-align: center;
507
  color: #2563eb;
508
  margin-bottom: 2rem;
509
  }
510
+ .gradio-container {
511
+ max-width: 1200px;
512
+ margin: auto;
 
 
 
 
 
 
 
 
 
513
  }
514
  """
515
 
516
  with gr.Blocks(
517
+ title="Face Emotion Detection - Improved",
518
  theme=gr.themes.Soft(),
519
  css=custom_css
520
  ) as iface:
 
522
  # Header
523
  gr.Markdown(
524
  """
525
+ # 😊 Face Emotion Detection (Improved)
526
 
527
+ ### Accurate emotion recognition with enhanced face detection
528
 
529
+ This improved version includes better face detection algorithms to reduce false positives
530
+ and provides more accurate emotion classification for detected faces.
531
  """,
532
  elem_classes=["main-header"]
533
  )
534
 
535
+ with gr.Tab("πŸ–ΌοΈ Single Image Analysis"):
536
  with gr.Row():
537
  with gr.Column(scale=1):
538
+ image_input = gr.Image(
539
  label="Upload Image",
540
  type="pil",
541
  height=400
542
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
  with gr.Row():
545
+ confidence_slider = gr.Slider(
546
+ minimum=0.1,
547
+ maximum=1.0,
548
+ value=0.5,
549
+ step=0.1,
550
+ label="🎯 Confidence Threshold",
551
+ info="Minimum confidence to display emotions"
552
+ )
553
+
554
+ face_size_slider = gr.Slider(
555
+ minimum=30,
556
+ maximum=200,
557
+ value=80,
558
+ step=10,
559
+ label="πŸ‘€ Minimum Face Size",
560
+ info="Minimum face size (pixels) to detect"
561
+ )
562
 
563
+ analyze_btn = gr.Button("πŸ” Analyze Emotions", variant="primary", size="lg")
 
 
 
 
 
564
 
565
  with gr.Column(scale=1):
566
+ output_image = gr.Image(
567
+ label="Emotion Detection Results",
568
  height=400
569
  )
570
+ result_text = gr.Textbox(
571
+ label="Detection Results",
572
+ lines=8,
573
+ show_copy_button=True
 
 
 
 
 
 
 
 
 
 
 
 
574
  )
575
 
576
  with gr.Tab("πŸ“Š Detailed Statistics"):
577
  with gr.Row():
578
  with gr.Column(scale=1):
579
  stats_image_input = gr.Image(
580
+ label="Upload Image for Statistical Analysis",
581
  type="pil",
582
  height=400
583
  )
584
+ analyze_stats_btn = gr.Button("πŸ“ˆ Generate Detailed Statistics", variant="primary", size="lg")
585
 
586
  with gr.Column(scale=1):
587
  stats_output = gr.Markdown(
588
+ value="Upload an image and click 'Generate Detailed Statistics' to see comprehensive emotion analysis...",
589
  label="Emotion Statistics"
590
  )
591
 
 
596
  file_count="multiple",
597
  file_types=["image"]
598
  )
599
+ batch_process_btn = gr.Button("⚑ Process All Images", variant="primary", size="lg")
600
  batch_results_output = gr.Textbox(
601
  label="Batch Processing Results",
602
+ lines=15,
603
  show_copy_button=True
604
  )
605
 
606
+ with gr.Tab("ℹ️ About & Tips"):
607
  gr.Markdown(
608
  """
609
+ ## πŸ”§ Improvements Made
 
 
 
610
 
611
+ ### βœ… Enhanced Face Detection
612
+ - **Stricter parameters** to reduce false positives
613
+ - **Overlap detection** to merge duplicate face detections
614
+ - **Size filtering** to ignore unrealistic face sizes
615
+ - **Aspect ratio validation** to filter non-face rectangles
616
 
617
+ ### 🎯 Better Accuracy
618
+ - **Confidence thresholds** to filter uncertain predictions
619
+ - **Improved preprocessing** for better emotion recognition
620
+ - **Face padding** for better context in emotion detection
 
 
 
621
 
622
+ ### πŸš€ Performance Optimizations
623
+ - **Removed problematic live camera** feature
624
+ - **Streamlined interface** for better user experience
625
+ - **Better error handling** and user feedback
626
 
627
+ ## πŸ“š Supported Emotions
 
 
 
 
628
 
629
+ - 😠 **Angry** - Expressions of anger, frustration
630
+ - 🀒 **Disgust** - Expressions of revulsion or distaste
631
+ - 😨 **Fear** - Expressions of fear, anxiety
632
+ - 😊 **Happy** - Expressions of joy, contentment
633
+ - 😒 **Sad** - Expressions of sadness, sorrow
634
+ - 😲 **Surprise** - Expressions of surprise, amazement
635
+ - 😐 **Neutral** - Calm, neutral expressions
 
636
 
637
+ ## πŸ’‘ Tips for Best Results
638
 
639
+ 1. **Use clear, well-lit images** with visible faces
640
+ 2. **Adjust confidence threshold** if you get too many/few results
641
+ 3. **Modify minimum face size** based on your image resolution
642
+ 4. **Frontal face views** work better than profile shots
643
+ 5. **Avoid heavily shadowed or blurry faces**
644
 
645
+ ## πŸ”§ Troubleshooting
646
 
647
+ - **No faces detected?** Try lowering the minimum face size
648
+ - **Too many false detections?** Increase the minimum face size or confidence threshold
649
+ - **Missing obvious faces?** Lower the confidence threshold
650
+ - **Multiple boxes on same face?** The system should automatically merge them now
651
 
652
  ---
653
 
654
+ **Model:** [abhilash88/face-emotion-detection](https://huggingface.co/abhilash88/face-emotion-detection)
 
 
655
  """
656
  )
657
 
658
  # Event handlers
659
+ analyze_btn.click(
660
+ fn=process_image,
661
+ inputs=[image_input, confidence_slider, face_size_slider],
662
+ outputs=[output_image, result_text],
663
+ api_name="analyze_image"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  )
665
 
666
  analyze_stats_btn.click(
667
  fn=get_emotion_statistics,
668
  inputs=stats_image_input,
669
  outputs=stats_output,
670
+ api_name="get_statistics"
671
+ )
672
+
673
+ batch_process_btn.click(
674
+ fn=analyze_emotions_batch,
675
+ inputs=batch_images_input,
676
+ outputs=batch_results_output,
677
+ api_name="batch_process"
678
  )
679
 
680
+ # Example images
681
  gr.Examples(
682
  examples=[
683
+ "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face",
684
+ "https://images.unsplash.com/photo-1554151228-14d9def656e4?w=400&h=400&fit=crop&crop=face",
685
+ "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&h=400&fit=crop&crop=face",
 
 
 
 
 
686
  ],
687
+ inputs=image_input,
688
+ label="πŸ–ΌοΈ Try these example images"
 
689
  )
690
 
691
  return iface
692
 
693
  # Initialize and launch
694
  if __name__ == "__main__":
695
+ logger.info("Initializing Improved Face Emotion Detection System...")
 
696
 
697
  if load_models():
698
  logger.info("Models loaded successfully!")
699
 
 
 
 
 
 
 
 
 
 
 
 
700
  iface = create_interface()
701
 
 
702
  iface.launch(
703
  share=False,
704
  show_error=True,
705
  server_name="0.0.0.0",
706
  server_port=7860,
 
707
  show_api=True
708
  )
709
  else:
710
  logger.error("Failed to load models. Please check your model configuration.")
 
711
  with gr.Blocks() as error_iface:
712
  gr.Markdown(
713
  """
714
  # ⚠️ Model Loading Error
715
 
716
+ The emotion detection model failed to load. Please check:
 
 
 
 
717
 
718
+ 1. Network connectivity
719
+ 2. Model dependencies
720
+ 3. System logs for details
721
  """
722
  )
723