CoolFace
Modelpublic

Prathmesh0001/interview-system

sourceHugging Facemitupdated 8mo agoView on Hugging Face
0likes
video_analyzer.py195 linesDownload Raw Back to root
1"""
2Video Analyzer Module
3Analyzes facial expressions, emotions, and visual cues during interview
4"""
5import sys
6import cv2
7import numpy as np
8from typing import Dict, List, Optional, Tuple
9import time
10from collections import deque
11
12# Trick MediaPipe into not looking for TensorFlow to avoid Protobuf crashes
13sys.modules['tensorflow'] = None 
14import mediapipe as mp
15
16class VideoAnalyzer:
17    def __init__(self, camera_index=0):
18        self.camera_index = camera_index
19        self.cap = None
20        
21        # 1. Initialize MediaPipe Solutions
22        self.mp_face_mesh = mp.solutions.face_mesh
23        self.mp_face_detection = mp.solutions.face_detection
24        self.mp_drawing = mp.solutions.drawing_utils
25        
26        # 2. Initialize Detectors (Fixes the 'face_detection' attribute error)
27        self.face_detection = self.mp_face_detection.FaceDetection(
28            model_selection=0, 
29            min_detection_confidence=0.5
30        )
31        self.face_mesh = self.mp_face_mesh.FaceMesh(
32            max_num_faces=1,
33            refine_landmarks=True,
34            min_detection_confidence=0.5,
35            min_tracking_confidence=0.5
36        )
37
38        # 3. Initialize History Trackers (Required for get_session_summary)
39        self.emotion_history = deque(maxlen=100)
40        self.eye_contact_history = deque(maxlen=100)
41        self.posture_history = deque(maxlen=100)
42        
43        self.session_data = {
44            'total_frames': 0,
45            'face_detected_frames': 0
46        }
47
48    def start_camera(self) -> bool:
49        try:
50            self.cap = cv2.VideoCapture(self.camera_index)
51            if not self.cap.isOpened():
52                print("Error: Could not open camera")
53                return False
54            
55            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
56            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
57            return True
58        except Exception as e:
59            print(f"Error starting camera: {e}")
60            return False
61
62    def stop_camera(self):
63        if self.cap is not None:
64            self.cap.release()
65            self.cap = None
66        cv2.destroyAllWindows()
67
68    def capture_frame(self) -> Tuple[bool, Optional[np.ndarray]]:
69        if self.cap is None or not self.cap.isOpened():
70            return False, None
71        return self.cap.read()
72
73    def analyze_frame(self, frame: np.ndarray) -> Dict:
74        analysis = {
75            'face_detected': False,
76            'emotion': 'neutral',
77            'confidence': 0.0,
78            'eye_contact': False,
79            'head_pose': 'neutral',
80            'facial_landmarks': None
81        }
82
83        if frame is None: return analysis
84
85        # Convert BGR to RGB
86        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
87        
88        # Detect face
89        detection_results = self.face_detection.process(rgb_frame)
90        
91        if detection_results.detections:
92            analysis['face_detected'] = True
93            self.session_data['face_detected_frames'] += 1
94            
95            mesh_results = self.face_mesh.process(rgb_frame)
96            
97            if mesh_results.multi_face_landmarks:
98                face_landmarks = mesh_results.multi_face_landmarks[0]
99                analysis['facial_landmarks'] = face_landmarks
100                
101                # Analyze facial features
102                emotion, confidence = self._analyze_emotion(face_landmarks, frame.shape)
103                analysis['emotion'] = emotion
104                analysis['confidence'] = confidence
105                
106                eye_contact = self._check_eye_contact(face_landmarks, frame.shape)
107                analysis['eye_contact'] = eye_contact
108                
109                head_pose = self._analyze_head_pose(face_landmarks, frame.shape)
110                analysis['head_pose'] = head_pose
111                
112                # Update history
113                self.emotion_history.append(emotion)
114                self.eye_contact_history.append(eye_contact)
115                self.posture_history.append(head_pose)
116        
117        self.session_data['total_frames'] += 1
118        return analysis
119
120    def _analyze_emotion(self, landmarks, frame_shape) -> Tuple[str, float]:
121        h, w = frame_shape[:2]
122        mouth_top = landmarks.landmark[13]
123        mouth_bottom = landmarks.landmark[14]
124        left_eye_top = landmarks.landmark[159]
125        left_eye_bottom = landmarks.landmark[145]
126        
127        mouth_open = abs(mouth_top.y - mouth_bottom.y) * h
128        left_eye_open = abs(left_eye_top.y - left_eye_bottom.y) * h
129        
130        if mouth_open > 15: return 'happy', 0.75
131        elif mouth_open > 8: return 'happy', 0.65
132        elif left_eye_open < 5: return 'focused', 0.60
133        else: return 'neutral', 0.80
134
135    def _check_eye_contact(self, landmarks, frame_shape) -> bool:
136        nose_tip = landmarks.landmark[1]
137        left_eye = landmarks.landmark[33]
138        right_eye = landmarks.landmark[263]
139        eye_center_x = (left_eye.x + right_eye.x) / 2
140        return abs(nose_tip.x - eye_center_x) < 0.03
141
142    def _analyze_head_pose(self, landmarks, frame_shape) -> str:
143        nose_tip = landmarks.landmark[1]
144        chin = landmarks.landmark[152]
145        forehead = landmarks.landmark[10]
146        face_height = abs(forehead.y - chin.y)
147        nose_position = (nose_tip.y - forehead.y) / face_height if face_height > 0 else 0.5
148        
149        if nose_position < 0.4: return 'looking_up'
150        elif nose_position > 0.6: return 'looking_down'
151        else: return 'centered'
152
153    def get_session_summary(self) -> Dict:
154        total = self.session_data['total_frames']
155        face_rate = (self.session_data['face_detected_frames'] / total) if total > 0 else 0
156        eye_pct = (sum(self.eye_contact_history) / len(self.eye_contact_history)) if self.eye_contact_history else 0
157        
158        return {
159            'total_frames_analyzed': total,
160            'face_detection_rate': face_rate * 100,
161            'eye_contact_percentage': eye_pct * 100,
162            'dominant_emotion': max(set(self.emotion_history), default='neutral') if self.emotion_history else 'neutral',
163            'dominant_posture': max(set(self.posture_history), default='centered') if self.posture_history else 'centered',
164            'engagement_score': self._calculate_engagement_score()
165        }
166
167    def _calculate_engagement_score(self) -> float:
168        score = 50
169        if self.eye_contact_history:
170            score += (sum(self.eye_contact_history) / len(self.eye_contact_history)) * 25
171        return min(100, max(0, score))
172
173    def visualize_frame(self, frame: np.ndarray, analysis: Dict) -> np.ndarray:
174        vis_frame = frame.copy()
175        if analysis['face_detected'] and analysis['facial_landmarks']:
176            self.mp_drawing.draw_landmarks(
177                image=vis_frame,
178                landmark_list=analysis['facial_landmarks'],
179                connections=self.mp_face_mesh.FACEMESH_CONTOURS,
180                connection_drawing_spec=self.mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1)
181            )
182        return vis_frame
183
184# --- MAIN BLOCK FOR TESTING ---
185if __name__ == "__main__":
186    analyzer = VideoAnalyzer()
187    if analyzer.start_camera():
188        print("Camera started. Press 'q' to stop.")
189        while True:
190            ret, frame = analyzer.capture_frame()
191            if not ret: break
192            res = analyzer.analyze_frame(frame)
193            cv2.imshow('Test', analyzer.visualize_frame(frame, res))
194            if cv2.waitKey(1) & 0xFF == ord('q'): break
195        analyzer.stop_camera()