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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
| """ ZF Infrared Belt 检测集成示例 适用于 Euro NCAP 合规的安全带误用检测 """
import numpy as np from typing import Tuple, List, Optional from dataclasses import dataclass from enum import Enum
class BeltStatus(Enum): UNBUCKLED = 0 CORRECT = 1 MISUSE_SHOULDER = 2 MISUSE_LAP = 3 MISUSE_BOTH = 4 NOT_DETECTED = 5
@dataclass class SeatPosition: row: int side: str occupied: bool = False
class InfraredBeltDetector: """ 红外安全带检测器 硬件要求: - 940nm 红外摄像头 (前排+后排) - ZF LIFETEC Infrared Belt 安全带 - 最小分辨率: 1280x720 - 帧率: ≥15fps (NCAP 最低要求) """ IR_THRESHOLD = 0.65 MIN_BELT_LENGTH = 80 ANGLE_TOLERANCE = 15 def __init__(self, config: dict): self.camera_front = config.get('camera_front', 'default') self.camera_rear = config.get('camera_rear', 'default') self.seats = self._init_seats(config) def _init_seats(self, config: dict) -> List[SeatPosition]: seats = [] rows = config.get('rows', 2) for row in range(1, rows + 1): sides = config.get(f'row{row}_sides', ['left', 'right']) for side in sides: seats.append(SeatPosition(row=row, side=side)) return seats def detect_belt_status( self, ir_image: np.ndarray, seat: SeatPosition ) -> Tuple[BeltStatus, float]: """ 检测安全带佩戴状态 Args: ir_image: 红外图像 (H, W), uint8 seat: 座位位置 Returns: status: BeltStatus 枚举 confidence: 置信度 0-1 """ belt_mask = self._extract_ir_signature(ir_image) if belt_mask.sum() < self.MIN_BELT_LENGTH: return BeltStatus.NOT_DETECTED, 0.0 if not seat.occupied: return BeltStatus.UNBUCKLED, 0.95 belt_path = self._trace_belt_path(belt_mask) shoulder_ok, lap_ok = self._classify_belt_position( belt_path, seat ) confidence = self._calculate_confidence(belt_mask, belt_path) if shoulder_ok and lap_ok: return BeltStatus.CORRECT, confidence elif not shoulder_ok and not lap_ok: return BeltStatus.MISUSE_BOTH, confidence elif not shoulder_ok: return BeltStatus.MISUSE_SHOULDER, confidence else: return BeltStatus.MISUSE_LAP, confidence def _extract_ir_signature(self, ir_image: np.ndarray) -> np.ndarray: """ 从红外图像提取安全带签名 ZF 红外安全带的特征: - 在 940nm 波段有高反射率 - 与皮肤/衣物/座椅形成高对比 """ normalized = ir_image.astype(np.float32) / 255.0 belt_mask = normalized > self.IR_THRESHOLD return belt_mask.astype(np.uint8) def _trace_belt_path(self, mask: np.ndarray) -> np.ndarray: """追踪安全带路径""" from scipy import ndimage skeleton = ndimage.morphology.distance_transform_edt(mask) path = np.argwhere(skeleton > 0) return path def _classify_belt_position( self, path: np.ndarray, seat: SeatPosition ) -> Tuple[bool, bool]: """ 分类肩带和腰带位置是否正确 正确佩戴标准 (Euro NCAP): - 肩带: 从肩部跨锁骨到对侧腰部 - 腰带: 跨越盆骨(非腹部) - 角度偏差 < 15度 """ if len(path) < 10: return False, False angle = np.degrees(np.arctan2( path[-1, 0] - path[0, 0], path[-1, 1] - path[0, 1] )) shoulder_ok = 25 < abs(angle) < 65 lap_ok = abs(angle) < self.ANGLE_TOLERANCE return shoulder_ok, lap_ok def _calculate_confidence( self, mask: np.ndarray, path: np.ndarray ) -> float: """计算检测置信度""" ir_strength = mask.mean() path_length = len(path) confidence = min(ir_strength * 1.5, 1.0) length_factor = min(path_length / 200, 1.0) return (confidence + length_factor) / 2
class EuroNCAPCompliance: """Euro NCAP 安全带检测合规检查""" def __init__(self): self.detector = InfraredBeltDetector({ 'rows': 2, 'row1_sides': ['left', 'right'], 'row2_sides': ['left', 'center', 'right'] }) def check_all_seats(self, ir_images: dict) -> dict: """ 检查所有座位 Args: ir_images: {'front': ndarray, 'rear': ndarray} Returns: compliance_report: 每座状态 + 整体合规 """ results = {} for seat in self.detector.seats: img_key = 'front' if seat.row == 1 else 'rear' if img_key not in ir_images: continue status, conf = self.detector.detect_belt_status( ir_images[img_key], seat ) results[f"r{seat.row}_{seat.side}"] = { 'status': status.name, 'confidence': round(conf, 3), 'compliant': status in [BeltStatus.CORRECT, BeltStatus.UNBUCKLED] } results['overall_compliant'] = all( r['compliant'] for r in results.values() if isinstance(r, dict) ) return results
if __name__ == "__main__": compliance = EuroNCAPCompliance() front_ir = np.random.randint(0, 100, (720, 1280), dtype=np.uint8) rear_ir = np.random.randint(0, 100, (720, 1280), dtype=np.uint8) front_ir[200:600, 400:420] = 200 front_ir[600:630, 300:800] = 200 report = compliance.check_all_seats({ 'front': front_ir, 'rear': rear_ir }) for seat, result in report.items(): if isinstance(result, dict): print(f"{seat}: {result['status']} (conf={result['confidence']})") print(f"\nOverall NCAP Compliant: {report['overall_compliant']}")
|