Prathmesh0001/interview-system
0
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()