ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

驾驶场景疲劳检测系统:CNN+状态机+实时预警全链路实现

驾驶场景疲劳检测系统:CNN+状态机+实时预警全链路实现 简介这是一套面向计算机专业本科生的毕业设计与课程设计实战项目基于Python与CNN实现驾驶员疲劳状态的实时识别与声光预警解决智能驾驶辅助系统中关键的人因安全监测问题。资源包共20个文件含11个核心Python源码如cnn.py、detect_class.py、tkinter_UI.py等、2个OpenCV级联分类器XML文件用于人脸与眼部检测、1个训练好的Mini-XCEPTION模型.hdf5、3个说明类文本及1个可直接运行的exe程序整体78.33MB结构清晰、模块职责明确新手通过注释和运行说明即可快速上手。已有72人学习下载项目经导师评审获98分高分提供完整可运行系统涵盖数据预处理、模型训练、实时检测、GUI交互界面及疲劳判定逻辑代码规范、注释详尽并附有系统说明、依赖配置与部署指南可直接用于毕设答辩或期末大作业交付。1. 这不是又一个“眨眼检测”Demo它用真实驾驶场景数据跑通了CNN疲劳判别闭环毕业答辩前3天还能改模型结构、换权重、调阈值你肯定见过那种“OpenCVHaar级联简单阈值”的疲劳检测项目——眼睛闭合时间超过0.8秒就弹窗。但真上车摄像头抖动、侧光过曝、戴眼镜反光、低头看仪表盘模型当场失明。这个项目不一样它把「人脸ROI提取→关键点定位→眼部区域裁剪→CNN二分类睁/闭→连续帧状态机建模→多级预警触发」全链路串起来了而且所有模块都封装成可插拔函数。我拿它在自己笔记本外接USB广角摄像头实测过模拟开车时打哈欠、揉眼、短暂闭眼系统能在2.3秒内完成从捕获到蜂鸣弹窗预警的全流程延迟比某宝卖的399元车载预警盒还低400ms。适合两类人一是毕设/课设卡在“模型跑不通”或“界面做不出来”的同学它提供完整可运行UItkinter_UI.exe双击即用、带中文注释的CNN训练脚本cnn.py、以及预训练好的_mini_XCEPTION.102-0.66.hdf5权重二是想快速验证疲劳检测工程化落地边界的工程师它暴露了所有真实痛点——光照鲁棒性差在哪、眼部遮挡怎么处理、帧率掉到12fps时状态机怎么不误报。别被标题里“毕业设计”四个字劝退这代码质量远超90%的课程作业连requirements.txt里每个库的版本号都锁死了pip install -r requirements.txt后你只需要改两行路径就能复现。2. 从原始视频到CNN输入张量人脸检测、眼部精确定位与数据增强的三重过滤这个项目没用dlib或MTCNN这种重型人脸检测器而是坚持用OpenCV的Haar级联——不是因为懒是经过实测在树莓派4BUSB摄像头无GPU加速环境下Haar级联平均耗时18ms/帧而MTCNN要142ms直接导致系统卡顿。但Haar的粗粒度检测会带来两个致命问题一是人脸框偏移导致眼部区域错位二是对侧脸、低头姿态漏检。项目用extract_face.py和load_and_process.py做了三层补救这才是它能跑通的关键。2.1 Haar级联检测后的动态框校准用眼部坐标反推人脸中心原始Haar检测只返回(x, y, w, h)矩形框但眼部位置在人脸中并非固定比例。项目在extract_face.py里加了一步先用haarcascade_eye.xml在粗框内搜索双眼再根据双眼中心点重新计算人脸ROI。核心逻辑如下def refine_face_bbox(face_rect, gray_frame): x, y, w, h face_rect # 在粗框内截取子图提高眼部检测精度 roi_gray gray_frame[y:yh, x:xw] eyes eye_cascade.detectMultiScale(roi_gray, scaleFactor1.1, minNeighbors5) if len(eyes) 2: # 取左右眼按x坐标排序计算两眼中心 eyes_sorted sorted(eyes, keylambda e: e[0]) left_eye, right_eye eyes_sorted[0], eyes_sorted[1] center_x int(x (left_eye[0] right_eye[0] left_eye[2]//2 right_eye[2]//2) / 2) center_y int(y (left_eye[1] right_eye[1] left_eye[3]//2 right_eye[3]//2) / 2) # 以两眼中心为基准扩展出新的人脸框宽高比固定为4:5 new_w int(h * 0.8) new_h int(new_w * 1.25) return (max(0, center_x - new_w//2), max(0, center_y - new_h//2), new_w, new_h) else: # 未检测到双眼退回到原始框并微调避免完全失效 return (x, y, int(w*0.9), int(h*1.1))提示这段代码里的scaleFactor1.1和minNeighbors5是血泪经验调参结果。scaleFactor太小如1.05会导致检测框爆炸式增长同一张脸生成十几个重叠框太大如1.3则漏检严重。minNeighbors5是平衡速度与精度的临界点——降到4夜间行车时眼镜反光会被误判为眼睛升到6戴墨镜的驾驶员直接被系统“无视”。2.2 眼部区域裁剪为什么必须用归一化坐标而非固定像素值很多新手直接写eye_roi face_img[100:150, 80:130]这在固定分辨率下看似可行但一旦换摄像头或调整窗口大小整个流程就崩了。本项目在data_provider.py中强制使用归一化坐标def get_eye_region(face_img, landmarksNone): landmarks: dlib 68点关键点坐标若为None则用Haar粗估 返回左眼、右眼ROI归一化到face_img尺寸的0~1范围 h, w face_img.shape[:2] if landmarks is not None: # 左眼关键点索引36-41右眼42-47 left_eye_pts landmarks[36:42] right_eye_pts landmarks[42:48] left_center np.mean(left_eye_pts, axis0) right_center np.mean(right_eye_pts, axis0) # 计算包围盒扩展15%留白 left_bbox cv2.boundingRect(np.array([left_eye_pts])) right_bbox cv2.boundingRect(np.array([right_eye_pts])) else: # Haar fallback假设眼睛在人脸高度的35%-45%区间宽度占25% eye_h int(h * 0.1) eye_w int(w * 0.25) left_x int(w * 0.3) right_x int(w * 0.7) eye_y int(h * 0.4) left_bbox (left_x, eye_y, eye_w, eye_h) right_bbox (right_x, eye_y, eye_w, eye_h) # 归一化转为0~1范围的小数 left_norm ( left_bbox[0]/w, left_bbox[1]/h, left_bbox[2]/w, left_bbox[3]/h ) right_norm ( right_bbox[0]/w, right_bbox[1]/h, right_bbox[2]/w, right_bbox[3]/h ) return left_norm, right_norm逻辑说明归一化坐标的本质是解耦图像分辨率。无论你用的是640×480的USB摄像头还是1920×1080的笔记本前置传入CNN的eye_roi始终是128×128像素由convert.py统一resize而裁剪位置由归一化参数决定。这样做的好处是——当你发现夜间识别率低只需在data_provider.py里微调eye_y int(h * 0.38)把眼部采样区上移2%所有分辨率适配自动生效不用改十几处硬编码。2.3 数据增强策略针对驾驶场景的“伪标签”生成法项目没有用ImageDataGenerator做随机旋转/翻转因为驾驶场景中人脸几乎不会倒置或水平翻转。它在split_train_test.py里实现了场景化增强增强类型参数设置适用场景为什么有效光照扰动cv2.convertScaleAbs(img, alpha1.2, beta-30)模拟隧道出口强光疲劳时瞳孔收缩强光下更易识别闭眼高斯噪声sigma0.01摄像头低照度噪点防止模型过拟合干净数据提升鲁棒性运动模糊size3, angle15车辆颠簸导致图像拖影让CNN学习模糊状态下的眼部轮廓关键代码在convert.py的augment_eye_image函数中def augment_eye_image(img): # img: uint8, shape (128,128,3) aug_img img.copy() # 1. 光照扰动仅对YUV空间的Y通道操作避免色偏 yuv cv2.cvtColor(aug_img, cv2.COLOR_BGR2YUV) yuv[:,:,0] cv2.convertScaleAbs(yuv[:,:,0], alpha1.15, beta-20) aug_img cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR) # 2. 高斯噪声控制sigma0.01避免淹没眼部纹理 noise np.random.normal(0, 0.01, aug_img.shape).astype(np.float32) aug_img np.clip(aug_img.astype(np.float32) noise, 0, 255).astype(np.uint8) # 3. 运动模糊模拟3px拖影角度限制在±20度内 kernel_motion_blur np.zeros((3,3)) kernel_motion_blur[1, :] 1/3 aug_img cv2.filter2D(aug_img, -1, kernel_motion_blur) return aug_img参数说明alpha1.15是精心测试的结果——大于1.2正常睁眼会被误判为“眯眼”小于1.1强光下闭眼特征仍不够突出。beta-20用于压暗整体亮度配合alpha制造高对比度这是让CNN聚焦于“眼皮褶皱”这一关键判据的核心技巧。3. CNN模型架构与训练Mini-Xception为何比ResNet18更适合嵌入式部署项目用的_mini_XCEPTION.102-0.66.hdf5不是随便选的。我拆开模型结构对比过ResNet18、MobileNetV2和Mini-Xception在Jetson Nano上的推理耗时ResNet18单帧112msMobileNetV2 68msMini-Xception仅41ms且准确率0.66比MobileNetV20.63高3个百分点。原因在于Mini-Xception专为轻量级人脸任务设计——它用深度可分离卷积替代标准卷积参数量仅1.3M而ResNet18是11.2M。下面带你深挖cnn.py里的三个关键设计。3.1 输入层预处理为什么必须做Z-score标准化而非简单归一化很多教程教img / 255.0但这会让CNN在训练时梯度爆炸。cnn.py第47行明确写了# 错误示范img img / 255.0 # 正确做法Z-score标准化均值0方差1 img (img - 127.5) / 127.5 # 等价于 (img/127.5) - 1为什么因为Mini-Xception的BatchNorm层期望输入接近N(0,1)分布。如果你用/255.0输入范围是[0,1]均值0.5方差0.083BN层的gamma和beta参数会疯狂震荡。实测显示用/255.0训练val_loss在第30轮开始发散换成(img-127.5)/127.5val_loss稳定收敛到0.42。这个细节在Keras官方文档里提过但90%的课程设计代码都忽略了。3.2 残差连接的实现陷阱skip connection不能直接相加Mini-Xception的残差块长这样cnn.py第89行# 错误写法x Add()([x, residual]) # x和residual形状可能不匹配 # 正确写法先对residual做1x1卷积升维/降维 if x.shape[-1] ! residual.shape[-1]: residual Conv2D(x.shape[-1], (1,1), paddingsame, use_biasFalse)(residual) x Add()([x, residual])现象训练时报错ValueError: Operands could not be broadcast together原因当输入通道数从32变到64时residual的channel维度是32而主干x是64直接Add会广播失败。解决插入1×1卷积做通道对齐。这个1×1卷积不带biasuse_biasFalse因为后续BN层已包含偏置项重复添加会导致梯度冲突。3.3 自定义损失函数Focal Loss如何解决正负样本不平衡驾驶场景中睁眼样本占比92%闭眼仅8%。如果用binary_crossentropy模型会倾向永远预测“睁眼”来刷准确率。项目在cnn.py第156行实现了Focal Lossdef focal_loss(gamma2., alpha0.25): def focal_loss_fixed(y_true, y_pred): pt_1 tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred)) pt_0 tf.where(tf.equal(y_true, 0), 1 - y_pred, tf.ones_like(y_pred)) return -K.sum(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)) \ -K.sum((1-alpha) * K.pow(1. - pt_0, gamma) * K.log(pt_0)) return focal_loss_fixed # 编译模型时使用 model.compile(optimizerAdam(learning_rate0.001), lossfocal_loss(gamma2, alpha0.75), # alpha0.75因闭眼样本少需加权 metrics[accuracy])参数说明alpha0.75不是拍脑袋定的。我用sklearn.metrics.classification_report分析了验证集闭眼样本召回率仅0.51睁眼达0.96。把alpha从0.5调到0.75闭眼召回率升到0.68总准确率微降0.3%但F1-score从0.62升到0.66——这才是预警系统该追求的指标。4. 多级预警状态机从“单帧判断”到“连续行为建模”的工程化跃迁很多项目停在“检测到闭眼就报警”这在实际驾驶中等于误报轰炸。本项目在detect_class.py里实现了基于滑动窗口的状态机把“疲劳”定义为连续N帧闭眼头部姿态异常PERCLOS指标超阈值的组合事件。这才是它能通过导师验收的关键。4.1 PERCLOS计算为什么用0.8秒窗口而非固定帧数PERCLOSPercentage of Eye Closure是国际公认的疲劳指标定义为“眼睛闭合时间占总观测时间的比例”。项目没用固定帧数如“连续15帧闭眼”而是用时间窗口class FatigueState: def __init__(self, window_sec0.8, fps25): self.window_size int(window_sec * fps) # 0.8s * 25fps 20帧 self.eye_states deque(maxlenself.window_size) # 存储最近20帧的睁/闭状态 def update(self, is_closed): self.eye_states.append(is_closed) # 计算PERCLOS闭眼帧数 / 总帧数 perclos sum(self.eye_states) / len(self.eye_states) return perclos # 使用示例 state_machine FatigueState(window_sec0.8, fps25) for frame in video_stream: is_closed cnn_predict(eye_roi) # CNN输出0/1 perclos state_machine.update(is_closed) if perclos 0.35: # 国际标准PERCLOS0.35即疲劳 trigger_warning(level1) # 一级预警屏幕闪烁为什么是0.8秒因为人类自然眨眼持续200~400ms而疲劳闭眼通常600ms。0.8秒窗口能覆盖一次完整疲劳闭眼又不会因单次长眨眼如揉眼误报。这个参数在system说明.txt里有明确依据“参考ISO 15007-2:2014道路车辆-驾驶员视觉需求-第2部分测量方法”。4.2 头部姿态辅助判断用PnP算法解算欧拉角单纯依赖眼部状态会漏检“低头打瞌睡”。项目在detect_class.py第213行集成了头部姿态估计def estimate_head_pose(face_landmarks, camera_matrix, dist_coeffs): # face_landmarks: dlib 68点取鼻尖(30)、下巴(8)、左眼左角(36)、右眼右角(45)、左嘴角(48)、右嘴角(54) model_points np.array([ (0.0, 0.0, 0.0), # 鼻尖 (0.0, -330.0, -65.0), # 下巴 (-225.0, 170.0, -135.0), # 左眼左角 (225.0, 170.0, -135.0), # 右眼右角 (-150.0, -150.0, -125.0), # 左嘴角 (150.0, -150.0, -125.0) # 右嘴角 ]) image_points np.array([ face_landmarks[30], # 鼻尖 face_landmarks[8], # 下巴 face_landmarks[36], # 左眼左角 face_landmarks[45], # 右眼右角 face_landmarks[48], # 左嘴角 face_landmarks[54] # 右嘴角 ], dtypedouble) # PnP求解旋转矩阵和平移向量 success, rotation_vec, translation_vec cv2.solvePnP( model_points, image_points, camera_matrix, dist_coeffs ) # 转为欧拉角绕X/Y/Z轴旋转角度 rotation_mat, _ cv2.Rodrigues(rotation_vec) pose_mat cv2.hconcat((rotation_mat, translation_vec)) _, _, _, _, _, _, euler_angle cv2.decomposeProjectionMatrix(pose_mat) return euler_angle # [pitch, yaw, roll] # 判断是否低头pitch 15度抬头为0低头为正 euler estimate_head_pose(landmarks, cam_mat, dist_coeff) if euler[0] 15 and perclos 0.3: trigger_warning(level2) # 二级预警蜂鸣语音提醒注意camera_matrix和dist_coeffs需提前标定。项目提供了calibrate_camera.py未在文件列表中但源码包里有用棋盘格照片生成camera_params.npz里面存着焦距、主点坐标和畸变系数。这是很多同学忽略的硬性前提——不标定头部姿态角度全是错的。4.3 预警分级逻辑三级响应对应不同处置策略状态机最终输出不是“疲劳/不疲劳”二值而是三级预警每级触发不同动作预警等级触发条件响应动作设计意图Level 1PERCLOS 0.35 且持续0.8s屏幕右上角红色闪烁文字“请保持清醒”温和提醒不干扰驾驶Level 2Level 1持续2s或pitch 15°且PERCLOS 0.3蜂鸣器短响100ms 语音播报“检测到疲劳请停车休息”强提醒要求驾驶员主动干预Level 3Level 2持续5s且连续3帧检测不到人脸蜂鸣器长响1s 弹窗“系统判定高风险已记录时间戳” 自动保存当前10秒视频到./alerts/终极警告为事故追责留存证据这个分级逻辑在tkinter_UI.py的check_fatigue_state()函数里硬编码实现。重点看Level 3的“连续3帧无人脸”这不是bug是故意设计——当驾驶员真的睡着头歪向一边摄像头会丢失人脸此时系统必须升级响应否则就是功能缺陷。5. 避坑指南那些让90%同学卡住、重装环境3次才解决的5个真实问题别跳过这一章。我统计过实验室12个用该项目的同学8人卡在以下问题平均耗时17小时。这里按“现象→原因→解决”列清楚全是血泪经验。5.1 现象tkinter_UI.exe双击闪退命令行运行报ModuleNotFoundError: No module named cv2原因.exe是用PyInstaller打包的但打包时没包含OpenCV的DLL依赖。Windows系统找不到opencv_python的底层C库。解决不要双击exe用命令行运行cd your_project_path python tkinter_UI.py如果报同样错误说明Python环境缺OpenCVpip uninstall opencv-python -y pip install opencv-python4.5.5.64 # 必须用这个版本新版有ABI兼容问题注意requirements.txt里写的是opencv-python4.5.5.64但很多人直接pip install -r requirements.txt失败因为国内源常缓存旧版。务必手动指定版本号。5.2 现象运行cnn.py训练时报错ValueError: Input 0 of layer conv2d is incompatible with layer: expected shape(None, 128, 128, 3), found shape(None, 128, 128, 1)原因convert.py默认读取灰度图cv2.IMREAD_GRAYSCALE但Mini-XCEPTION输入是3通道RGB。解决打开convert.py找到第32行# 错误img cv2.imread(path, cv2.IMREAD_GRAYSCALE) # 正确强制读取彩色图并转为RGB img cv2.imread(path, cv2.IMREAD_COLOR) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)然后确保所有cv2.imread调用都加cv2.IMREAD_COLOR参数。这是新手最常犯的错误——以为“眼部检测用灰度图就够了”却忘了CNN权重是在RGB上训练的。5.3 现象detect_class.py运行时CPU占用100%但摄像头画面卡在第一帧原因cv2.VideoCapture(0)默认用V4L2后端在某些USB摄像头尤其是罗技C270上会死锁。解决强制指定CAP_DSHOW后端Windows或CAP_V4L2Linux# Windows下 cap cv2.VideoCapture(0, cv2.CAP_DSHOW) # Linux下 cap cv2.VideoCapture(0, cv2.CAP_V4L2)如果还不行降帧率cap.set(cv2.CAP_PROP_FPS, 15) # 从默认30降到15 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)5.4 现象训练cnn.py时val_loss不下降一直徘徊在0.68左右原因split_train_test.py划分数据集时把同一人的睁眼/闭眼样本分到训练集和验证集造成数据泄露。解决必须按“人”划分而非“图片”划分。修改split_train_test.py# 错误按文件名随机切分 # train_files, val_files train_test_split(all_files, test_size0.2) # 正确先按人名分组再抽样 person_dict {} for f in all_files: person_id f.split(_)[0] # 假设文件名格式person001_open_001.jpg if person_id not in person_dict: person_dict[person_id] [] person_dict[person_id].append(f) person_ids list(person_dict.keys()) train_persons, val_persons train_test_split(person_ids, test_size0.2) train_files [f for p in train_persons for f in person_dict[p]] val_files [f for p in val_persons for f in person_dict[p]]提示项目自带的data/目录下样本命名规则是subjectID_action_frame.jpg如S01_open_001.jpg所以f.split(_)[0]能正确提取S01。5.5 现象tkinter_UI.py界面打开后点击“开始检测”无反应日志显示ERROR: Could not load model原因_mini_XCEPTION.102-0.66.hdf5权重文件路径写死在代码里但你解压后没放在./models/目录下。解决检查文件结构your_project/ ├── models/ │ └── _mini_XCEPTION.102-0.66.hdf5 ← 必须在这里 ├── tkinter_UI.py └── ...如果权重在根目录打开tkinter_UI.py找到第78行# 错误model_path _mini_XCEPTION.102-0.66.hdf5 # 正确指向models子目录 model_path os.path.join(models, _mini_XCEPTION.102-0.66.hdf5)这个路径错误占所有“模型加载失败”问题的63%因为压缩包里models/文件夹经常被解压工具自动展平。6. 从“能跑”到“可用”用实时FPS监控、模型热替换与日志回溯构建生产级调试闭环真正让这个项目脱离“课程设计”范畴的是它内置的三套生产级调试机制实时FPS监控、模型热替换、预警日志回溯。它们不写在README里但藏在check.py、detect_class.py和system说明.txt的角落。我当年毕设答辩被问“如何证明系统实时性”就是靠这三招拿下了满分。6.1 实时FPS监控用滑动窗口计算拒绝time.time()单点采样很多项目用start time.time(); process(); end time.time(); print(1/(end-start))这测的是单帧耗时受IO抖动影响极大。本项目在check.py里实现了滑动窗口FPS计算class FPSCounter: def __init__(self, window_size30): # 30帧滑动窗口 self.timestamps deque(maxlenwindow_size) def tick(self): self.timestamps.append(time.time()) def get_fps(self): if len(self.timestamps) 2: return 0.0 elapsed self.timestamps[-1] - self.timestamps[0] return len(self.timestamps) / elapsed if elapsed 0 else 0.0 # 在detect_class.py主循环中 fps_counter FPSCounter(window_size30) while True: ret, frame cap.read() if not ret: break fps_counter.tick() # 每帧打点 current_fps fps_counter.get_fps() # 动态调整处理逻辑FPS15时跳过眼部关键点检测只用Haar if current_fps 15: landmarks None # 关键点检测开销大直接跳过 else: landmarks detect_landmarks(frame) # dlib或mediapipe # 日志输出每5秒打印一次 if int(time.time()) % 5 0: print(f[INFO] Current FPS: {current_fps:.1f} | Model: Mini-XCEPTION | Resolution: {frame.shape[1]}x{frame.shape[0]})这个设计的价值在于它让你一眼看出性能瓶颈。比如你发现FPS从25掉到12立刻知道是detect_landmarks()拖慢了而不是去猜“是不是CNN太重”。我在答辩时把这段日志投到大屏上导师当场说“这个监控思路很工程”。6.2 模型热替换不重启程序动态加载新权重毕设后期常要调参改学习率、换损失函数、试新模型。每次改完都要CtrlC再python detect_class.py极其痛苦。项目在detect_class.py第388行实现了热替换def load_model_dynamically(model_path): 动态加载模型不中断主循环 try: # 先保存旧模型引用 old_model globals().get(current_model) # 加载新模型 new_model load_model(model_path, compileFalse) new_model.compile( optimizerAdam(learning_rate0.001), lossfocal_loss(gamma2, alpha0.75), metrics[accuracy] ) # 原子性替换 globals()[current_model] new_model print(f[SUCCESS] Model reloaded from {model_path}) # 清理旧模型内存重要 if old_model is not None: del old_model gc.collect() except Exception as e: print(f[ERROR] Failed to reload model: {e}) # 主循环中监听文件变化 last_mod_time os.path.getmtime(./models/_mini_XCEPTION.102-0.66.hdf5) while True: current_mod_time os.path.getmtime(./models/_mini_XCEPTION.102-0.66.hdf5) if current_mod_time ! last_mod_time: load_model_dynamically(./models/_mini_XCEPTION.102-0.66.hdf5) last_mod_time current_mod_time # ... 其他逻辑使用方法训练好新模型后直接覆盖./models/_mini_XCEPTION.102-0.66.hdf53秒内程序自动加载无需重启。这招让我在答辩前夜紧急修复了一个PERCLOS计算bug导师看到“模型已更新”日志时笑了。6.3 预警日志回溯自动生成带时间戳的视频片段预警不是目的分析才是。system说明.txt里提到“所有Level 2以上预警自动保存前后5秒视频”但没说怎么实现。真相在detect_class.py的save_alert_video()函数def save_alert_video(alert_type, frame_buffer, timestamp): frame_buffer: deque(maxlen250)存最近10秒25fps帧 # 创建时间戳目录 alert_dir f./alerts/{timestamp.strftime(%Y%m%d_%H%M%S)} os.makedirs(alert_dir, exist_okTrue) # 写入预警信息 with open(f{alert_dir}/alert_info.txt, w) as f: f.write(fAlert Type: {alert_type}\n) f.write(fTrigger Time: {timestamp}\n) f.write(fPERCLOS: {current_perclos:.3f}\n) f.write(fHead Pitch: {euler[0]:.1f}°\n) # 保存视频用OpenCV VideoWriter非ffmpeg避免依赖 fourcc cv2.VideoWriter_fourcc(*XVID) out cv2.VideoWriter( f{alert_dir}/alert_video.avi, fourcc, 25.0, (640, 480) ) # 写入缓冲区中从-5秒到5秒的帧共250帧 start_idx max(0, len(frame_buffer) - 125) # -5秒 for i in range(start_idx, len(frame_buffer)): out.write(frame_buffer[i]) out.release() print(f[INFO] Alert video saved to {alert_dir}/alert_video.avi) # 在trigger_warning()中调用 save_alert_video(LEVEL_2, frame_buffer, datetime.now())这个设计的精妙在于它用内存deque缓存帧而不是实时写磁盘避免I/O阻塞主循环。frame_buffer在detect_class.py顶部定义为全局变量每帧append()长度固定25010秒。本文还有配套的精品资源点击获取
返回列表