1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
| import cv2 import numpy as np from typing import Tuple, List, Dict
class OOPDetector: """ 基于视觉的Out-of-Position检测 """ def __init__(self): self.pose_estimator = PoseEstimator() self.normal_pose_threshold = { 'forward_lean': 15, 'lateral_lean': 10, 'head_distance': 300, } def detect_oop(self, frame: np.ndarray) -> Dict: """ 检测OOP状态 Args: frame: 输入图像 Returns: oop_status: OOP检测结果 """ keypoints = self.pose_estimator.detect(frame) if keypoints is None: return {'is_oop': False, 'reason': 'no_person'} forward_lean = self._calculate_forward_lean(keypoints) lateral_lean = self._calculate_lateral_lean(keypoints) head_position = self._get_head_position(keypoints) oop_flags = [] if forward_lean > self.normal_pose_threshold['forward_lean']: oop_flags.append(f'forward_lean: {forward_lean:.1f}°') if abs(lateral_lean) > self.normal_pose_threshold['lateral_lean']: oop_flags.append(f'lateral_lean: {lateral_lean:.1f}°') head_distance = self._estimate_head_to_dashboard(head_position, frame.shape) if head_distance < self.normal_pose_threshold['head_distance']: oop_flags.append(f'too_close: {head_distance:.0f}mm') is_oop = len(oop_flags) > 0 return { 'is_oop': is_oop, 'flags': oop_flags, 'forward_lean': forward_lean, 'lateral_lean': lateral_lean, 'head_distance': head_distance, } def _calculate_forward_lean(self, keypoints: Dict) -> float: """ 计算前倾角度 Args: keypoints: 关键点字典 Returns: forward_lean: 前倾角度(度) """ shoulder = keypoints.get('left_shoulder') hip = keypoints.get('left_hip') ear = keypoints.get('left_ear') if None in [shoulder, hip, ear]: return 0.0 shoulder_hip = np.array(shoulder) - np.array(hip) vertical = np.array([0, 1]) cos_angle = np.dot(shoulder_hip, vertical) / np.linalg.norm(shoulder_hip) angle = np.arccos(np.clip(cos_angle, -1, 1)) return np.degrees(angle) def _calculate_lateral_lean(self, keypoints: Dict) -> float: """ 计算侧倾角度 """ left_shoulder = keypoints.get('left_shoulder') right_shoulder = keypoints.get('right_shoulder') if None in [left_shoulder, right_shoulder]: return 0.0 shoulder_vector = np.array(right_shoulder) - np.array(left_shoulder) horizontal = np.array([1, 0]) cos_angle = np.dot(shoulder_vector, horizontal) / np.linalg.norm(shoulder_vector) angle = np.arccos(np.clip(cos_angle, -1, 1)) if shoulder_vector[1] > 0: return np.degrees(angle) else: return -np.degrees(angle) def _get_head_position(self, keypoints: Dict) -> Tuple[float, float]: """获取头部位置""" nose = keypoints.get('nose') if nose: return nose return (0, 0) def _estimate_head_to_dashboard(self, head_pos: Tuple, frame_shape: Tuple) -> float: """ 估计头部到仪表盘距离 简化方法:基于头部在图像中的垂直位置 """ height, width = frame_shape[:2] head_y = head_pos[1] dashboard_y = height * 0.8 pixel_distance = dashboard_y - head_y mm_per_pixel = 5 return pixel_distance * mm_per_pixel
class PoseEstimator: """姿态估计器(简化版)""" def __init__(self): self.mp_pose = None def detect(self, frame: np.ndarray) -> Dict: """ 检测人体关键点 Returns: keypoints: 关键点字典 """ return { 'nose': (320, 100), 'left_ear': (300, 90), 'right_ear': (340, 90), 'left_shoulder': (280, 200), 'right_shoulder': (360, 200), 'left_hip': (290, 350), 'right_hip': (350, 350), }
|