
简介面向智能驾驶与计算机视觉方向的开发者这是一套驾驶员分心行为预警系统的完整可运行项目。项目分为两个核心模块疲劳检测模块利用Dlib人脸关键点检测计算眼睛与嘴巴的开合度识别闭眼和打哈欠并通过Perclos模型评估疲劳程度分心行为检测模块使用YOLOv5识别玩手机、抽烟、喝水三类危险动作配合Deepsort实现持续跟踪预警。V1.0在原作者基础上重新训练YOLOv5权重、精简前端UI只保留闭眼与哈欠检测较原版更易部署。压缩包共58个文件包括20个Python脚本、18个YAML配置、UI界面文件、Dlib的68点人脸关键点模型、YOLOv5权重、演示动图、Dockerfile及README等整体约89MB目录结构清晰便于二次开发对初学者友好。目前已有729人学习下载适合用于课程设计、毕业项目或工程实践参考。1. 分心驾驶预警系统到底解决什么问题从一帧检测到一段行为判断很多做驾驶场景视频分析的团队第一次接到“驾驶员疲劳危险行为预警”需求时第一反应是找个目标检测模型跑一跑能框出“人”就算完事。真正落地才发现单帧检测解决不了“持续状态”的判断司机低头看一眼导航和持续低头十几秒是两种完全不同的风险等级正常眨眼 0.1 秒和疲劳闭眼 0.5 秒也不能用同一个规则去报警。这套基于深度学习的驾驶员分心驾驶行为预警系统用 YOLOv5 做单帧目标检测用 DeepSORT 做跨帧目标跟踪把离散的检测结果串成带 ID 的轨迹流再按时间维度做行为判定才把“检测到动作”升级成“识别出状态”。它适合两类人一类是正在做毕业设计或课程大作业的学生另一类是想快速搭一个安全驾驶预警原型的工程师。2. 方案选型与数据准备YOLOv5 和 DeepSORT 的分工以及行为类别怎么定2.1 检测器加跟踪器而不是一个端到端单模型先说清楚一个容易绕弯的点DeepSORT 不是一个检测器它自己不会在画面里找目标。DeepSORT 的输入是检测器给出来的边界框输出是每个框对应的 track_id。把 YOLOv5 和 DeepSORT 接在一起等于先让 YOLOv5 回答“这一帧里有什么”再让 DeepSORT 回答“上一帧的那个人还是不是这个人”。不少初学者会把这两件事搞混。有人直接拿 DeepSORT 的源码去跑发现什么都检测不到因为 DeepSORT 压根没有目标定位能力。也有人觉得那直接用 SORT 就行不用 DeepSORT但 SORT 只靠边界框的 IOU 做帧间匹配目标一旦被方向盘、遮阳板挡住几帧重新出现时就换了一个新 ID。驾驶员分心检测最怕这个同一个司机 ID 跳变了前面累计的闭眼帧数清零疲劳预警永远触发不了。DeepSORT 在 SORT 基础上引入了外观特征ReID 特征向量即使目标短暂消失也能靠表观相似度把轨迹接回来这正是驾驶舱这种遮挡频繁的场景需要的。YOLOv5 在这套方案里承担的是视觉感知的底座。它用 CSPDarknet 做骨干网络PANet 做多尺度特征融合输出三个不同尺度的预测头兼顾了大目标近距离的人脸和小目标驾驶位远端的人脸。对驾驶员行为识别来说单阶段检测器比两阶段检测器更合适分心预警对实时性要求很高车载设备或 Jetson 这类边缘设备算力有限YOLOv5 的 s 和 n 系列模型在 640×640 输入下能跑到几十帧同时 mAP 也够用。它的 Mosaic 数据增强和自适应 anchor 计算在训练小样本行为数据集时也有明显优势不用自己费劲调 anchor 的初始值。常见做法是先用官方 COCO 预训练权重yolov5s.pt做迁移学习用自建的分心行为数据集微调。这样收敛快也不会像从头训练那样动不动就出现 Loss 爆炸。类别的数量会直接改写网络的输出头维度所以数据准备阶段的类别设计必须在训练之前一次性想清楚。2.2 行为类别设计疲劳三类、危险行为四类别把状态和动作混在一起设计类别集是第一步也是后期最不想返工的一步。我一般会把驾驶员异常行为拆成两组疲劳状态和行为动作。疲劳状态是持续性的需要靠帧数来判断危险行为是动作类的只要出现就要告警。分组类别名判定要点预警方式疲劳状态close_eye 闭眼眼睛闭合且持续超过阈值连续帧计数触发疲劳状态yawn 打哈欠嘴部张大并持续超过阈值连续帧计数触发疲劳状态look_down 低头头部垂落角度大且持续连续帧计数触发危险行为hand_phone 玩手机手部持握手机并操作单帧命中累计触发危险行为call 打电话手机贴近耳侧单帧命中累计触发危险行为drink 喝水手持水杯/水瓶入口单帧命中累计触发危险行为smoke 抽烟烟支在口部附近单帧命中累计触发这个设计里有一个常被忽略的细节“打电话”和“玩手机”不要按通话状态去区分而是按手机在耳侧还是在手上来区分否则标注人员会吵起来。还有一种做法是参考公开的分心驾驶数据集把“整理头发”“与乘客交谈”也放进类别里——如果做的是大作业或毕设这类类别能让模型泛化得更好如果做的是企业预警系统类别太细反而会增加误报建议砍掉。最怕的是把行为和状态混在一起标比如“闭眼”这一帧同时被标成“疲劳开车”。同一帧只能有一个类别标注规范必须在开工前写死。另一个血泪教训是类别名一旦在标注阶段定下来就不要在中途加类别。每加一类之前标注的所有数据都要重新过一遍模型输出层也要改等于白训一次。所以第一版宁可多花两天把类别定义清楚也别急着开训。2.3 数据从哪来公开集打底自采补疲劳样本分心驾驶的公开数据集是有的常见的有 State Farm 那个竞赛数据集里面有打电话、发短信、喝水、操作收音机、整理头发这几类图片质量也不错。但公开集基本没有疲劳闭眼和打哈欠的样本这是个缺口竞赛数据侧重“分心动作”而真实运营场景里最要命的是疲劳状态。我的做法是公开集加自采配合。公开集用来做危险行为类的预训练和类别平衡自采数据专门补疲劳状态。自采不需要专业设备手机固定在驾驶位侧前方模拟行车记录仪视角找不同的人分别做闭眼 1 秒到 5 秒、打哈欠、低头看仪表盘的动作每种动作录 5 到 10 分钟视频然后抽帧标注。抽帧标注有个技巧不用每帧都标。视频里相邻两帧画面差异极小全部标注既浪费人力又让模型学到大量重复样本。我一般按每 5 帧抽一帧也就是 30fps 的视频只标 6fps 的帧训练时再把 Mosaic 增强打开数据量完全够用。标注环节要盯住三个坑。第一戴墨镜的样本人手补一批不然夜间场景和墨镜场景会集体失效。第二闭眼和半闭眼的边界很难界定标注规范里要写清楚“眼睑覆盖瞳孔超过 80% 算闭眼”不然一个标注员和另一个标注员的框能差出很多。第三框的边界要统一YOLO 格式的标注框是左上角和右下角两个点稍微偏一点对训练影响不大但同一个动作的标准偏差太大AP 就上不去。数据准备好之后下一步是把它喂给 YOLOv5。3. YOLOv5 训练自己的分心检测模型配置文件、训练命令和超参数怎么调3.1 先写对 data.yaml类别顺序决定了输出头YOLOv5 训练的第一步不是敲训练命令而是先把数据配置文件写好。我在data.yaml里维护训练集、验证集路径和类别列表# data.yaml train: ./data/driver/train.txt # 训练集图片路径列表一行一张 val: ./data/driver/val.txt # 验证集图片路径列表 nc: 7 # 类别总数和 names 一一对应 names: - close_eye - yawn - look_down - hand_phone - call - drink - smoke这里有一个隐蔽的坑names 的顺序必须和标注文件里的类别编号严格对应。YOLO 格式的标签文件里每一行是“类别编号 x_center y_center width height”类别编号是整数0 对应 names 列表里的第一个元素。如果标注工具导出的顺序和 names 不一致模型会学到完全错乱的东西而且 Loss 看起来还在下降实际 P、R 全是零。我每次新建数据集都会用脚本把标签和图片随机抽几个出来叠着看一眼这一步能省下后面三天的排查时间。路径部分可以写目录也可以写 txt 列表。我习惯用 txt 列表因为后续做训练集和验证集划分更灵活也方便在不同机器之间迁移。nc这个参数会决定 YOLOv5 输出头的通道数如果后面新增了一个类别忘记同步改nc模型输出的维度对不上训练会在解析标签时报错。所以每次改类别先改names再数一遍nc。3.2 训练命令和关键超参数先用官方预训练权重数据准备完毕训练命令本身不复杂但参数选择有讲究。我常用的一组配置如下python train.py \ --data ./data/driver.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0这套命令的关键参数逐一说一下。--weights yolov5s.pt是 COCO 预训练权重迁移学习用它起步收敛速度比随机初始化快很多。显存不够时换yolov5n.pt这是最小的网络8GB 显存也能轻松跑显存充足且对精度要求高再上yolov5m.pt但对驾驶员行为检测来说s 系列的性价比通常是最高的m 系列的收益在推理阶段会被帧率吃掉。--img 640是训练输入分辨率。驾驶舱场景里人脸可能只占画面的十分之一属于偏小的目标分辨率太低会直接丢掉闭眼这类细粒度特征。我试过用--img 320训练速度很快但闭眼类别的 AP 掉了近一半。--img 640是驾驶员行为检测的甜点值再往上 1280 对小目标有改善但训练时间和推理时间都会显著增长。--batch-size 16是显存和训练速度的折中。显存 24GB 可以上 328GB 就老老实实 16不够时用--batch-size 8加梯度的累积效果来接近。还要注意batch size 改变后学习率建议同步调整。YOLOv5 默认会按 batch size 自动缩放学习率但如果手动指定了--lr就要自己和 batch 联动着改不然模型容易震荡。--epochs 100看起来很多但 YOLOv5 自带早停机制验证集 Loss 连续多轮不下降就会自动停。实际训下来分心行为数据集如果只有几千张通常 40 到 60 轮就收敛了。训练日志里 val 指标不再提升又没触发早停可以直接 CtrlC 停掉不需要死等 100 轮。提示训练过程中如果obj_loss或box_loss出现 NaN先检查标签文件里有没有越界的坐标值小于 0 或大于 1再检查是不是学习率开大了。这两条占了训练翻车原因的八成。3.3 训练完怎么判断模型能不能用不能只看总 Loss训练结束后YOLOv5 会在runs/train目录下输出results.png和一系列指标。我判断一个分心检测模型能不能用优先看 P精确率、R召回率、mAP50 这三项而不是盯着总 Loss。在驾驶员预警场景里召回率比精确率重要。漏一次闭眼不报警可能就错过一次事故预警误报一次司机顶多觉得系统烦。所以我通常要求 R 在 90% 以上mAP50 在 85% 以上P 可以在 75% 左右也可以接受。如果反过来 P 很高但 R 很低说明模型非常保守该报的没报出来这种模型在预警系统里是废的。还要看类别维度的指标YOLOv5 会在results.png里按类别画 PR 曲线。最常见的失衡情况是玩手机这种样本多的类别 AP 很高闭眼、打哈欠这种疲劳类别的 AP 不到 60%。原因几乎都是样本量不够解决方向是回数据侧补样本而不是调超参数。补完样本重新训练比调任何参数都见效。YOLOv5 后处理部分也会影响实际使用效果。模型输出的原始预测经过置信度阈值过滤和 NMS非极大值抑制才得到最终的框。默认置信度阈值在推理阶段是 0.25NMS 的 IOU 阈值是 0.45这两个值在 detect 脚本里调。驾驶舱里的乘客和驾驶员有时会离得很近NMS 阈值太高会把两个人的框合并成一个调低到 0.3 会好一些。超参数这块YOLOv5 自带一组遗传算法优化过的默认超参数放在data/hyps目录下。我一般不动它只有在 mAP 已经上不去、数据侧也确实没得补的时候才会去动lr、mosaic这些项。说实话超参数微调在分心检测这类单一场景里的收益不大更多是玄学数据量和技术路线才是决定精度的根本。4. 把 DeepSORT 接进来从单帧检测到带 track_id 的轨迹流再到疲劳判定4.1 把 YOLOv5 的输出转成 DeepSORT 的输入格式模型训练好了接下来的核心工作是把 YOLOv5 的检测结果喂给 DeepSORT。DeepSORT 的常见实现里定义了自己的 Detection 类需要目标框坐标、置信度和外观特征三者缺一不可。YOLOv5 的 detect 输出只有坐标和置信度外观特征需要单独从目标裁剪图里提。# 将 YOLOv5 的 detections 转成 DeepSORT 的输入格式 import numpy as np from deep_sort.detection import Detection def yolo_to_deepsort_detections(boxes, scores, track_classes, encoder, frame): 把 YOLOv5 推理结果转成 DeepSORT 可接收的 Detection 列表 detections [] for box, score, cls in zip(boxes, scores, track_classes): x1, y1, x2, y2 [int(v) for v in box] # DeepSORT 内部用 [x, y, w, h] 格式这里直接给左上角和宽高 w, h x2 - x1, y2 - y1 # 用 ReID 编码器对目标裁片提取外观特征 crop frame[y1:y2, x1:x2] feature encoder(crop) detections.append( Detection(np.array([x1, y1, w, h]), score, feature) ) return detections这段代码的逻辑是把 YOLOv5 输出的 x1、y1、x2、y2 坐标转换成 DeepSORT 内部使用的 x、y、w、h 形式同时用 ReID 编码器对每个目标的裁剪区域提取特征向量。DeepSORT 的关联算法会同时使用两类度量运动上的马氏距离由卡尔曼滤波预测的位置和实际检测框的位置差距和外观上的余弦距离两个目标特征向量的相似度。两者加权后计算出代价矩阵再用匈牙利算法做最优匹配。参数配套要一起改。常见的 DeepSORT 配置里有三个关键参数全局距离阈值max_dist默认 0.2表示外观特征余弦距离超过这个值就不接受匹配max_age默认 30表示目标消失多少帧内仍保留轨迹n_init默认 3表示轨迹至少要连续匹配 3 帧才会被确认。驾驶员场景里max_dist可以放宽到 0.3因为车内光线变化、角度变化会让同一个人的表观特征有一定漂移max_age我习惯调大到 60挡住遮阳板、低头看仪表这类短暂遮挡不会导致 ID 丢失。这里要提一句DeepSORT 的 ReID 编码器也是一个深度神经网络在线跑的时候会增加计算开销。如果部署设备的算力紧张可以退而求其次把 ReID 特征降维或者减少特征提取的频率每隔一帧提取一次中间帧用卡尔曼滤波的位置预测来补。4.2 疲劳判定逻辑按 track_id 聚合连续帧而不是全图统计DeepSORT 给了每个目标稳定的 track_id 之后行为判定的思路就从“这一帧有没有闭眼”升级成“这个 ID 最近 30 帧里闭了多少帧”。我一般用一个简单的时间窗口状态表来做这个聚合# 按 track_id 聚合连续帧检测结果输出预警事件 from collections import defaultdict # (track_id) - (最近类别序列) track_state defaultdict(list) # 连续帧数阈值(30fps 下的时间估算) WARN_RULES { close_eye: 8, # 约 0.27 秒 yawn: 10, # 约 0.33 秒 look_down: 5, # 约 0.17 秒 } def on_frame(track_id, category): 每帧对单个 track_id 调用一次更新状态并判定是否预警 track_state[track_id].append(category) track_state[track_id] track_state[track_id][-30:] # 只保留 30 帧 # 只统计“尾部连续命中”的帧数 if category in WARN_RULES: hist track_state[track_id] cnt 0 for c in reversed(hist): if c category: cnt 1 else: break if cnt WARN_RULES[category]: fire_warning(track_id, category)这个逻辑的关键在于“尾部连续命中”而不是“窗口内累计命中”。闭眼是连续状态中间睁开眼睛哪怕一帧就说明人还是有意识的重置计数更合理。正常人的眨眼时长约 0.1 到 0.15 秒也就是 3 到 5 帧闭眼阈值设在 8 帧能把正常眨眼和疲劳闭眼区分开。打哈欠的嘴部张开动作本身持续更久阈值 10 帧差不多。低头看导航这类动作有时只有 3 到 4 帧阈值 5 帧能覆盖短暂视线偏移和危险低头之间的边界。危险行为类的判定要换个写法玩手机、打电话、喝水、抽烟是动作不是状态不需要连续帧。我一般用累计计数1 分钟内同一 track_id 被检测到该类别超过 3 帧就触发预警单帧不触发这样可以避免检测器偶尔闪一下造成的误报。这里还要考虑多目标问题。车内场景不止驾驶员一个人副驾驶和后座乘客可能也在画面里。判定阶段必须加一个空间约束只有目标中心点持续落在驾驶位 ROI 区域内的 track_id才参与疲劳判定。DeepSORT 的轨迹信息里有历史位置序列可以判断“这个 ID 一直在驾驶位附近”比单纯看当前帧更稳。4.3 阈值应设帧数还是秒数先统一帧率再谈参数上面代码里的阈值是帧数但实际部署时摄像头帧率不一定是 30fps。行车记录仪有 25fps、30fpsUSB 摄像头在边缘设备上跑到 15fps 也很常见。同一套代码15fps 下 8 帧闭眼等于 0.53 秒30fps 下只有 0.27 秒同样闭一下眼一个报警一个不报警这可不行。我习惯在代码里把帧数阈值转化成时间阈值每帧读取时先换算一次。做法是定义一个基础帧率BASE_FPS 30所有阈值写成秒数再用int(seconds * BASE_FPS)算出当前帧率下的实际帧数阈值。这样更换摄像头或调整推理帧率时预警逻辑不需要改。还有一个参数容易被忽略检测置信度阈值。疲劳预警场景下我建议把检测阈值从默认的 0.25 提到 0.4 或 0.5。因为跟踪器会利用跨帧信息自动过滤一部分抖动误检检测阈值低一点不会直接导致报警反而能保留更多低置信度框给时序模块去做平滑。但如果 DeepSORT 那边总是匹配错 ID就反过来把检测阈值调高一点宁可不跟踪也不要乱跟踪。判定项时序判定方式参考阈值30fps调参方向闭眼尾部连续帧8 帧误报多调大漏报多调小打哈欠尾部连续帧10 帧检测置信度低时调大低头尾部连续帧5 帧区分短暂低头与疲劳点头玩手机/打电话窗口内累计3 帧/分钟误报多时提高置信度阈值喝水/抽烟窗口内累计3 帧/分钟同上这些取值不是来自任何官方文档而是我在真实视频上按“误报和漏报平衡点”试出来的经验起点。不同车型、不同摄像头角度阈值都会偏移上线前用一段标注好的人工视频做一次验证比照抄任何参数都可靠。5. 避坑与常见问题排查不收敛、ID 跳变、夜间漏检的五个现场5.1 训练 Loss 变 NaN八成是数据问题不是网络问题现象训练到第 10 轮左右控制台输出里box_loss或obj_loss变成nan精确率和召回率直接掉到 0后续所有指标都不恢复。原因最常见的是标签文件里出现了越界坐标。YOLO 格式要求坐标值归一化到 0 到 1 之间但标注工具在视频抽帧场景下容易导出个别异常值比如宽度或高度为 0或者中心点坐标超出图片范围。另一个常见原因是学习率经自动缩放后仍然偏大尤其在数据集较小时模型在初始阶段就发散。解决先跑一个简单的脚本扫描标签目录检查所有坐标是否在 0 到 1 区间内、宽高是否大于 0 的行把异常行直接删掉。删完再训练如果还是一样把--lr手动设成 0.001 重试。我在分心驾驶数据集上遇到过两次 NaN一次是标签越界一次是 Mosaic 增强时读到了损坏的图片文件删掉损坏图片后一切恢复正常。5.2 track_id 频繁跳变一个人被当成三个人疲劳计数清零现象画面里只有一个驾驶员但 DeepSORT 输出里出现了好几个不同的 track_id。闭眼计数到一半ID 从 1 跳成 2前面累加的帧数全部归零预警永远不触发。原因检测器在目标被遮挡的瞬间丢帧了。司机低头、伸手拿水杯、转动身体都会让人脸短暂离开画面DeepSORT 的轨迹如果超过max_age还在等匹配就会判定轨迹终止目标重现时生成新 ID。解决把max_age从默认 30 调到 60n_init从 3 改成 2让轨迹更“恋旧”。同时把检测器的置信度阈值从 0.5 降一点到 0.35让跟踪模块在目标短暂变模糊时仍能拿到检测框。还有一个偏门但好用的做法在 DeepSORT 的成本矩阵里加大运动预测的权重让卡尔曼滤波的位置预测在遮挡恢复后能用上一段速度外推框住目标重现的位置。5.3 白天效果正常夜间几乎完全失效现象同一套模型白天视频里闭眼、玩手机检出率都在 90% 以上换成夜间行车记录仪视频漏检一大半夜间戴墨镜场景更是全灭。原因训练集里白天样本占绝大多数YOLOv5 训练时的色彩增强参数HSV 扰动默认范围不够覆盖低照度场景。更隐蔽的是夜间摄像头成像的噪点模式和白天完全不同模型学到的纹理特征在夜间匹配不上。解决两条腿走路。第一训练阶段把 HSV 增强的饱和度扰动调大比如把hsv_s从 0.5 提到 0.8hsv_v从 0.4 提到 0.6模拟光照变化。第二找一段夜间行车视频抽 500 到 1000 帧补标夜间闭眼和玩手负面样本加入训练集做二次微调。注意不要只做全局亮度归一化那种预处理反而会让模型丢掉夜间特有的暗部纹理。5.4 后座乘客玩手机也触发预警现象预警系统把副驾驶或后座乘客的动作也识别为驾驶员行为一人报警全家报警。原因YOLOv5 检测的是全图目标DeepSORT 跟踪的也是全图目标判定模块如果只看类别不看目标位置就会把非驾驶位的人的异常行为也算进来。解决在判定阶段加一个驾驶位 ROI 掩码。不要用固定的矩形框写死因为不同车辆、不同摄像头安装位置的 ROI 差别很大固定框换个车就废了。我一般把 ROI 做成配置文件里的四个角坐标部署时先暂停视频流手动标一下驾驶位边界。同时结合 DeepSORT 的轨迹位置信息只有 track_id 的历史中心点有 80% 以上落在 ROI 内才对这个 ID 做行为判定。这个过滤做完误报能去掉一大半。5.5 闭眼和正常眼的边界模糊导致疲劳误报现象司机眼睛小、笑起来眯眼、或者阳光刺眼皱眉系统都报“疲劳闭眼”实际人清醒得很。原因闭眼类别的标注边界不统一。有的标注员把“半闭眼”也标成闭眼有的标成正常模型学到的决策边界就很模糊。加上目标框在跟踪过程中有轻微抖动眼睛区域裁切位置变化特征就偏了。解决统一标注规范只把“眼睑覆盖瞳孔超过 80%”标为闭眼半闭眼一律算正常。同时把训练时对眼睛小目标的检测分辨率提上来--img 640起步不要用 320。如果照做了还是误报可以在时序判定里加一个“眨眼间隔”约束正常人的疲劳闭眼之后会伴随缓慢睁眼眨眼则是快速闭合快速睁开用睁眼帧的间隔做二次确认。这个逻辑简单但对降低误报非常有效。6. 端到端验证与调优用一段真实视频检验这套预警系统6.1 找一段你没在训练里用过的视频按帧标注再对比模型训练完、跟踪接完了最忌讳直接拿同一段测试视频看效果因为你早就把答案背下来了。正确的做法是留一段从未进入训练集和验证集的真实驾驶视频约 3 到 5 分钟人工逐帧标注异常行为区间。标注输出是一张表起始帧、结束帧、行为类别。系统输出则是每一帧的 track_id 和类别。把两者对齐后按帧计算真正例、假正例和假反例。我用一个很朴素的匹配规则人工标注的某个行为区间内系统至少报警一次算这个事件被检到系统报了但人工标注里没有任何对应行为算误报。然后按事件数算召回率和误报率比按帧算更贴近实际体验。实际操作时我建议先把系统报警的日志打印出来再对着视频逐条核实这条报警对应的是司机真实动作还是后座乘客、反光、阴影造成的误报。把每条报警的触发原因记下来通常两轮之后就能看出系统的短板集中在哪个类别、哪个场景比盯着 mAP 调参数更直观。6.2 三个见效最快的调优点第一个是置信度阈值分场景配置。白天用 0.4夜间用 0.25同一套模型两套阈值能同时兼顾白天漏报和夜间误报。第二个是 DeepSORT 的max_cosine_distance从默认 0.2 放宽到 0.3代价是可能出现不同的人串 ID但只要 ROI 区域限定在驾驶位串 ID 的概率很低。第三个是后处理 NMS 的 IOU 阈值从 0.45 调低到 0.3驾驶位和副驾驶目标距离近时能保留两个独立框而不是合并成一个跟踪稳定性会好很多。6.3 从验证结论反推下一步如果验证结果里闭眼召回率低回去补闭眼样本而不是调跟踪参数如果报警延迟太高检查是不是每帧都跑 ReID 特征提取考虑隔帧提取来省时间如果帧率不够先把模型换成 YOLOv5n再考虑 int8 量化和在 RK3568 这类边缘设备上的部署。完整链路跑通之后下一步通常是加一个关键点模型算 EAR 和 PERCLOS 做疲劳二次确认或者把整套逻辑迁到 TensorRT 上去提速。我自己在这个项目上踩过最大的坑就是只顾着把模型的 mAP 刷高忘了验证阶段才是真正找短板的地方。后来养成习惯数据标注好之后先跑通一版端到端把误报和漏报的现场记下来再回头决定是补数据还是调参数。希望帮到你。本文还有配套的精品资源点击获取