Zakha123-cyber
commited on
Commit
·
2134ff4
1
Parent(s):
9b30e86
Remove frame skipping and debug video output to save storage
Browse filesMAJOR CHANGES - Process full video quality:
- Remove adaptive frame sampling from all 3 services
- Process ALL frames (no frame skip) for accurate analysis
- Remove annotated video output to save server storage
- Remove debug_videos section from API response
Changes by service:
1. facial_expression.py: Process all frames, no video output
2. eye_tracking.py: Process all frames, remove _annotate_frame usage
3. gesture_detection.py: Process all frames
4. video_processor.py: Remove debug_videos collection
Benefits:
- More accurate analysis (100% frames processed)
- Save storage space (no debug videos saved)
- Cleaner API response (no debug_videos field)
Trade-off: Longer processing time but better accuracy
app/services/eye_tracking.py
CHANGED
|
@@ -700,9 +700,7 @@ class EyeTrackingService:
|
|
| 700 |
def analyze_video(
|
| 701 |
self,
|
| 702 |
video_path: str,
|
| 703 |
-
progress_callback: Optional[callable] = None
|
| 704 |
-
save_annotated_video: bool = True, # Enable by default for debugging
|
| 705 |
-
output_path: Optional[str] = None
|
| 706 |
) -> Dict[str, Any]:
|
| 707 |
"""
|
| 708 |
Analyze video for eye contact
|
|
@@ -710,15 +708,12 @@ class EyeTrackingService:
|
|
| 710 |
Args:
|
| 711 |
video_path: Path to video file
|
| 712 |
progress_callback: Optional callback for progress updates
|
| 713 |
-
save_annotated_video: Whether to save annotated video (default: True)
|
| 714 |
-
output_path: Path for output video (default: auto-generated in temp/debug/)
|
| 715 |
|
| 716 |
Returns:
|
| 717 |
-
Dict containing eye tracking analysis results
|
| 718 |
"""
|
| 719 |
try:
|
| 720 |
logger.info(f"Analyzing video with Eye Tracking Service: {video_path}")
|
| 721 |
-
logger.info(f"Save annotated video: {save_annotated_video}")
|
| 722 |
|
| 723 |
cap = cv.VideoCapture(video_path)
|
| 724 |
if not cap.isOpened():
|
|
@@ -731,22 +726,6 @@ class EyeTrackingService:
|
|
| 731 |
total_frames = int(cap.get(cv.CAP_PROP_FRAME_COUNT))
|
| 732 |
|
| 733 |
logger.info(f"Video properties: {width}x{height} @ {fps}FPS, {total_frames} frames")
|
| 734 |
-
logger.info(f"Video properties: {width}x{height} @ {fps}FPS, {total_frames} frames")
|
| 735 |
-
|
| 736 |
-
# Setup video writer if needed
|
| 737 |
-
out = None
|
| 738 |
-
if save_annotated_video:
|
| 739 |
-
if output_path is None:
|
| 740 |
-
import os
|
| 741 |
-
# Generate unique filename based on input video
|
| 742 |
-
video_basename = os.path.basename(video_path)
|
| 743 |
-
video_name = os.path.splitext(video_basename)[0]
|
| 744 |
-
os.makedirs('temp/debug', exist_ok=True)
|
| 745 |
-
output_path = f'temp/debug/{video_name}_eye_tracking.mp4'
|
| 746 |
-
|
| 747 |
-
fourcc = cv.VideoWriter_fourcc(*'mp4v')
|
| 748 |
-
out = cv.VideoWriter(output_path, fourcc, fps, (width, height))
|
| 749 |
-
logger.info(f"📹 Annotated video will be saved to: {output_path}")
|
| 750 |
|
| 751 |
# Initialize counters
|
| 752 |
frame_count = 0
|
|
@@ -812,11 +791,6 @@ class EyeTrackingService:
|
|
| 812 |
if gaze_pos != 'CENTER' and gaze_pos != 'UNKNOWN':
|
| 813 |
gaze_away_frames += 1
|
| 814 |
|
| 815 |
-
# Annotate frame if video output enabled
|
| 816 |
-
if save_annotated_video and out is not None:
|
| 817 |
-
annotated_frame = self._annotate_frame(frame, result, frame_count, blink_count, gaze_pos)
|
| 818 |
-
out.write(annotated_frame)
|
| 819 |
-
|
| 820 |
# Log every 100 frames
|
| 821 |
if frame_count % 100 == 0:
|
| 822 |
logger.info(f"Processed {frame_count}/{total_frames} frames | "
|
|
@@ -826,9 +800,6 @@ class EyeTrackingService:
|
|
| 826 |
f"Blinks: {blink_count}")
|
| 827 |
|
| 828 |
cap.release()
|
| 829 |
-
if out is not None:
|
| 830 |
-
out.release()
|
| 831 |
-
logger.info(f"✓ Annotated video saved: {output_path}")
|
| 832 |
|
| 833 |
# Calculate metrics
|
| 834 |
duration = frame_count / fps
|
|
@@ -886,11 +857,6 @@ class EyeTrackingService:
|
|
| 886 |
'debug_stats': debug_stats
|
| 887 |
}
|
| 888 |
|
| 889 |
-
# Always include annotated video path if saved
|
| 890 |
-
if save_annotated_video and output_path:
|
| 891 |
-
result['annotated_video_path'] = output_path
|
| 892 |
-
logger.info(f"✓ Annotated video saved: {output_path}")
|
| 893 |
-
|
| 894 |
logger.info(f"✓ Eye Tracking analysis completed: Score {score}/5 - {rating}")
|
| 895 |
return result
|
| 896 |
|
|
|
|
| 700 |
def analyze_video(
|
| 701 |
self,
|
| 702 |
video_path: str,
|
| 703 |
+
progress_callback: Optional[callable] = None
|
|
|
|
|
|
|
| 704 |
) -> Dict[str, Any]:
|
| 705 |
"""
|
| 706 |
Analyze video for eye contact
|
|
|
|
| 708 |
Args:
|
| 709 |
video_path: Path to video file
|
| 710 |
progress_callback: Optional callback for progress updates
|
|
|
|
|
|
|
| 711 |
|
| 712 |
Returns:
|
| 713 |
+
Dict containing eye tracking analysis results
|
| 714 |
"""
|
| 715 |
try:
|
| 716 |
logger.info(f"Analyzing video with Eye Tracking Service: {video_path}")
|
|
|
|
| 717 |
|
| 718 |
cap = cv.VideoCapture(video_path)
|
| 719 |
if not cap.isOpened():
|
|
|
|
| 726 |
total_frames = int(cap.get(cv.CAP_PROP_FRAME_COUNT))
|
| 727 |
|
| 728 |
logger.info(f"Video properties: {width}x{height} @ {fps}FPS, {total_frames} frames")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
|
| 730 |
# Initialize counters
|
| 731 |
frame_count = 0
|
|
|
|
| 791 |
if gaze_pos != 'CENTER' and gaze_pos != 'UNKNOWN':
|
| 792 |
gaze_away_frames += 1
|
| 793 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
# Log every 100 frames
|
| 795 |
if frame_count % 100 == 0:
|
| 796 |
logger.info(f"Processed {frame_count}/{total_frames} frames | "
|
|
|
|
| 800 |
f"Blinks: {blink_count}")
|
| 801 |
|
| 802 |
cap.release()
|
|
|
|
|
|
|
|
|
|
| 803 |
|
| 804 |
# Calculate metrics
|
| 805 |
duration = frame_count / fps
|
|
|
|
| 857 |
'debug_stats': debug_stats
|
| 858 |
}
|
| 859 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
logger.info(f"✓ Eye Tracking analysis completed: Score {score}/5 - {rating}")
|
| 861 |
return result
|
| 862 |
|
app/services/facial_expression.py
CHANGED
|
@@ -60,27 +60,12 @@ class FacialExpressionService:
|
|
| 60 |
# Get video properties
|
| 61 |
fps = int(cap.get(cv2.CAP_PROP_FPS))
|
| 62 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 63 |
-
duration_seconds = total_frames / fps if fps > 0 else 0
|
| 64 |
|
| 65 |
-
|
| 66 |
-
# Short video (<30s): process every 2 frames (15-25 FPS)
|
| 67 |
-
# Medium video (30-60s): process every 3 frames (10-17 FPS)
|
| 68 |
-
# Long video (>60s): process every 4 frames (7-12 FPS)
|
| 69 |
-
if duration_seconds > 60:
|
| 70 |
-
frame_skip = 4
|
| 71 |
-
elif duration_seconds > 30:
|
| 72 |
-
frame_skip = 3
|
| 73 |
-
elif fps > 30:
|
| 74 |
-
frame_skip = 2
|
| 75 |
-
else:
|
| 76 |
-
frame_skip = 1
|
| 77 |
-
|
| 78 |
-
logger.info(f"Video duration: {duration_seconds:.1f}s, Processing every {frame_skip} frame(s) from {total_frames} total frames")
|
| 79 |
|
| 80 |
# Data storage
|
| 81 |
frame_data = []
|
| 82 |
frame_number = 0
|
| 83 |
-
processed_count = 0
|
| 84 |
|
| 85 |
# Process each frame
|
| 86 |
while True:
|
|
@@ -89,12 +74,6 @@ class FacialExpressionService:
|
|
| 89 |
break
|
| 90 |
|
| 91 |
frame_number += 1
|
| 92 |
-
|
| 93 |
-
# Skip frames if needed
|
| 94 |
-
if frame_number % frame_skip != 0:
|
| 95 |
-
continue
|
| 96 |
-
|
| 97 |
-
processed_count += 1
|
| 98 |
timestamp_start = (frame_number - 1) / fps
|
| 99 |
timestamp_end = frame_number / fps
|
| 100 |
|
|
@@ -152,7 +131,7 @@ class FacialExpressionService:
|
|
| 152 |
|
| 153 |
cap.release()
|
| 154 |
|
| 155 |
-
logger.info(f"✓ Processed {
|
| 156 |
|
| 157 |
# Analyze expressions
|
| 158 |
df_faces = [f for f in frame_data if f['expression'] not in ['no_face', 'background']]
|
|
|
|
| 60 |
# Get video properties
|
| 61 |
fps = int(cap.get(cv2.CAP_PROP_FPS))
|
| 62 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
|
|
| 63 |
|
| 64 |
+
logger.info(f"Processing {total_frames} frames at {fps} FPS")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
# Data storage
|
| 67 |
frame_data = []
|
| 68 |
frame_number = 0
|
|
|
|
| 69 |
|
| 70 |
# Process each frame
|
| 71 |
while True:
|
|
|
|
| 74 |
break
|
| 75 |
|
| 76 |
frame_number += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
timestamp_start = (frame_number - 1) / fps
|
| 78 |
timestamp_end = frame_number / fps
|
| 79 |
|
|
|
|
| 131 |
|
| 132 |
cap.release()
|
| 133 |
|
| 134 |
+
logger.info(f"✓ Processed {frame_number} frames")
|
| 135 |
|
| 136 |
# Analyze expressions
|
| 137 |
df_faces = [f for f in frame_data if f['expression'] not in ['no_face', 'background']]
|
app/services/gesture_detection.py
CHANGED
|
@@ -107,29 +107,12 @@ class GestureDetectionService:
|
|
| 107 |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 108 |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 109 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 110 |
-
duration_seconds = total_frames / fps if fps > 0 else 0
|
| 111 |
|
| 112 |
-
logger.info(f"Video Info: {width}x{height} @ {fps}FPS, Total frames: {total_frames}
|
| 113 |
-
|
| 114 |
-
# ADAPTIVE FRAME SAMPLING based on video length
|
| 115 |
-
# Short video (<30s): process every 2 frames
|
| 116 |
-
# Medium video (30-60s): process every 3 frames
|
| 117 |
-
# Long video (>60s): process every 4 frames
|
| 118 |
-
if duration_seconds > 60:
|
| 119 |
-
frame_skip = 4
|
| 120 |
-
elif duration_seconds > 30:
|
| 121 |
-
frame_skip = 3
|
| 122 |
-
elif fps > 30:
|
| 123 |
-
frame_skip = 2
|
| 124 |
-
else:
|
| 125 |
-
frame_skip = 1
|
| 126 |
-
|
| 127 |
-
logger.info(f"Processing every {frame_skip} frame(s) for gesture analysis")
|
| 128 |
|
| 129 |
# Data storage
|
| 130 |
frame_data = []
|
| 131 |
frame_count = 0
|
| 132 |
-
processed_count = 0
|
| 133 |
prev_landmarks = None
|
| 134 |
|
| 135 |
while True:
|
|
@@ -139,14 +122,8 @@ class GestureDetectionService:
|
|
| 139 |
|
| 140 |
frame_count += 1
|
| 141 |
|
| 142 |
-
# Skip frames if needed
|
| 143 |
-
if frame_count % frame_skip != 0:
|
| 144 |
-
continue
|
| 145 |
-
|
| 146 |
-
processed_count += 1
|
| 147 |
-
|
| 148 |
# Progress callback
|
| 149 |
-
if progress_callback and
|
| 150 |
progress = int((frame_count / total_frames) * 100)
|
| 151 |
progress_callback(frame_count, total_frames, f"Processing gestures: {progress}%")
|
| 152 |
|
|
@@ -240,7 +217,7 @@ class GestureDetectionService:
|
|
| 240 |
|
| 241 |
cap.release()
|
| 242 |
|
| 243 |
-
logger.info(f"✓ Processed {
|
| 244 |
|
| 245 |
if not frame_data:
|
| 246 |
logger.warning("No frames processed")
|
|
|
|
| 107 |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 108 |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 109 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
|
|
| 110 |
|
| 111 |
+
logger.info(f"Video Info: {width}x{height} @ {fps}FPS, Total frames: {total_frames}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
# Data storage
|
| 114 |
frame_data = []
|
| 115 |
frame_count = 0
|
|
|
|
| 116 |
prev_landmarks = None
|
| 117 |
|
| 118 |
while True:
|
|
|
|
| 122 |
|
| 123 |
frame_count += 1
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
# Progress callback
|
| 126 |
+
if progress_callback and frame_count % 30 == 0:
|
| 127 |
progress = int((frame_count / total_frames) * 100)
|
| 128 |
progress_callback(frame_count, total_frames, f"Processing gestures: {progress}%")
|
| 129 |
|
|
|
|
| 217 |
|
| 218 |
cap.release()
|
| 219 |
|
| 220 |
+
logger.info(f"✓ Processed {frame_count} frames")
|
| 221 |
|
| 222 |
if not frame_data:
|
| 223 |
logger.warning("No frames processed")
|
app/services/video_processor.py
CHANGED
|
@@ -297,7 +297,7 @@ class VideoProcessor:
|
|
| 297 |
}
|
| 298 |
|
| 299 |
# Build final response
|
| 300 |
-
|
| 301 |
"level": level,
|
| 302 |
"video_metadata": {
|
| 303 |
"duration": metadata.duration,
|
|
@@ -309,6 +309,8 @@ class VideoProcessor:
|
|
| 309 |
"bonus_indicators": bonus_indicators,
|
| 310 |
"processing_time": 0 # Will be set by task handler
|
| 311 |
}
|
|
|
|
|
|
|
| 312 |
|
| 313 |
|
| 314 |
# Singleton instance
|
|
|
|
| 297 |
}
|
| 298 |
|
| 299 |
# Build final response
|
| 300 |
+
result = {
|
| 301 |
"level": level,
|
| 302 |
"video_metadata": {
|
| 303 |
"duration": metadata.duration,
|
|
|
|
| 309 |
"bonus_indicators": bonus_indicators,
|
| 310 |
"processing_time": 0 # Will be set by task handler
|
| 311 |
}
|
| 312 |
+
|
| 313 |
+
return result
|
| 314 |
|
| 315 |
|
| 316 |
# Singleton instance
|