Smooth_FrameProcessing
This commit is contained in:
parent
51d1ed8d2c
commit
6b42b731b4
183
src/poc_demo.py
183
src/poc_demo.py
@ -208,34 +208,57 @@ class POCPredictor:
|
|||||||
self.logs = []
|
self.logs = []
|
||||||
|
|
||||||
def detect_objects(self, frame):
|
def detect_objects(self, frame):
|
||||||
"""YOLO object detection - optimized for POC."""
|
"""YOLO object detection - optimized for POC with performance improvements."""
|
||||||
# Resize to square for YOLO
|
try:
|
||||||
yolo_input = cv2.resize(frame, (640, 640))
|
# Resize to square for YOLO (use INTER_LINEAR for speed)
|
||||||
|
yolo_input = cv2.resize(frame, (640, 640), interpolation=cv2.INTER_LINEAR)
|
||||||
|
|
||||||
# Convert HWC to CHW
|
# Convert HWC to CHW (optimized)
|
||||||
yolo_input = yolo_input.transpose(2, 0, 1)
|
yolo_input = yolo_input.transpose(2, 0, 1)
|
||||||
yolo_input = yolo_input[None].astype(np.float32) / 255.0
|
yolo_input = np.ascontiguousarray(yolo_input[None].astype(np.float32) / 255.0)
|
||||||
|
|
||||||
# Run inference
|
# Run inference
|
||||||
input_name = self.yolo_session.get_inputs()[0].name
|
input_name = self.yolo_session.get_inputs()[0].name
|
||||||
outputs = self.yolo_session.run(None, {input_name: yolo_input})
|
outputs = self.yolo_session.run(None, {input_name: yolo_input})
|
||||||
|
|
||||||
# Parse YOLOv8 ONNX output: (1, 84, 8400)
|
# Parse YOLOv8 ONNX output: (1, 84, 8400)
|
||||||
output = outputs[0]
|
output = outputs[0]
|
||||||
bboxes = output[0, :4, :].transpose() # (8400, 4)
|
bboxes = output[0, :4, :].transpose() # (8400, 4)
|
||||||
class_scores = output[0, 4:, :] # (80, 8400)
|
class_scores = output[0, 4:, :] # (80, 8400)
|
||||||
classes = np.argmax(class_scores, axis=0)
|
classes = np.argmax(class_scores, axis=0).astype(np.int32) # Ensure int32
|
||||||
confs = np.max(class_scores, axis=0)
|
confs = np.max(class_scores, axis=0)
|
||||||
|
|
||||||
# Filter by confidence and relevant classes (phone and person)
|
# Filter by confidence and relevant classes (phone and person)
|
||||||
relevant_classes = [0, 67] # person, cell phone
|
relevant_classes = np.array([0, 67], dtype=np.int32) # person, cell phone
|
||||||
mask = (confs > CONFIG['conf_threshold']) & np.isin(classes, relevant_classes)
|
conf_mask = confs > CONFIG['conf_threshold']
|
||||||
|
class_mask = np.isin(classes, relevant_classes)
|
||||||
|
mask = conf_mask & class_mask
|
||||||
|
|
||||||
return {
|
# Ensure mask is boolean and arrays are properly indexed
|
||||||
'bboxes': bboxes[mask],
|
mask = mask.astype(bool)
|
||||||
'confs': confs[mask],
|
|
||||||
'classes': classes[mask]
|
# Get indices where mask is True
|
||||||
}
|
valid_indices = np.where(mask)[0]
|
||||||
|
|
||||||
|
if len(valid_indices) > 0:
|
||||||
|
return {
|
||||||
|
'bboxes': bboxes[valid_indices],
|
||||||
|
'confs': confs[valid_indices],
|
||||||
|
'classes': classes[valid_indices]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in detect_objects: {e}")
|
||||||
|
return {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
|
||||||
def analyze_face(self, frame):
|
def analyze_face(self, frame):
|
||||||
"""OpenCV face analysis - NO MediaPipe!"""
|
"""OpenCV face analysis - NO MediaPipe!"""
|
||||||
@ -286,17 +309,23 @@ class POCPredictor:
|
|||||||
return has_seatbelt, confidence
|
return has_seatbelt, confidence
|
||||||
|
|
||||||
def process_frame(self, frame, frame_idx, last_results=None):
|
def process_frame(self, frame, frame_idx, last_results=None):
|
||||||
"""Process single frame - streamlined and optimized."""
|
"""Process single frame - streamlined and optimized with smooth video support."""
|
||||||
|
|
||||||
should_process = (frame_idx % CONFIG['inference_skip'] == 0)
|
should_process = (frame_idx % CONFIG['inference_skip'] == 0)
|
||||||
|
|
||||||
# If not processing this frame, return last results
|
# Always use last results for smooth video (even if not processing this frame)
|
||||||
if not should_process and last_results is not None:
|
if not should_process and last_results is not None:
|
||||||
last_alerts = last_results[0]
|
last_alerts = last_results[0]
|
||||||
last_face_data = last_results[1]
|
last_face_data = last_results[5] if len(last_results) > 5 else {'present': False, 'perclos': 0, 'head_yaw': 0}
|
||||||
annotated = self.draw_detections(frame, {'bboxes': [], 'confs': [], 'classes': []},
|
last_detections = last_results[6] if len(last_results) > 6 else {
|
||||||
last_face_data, last_alerts)
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
return last_alerts, annotated, False, last_face_data
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
# Draw last predictions on current frame for smooth video
|
||||||
|
annotated = self.draw_detections(frame, last_detections, last_face_data, last_alerts)
|
||||||
|
return last_alerts, annotated, False, last_results[2] if len(last_results) > 2 else False, \
|
||||||
|
last_results[3] if len(last_results) > 3 else 0.0, last_face_data, last_detections
|
||||||
|
|
||||||
# Process this frame
|
# Process this frame
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@ -306,7 +335,11 @@ class POCPredictor:
|
|||||||
|
|
||||||
if not face_data['present']:
|
if not face_data['present']:
|
||||||
alerts = {'Driver Absent': True}
|
alerts = {'Driver Absent': True}
|
||||||
detections = {'bboxes': [], 'confs': [], 'classes': []}
|
detections = {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
seatbelt, belt_conf = False, 0.0
|
seatbelt, belt_conf = False, 0.0
|
||||||
else:
|
else:
|
||||||
# Run object detection
|
# Run object detection
|
||||||
@ -327,7 +360,14 @@ class POCPredictor:
|
|||||||
alerts['Drowsiness'] = face_data['perclos'] > CONFIG['perclos_threshold']
|
alerts['Drowsiness'] = face_data['perclos'] > CONFIG['perclos_threshold']
|
||||||
alerts['Distraction'] = abs(face_data['head_yaw']) > (CONFIG['head_pose_threshold'] * 0.8)
|
alerts['Distraction'] = abs(face_data['head_yaw']) > (CONFIG['head_pose_threshold'] * 0.8)
|
||||||
alerts['Driver Absent'] = not face_data['present']
|
alerts['Driver Absent'] = not face_data['present']
|
||||||
alerts['Phone Detected'] = np.any(detections['classes'] == 67) if len(detections['classes']) > 0 else False
|
# Safe check for phone detection
|
||||||
|
phone_detected = False
|
||||||
|
if len(detections['classes']) > 0:
|
||||||
|
try:
|
||||||
|
phone_detected = np.any(detections['classes'] == 67)
|
||||||
|
except:
|
||||||
|
phone_detected = False
|
||||||
|
alerts['Phone Detected'] = phone_detected
|
||||||
alerts['No Seatbelt'] = not seatbelt and belt_conf > 0.3
|
alerts['No Seatbelt'] = not seatbelt and belt_conf > 0.3
|
||||||
|
|
||||||
# Update states with temporal smoothing (clear alerts when condition stops)
|
# Update states with temporal smoothing (clear alerts when condition stops)
|
||||||
@ -363,34 +403,48 @@ class POCPredictor:
|
|||||||
if len(self.logs) > CONFIG['max_logs']:
|
if len(self.logs) > CONFIG['max_logs']:
|
||||||
self.logs = self.logs[-CONFIG['max_logs']:]
|
self.logs = self.logs[-CONFIG['max_logs']:]
|
||||||
|
|
||||||
return alerts, annotated_frame, True, seatbelt, belt_conf, face_data
|
return alerts, annotated_frame, True, seatbelt, belt_conf, face_data, detections
|
||||||
|
|
||||||
def draw_detections(self, frame, detections, face_data, alerts):
|
def draw_detections(self, frame, detections, face_data, alerts):
|
||||||
"""Draw detections and alerts on frame."""
|
"""Draw detections and alerts on frame."""
|
||||||
annotated = frame.copy()
|
annotated = frame.copy()
|
||||||
h, w = annotated.shape[:2]
|
h, w = annotated.shape[:2]
|
||||||
|
|
||||||
# Draw bounding boxes
|
# Draw bounding boxes (safe iteration)
|
||||||
for i, (bbox, conf, cls) in enumerate(zip(detections['bboxes'], detections['confs'], detections['classes'])):
|
if len(detections['bboxes']) > 0 and len(detections['confs']) > 0 and len(detections['classes']) > 0:
|
||||||
# Scale bbox from 640x640 to frame size
|
try:
|
||||||
x1, y1, x2, y2 = bbox
|
# Ensure all arrays have same length
|
||||||
x1, x2 = int(x1 * w / 640), int(x2 * w / 640)
|
min_len = min(len(detections['bboxes']), len(detections['confs']), len(detections['classes']))
|
||||||
y1, y2 = int(y1 * h / 640), int(y2 * h / 640)
|
for i in range(min_len):
|
||||||
|
bbox = detections['bboxes'][i]
|
||||||
|
conf = float(detections['confs'][i])
|
||||||
|
cls = int(detections['classes'][i])
|
||||||
|
|
||||||
# Color by class
|
# Scale bbox from 640x640 to frame size
|
||||||
if cls == 0: # person
|
x1, y1, x2, y2 = bbox
|
||||||
color = (0, 255, 0) # Green
|
x1, x2 = int(x1 * w / 640), int(x2 * w / 640)
|
||||||
label = "Person"
|
y1, y2 = int(y1 * h / 640), int(y2 * h / 640)
|
||||||
elif cls == 67: # phone
|
|
||||||
color = (255, 0, 255) # Magenta
|
|
||||||
label = "Phone"
|
|
||||||
else:
|
|
||||||
color = (255, 255, 0) # Cyan
|
|
||||||
label = "Object"
|
|
||||||
|
|
||||||
cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2)
|
# Ensure coordinates are valid
|
||||||
cv2.putText(annotated, f"{label}: {conf:.2f}", (x1, y1-10),
|
x1, x2 = max(0, min(x1, w)), max(0, min(x2, w))
|
||||||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
y1, y2 = max(0, min(y1, h)), max(0, min(y2, h))
|
||||||
|
|
||||||
|
# Color by class
|
||||||
|
if cls == 0: # person
|
||||||
|
color = (0, 255, 0) # Green
|
||||||
|
label = "Person"
|
||||||
|
elif cls == 67: # phone
|
||||||
|
color = (255, 0, 255) # Magenta
|
||||||
|
label = "Phone"
|
||||||
|
else:
|
||||||
|
color = (255, 255, 0) # Cyan
|
||||||
|
label = "Object"
|
||||||
|
|
||||||
|
cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2)
|
||||||
|
cv2.putText(annotated, f"{label}: {conf:.2f}", (x1, max(y1-10, 10)),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error drawing detections: {e}")
|
||||||
|
|
||||||
# Draw face status
|
# Draw face status
|
||||||
if face_data['present']:
|
if face_data['present']:
|
||||||
@ -457,37 +511,54 @@ def video_capture_loop(predictor, frame_queue, video_source=None):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Always process frame (for smooth video - shows all frames with last predictions)
|
||||||
try:
|
try:
|
||||||
results = predictor.process_frame(frame, frame_idx, last_results)
|
results = predictor.process_frame(frame, frame_idx, last_results)
|
||||||
alerts = results[0]
|
alerts = results[0]
|
||||||
processed_frame = results[1]
|
processed_frame = results[1]
|
||||||
was_processed = results[2]
|
was_processed = results[2]
|
||||||
|
|
||||||
|
# Update last results if we got new predictions
|
||||||
if was_processed:
|
if was_processed:
|
||||||
last_results = results
|
last_results = results
|
||||||
|
# If not processed, we still use last_results for drawing (already handled in process_frame)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing frame: {e}")
|
logger.error(f"Error processing frame: {e}", exc_info=True)
|
||||||
processed_frame = frame
|
# On error, show raw frame with last predictions if available
|
||||||
alerts = {}
|
if last_results:
|
||||||
was_processed = False
|
try:
|
||||||
|
last_alerts = last_results[0]
|
||||||
|
last_face_data = last_results[5] if len(last_results) > 5 else {'present': False, 'perclos': 0, 'head_yaw': 0}
|
||||||
|
last_detections = last_results[6] if len(last_results) > 6 else {'bboxes': np.array([]), 'confs': np.array([]), 'classes': np.array([])}
|
||||||
|
processed_frame = predictor.draw_detections(frame, last_detections, last_face_data, last_alerts)
|
||||||
|
except:
|
||||||
|
processed_frame = frame
|
||||||
|
else:
|
||||||
|
processed_frame = frame
|
||||||
|
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
|
||||||
|
# Convert to RGB for display
|
||||||
frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
|
frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
|
# Always put frame in queue (smooth video - all frames shown)
|
||||||
try:
|
try:
|
||||||
frame_queue.put_nowait(frame_rgb)
|
frame_queue.put_nowait(frame_rgb)
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
|
# If queue is full, replace oldest frame
|
||||||
try:
|
try:
|
||||||
frame_queue.get_nowait()
|
frame_queue.get_nowait()
|
||||||
frame_queue.put_nowait(frame_rgb)
|
frame_queue.put_nowait(frame_rgb)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Frame rate control
|
||||||
if video_source is not None:
|
if video_source is not None:
|
||||||
fps = cap.get(cv2.CAP_PROP_FPS) or 30
|
fps = cap.get(cv2.CAP_PROP_FPS) or 30
|
||||||
time.sleep(1.0 / fps)
|
time.sleep(1.0 / fps)
|
||||||
else:
|
else:
|
||||||
|
# For camera, target 30 FPS (smooth video)
|
||||||
time.sleep(0.033)
|
time.sleep(0.033)
|
||||||
|
|
||||||
cap.release()
|
cap.release()
|
||||||
@ -683,7 +754,7 @@ with col1:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
frame = frame_queue.get_nowait()
|
frame = frame_queue.get_nowait()
|
||||||
video_placeholder.image(frame, channels='RGB', use_container_width=True)
|
video_placeholder.image(frame, channels='RGB', width='stretch')
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
video_placeholder.info("Waiting for camera feed...")
|
video_placeholder.info("Waiting for camera feed...")
|
||||||
|
|
||||||
|
|||||||
@ -209,33 +209,56 @@ class POCPredictor:
|
|||||||
|
|
||||||
def detect_objects(self, frame):
|
def detect_objects(self, frame):
|
||||||
"""YOLO object detection - optimized for POC with performance improvements."""
|
"""YOLO object detection - optimized for POC with performance improvements."""
|
||||||
# Resize to square for YOLO (use INTER_LINEAR for speed)
|
try:
|
||||||
yolo_input = cv2.resize(frame, (640, 640), interpolation=cv2.INTER_LINEAR)
|
# Resize to square for YOLO (use INTER_LINEAR for speed)
|
||||||
|
yolo_input = cv2.resize(frame, (640, 640), interpolation=cv2.INTER_LINEAR)
|
||||||
|
|
||||||
# Convert HWC to CHW (optimized)
|
# Convert HWC to CHW (optimized)
|
||||||
yolo_input = yolo_input.transpose(2, 0, 1)
|
yolo_input = yolo_input.transpose(2, 0, 1)
|
||||||
yolo_input = np.ascontiguousarray(yolo_input[None].astype(np.float32) / 255.0)
|
yolo_input = np.ascontiguousarray(yolo_input[None].astype(np.float32) / 255.0)
|
||||||
|
|
||||||
# Run inference
|
# Run inference
|
||||||
input_name = self.yolo_session.get_inputs()[0].name
|
input_name = self.yolo_session.get_inputs()[0].name
|
||||||
outputs = self.yolo_session.run(None, {input_name: yolo_input})
|
outputs = self.yolo_session.run(None, {input_name: yolo_input})
|
||||||
|
|
||||||
# Parse YOLOv8 ONNX output: (1, 84, 8400)
|
# Parse YOLOv8 ONNX output: (1, 84, 8400)
|
||||||
output = outputs[0]
|
output = outputs[0]
|
||||||
bboxes = output[0, :4, :].transpose() # (8400, 4)
|
bboxes = output[0, :4, :].transpose() # (8400, 4)
|
||||||
class_scores = output[0, 4:, :] # (80, 8400)
|
class_scores = output[0, 4:, :] # (80, 8400)
|
||||||
classes = np.argmax(class_scores, axis=0)
|
classes = np.argmax(class_scores, axis=0).astype(np.int32) # Ensure int32
|
||||||
confs = np.max(class_scores, axis=0)
|
confs = np.max(class_scores, axis=0)
|
||||||
|
|
||||||
# Filter by confidence and relevant classes (phone and person)
|
# Filter by confidence and relevant classes (phone and person)
|
||||||
relevant_classes = [0, 67] # person, cell phone
|
relevant_classes = np.array([0, 67], dtype=np.int32) # person, cell phone
|
||||||
mask = (confs > CONFIG['conf_threshold']) & np.isin(classes, relevant_classes)
|
conf_mask = confs > CONFIG['conf_threshold']
|
||||||
|
class_mask = np.isin(classes, relevant_classes)
|
||||||
|
mask = conf_mask & class_mask
|
||||||
|
|
||||||
return {
|
# Ensure mask is boolean and arrays are properly indexed
|
||||||
'bboxes': bboxes[mask],
|
mask = mask.astype(bool)
|
||||||
'confs': confs[mask],
|
|
||||||
'classes': classes[mask]
|
# Get indices where mask is True
|
||||||
}
|
valid_indices = np.where(mask)[0]
|
||||||
|
|
||||||
|
if len(valid_indices) > 0:
|
||||||
|
return {
|
||||||
|
'bboxes': bboxes[valid_indices],
|
||||||
|
'confs': confs[valid_indices],
|
||||||
|
'classes': classes[valid_indices]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in detect_objects: {e}")
|
||||||
|
return {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
|
||||||
def analyze_face(self, frame):
|
def analyze_face(self, frame):
|
||||||
"""OpenCV face analysis - NO MediaPipe!"""
|
"""OpenCV face analysis - NO MediaPipe!"""
|
||||||
@ -286,17 +309,23 @@ class POCPredictor:
|
|||||||
return has_seatbelt, confidence
|
return has_seatbelt, confidence
|
||||||
|
|
||||||
def process_frame(self, frame, frame_idx, last_results=None):
|
def process_frame(self, frame, frame_idx, last_results=None):
|
||||||
"""Process single frame - streamlined and optimized."""
|
"""Process single frame - streamlined and optimized with smooth video support."""
|
||||||
|
|
||||||
should_process = (frame_idx % CONFIG['inference_skip'] == 0)
|
should_process = (frame_idx % CONFIG['inference_skip'] == 0)
|
||||||
|
|
||||||
# If not processing this frame, return last results
|
# Always use last results for smooth video (even if not processing this frame)
|
||||||
if not should_process and last_results is not None:
|
if not should_process and last_results is not None:
|
||||||
last_alerts = last_results[0]
|
last_alerts = last_results[0]
|
||||||
last_face_data = last_results[1]
|
last_face_data = last_results[5] if len(last_results) > 5 else {'present': False, 'perclos': 0, 'head_yaw': 0}
|
||||||
annotated = self.draw_detections(frame, {'bboxes': [], 'confs': [], 'classes': []},
|
last_detections = last_results[6] if len(last_results) > 6 else {
|
||||||
last_face_data, last_alerts)
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
return last_alerts, annotated, False, last_face_data
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
|
# Draw last predictions on current frame for smooth video
|
||||||
|
annotated = self.draw_detections(frame, last_detections, last_face_data, last_alerts)
|
||||||
|
return last_alerts, annotated, False, last_results[2] if len(last_results) > 2 else False, \
|
||||||
|
last_results[3] if len(last_results) > 3 else 0.0, last_face_data, last_detections
|
||||||
|
|
||||||
# Process this frame
|
# Process this frame
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@ -306,7 +335,11 @@ class POCPredictor:
|
|||||||
|
|
||||||
if not face_data['present']:
|
if not face_data['present']:
|
||||||
alerts = {'Driver Absent': True}
|
alerts = {'Driver Absent': True}
|
||||||
detections = {'bboxes': [], 'confs': [], 'classes': []}
|
detections = {
|
||||||
|
'bboxes': np.array([], dtype=np.float32).reshape(0, 4),
|
||||||
|
'confs': np.array([], dtype=np.float32),
|
||||||
|
'classes': np.array([], dtype=np.int32)
|
||||||
|
}
|
||||||
seatbelt, belt_conf = False, 0.0
|
seatbelt, belt_conf = False, 0.0
|
||||||
else:
|
else:
|
||||||
# Run object detection
|
# Run object detection
|
||||||
@ -327,7 +360,14 @@ class POCPredictor:
|
|||||||
alerts['Drowsiness'] = face_data['perclos'] > CONFIG['perclos_threshold']
|
alerts['Drowsiness'] = face_data['perclos'] > CONFIG['perclos_threshold']
|
||||||
alerts['Distraction'] = abs(face_data['head_yaw']) > (CONFIG['head_pose_threshold'] * 0.8)
|
alerts['Distraction'] = abs(face_data['head_yaw']) > (CONFIG['head_pose_threshold'] * 0.8)
|
||||||
alerts['Driver Absent'] = not face_data['present']
|
alerts['Driver Absent'] = not face_data['present']
|
||||||
alerts['Phone Detected'] = np.any(detections['classes'] == 67) if len(detections['classes']) > 0 else False
|
# Safe check for phone detection
|
||||||
|
phone_detected = False
|
||||||
|
if len(detections['classes']) > 0:
|
||||||
|
try:
|
||||||
|
phone_detected = np.any(detections['classes'] == 67)
|
||||||
|
except:
|
||||||
|
phone_detected = False
|
||||||
|
alerts['Phone Detected'] = phone_detected
|
||||||
alerts['No Seatbelt'] = not seatbelt and belt_conf > 0.3
|
alerts['No Seatbelt'] = not seatbelt and belt_conf > 0.3
|
||||||
|
|
||||||
# Update states with temporal smoothing (clear alerts when condition stops)
|
# Update states with temporal smoothing (clear alerts when condition stops)
|
||||||
@ -363,34 +403,48 @@ class POCPredictor:
|
|||||||
if len(self.logs) > CONFIG['max_logs']:
|
if len(self.logs) > CONFIG['max_logs']:
|
||||||
self.logs = self.logs[-CONFIG['max_logs']:]
|
self.logs = self.logs[-CONFIG['max_logs']:]
|
||||||
|
|
||||||
return alerts, annotated_frame, True, seatbelt, belt_conf, face_data
|
return alerts, annotated_frame, True, seatbelt, belt_conf, face_data, detections
|
||||||
|
|
||||||
def draw_detections(self, frame, detections, face_data, alerts):
|
def draw_detections(self, frame, detections, face_data, alerts):
|
||||||
"""Draw detections and alerts on frame."""
|
"""Draw detections and alerts on frame."""
|
||||||
annotated = frame.copy()
|
annotated = frame.copy()
|
||||||
h, w = annotated.shape[:2]
|
h, w = annotated.shape[:2]
|
||||||
|
|
||||||
# Draw bounding boxes
|
# Draw bounding boxes (safe iteration)
|
||||||
for i, (bbox, conf, cls) in enumerate(zip(detections['bboxes'], detections['confs'], detections['classes'])):
|
if len(detections['bboxes']) > 0 and len(detections['confs']) > 0 and len(detections['classes']) > 0:
|
||||||
# Scale bbox from 640x640 to frame size
|
try:
|
||||||
x1, y1, x2, y2 = bbox
|
# Ensure all arrays have same length
|
||||||
x1, x2 = int(x1 * w / 640), int(x2 * w / 640)
|
min_len = min(len(detections['bboxes']), len(detections['confs']), len(detections['classes']))
|
||||||
y1, y2 = int(y1 * h / 640), int(y2 * h / 640)
|
for i in range(min_len):
|
||||||
|
bbox = detections['bboxes'][i]
|
||||||
|
conf = float(detections['confs'][i])
|
||||||
|
cls = int(detections['classes'][i])
|
||||||
|
|
||||||
# Color by class
|
# Scale bbox from 640x640 to frame size
|
||||||
if cls == 0: # person
|
x1, y1, x2, y2 = bbox
|
||||||
color = (0, 255, 0) # Green
|
x1, x2 = int(x1 * w / 640), int(x2 * w / 640)
|
||||||
label = "Person"
|
y1, y2 = int(y1 * h / 640), int(y2 * h / 640)
|
||||||
elif cls == 67: # phone
|
|
||||||
color = (255, 0, 255) # Magenta
|
|
||||||
label = "Phone"
|
|
||||||
else:
|
|
||||||
color = (255, 255, 0) # Cyan
|
|
||||||
label = "Object"
|
|
||||||
|
|
||||||
cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2)
|
# Ensure coordinates are valid
|
||||||
cv2.putText(annotated, f"{label}: {conf:.2f}", (x1, y1-10),
|
x1, x2 = max(0, min(x1, w)), max(0, min(x2, w))
|
||||||
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
y1, y2 = max(0, min(y1, h)), max(0, min(y2, h))
|
||||||
|
|
||||||
|
# Color by class
|
||||||
|
if cls == 0: # person
|
||||||
|
color = (0, 255, 0) # Green
|
||||||
|
label = "Person"
|
||||||
|
elif cls == 67: # phone
|
||||||
|
color = (255, 0, 255) # Magenta
|
||||||
|
label = "Phone"
|
||||||
|
else:
|
||||||
|
color = (255, 255, 0) # Cyan
|
||||||
|
label = "Object"
|
||||||
|
|
||||||
|
cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2)
|
||||||
|
cv2.putText(annotated, f"{label}: {conf:.2f}", (x1, max(y1-10, 10)),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error drawing detections: {e}")
|
||||||
|
|
||||||
# Draw face status
|
# Draw face status
|
||||||
if face_data['present']:
|
if face_data['present']:
|
||||||
@ -457,37 +511,54 @@ def video_capture_loop(predictor, frame_queue, video_source=None):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Always process frame (for smooth video - shows all frames with last predictions)
|
||||||
try:
|
try:
|
||||||
results = predictor.process_frame(frame, frame_idx, last_results)
|
results = predictor.process_frame(frame, frame_idx, last_results)
|
||||||
alerts = results[0]
|
alerts = results[0]
|
||||||
processed_frame = results[1]
|
processed_frame = results[1]
|
||||||
was_processed = results[2]
|
was_processed = results[2]
|
||||||
|
|
||||||
|
# Update last results if we got new predictions
|
||||||
if was_processed:
|
if was_processed:
|
||||||
last_results = results
|
last_results = results
|
||||||
|
# If not processed, we still use last_results for drawing (already handled in process_frame)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing frame: {e}")
|
logger.error(f"Error processing frame: {e}", exc_info=True)
|
||||||
processed_frame = frame
|
# On error, show raw frame with last predictions if available
|
||||||
alerts = {}
|
if last_results:
|
||||||
was_processed = False
|
try:
|
||||||
|
last_alerts = last_results[0]
|
||||||
|
last_face_data = last_results[5] if len(last_results) > 5 else {'present': False, 'perclos': 0, 'head_yaw': 0}
|
||||||
|
last_detections = last_results[6] if len(last_results) > 6 else {'bboxes': np.array([]), 'confs': np.array([]), 'classes': np.array([])}
|
||||||
|
processed_frame = predictor.draw_detections(frame, last_detections, last_face_data, last_alerts)
|
||||||
|
except:
|
||||||
|
processed_frame = frame
|
||||||
|
else:
|
||||||
|
processed_frame = frame
|
||||||
|
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
|
||||||
|
# Convert to RGB for display
|
||||||
frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
|
frame_rgb = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
|
# Always put frame in queue (smooth video - all frames shown)
|
||||||
try:
|
try:
|
||||||
frame_queue.put_nowait(frame_rgb)
|
frame_queue.put_nowait(frame_rgb)
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
|
# If queue is full, replace oldest frame
|
||||||
try:
|
try:
|
||||||
frame_queue.get_nowait()
|
frame_queue.get_nowait()
|
||||||
frame_queue.put_nowait(frame_rgb)
|
frame_queue.put_nowait(frame_rgb)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Frame rate control
|
||||||
if video_source is not None:
|
if video_source is not None:
|
||||||
fps = cap.get(cv2.CAP_PROP_FPS) or 30
|
fps = cap.get(cv2.CAP_PROP_FPS) or 30
|
||||||
time.sleep(1.0 / fps)
|
time.sleep(1.0 / fps)
|
||||||
else:
|
else:
|
||||||
|
# For camera, target 30 FPS (smooth video)
|
||||||
time.sleep(0.033)
|
time.sleep(0.033)
|
||||||
|
|
||||||
cap.release()
|
cap.release()
|
||||||
@ -683,7 +754,7 @@ with col1:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
frame = frame_queue.get_nowait()
|
frame = frame_queue.get_nowait()
|
||||||
video_placeholder.image(frame, channels='RGB', use_container_width=True)
|
video_placeholder.image(frame, channels='RGB', width='stretch')
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
video_placeholder.info("Waiting for camera feed...")
|
video_placeholder.info("Waiting for camera feed...")
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user