ARTICLE DETAIL

资讯详情

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

PyQt5+YOLOv5+Dlib驾驶员行为监控系统实战

PyQt5+YOLOv5+Dlib驾驶员行为监控系统实战 简介面向高校课程设计与毕设场景的驾驶员行为监控系统完整源码包基于PyQT、YOLOv5与Dlib三种主流框架协同实现。系统采用PyQT5构建可视化交互界面借助YOLOv5完成面部与头部姿态的实时检测再配合Dlib关键点定位追踪眼睛、嘴巴等区域从而识别闭眼、张嘴、低头等危险驾驶行为并触发声音报警整体覆盖从视频采集、模型推理到GUI展示、数据记录的完整链路。资源共105个文件、约224MB以66个py源码为主体另含4套ui界面布局、2个yaml模型配置、多项dlib人脸模型与权重文件、mp3提醒音频及bat启动脚本可直接对照运行调试也便于二次扩展。目前已有273人学习适合需要快速搭建同类监测系统、理解目标检测与关键点融合方案的开发者参考。1. 驾驶员行为监控系统PyQt5 界面下 YOLOv5 与 Dlib 双模型怎么协同工作做课设做到「驾驶员行为监控」这个题目时我最初的想法很简单YOLOv5 负责检测人脸Dlib 负责抠眼睛和嘴巴再用 PyQt5 把画面和告警怼到界面上完事。真正动手才发现这个组合最大的难点根本不是「模型选谁」而是两个推理引擎Torch 和 OpenVINO、一个 68 点关键点检测器、一套行为判定逻辑和一个 GUI 主循环要同时跑在一条视频流上任何一环卡住摄像头画面就冻结给你看。这份资源是一个可以直接跑的课程设计成品包含 PyQt5 界面、YOLOv5 权重 last.bin 、Dlib 人脸关键点模型、行为记录 CSV 和两套启动脚本 run_torch.bat / run_openvino.bat 。它解决的是「闭眼、张嘴、低头」三类危险驾驶行为的实时识别与报警适合需要交课设的学生也适合想快速搭一个视觉行为监测原型的开发者。2. 技术选型与整体架构为什么是 YOLOv5 Dlib 双模型而不是单模型方案2.1 双模型分工空间定位交给 YOLO特征点回归交给 Dlib如果把「驾驶员行为监控」拆成感知子任务它其实是两个问题人脸在哪里以及眼睛和嘴巴处于什么状态。这两个问题用同一个模型解决不是不行但在这个项目场景下把两者拆开是性价比最高的做法。YOLOv5 在这里不是用来检测「闭眼」「张嘴」这类细粒度状态的而是负责第一层的目标定位——在复杂驾驶室背景下把人脸框出来。车载环境的光线变化大、眼镜反光、口罩遮挡、头部大幅度转动这些都会让传统的人脸检测器比如 OpenCV 的 Haar Cascade 或 Dlib 自带的 HOG 检测器频繁丢框。YOLOv5 作为深度卷积检测器在遮挡和光照变化下的鲁棒性明显好一截实测在 640×640 输入下单人脸场景的推理延迟在 GTX 1660 上大概 15~25ms完全赶得上视频流的实时处理。Dlib 则负责第二层在 YOLOv5 给出的人脸框内做 68 点关键点回归。为什么不用 YOLOv5 直接回归眼睛和嘴巴的位置一个很现实的原因是YOLOv5 官方预训练权重里没有针对「眼睛开合度」「嘴巴开合度」这种细粒度关键点的输出头要自己标注训练数据集并改输出层课设周期完全不够。而 Dlib 的 shape_predictor_68_face_landmarks.dat 是成熟的通用预训练模型输出 68 个关键点的坐标直接映射眼睛和嘴巴区域即可准确度经过大量验证。常见做法是YOLOv5 检测人脸框 → 将人脸框坐标映射到原图 → 在框内调用 Dlib 关键点检测 → 基于关键点计算 EAR / MAR 指标 → 判定行为状态。这套双模型串联的结构把「粗定位」和「细回归」两个任务解耦任何一个模型坏了都能单独定位问题比单模型方案好调试得多。2.2 项目文件结构每个文件是干什么的拿到这份资源后第一件事是先看清目录里这些文件分别起什么作用避免跑起来之后不知道哪个环节出了问题。项目里的 run_openvino.bat 和 run_torch.bat 是两个启动入口对应两套推理后端。Torch 是直接用 PyTorch 跑 YOLOv5精度高、部署简单前提是装了完整的 PyTorch 环境OpenVINO 则是把 YOLOv5 模型转换为 OpenVINO IR 格式后用 Intel 的推理引擎跑CPU 上的速度比原生 PyTorch 快不少这对没有 NVIDIA GPU 的机器来说很有价值。last.bin 是 YOLOv5 训练好的权重文件。严格来说 YOLOv5 官方权重格式是 .ptPyTorch 序列化格式但 last.bin 通常是经过转换或自定义导出后的二进制权重常见做法是在引擎加载层做一层适配把二进制流反序列化为模型参数。如果你看到这个文件说明原项目可能用了 OpenVINO 的转换管线或自定义的权重读写逻辑。shape_predictor_68_face_landmarks.dat 是 Dlib 的人脸关键点模型68 个关键点覆盖眉毛、眼睛、鼻子、嘴巴和下颌轮廓。dlib_face_recognition_resnet_model_v1.dat 是 Dlib 的 ResNet 人脸识别模型在这个项目里主要用于辅助验证——比如确认检测到的人脸是不是驾驶员本人或者为后续「分心识别做身份锚定。data.csv 是行为记录文件每次启动系统后检测到的闭眼、张嘴、低头事件会按时间戳追加写入这个文件格式大致是时间、行为类型、持续帧数、置信度。这是课设答辩时最好用的素材——直接把 CSV 拉出来画个折线图评委就能看到你的系统确实在持续运行并记录数据。2.3 两个启动脚本的设计意图CPU 和 GPU 环境兼顾run_torch.bat 优先走 GPU 推理run_openvino.bat 是为纯 CPU 环境准备的。这一点在课设演示时尤其重要——答辩现场不一定有 NVIDIA 显卡如果只准备了一套 Torch 环境机器散热不佳导致推理帧率掉到 5FPS演示效果会很尴尬。run_torch.bat 的常见内容大致长这样echo off cd /d %~dp0 call conda activate driver_monitor python main.py --engine torch --weights last.bin --conf 0.45 pauserun_openvino.bat 的差别在于传入的引擎参数和模型路径echo off cd /d %~dp0 call conda activate driver_monitor_openvino python main.py --engine openvino --weights last.bin --conf 0.45 --device CPU pause第一行cd /d %~dp0是切换到 bat 文件所在目录防止在系统其他路径下双击启动时找不到模型文件这是最常见的启动闪退原因之一。call conda activate激活对应虚拟环境两套环境分开装依赖避免 Torch 和 OpenVINO 的库冲突。--conf 0.45是检测置信度阈值如果现场检测不到人脸就往下调0.3~0.35如果误检太多往上调0.5 以上。这种双脚本设计也提醒你main.py 的入口参数一定要用 argparse 设计好这是课设代码规范里容易被扣分的点却直接决定了系统在不同硬件上的可用性。3. 把系统跑起来从环境安装到摄像头出画面的完整流程3.1 环境清单与版本搭配这个项目依赖的库比较多而且有些库的版本是有兼容性要求的。Dlib 在 Python 3.10 以上的版本安装会踩编译坑OpenVINO 的版本和 PyTorch 版本也存在相互约束。我一般建议按下面这套组合来装。依赖库推荐版本说明Python3.8 或 3.9Dlib 编译稳定torch 和 openvino 兼容最好PyTorch1.10~1.13对应 YOLOv5 v6 系列torchvision与 torch 版本匹配不能单独乱装openvino2022.3 或 2023.xCPU 推理时用dlib19.22.0 或 19.24.x需要 CMake 和 C 编译器支持PyQt55.15.x太新的版本可能出现 API 变更opencv-python4.5.x ~ 4.8.x视频捕获和图像预处理numpy1.21~1.24注意 torch 对 numpy 版本的上限要求Dlib 安装是第一个拦路虎。Windows 下直接pip install dlib十有八九会报错因为需要编译源码。常见做法是先去官网下载预编译的 wheel 包或者用 conda 安装conda install -c conda-forge dlib这条命令会跳过本地编译直接装二进制版本。创建虚拟环境时我习惯加上--no-default-packages参数避免全局包污染。环境建好之后先装 PyQt5 和 OpenCV再装 torch 和 dlib每装完一个就import验证一次不要一口气装完再查错。这个顺序能帮你把「环境问题」和「代码问题」彻底分开排查效率高得多。3.2 GUI 主流程与线程模型防止界面卡死PyQt5 的 GUI 循环是单线程的如果你把摄像头读取、模型推理、结果绘制全部塞进主线程画面帧率会掉到惨不忍睹——因为推理一次要几十毫秒界面刷新就停了。这个项目里一个合理的做法是使用三个线程的架构采集线程、推理线程、UI 主线程。import sys import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal, QTimer from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget class CaptureThread(QThread): frame_ready pyqtSignal(object) def __init__(self, camera_id0): super().__init__() self.camera_id camera_id self.running True def run(self): cap cv2.VideoCapture(self.camera_id) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while self.running: ret, frame cap.read() if ret: self.frame_ready.emit(frame) cap.release()CaptureThread只做一件事从摄像头读帧并通过信号发给下游。frame_ready pyqtSignal(object)是 PyQt5 的信号机制跨线程传递 OpenCV 帧不会被复制性能开销小。running标志位用于线程优雅退出避免点击关闭窗口时程序卡死。推理线程接收帧、调用 YOLOv5 和 Dlib、把带标注的结果再通过另一个信号发回给 UI 线程。UI 只负责把接收到的帧转成 QPixmap 显示在 QLabel 上。框架大致是class InferThread(QThread): result_ready pyqtSignal(object) def __init__(self): super().__init__() self.input_frame None self.running True def run(self): while self.running: if self.input_frame is None: self.msleep(5) continue frame self.input_frame.copy() # 调用 YOLOv5 人脸检测 Dlib 关键点提取 行为判定 annotated self.process_frame(frame) self.result_ready.emit(annotated) self.input_frame None def process_frame(self, frame): # 具体推理逻辑在下一章展开 return frame这里有一个容易踩的坑推理线程拿到input_frame后必须.copy()否则摄像头数据缓冲区持续写入推理还没算完帧已经被下一帧覆盖。信号槽连接时用Qt.QueuedConnection确保跨线程安全常用做法是在 connect 时不指定连接类型让 PyQt5 根据线程关系自动选择。3.3 模型加载与初始化权重路径必须是绝对路径模型加载部分的代码通常被放在main.py的main()函数里在启动 GUI 前初始化。YOLOv5 的加载方式取决于你用的是官方仓库还是自定义封装。常见做法是直接用官方仓库的torch.hub.load加载import torch from dlib import shape_predictor # 加载 YOLOv5 人脸检测模型 model torch.hub.load(ultralytics/yolov5, custom, pathweights/last.bin, force_reloadFalse) model.conf 0.45 model.iou 0.45 model.max_det 5 # 最多检测5个人脸 # 加载 Dlib 关键点模型 predictor shape_predictor(models/shape_predictor_68_face_landmarks.dat)torch.hub.load的第一个参数是 GitHub 仓库名第二个参数custom表示加载本地权重第三个参数path指向权重文件路径。这里的关键陷阱是path必须用项目目录下的相对路径或绝对路径而且启动脚本里的cd /d %~dp0保证了当前工作目录在项目根目录模型路径才不会断。model.conf是置信度阈值model.iou是 NMS 的 IoU 阈值model.max_det 5限制最大检测数量——驾驶舱场景最多也就一两个人脸设为 5 足够还能稍微提升推理速度。Dlib 的shape_predictor构造函数接收 .dat 文件的路径这个模型文件大小约 99MB首次加载需要 1~2 秒属于正常现象。如果加载时崩溃或报版本错误检查 Dlib 版本是否是 19.x 系列。4. 行为判定核心逻辑闭眼、张嘴、低头是怎么算出来的4.1 68 点关键点映射眼睛和嘴巴的编号范围Dlib 的 68 点模型有一套固定的编号规则写行为判定代码前必须先记住这几个关键区间左眼是 36 到 41 号点右眼是 42 到 47 号点嘴巴外轮廓是 48 到 59 号点嘴巴内轮廓是 60 到 67 号点。只要拿到这些点的坐标后续所有行为指标都从这些点里算出来。def get_eye_points(landmarks, eye_indices): 从 Dlib 关键点对象中提取指定眼睛的坐标列表。 landmarks: dlib.full_object_detection 对象 eye_indices: list例如 [36, 37, 38, 39, 40, 41] 返回值是六个 (x, y) 坐标的列表 return [(landmarks.part(i).x, landmarks.part(i).y) for i in eye_indices]这段代码的逻辑很直接landmarks.part(i)拿到第 i 个关键点对象.x和.y是其坐标。返回的六个点组成了眼睛轮廓边的顺序是连续的——左眼角到右眼角再绕回来。参数eye_indices决定你取左眼还是右眼左眼范围是[36, 37, 38, 39, 40, 41]右眼是[42, 43, 44, 45, 46, 47]。提取到坐标后下一步就是通过计算 EAR 来判断眼睛是否闭合。4.2 EAR 闭眼判定与连续帧防抖EAREye Aspect Ratio是判断眼睛开合度的经典指标原理是利用眼睛轮廓六个点之间的欧氏距离比值。当眼睛闭合时上下眼睑的距离垂直距离趋近于零EAR 值会显著下降。计算公式是EAR (|P2-P6| |P3-P5|) / (2 * |P1-P4|)其中 P1 到 P6 是从眼角开始顺时针排列的六个关键点。左眼和右眼各算一个 EAR驾驶员行为监控中通常取两只眼睛 EAR 的平均值作为最终判定值。实现代码如下import math def eye_aspect_ratio(eye_points): 计算眼睛纵横比 EAR。 eye_points: 6个坐标点的列表顺序为 [P1, P2, P3, P4, P5, P6] # 垂直方向距离P2-P6 和 P3-P5 vertical_1 math.dist(eye_points[1], eye_points[5]) vertical_2 math.dist(eye_points[2], eye_points[4]) # 水平方向距离P1-P4 horizontal math.dist(eye_points[0], eye_points[3]) ear (vertical_1 vertical_2) / (2.0 * horizontal) return earmath.dist计算两个坐标点的欧氏距离vertical_1和vertical_2是眼睛上下眼睑的两组对应点距离horizontal是左右眼角的距离。正常睁眼状态下 EAR 值大约在 0.25~0.35 之间闭眼时降到 0.10 以下。这里需要注意的是不能仅仅以单帧的 EAR 小于阈值就判定闭眼——因为眨眼是正常生理动作单帧闭眼的持续时长只有 100~200ms。正确的判断逻辑是连续 N 帧通常 N3 到 5 帧对应视频帧率下的 100~150msEAR 都低于闭眼阈值才判定为一次疲劳闭眼事件。我一般把阈值设为 0.2连续帧数设为 3。这个参数组合在大部分摄像头和光照条件下的误报率最低如果发现频繁误报先把连续帧数提高到 5再调整阈值到 0.18。4.3 MAR 张嘴判定与低头检测张嘴的判断思路与 EAR 完全一致只是把计算对象从眼睛换成嘴巴。MARMouth Aspect Ratio用嘴巴外轮廓的六个关键点计算公式与 EAR 同构def mouth_aspect_ratio(mouth_points): 计算嘴巴纵横比 MAR。 mouth_points: 使用嘴巴外轮廓关键点 [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59] 中的 关键点左右嘴角(48, 54)、上嘴唇上方(49, 53)、下嘴唇下方(51, 57) 返回 MAR 值正常闭嘴时约 0.2 以下张嘴打哈欠可达 0.5 以上 # 取嘴唇上下两组点50和58上嘴唇上沿52和56下嘴唇下沿 vertical_1 math.dist(mouth_points[2], mouth_points[8]) vertical_2 math.dist(mouth_points[4], mouth_points[10]) # 左右嘴角距离48 到 54 horizontal math.dist(mouth_points[0], mouth_points[6]) mar (vertical_1 vertical_2) / (2.0 * horizontal) return mar这里选取的坐标索引是mouth_points列表中的位置而不是 Dlib 原始编号。mouth_points[0]是 Dlib 第 48 号点左嘴角mouth_points[6]是第 54 号点右嘴角mouth_points[2]和mouth_points[4]是上嘴唇轮廓点mouth_points[8]和mouth_points[10]是下嘴唇轮廓点。这个选用逻辑保证了上下嘴唇的取值对称计算出的 MAR 在不同人脸形状下的稳定性较好。正常闭嘴状态下 MAR 通常低于 0.2张嘴说话或打哈欠时超过 0.5。实际项目中我把张嘴阈值设为 0.4连续帧数设为 5这是按「打哈欠持续约 3~5 秒」这个生理特征倒推出来的参数。低头检测有两种常见方案。第一种是用 Dlib 的 68 点中鼻尖点第 30 号点和下巴点第 8 号点的相对位置变化来判断第二种是计算面部关键点的旋转角度即头部姿态估计。逻辑上更轻量的做法是计算鼻尖与左右眼中心连线的相对距离当驾驶员低头时鼻尖在图像中的纵向位置会比正常平视时更靠近画面底部且鼻尖到眼部的距离会缩短def head_down_detect(landmarks): 基于关键点几何关系判断低头。 当鼻尖的 y 坐标大于两眼中心 y 坐标一定比例时判定为低头。 nose_y landmarks.part(30).y left_eye_center_y (landmarks.part(36).y landmarks.part(39).y) / 2 right_eye_center_y (landmarks.part(42).y landmarks.part(45).y) / 2 eye_center_y (left_eye_center_y right_eye_center_y) / 2 # 低头时鼻尖 y 坐标比平视时大图像坐标系 y 轴向下 # 阈值 0.35 表示鼻尖比眼部中心低出 35% 的眼间距 face_height math.dist((landmarks.part(27).x, landmarks.part(27).y), (landmarks.part(8).x, landmarks.part(8).y)) if (nose_y - eye_center_y) / face_height 0.35: return True return False这个判断的思路是面部正常朝向摄像头时鼻尖比眼睛中心略低一点点但差距不会超过面部长度的 35%。低头时鼻尖大幅下移这个比例迅速增大。face_height用眉间点第 27 号到下巴点第 8 号的距离作为面部的参考长度。注意这里用的是归一化比例而不是绝对像素差值这样可以适配不同距离、不同分辨率的摄像头画面。实际测试中这个 0.35 的比例阈值的性能接近基于 solvePnP 的完整头部姿态估计但计算开销小得多。4.4 data.csv 行为记录与报警联动有了行为判定结果下一步就是把它落盘和联动 UI。CSV 记录的逻辑比较简单但有一个细节需要处理写入频率不能太高否则文件被频繁打开关闭性能会降得很厉害。import csv import datetime class BehaviorLogger: def __init__(self, csv_pathdata.csv): self.csv_path csv_path self._init_file() def _init_file(self): # 初始化CSV文件如果不存在则写入表头 try: with open(self.csv_path, x, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([timestamp, behavior, duration_frames, confidence]) except FileExistsError: pass def log_event(self, behavior, frames, confidence): # 追加一行行为记录 with open(self.csv_path, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([datetime.datetime.now().isoformat(), behavior, frames, confidence])x模式表示文件不存在时才创建防止重复运行覆盖已有数据。csv.writer的newline是为了避免 Windows 下出现多余空行。在行为状态从「正常」切换到「闭眼」「张嘴」或「低头」时才调用log_event而不是每帧都写否则 CSV 会爆炸式增长。报警联动就是在检测到异常行为时让 PyQt5 主窗口发一个QTimer信号触发声音报警持续 0.5 秒再检查行为是否还在持续。5. 实战避坑五个最容易翻车的地方5.1 Dlib 在 Windows 上编译失败现象执行pip install dlib后报错Requires CMake或error: Microsoft Visual C 14.0 is required。整套环境装到一半卡住后面所有步骤都无法推进。原因Dlib 源码依赖 CMake 和 C 编译器完成本地构建Windows 默认环境没有这些工具。Python 3.10 以上的版本对 C 编译器的要求还更高即便装了 Visual Studio 也可能因为版本不匹配继续报错。解决不要折腾源码编译。直接conda install -c conda-forge dlib装预编译版本或者去 pypi 上找对应 Python 版本的 dlib wheel 包下载安装。装完后执行python -c import dlib验证能正常 import 再进下一步。5.2 摄像头画面冻结但程序不崩溃现象启动后 GUI 正常显示但画面卡在某一帧鼠标移到窗口上转圈几秒后画面直接黑掉。原因防冻帧的根因是采集线程和推理线程的帧同步机制有问题。推理线程处理一帧需要 50ms采集线程在这期间又放进新帧导致缓冲区数据被覆盖或锁冲突。最常见的问题是推理线程中frame self.input_frame没有做.copy()拿到的帧在推理中途被改写为空指针。解决在推理线程的run()里必须写frame self.input_frame.copy()并用一个单独的frame_lock锁保护帧的读写。另外把摄像头分辨率固定为 640×480不要用默认值默认值往往是 1280×720推理耗时翻倍更容易卡顿。5.3 run_torch.bat 双击后闪退现象双击 bat 文件黑色窗口一闪就消失了看不见任何报错信息。原因bat 脚本里用python main.py启动但当前系统 Python 环境根本没有安装 PyQt5或者cd /d %~dp0之后的模型路径不对错误信息一闪而过你根本来不及看。另外如果虚拟环境名不是driver_monitorconda activate也会静默失败。解决把 bat 文件末尾的pause保留住这是强制窗口停留的最后一道保险。启动前先手动在 cmd 里执行conda activate driver_monitor加python main.py --engine torch --weights last.bin逐行排查环境问题。我一般会把 bat 里加一行python -c import torch, dlib, cv2, PyQt5做启动自检哪一步 import 报错就说明哪个库没装。5.4 OpenVINO 版推理结果与 Torch 版本不一致现象同一段视频用 run_torch.bat 跑检测正常换 run_openvino.bat 后检测框位置偏移了几个像素置信度也整体下降。原因YOLOv5 从 PyTorch 格式导出到 OpenVINO IR 格式时预处理参数和数据布局会发生改变。OpenVINO 版模型通常要求输入是 NCHW 布局并且像素值归一化方式必须和导出时一致。YOLOv5 默认的归一化是除以 255如果你在 OpenVINO 推理时用了(frame / 255.0)但 Torch 版本用的是(frame / 255.0 - 0.5) / 0.5两个版本的输出就会对不上。解决确认 main.py 中推理前处理函数是根据--engine参数分开写的。Torch 版走 YOLOv5 官方预处理OpenVINO 版走blob cv2.dnn.blobFromImage(frame, 1/255.0, (640, 640), swapRBTrue)。后处理时注意 OpenVINO 输出的坐标是相对于 640×640 输入图的要按原图尺寸缩放回原始分辨率。5.5 夜间或逆光环境下误报率飙升现象白天测试闭眼检测很准到了傍晚或开过路灯时系统频繁误报「闭眼」几分钟就报警一次。原因摄像头自动增益在低照度下将画面亮度拉高导致 Dlib 关键点定位精度下降眼部的六个关键点在暗光下偏移明显EAR 值被压低了。另一个原因是 Dlib 模型本身是在光照条件均衡的数据集上训练的对低照度场景的泛化能力有限。解决在采集线程中对每一帧做光照预处理。最简单有效的是在输入推理前对图像做 CLAHE限制对比度自适应直方图均衡化仅作用于亮度通道def preprocess_lighting(frame): # 将 BGR 帧转成 LAB 色彩空间对 L 通道做 CLAHE 增强 lab cv2.cvtColor(frame, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) clahe_applied clahe.apply(l_channel) merged cv2.merge((clahe_applied, a_channel, b_channel)) return cv2.cvtColor(merged, cv2.COLOR_LAB2BGR)clipLimit2.0是限制对比度增强的幅度太大容易出噪点太小改善不明显。tileGridSize(8, 8)把图像分成 64 个小块分别均衡保留局部细节。这套预处理在夜间场景下能把误报率从每 5 分钟 3 次降到每 30 分钟 1 次左右。6. 进阶思路从课设到车载原型还差什么这个项目做到能稳定运行只是第一步如果你想把行为监控做成真正实用的系统有几个方向值得继续投入。疲劳驾驶的判断不能靠单帧指标而是要定义一个时间窗口内的行为统计。常见做法是维护一个 60 秒的滑动窗口统计这段时间内闭眼总帧数、哈欠次数、低头次数加权计算一个疲劳评分。当评分超过阈值才触发预警而不是检测到一次闭眼就报警这样能把眨眼误报压到几乎为零。你需要新增一个FatigueTracker类内部维护一个collections.deque(maxlen1800)按 30FPS 计算刚好存 60 秒的数据每 5 秒滚动计算一次评分记录到 data.csv 的新列里。注意力散焦检测是另一个值得加的功能——驾驶员虽然睁着眼但视线长时间离开前方路面比如低头看手机。我的做法是跟踪 50 帧内鼻尖点的平均位置偏移量结合方向盘中心在 GUI 初始化时手动框定做简单判断。这种方法在很多论文里是不入流的但课设和原型验证完全够用。往嵌入式方向走的话YOLOv5 可以量化到 INT8 后用树莓派 4B 或 RK3568 平台的 NPU 跑关键点检测换成轻量版 MobileNet 结构只是精度会有一定下降。讲一个我的真实教训第一次在课设答辩现场演示时我拿了教室的白炽灯做光源但摄像头正对窗户逆光导致 Dlib 在人脸上完全找不到关键点现场翻车。从那以后我每次启动这套系统都会先强制在 GUI 主窗口加一个「画质诊断」按钮把当前画面的亮度均值、人脸检测框是否稳定、EAR 值的实时曲线显示出来跑 10 秒确认各项指标正常再开始正式演示。这个习惯让我在后来的测试里少踩了很多坑。希望这次的拆解能帮到你跑通之后把 data.csv 里的行为记录拉出来做一次可视化分析你会对这个系统的实际性能建立更直观的认知。本文还有配套的精品资源点击获取
返回列表