开车的人可能都有过这种时刻:高速上眼皮突然打架,等惊醒过来方向盘已经偏了半条车道。我做“基于YOLOv5的驾驶员状态与危险行为检测”这个项目的出发点,就是想补上这类预警缺口——用视觉方案实时识别驾驶员是否疲劳、分心,以及有没有抽烟、打电话、不系安全带这些危险行为。这篇文章会把从数据准备、模型训练、行为判定到车载部署的完整链路讲清楚,适合正在做驾驶员监控系统(DMS)、或者想把目标检测模型真正落地到边缘设备的开发者参考。很长一段时间里,驾驶员状态检测的主流路线是“人脸关键点+眼睑开合度计算”,或者“视频帧序列分类”。这两种方案我先后都试过,各有各的坑:关键点方案在夜间红外、戴墨镜、侧脸角度大时经常丢点;帧序列分类方案训练成本高,部署也很笨重。后来我换了个思路——把“闭眼”“打哈欠”“低头看手机”“手持电话”这些驾驶员状态直接当作YOLOv5的目标类别,让模型输出每帧的检测框,再靠上层规则做时序判定。这个拆分方式简单直接,实测下来比预想中要稳得多,下面就把我踩过的坑和保留下来有效做法逐个展开。1. 为什么是YOLOv5:先想清楚检测对象再选算法1.1 驾驶员监控到底在监控什么驾驶员状态监控(DMS)要回答的核心问题其实只有两个:人是否清醒、人是否在专注开车。清醒对应疲劳状态,专注对应分心状态。而“危险行为”其实是分心状态的具体表现形式。把这个问题拆解成可计算的目标,我列了这样一张类别清单:疲劳类证据:闭眼、打哈欠、点头(瞌睡点头)分心类证据:低头看手机、手持电话、视线偏移(东张西望)、操作中控危险动作:抽烟、未系安全带、双手脱离方向盘这是我最初的决定性设计:用“证据”而不是“状态”来做检测目标。疲劳是一个持续状态,单张图像里根本无法直接定义“这个人疲劳”,但单张图像里有没有闭眼、有没有打哈欠,是可以稳定定义的。检测层把这些“证据”找出来,交给上层逻辑做时间累计,疲劳结论就非常可靠。举个例子:单帧检测到一次闭眼,可能只是正常眨眼;但统计一分钟内闭眼帧占比超过40%,或者连续哈欠达到3次,疲劳判定就很有说服力。这个分层设计的好处在于,检测层永远是一个干净的目标检测任务,复杂的状态判定逻辑放在规则层,改起来方便,调试时也能定位到具体是哪条规则出了问题,而不是去猜模型的“黑盒行为”。1.2 目标检测而非分类网络的原因动手之前,我把几条技术路线都摆出来做过对比:图像分类、人脸关键点、姿态估计、目标检测。图像分类的最大问题是缺少空间位置信息。你可以在画面中央裁出一个固定区域做“闭眼/睁眼”二分类,但驾驶员会转头、侧脸、被方向盘遮挡,全局图像分类根本没法定位行为发生的位置。更要命的是,手机、香烟这类目标本质上是“驾驶员身边的物体”,不是“人脸上的属性”,分类网络会把它们和驾驶员整体混在一起,完全无法区分。人脸关键点方案(比如经典的人脸68点)对疲劳检测确实非常有效,眼睑开合度、嘴巴张开程度都能精确计算。但它对图像质量要求太高,夜间红外、戴墨镜、低头角度大、部分遮挡时,关键点经常丢失。而且关键点只能描述人脸,香烟、手机、安全带这些非人脸目标完全无能为力。YOLOv5这类目标检测模型有两个结构性的优势:一是输出自带边界框,直接告诉你“什么东西在画面哪个位置”;二是可以同时检测驾驶员身体、手机、香烟、安全带多种目标。后处理阶段把手机框和手部区域做交集计算,就能得出“手持电话”的判定,这是分类网络根本做不到的。1.3 版本选型:YOLOv5、v6、v7、v8的取舍当前YOLO系列已有多个版本,我最终选择了v5,理由不是它精度最高,而是它在工程化成熟度上最合适。版本核心改动优势对这个项目的适用性YOLOv5C3模块、PANet结构生态最成熟,ONNX/TensorRT导出资料最多首选,踩坑成本最低YOLOv6重参数化、anchor-free工业部署优化做得好可作为备选,但社区资料少YOLOv7E-ELAN扩展结构精度上限较高结构复杂度偏高,轻量设备上有压力YOLOv8C2f模块、anchor-free新特性多,精度潜力大需要额外调参,部署验证成本高车载边缘设备的推理引擎普遍对YOLOv5的支持最完善,很多板卡SDK甚至直接内置了v5的解析层。这一点在后期实际部署时价值很大:我遇到的大部分TensorRT算子兼容问题,网上都已经有现成的讨论和解决方案。对于安全类项目,稳定可控比多几个点的精度更值钱。2. 数据与类别体系:行为定义决定系统上限2.1 行为分类先解决语义模糊问题数据标注之前,类别体系必须定义得足够清晰,否则模型会自己学出一套奇怪的定义。比如“手持电话”,到底是指手碰到手机就算,还是举到耳边才算?我的定义是:驾驶员伸手拿着手机且屏幕朝向驾驶员,手机处于可见状态(大多发生在低头或举在耳边时)才标注为手持电话;手机放在支架上,不算手持。这个定义落到标注规则里,模型才知道边界在哪里。闭眼和打哈欠也要细化定义:闭眼:单帧内眼睑闭合超过80%,且持续帧数由后续状态机统计。打哈欠:嘴巴张开且宽高比超过阈值,持续多帧;张嘴说话不算,因为说话时嘴型变化很快,打哈欠时嘴部张度会稳定保持一段时间。如果不做这类语义切分,模型训练时就会出现“把大笑当成打哈欠”“把玩手机当成手持电话”的误判。误判恰恰是这类项目中最容易被业务方挑剔的痛点。2.2 数据来源:开源打底、合成补量、自采纠偏数据方面,我用了三条腿走路:开源公开数据集:包括驾驶员状态监测公开数据、面部表情相关数据,提供大量人脸疲劳证据样本,覆盖不同角度和基础光照,量大概在几千张。合成数据:在三维驾驶仿真场景中批量渲染不同姿势、不同光线下的驾驶员图像。合成数据最主要的价值在于覆盖“罕见角度”——例如低头到极致的姿态、手机高度贴近面部、香烟含在嘴边的侧脸图,这些在真实拍摄场景中数量很少,但在仿真环境里可以低成本批量生成。自采数据:用摄像头在模拟驾驶舱和真实车辆内补拍,重点采集早晚逆光、夜间红外、戴墨镜等场景。公开数据集和合成数据大多来自理想光照条件,真实行驶场景的光照复杂度远高于它们。我最后把数据比例调到公开数据约60%、合成数据约25%、自采数据约15%。合成数据虽然好用,但比例不能太高,否则模型会过拟合到仿真渲染风格,在真实图像上反而掉点。自采数据最耗时,但也是让模型在真实场景中不翻车的关键部分。2.3 标注规范里的三个大坑第一个坑是小目标。手机、香烟在画面中往往只有几十个像素,边界框稍微画大一点,IoU计算就会严重偏移。我定的规则是:手机框必须紧贴屏幕外边缘,香烟框要包含烟体和烟头发光区域。标注工具里统一放大后再标,小目标的质量提升非常明显。第二个坑是遮挡。驾驶员的手可能遮挡手机,烟气的短暂出现可能遮住面部。统一规则是:目标被遮挡超过60%时不标注;遮挡小于60%时按可见部分画框,不推测被遮住的完整轮廓。这个规则让模型学会在部分遮挡时输出保守的预测框,而不是硬猜一个离谱位置。第三个坑是边界帧。闭眼和睁眼之间存在过渡帧,哈欠张嘴的起点和终点也模糊。统一策略是把过渡帧直接剔除,只保留状态明确的帧参与训练。我最初偷懒保留了部分“半闭眼”帧,结果模型在模糊帧上输出明显抖动,全部清洗重新训练后这个问题就消失了。这些标注层面的功夫往往比调模型更值得投入。3. 训练中的关键设置:从默认配置到稳定收敛3.1 模型规格选型与训练超参数YOLOv5的s、m、l、x四个规格中,我一开始直接用s。驾驶员监控的场景相对固定、类别数量有限,不需要大模型也能跑出不错的精度。实测下来也是这样:s模型参数量约7.3M,在自建测试集上mAP50可以到91%左右;换成l模型mAP只高了不到1个百分点,推理时间却翻了一倍多。所以最后锁定s作为主线。预训练权重的选择也有讲究。直接用COCO预训练权重(YOLOv5s.pt)比从零训练快很多,COCO里已有的person、cell phone、tie等类别特征可以迁移到驾驶员检测场景上。我实测从零训练需要约80个epoch稳定收敛,带COCO预训练大约40个epoch就够,而且最终mAP更高。关键超参数参考:输入分辨率:训练和部署统一640×640批量大小(batch size):16(边缘设备显存有限时8也可以)初始学习率:0.01,采用Cosine退火权重衰减:0.0005预热(warmup):前3个epoch,学习率从0.001逐渐升到0.01这些不是标准答案,但可以作为起步配置。真正重要的是训练过程中持续观察验证集的变化,而不是把参数丢下去就等结果。3.2 输入分辨率与锚框重聚类驾驶员场景里,人脸在画面中占的比例通常远大于COCO中的小目标,但手机、香烟这类小目标又比COCO更难。于是问题变成:输入分辨率太低,小目标丢得厉害;输入分辨率太高,边缘设备推理时间扛不住。我在640×640和416×416之间做了多组对比实验。640下小目标的召回率高出约7个百分点,尤其是香烟和手机这两个类别;但推理时间在嵌入式平台从约13ms涨到了约22ms。最终方案是训练用640×640来提升小目标学习能力,部署时再用推理引擎做进一步加速。锚框方面,YOLOv5默认会在训练前基于数据集重新聚类锚框。自建数据集的目标框尺寸分布明显比COCO更集中——人脸区域偏大、手机香烟偏小。用k-means重新聚类后,中等尺寸锚框上的召回提升明显。这一步属于白送收益,跑一遍聚类脚本就行。3.3 数据增强组合:小目标与夜间场景驾驶员监控的训练里,数据增强的目标和通用目标检测不完全一样。我试了一组组合,最终效果最好的是这几个:Mosaic增强:把4张图拼成1张,极大丰富小目标的上下文,对手机、香烟这类小目标的提升非常明显。HSV增强:驾驶员场景中光照变化剧烈,车内外光线差异大,需要保留一定HSV扰动,让模型对黄昏、隧道灯光有鲁棒性。随机灰度化:这一点对夜间红外摄像头至关重要。红外图本质是灰度图,如果训练集全是彩色可见光图,模型在红外人眼图像上表现会很差。我把一定比例的训练数据随机转成灰度来模拟红外输入,实测夜间召回率上升了大约9个百分点。但这些增强不是越多越好。我一开始把亮度扰动设得很强,结果模型在正常室内光照下反而变得不稳。合理的做法是:先跑一轮默认增强,统计验证集上每个类别的误报来源,再有针对性地调整增强参数。3.4 收敛曲线与误报来源分析训练过程里,我最关注三个信号:验证集上各类别的mAP、Precision/Recall曲线、以及误报来源类别分布。一个容易犯的错误是只盯着总mAP。驾驶员监控场景里,闭眼和哈欠识别精度再高,如果“未系安全带”类别大量误报,交付给下游就会产生无数无意义的告警。我按类别拆分统计后发现,“未系安全带”的误报大多来自深色衣服——深色衣服和安全带的纹理在部分光照下实在太像了。针对这个情况做了三类修正:一是增加未系安全带正样本的多样性,加入灰黑衣物、斜挎安全带的样本;二是把类别改为安全带局部框选,避免模型把整条安全带纹路当成安全带类别;三是把“安全带”类别的置信度阈值单独提高,从默认0.25提到0.45,误报率直接降了一半。这个经验可以复制到其他场景:通用阈值永远不如“类别独立阈值+针对性数据补充”的组合好使。4. 行为判定层:单帧检测结果如何变成可靠告警4.1 单帧检测和状态判定之间有一道鸿沟YOLOv5输出的只是每一帧的目标框和类别置信度,但这不等于告警。如果每一帧检测到手机就立刻报警,那驾驶员只是短暂看一眼手机导航,系统就已经在尖叫了。真正可用的DMS需要对单帧结果做时间维度的聚合。我把整体方案拆成两层:目标检测层负责输出每帧的边界框和类别,状态判定层负责把“偶发的单帧检测”转化为“连续一段时间内的稳定行为”,再决定是否告警。后一层是纯规则引擎,不涉及神经网络,延迟在毫秒级,而且规则透明,出任何问题都能追溯到具体是哪条逻辑导致的。4.2 疲劳状态判定:PERCLOS与眨眼频率疲劳判定的核心指标是PERCLOS,也就是眼睑闭合时间占特定时长的比例。这是学术界和工业界公认的疲劳指标之一,比单纯统计“闭眼次数”可靠得多。我实现的逻辑是:每隔固定时间窗(比如30秒)统计闭眼帧占总帧数的比例。若闭眼比例超过阈值(常见取值范围0.15~0.4,我取0.2),触发疲劳预警。同时统计眨眼间隔:正常驾驶员的眨眼频率大约每分钟15-20次,疲劳时会下降,或者出现类似“慢闭眼”的长闭眼事件。哈欠检测的逻辑不太一样。我计算嘴巴张开比例后,要求该状态持续1秒以上才记为一次哈欠;在2分钟内出现3次以上哈欠,就提升疲劳等级。如果连续两次疲劳预警都没有得到驾驶员回应(比如没有握方向盘动作、视线仍偏低),就升级为紧急告警并给出语音提示。这里有个很重要的细节:YOLOv5检测的是“闭眼”和“打哈欠”的边界框,但边界框本身不包含眼睑开合程度。所以状态判定层会额外读取人脸关键点的眼睑距离来做更精细的百分比计算,YOLOv5的检测框只负责粗判和时序统计。双引擎配合,比单用任何一者都稳定。4.3 分心行为的帧间判定逻辑分心行为包括低头看手机、手持电话、视线偏移和操作中控。这些行为与疲劳不同,更多依赖“持续性”判断:手持电话:检测到手机框,且手机框与手部框或耳部区域重叠度超过阈值,并在连续5帧内保持该状态,判定为一次手持电话事件。低头看手机:检测到人脸框的垂直位置相对头部框明显下移、同时人脸框下方存在手机框,满足持续N帧后触发分心提示。视线偏移:YOLOv5本身不直接输出头部姿态角度,我通过人脸框与头部框的相对位置做简化计算:人脸框中心偏离头部框中心的水平距离持续超过阈值,说明驾驶员持续偏头。状态判定层本质上就是一个简单的状态机,每帧拿检测结果做状态转移。伪代码逻辑大概长这样:state = "normal" phone_near_face_frames = 0 for frame in video_stream: detections = yolo_detect(frame) phone_box = get_phone_box(detections) face_box = get_face_box(detections) if phone_box and face_box and iou(phone_box, face_box) > 0.3: phone_near_face_frames += 1 else: phone_near_face_frames = 0 if phone_near_face_frames >= 5 and state == "normal": state = "phone_warning" trigger_alert("手持电话")这段逻辑看起来简单,但真正稳定运行需要处理非常多的边界情况,比如检测框闪烁、多目标ID切换、行人误检为驾驶员等。我建议在写规则之前先画一张完整的状态转移表,把所有可能进入和退出的条件列出来,否则后期加逻辑会越来越乱。4.4 误报抑制与恢复机制无论模型训练得多好,视觉系统总会有瞬时误检。误报抑制的核心思路是“事件确认”和“事件取消”双向机制:确认机制:检测结果在连续M帧内保持同一类别,才从候选事件升级为确认事件,触发告警。恢复机制:确认事件触发后,不因为单帧漏检就立刻撤销。设定一个事件保持时间(比如3秒),在这个时间窗内继续追踪同类检测框,中间即使断了几帧,事件依然有效。此外,在检测层对检测框坐标做了一级卡尔曼滤波,减少因图像抖动导致的框位置跳动。这套机制叠加后,我在约80分钟的真实驾驶路测中统计,误报率从每分钟7次降到了每分钟不到0.5次。这个数据是我个人实测的,不同车型、摄像头和光线条件下会有差异,但总体趋势是一致的。5. 车载部署实测:算力、延迟和误报的取舍5.1 硬件平台与推理后端选择在嵌入式设备上,YOLOv5s的裸框架推理速度通常不足以直接商用,必须用推理引擎做加速。我最终在Jetson系列(Orin Nano级别)上使用TensorRT将模型导出为FP16精度的引擎,实测640×640输入的单帧推理延迟在13~22毫秒之间。这个数据浮动和TensorRT版本以及板卡状态有关,但已经能满足30FPS视频流的实时处理需求。如果换成更低功耗的车规级盒子,可以把输入降到416×416,此时小目标(香烟、手机)的召回率会明显下降,但算力占用也更低。安全场景需要在“小目标检测能力”和“功耗散热”之间做明确取舍,不能贪心。整个端侧处理流程大概是:摄像头采集原始帧 → 预处理(缩放、归一化、灰度适配) → TensorRT上运行YOLOv5推理 → NMS后处理 → 卡尔曼平滑 → 状态判定层 → 告警输出。流程里的每一步都有优化空间,但瓶颈通常不在模型本身,而在图像输入质量和后处理逻辑的稳定性。5.2 量化与加速的实测收益FP16量化是这次部署中收益最明显的一项。三组实验对比数据如下:引擎精度单帧推理耗时(约)精度变化备注FP3230ms与训练时一致动态范围大,但速度不足FP1617msmAP下降不到0.5%速度和精度平衡,显存减半INT812msmAP下降约2%低光照下手机、香烟误检增加最终生产配置选FP16。INT8虽然更快,但驾驶员监控属于安全类场景,召回率下降的代价远高于节省几毫秒延迟。安全类系统宁可慢一点,也要保证检得准。5.3 摄像头安装位置:容易被忽视的数据源头DMS项目的成败不只在模型和算法,摄像头安装位置的影响比看起来大得多。红外或可见光摄像头装在方向盘柱、仪表盘上方、后视镜区域,得到的视角完全不同。方向盘柱下视角:能拍到完整的方向盘区域,但容易遮挡部分面部。仪表盘上方:面部角度较正,但夜间反光严重。后视镜区域:面部覆盖较好,但视线偏移检测容易收到后视镜反光干扰。我最后在仪表盘上方和后视镜区域各装一路,用双路视频输入做融合判定。这个方案能减轻单视角遮挡问题,但同时带来标定工作量。另外,摄像头需要固定牢靠避免抖动,逆光环境要加偏振片或红外滤片,这些工程细节直接决定算法能拿到什么质量的输入数据。5.4 真实路测中的典型失败模式把系统从实验室搬到真车上,会出现大量实验室里根本看不到的失败模式:副驾驶干扰:副驾的手机被误判为驾驶员手持电话。解决方式简单有效——只关注驾驶员检测框附近的手机框,把副驾区域的检测结果直接丢弃。过暗的隧道:夜间进入长隧道后,自动大灯的光线变化导致画面过暗,需要引入自动增益控制,并在算法侧调高低光照增强。车体振动:经过减速带时画面抖动,检测框出现跳动,卡尔曼滤波能缓解,但严重时要结合IMU信号做去抖。长时间运行的漂移:摄像头持续工作温度升高后,图像噪声增加,检测置信度轻微下降。需要考虑散热设计和周期性软重启恢复。这些失败模式没有一项是模型结构本身的问题,全部是系统层面的工程问题。这也解释了为什么DMS这类项目真正花时间的不是堆模型结构,而是把数据、部署、边界情况这些“脏活累活”做扎实。6. 关于“告警好用”这件事的个人体会实际把系统给几位朋友试用后,我最大的体会是:检测准不等于体验好。一个系统如果频繁误报,驾驶员在头几天之后就会彻底无视它。所以“决定什么时候不报警”和“决定什么时候报警”同等重要。我的最后一条经验是报警分级。把所有事件分为注意、警告、严重三个等级,只有严重等级才强制弹出语音和画面提示,注意和警告只在后台记录,不打扰驾驶。这样系统既保持了预警能力,又不会让驾驶员产生“狼来了”式的疲劳感。目前这个版本还不够完善,我后续打算加入更多上下文信息(比如车速、跟车距离)做联动判定,让告警更贴合真实驾驶场景。如果你也在做类似项目,建议从数据标注规范开始就认真打磨——这一块的工夫永远不会白费。
开车的人可能都有过这种时刻:高速上眼皮突然打架,等惊醒过来方向盘已经偏了半条车道。我做“基于YOLOv5的驾驶员状态与危险行为检测”这个项目的出发点,就是想补上这类预警缺口——用视觉方案实时识别驾驶员是否疲劳、分心,以及有没有抽烟、打电话、不系安全带这些危险行为。这篇文章会把从数据准备、模型训练、行为判定到车载部署的完整链路讲清楚,适合正在做驾驶员监控系统(DMS)、或者想把目标检测模型真正落地到边缘设备的开发者参考。很长一段时间里,驾驶员状态检测的主流路线是“人脸关键点+眼睑开合度计算”,或者“视频帧序列分类”。这两种方案我先后都试过,各有各的坑:关键点方案在夜间红外、戴墨镜、侧脸角度大时经常丢点;帧序列分类方案训练成本高,部署也很笨重。后来我换了个思路——把“闭眼”“打哈欠”“低头看手机”“手持电话”这些驾驶员状态直接当作YOLOv5的目标类别,让模型输出每帧的检测框,再靠上层规则做时序判定。这个拆分方式简单直接,实测下来比预想中要稳得多,下面就把我踩过的坑和保留下来有效做法逐个展开。1. 为什么是YOLOv5:先想清楚检测对象再选算法1.1 驾驶员监控到底在监控什么驾驶员状态监控(DMS)要回答的核心问题其实只有两个:人是否清醒、人是否在专注开车。清醒对应疲劳状态,专注对应分心状态。而“危险行为”其实是分心状态的具体表现形式。把这个问题拆解成可计算的目标,我列了这样一张类别清单:疲劳类证据:闭眼、打哈欠、点头(瞌睡点头)分心类证据:低头看手机、手持电话、视线偏移(东张西望)、操作中控危险动作:抽烟、未系安全带、双手脱离方向盘这是我最初的决定性设计:用“证据”而不是“状态”来做检测目标。疲劳是一个持续状态,单张图像里根本无法直接定义“这个人疲劳”,但单张图像里有没有闭眼、有没有打哈欠,是可以稳定定义的。检测层把这些“证据”找出来,交给上层逻辑做时间累计,疲劳结论就非常可靠。举个例子:单帧检测到一次闭眼,可能只是正常眨眼;但统计一分钟内闭眼帧占比超过40%,或者连续哈欠达到3次,疲劳判定就很有说服力。这个分层设计的好处在于,检测层永远是一个干净的目标检测任务,复杂的状态判定逻辑放在规则层,改起来方便,调试时也能定位到具体是哪条规则出了问题,而不是去猜模型的“黑盒行为”。1.2 目标检测而非分类网络的原因动手之前,我把几条技术路线都摆出来做过对比:图像分类、人脸关键点、姿态估计、目标检测。图像分类的最大问题是缺少空间位置信息。你可以在画面中央裁出一个固定区域做“闭眼/睁眼”二分类,但驾驶员会转头、侧脸、被方向盘遮挡,全局图像分类根本没法定位行为发生的位置。更要命的是,手机、香烟这类目标本质上是“驾驶员身边的物体”,不是“人脸上的属性”,分类网络会把它们和驾驶员整体混在一起,完全无法区分。人脸关键点方案(比如经典的人脸68点)对疲劳检测确实非常有效,眼睑开合度、嘴巴张开程度都能精确计算。但它对图像质量要求太高,夜间红外、戴墨镜、低头角度大、部分遮挡时,关键点经常丢失。而且关键点只能描述人脸,香烟、手机、安全带这些非人脸目标完全无能为力。YOLOv5这类目标检测模型有两个结构性的优势:一是输出自带边界框,直接告诉你“什么东西在画面哪个位置”;二是可以同时检测驾驶员身体、手机、香烟、安全带多种目标。后处理阶段把手机框和手部区域做交集计算,就能得出“手持电话”的判定,这是分类网络根本做不到的。1.3 版本选型:YOLOv5、v6、v7、v8的取舍当前YOLO系列已有多个版本,我最终选择了v5,理由不是它精度最高,而是它在工程化成熟度上最合适。版本核心改动优势对这个项目的适用性YOLOv5C3模块、PANet结构生态最成熟,ONNX/TensorRT导出资料最多首选,踩坑成本最低YOLOv6重参数化、anchor-free工业部署优化做得好可作为备选,但社区资料少YOLOv7E-ELAN扩展结构精度上限较高结构复杂度偏高,轻量设备上有压力YOLOv8C2f模块、anchor-free新特性多,精度潜力大需要额外调参,部署验证成本高车载边缘设备的推理引擎普遍对YOLOv5的支持最完善,很多板卡SDK甚至直接内置了v5的解析层。这一点在后期实际部署时价值很大:我遇到的大部分TensorRT算子兼容问题,网上都已经有现成的讨论和解决方案。对于安全类项目,稳定可控比多几个点的精度更值钱。2. 数据与类别体系:行为定义决定系统上限2.1 行为分类先解决语义模糊问题数据标注之前,类别体系必须定义得足够清晰,否则模型会自己学出一套奇怪的定义。比如“手持电话”,到底是指手碰到手机就算,还是举到耳边才算?我的定义是:驾驶员伸手拿着手机且屏幕朝向驾驶员,手机处于可见状态(大多发生在低头或举在耳边时)才标注为手持电话;手机放在支架上,不算手持。这个定义落到标注规则里,模型才知道边界在哪里。闭眼和打哈欠也要细化定义:闭眼:单帧内眼睑闭合超过80%,且持续帧数由后续状态机统计。打哈欠:嘴巴张开且宽高比超过阈值,持续多帧;张嘴说话不算,因为说话时嘴型变化很快,打哈欠时嘴部张度会稳定保持一段时间。如果不做这类语义切分,模型训练时就会出现“把大笑当成打哈欠”“把玩手机当成手持电话”的误判。误判恰恰是这类项目中最容易被业务方挑剔的痛点。2.2 数据来源:开源打底、合成补量、自采纠偏数据方面,我用了三条腿走路:开源公开数据集:包括驾驶员状态监测公开数据、面部表情相关数据,提供大量人脸疲劳证据样本,覆盖不同角度和基础光照,量大概在几千张。合成数据:在三维驾驶仿真场景中批量渲染不同姿势、不同光线下的驾驶员图像。合成数据最主要的价值在于覆盖“罕见角度”——例如低头到极致的姿态、手机高度贴近面部、香烟含在嘴边的侧脸图,这些在真实拍摄场景中数量很少,但在仿真环境里可以低成本批量生成。自采数据:用摄像头在模拟驾驶舱和真实车辆内补拍,重点采集早晚逆光、夜间红外、戴墨镜等场景。公开数据集和合成数据大多来自理想光照条件,真实行驶场景的光照复杂度远高于它们。我最后把数据比例调到公开数据约60%、合成数据约25%、自采数据约15%。合成数据虽然好用,但比例不能太高,否则模型会过拟合到仿真渲染风格,在真实图像上反而掉点。自采数据最耗时,但也是让模型在真实场景中不翻车的关键部分。2.3 标注规范里的三个大坑第一个坑是小目标。手机、香烟在画面中往往只有几十个像素,边界框稍微画大一点,IoU计算就会严重偏移。我定的规则是:手机框必须紧贴屏幕外边缘,香烟框要包含烟体和烟头发光区域。标注工具里统一放大后再标,小目标的质量提升非常明显。第二个坑是遮挡。驾驶员的手可能遮挡手机,烟气的短暂出现可能遮住面部。统一规则是:目标被遮挡超过60%时不标注;遮挡小于60%时按可见部分画框,不推测被遮住的完整轮廓。这个规则让模型学会在部分遮挡时输出保守的预测框,而不是硬猜一个离谱位置。第三个坑是边界帧。闭眼和睁眼之间存在过渡帧,哈欠张嘴的起点和终点也模糊。统一策略是把过渡帧直接剔除,只保留状态明确的帧参与训练。我最初偷懒保留了部分“半闭眼”帧,结果模型在模糊帧上输出明显抖动,全部清洗重新训练后这个问题就消失了。这些标注层面的功夫往往比调模型更值得投入。3. 训练中的关键设置:从默认配置到稳定收敛3.1 模型规格选型与训练超参数YOLOv5的s、m、l、x四个规格中,我一开始直接用s。驾驶员监控的场景相对固定、类别数量有限,不需要大模型也能跑出不错的精度。实测下来也是这样:s模型参数量约7.3M,在自建测试集上mAP50可以到91%左右;换成l模型mAP只高了不到1个百分点,推理时间却翻了一倍多。所以最后锁定s作为主线。预训练权重的选择也有讲究。直接用COCO预训练权重(YOLOv5s.pt)比从零训练快很多,COCO里已有的person、cell phone、tie等类别特征可以迁移到驾驶员检测场景上。我实测从零训练需要约80个epoch稳定收敛,带COCO预训练大约40个epoch就够,而且最终mAP更高。关键超参数参考:输入分辨率:训练和部署统一640×640批量大小(batch size):16(边缘设备显存有限时8也可以)初始学习率:0.01,采用Cosine退火权重衰减:0.0005预热(warmup):前3个epoch,学习率从0.001逐渐升到0.01这些不是标准答案,但可以作为起步配置。真正重要的是训练过程中持续观察验证集的变化,而不是把参数丢下去就等结果。3.2 输入分辨率与锚框重聚类驾驶员场景里,人脸在画面中占的比例通常远大于COCO中的小目标,但手机、香烟这类小目标又比COCO更难。于是问题变成:输入分辨率太低,小目标丢得厉害;输入分辨率太高,边缘设备推理时间扛不住。我在640×640和416×416之间做了多组对比实验。640下小目标的召回率高出约7个百分点,尤其是香烟和手机这两个类别;但推理时间在嵌入式平台从约13ms涨到了约22ms。最终方案是训练用640×640来提升小目标学习能力,部署时再用推理引擎做进一步加速。锚框方面,YOLOv5默认会在训练前基于数据集重新聚类锚框。自建数据集的目标框尺寸分布明显比COCO更集中——人脸区域偏大、手机香烟偏小。用k-means重新聚类后,中等尺寸锚框上的召回提升明显。这一步属于白送收益,跑一遍聚类脚本就行。3.3 数据增强组合:小目标与夜间场景驾驶员监控的训练里,数据增强的目标和通用目标检测不完全一样。我试了一组组合,最终效果最好的是这几个:Mosaic增强:把4张图拼成1张,极大丰富小目标的上下文,对手机、香烟这类小目标的提升非常明显。HSV增强:驾驶员场景中光照变化剧烈,车内外光线差异大,需要保留一定HSV扰动,让模型对黄昏、隧道灯光有鲁棒性。随机灰度化:这一点对夜间红外摄像头至关重要。红外图本质是灰度图,如果训练集全是彩色可见光图,模型在红外人眼图像上表现会很差。我把一定比例的训练数据随机转成灰度来模拟红外输入,实测夜间召回率上升了大约9个百分点。但这些增强不是越多越好。我一开始把亮度扰动设得很强,结果模型在正常室内光照下反而变得不稳。合理的做法是:先跑一轮默认增强,统计验证集上每个类别的误报来源,再有针对性地调整增强参数。3.4 收敛曲线与误报来源分析训练过程里,我最关注三个信号:验证集上各类别的mAP、Precision/Recall曲线、以及误报来源类别分布。一个容易犯的错误是只盯着总mAP。驾驶员监控场景里,闭眼和哈欠识别精度再高,如果“未系安全带”类别大量误报,交付给下游就会产生无数无意义的告警。我按类别拆分统计后发现,“未系安全带”的误报大多来自深色衣服——深色衣服和安全带的纹理在部分光照下实在太像了。针对这个情况做了三类修正:一是增加未系安全带正样本的多样性,加入灰黑衣物、斜挎安全带的样本;二是把类别改为安全带局部框选,避免模型把整条安全带纹路当成安全带类别;三是把“安全带”类别的置信度阈值单独提高,从默认0.25提到0.45,误报率直接降了一半。这个经验可以复制到其他场景:通用阈值永远不如“类别独立阈值+针对性数据补充”的组合好使。4. 行为判定层:单帧检测结果如何变成可靠告警4.1 单帧检测和状态判定之间有一道鸿沟YOLOv5输出的只是每一帧的目标框和类别置信度,但这不等于告警。如果每一帧检测到手机就立刻报警,那驾驶员只是短暂看一眼手机导航,系统就已经在尖叫了。真正可用的DMS需要对单帧结果做时间维度的聚合。我把整体方案拆成两层:目标检测层负责输出每帧的边界框和类别,状态判定层负责把“偶发的单帧检测”转化为“连续一段时间内的稳定行为”,再决定是否告警。后一层是纯规则引擎,不涉及神经网络,延迟在毫秒级,而且规则透明,出任何问题都能追溯到具体是哪条逻辑导致的。4.2 疲劳状态判定:PERCLOS与眨眼频率疲劳判定的核心指标是PERCLOS,也就是眼睑闭合时间占特定时长的比例。这是学术界和工业界公认的疲劳指标之一,比单纯统计“闭眼次数”可靠得多。我实现的逻辑是:每隔固定时间窗(比如30秒)统计闭眼帧占总帧数的比例。若闭眼比例超过阈值(常见取值范围0.15~0.4,我取0.2),触发疲劳预警。同时统计眨眼间隔:正常驾驶员的眨眼频率大约每分钟15-20次,疲劳时会下降,或者出现类似“慢闭眼”的长闭眼事件。哈欠检测的逻辑不太一样。我计算嘴巴张开比例后,要求该状态持续1秒以上才记为一次哈欠;在2分钟内出现3次以上哈欠,就提升疲劳等级。如果连续两次疲劳预警都没有得到驾驶员回应(比如没有握方向盘动作、视线仍偏低),就升级为紧急告警并给出语音提示。这里有个很重要的细节:YOLOv5检测的是“闭眼”和“打哈欠”的边界框,但边界框本身不包含眼睑开合程度。所以状态判定层会额外读取人脸关键点的眼睑距离来做更精细的百分比计算,YOLOv5的检测框只负责粗判和时序统计。双引擎配合,比单用任何一者都稳定。4.3 分心行为的帧间判定逻辑分心行为包括低头看手机、手持电话、视线偏移和操作中控。这些行为与疲劳不同,更多依赖“持续性”判断:手持电话:检测到手机框,且手机框与手部框或耳部区域重叠度超过阈值,并在连续5帧内保持该状态,判定为一次手持电话事件。低头看手机:检测到人脸框的垂直位置相对头部框明显下移、同时人脸框下方存在手机框,满足持续N帧后触发分心提示。视线偏移:YOLOv5本身不直接输出头部姿态角度,我通过人脸框与头部框的相对位置做简化计算:人脸框中心偏离头部框中心的水平距离持续超过阈值,说明驾驶员持续偏头。状态判定层本质上就是一个简单的状态机,每帧拿检测结果做状态转移。伪代码逻辑大概长这样:state = "normal" phone_near_face_frames = 0 for frame in video_stream: detections = yolo_detect(frame) phone_box = get_phone_box(detections) face_box = get_face_box(detections) if phone_box and face_box and iou(phone_box, face_box) > 0.3: phone_near_face_frames += 1 else: phone_near_face_frames = 0 if phone_near_face_frames >= 5 and state == "normal": state = "phone_warning" trigger_alert("手持电话")这段逻辑看起来简单,但真正稳定运行需要处理非常多的边界情况,比如检测框闪烁、多目标ID切换、行人误检为驾驶员等。我建议在写规则之前先画一张完整的状态转移表,把所有可能进入和退出的条件列出来,否则后期加逻辑会越来越乱。4.4 误报抑制与恢复机制无论模型训练得多好,视觉系统总会有瞬时误检。误报抑制的核心思路是“事件确认”和“事件取消”双向机制:确认机制:检测结果在连续M帧内保持同一类别,才从候选事件升级为确认事件,触发告警。恢复机制:确认事件触发后,不因为单帧漏检就立刻撤销。设定一个事件保持时间(比如3秒),在这个时间窗内继续追踪同类检测框,中间即使断了几帧,事件依然有效。此外,在检测层对检测框坐标做了一级卡尔曼滤波,减少因图像抖动导致的框位置跳动。这套机制叠加后,我在约80分钟的真实驾驶路测中统计,误报率从每分钟7次降到了每分钟不到0.5次。这个数据是我个人实测的,不同车型、摄像头和光线条件下会有差异,但总体趋势是一致的。5. 车载部署实测:算力、延迟和误报的取舍5.1 硬件平台与推理后端选择在嵌入式设备上,YOLOv5s的裸框架推理速度通常不足以直接商用,必须用推理引擎做加速。我最终在Jetson系列(Orin Nano级别)上使用TensorRT将模型导出为FP16精度的引擎,实测640×640输入的单帧推理延迟在13~22毫秒之间。这个数据浮动和TensorRT版本以及板卡状态有关,但已经能满足30FPS视频流的实时处理需求。如果换成更低功耗的车规级盒子,可以把输入降到416×416,此时小目标(香烟、手机)的召回率会明显下降,但算力占用也更低。安全场景需要在“小目标检测能力”和“功耗散热”之间做明确取舍,不能贪心。整个端侧处理流程大概是:摄像头采集原始帧 → 预处理(缩放、归一化、灰度适配) → TensorRT上运行YOLOv5推理 → NMS后处理 → 卡尔曼平滑 → 状态判定层 → 告警输出。流程里的每一步都有优化空间,但瓶颈通常不在模型本身,而在图像输入质量和后处理逻辑的稳定性。5.2 量化与加速的实测收益FP16量化是这次部署中收益最明显的一项。三组实验对比数据如下:引擎精度单帧推理耗时(约)精度变化备注FP3230ms与训练时一致动态范围大,但速度不足FP1617msmAP下降不到0.5%速度和精度平衡,显存减半INT812msmAP下降约2%低光照下手机、香烟误检增加最终生产配置选FP16。INT8虽然更快,但驾驶员监控属于安全类场景,召回率下降的代价远高于节省几毫秒延迟。安全类系统宁可慢一点,也要保证检得准。5.3 摄像头安装位置:容易被忽视的数据源头DMS项目的成败不只在模型和算法,摄像头安装位置的影响比看起来大得多。红外或可见光摄像头装在方向盘柱、仪表盘上方、后视镜区域,得到的视角完全不同。方向盘柱下视角:能拍到完整的方向盘区域,但容易遮挡部分面部。仪表盘上方:面部角度较正,但夜间反光严重。后视镜区域:面部覆盖较好,但视线偏移检测容易收到后视镜反光干扰。我最后在仪表盘上方和后视镜区域各装一路,用双路视频输入做融合判定。这个方案能减轻单视角遮挡问题,但同时带来标定工作量。另外,摄像头需要固定牢靠避免抖动,逆光环境要加偏振片或红外滤片,这些工程细节直接决定算法能拿到什么质量的输入数据。5.4 真实路测中的典型失败模式把系统从实验室搬到真车上,会出现大量实验室里根本看不到的失败模式:副驾驶干扰:副驾的手机被误判为驾驶员手持电话。解决方式简单有效——只关注驾驶员检测框附近的手机框,把副驾区域的检测结果直接丢弃。过暗的隧道:夜间进入长隧道后,自动大灯的光线变化导致画面过暗,需要引入自动增益控制,并在算法侧调高低光照增强。车体振动:经过减速带时画面抖动,检测框出现跳动,卡尔曼滤波能缓解,但严重时要结合IMU信号做去抖。长时间运行的漂移:摄像头持续工作温度升高后,图像噪声增加,检测置信度轻微下降。需要考虑散热设计和周期性软重启恢复。这些失败模式没有一项是模型结构本身的问题,全部是系统层面的工程问题。这也解释了为什么DMS这类项目真正花时间的不是堆模型结构,而是把数据、部署、边界情况这些“脏活累活”做扎实。6. 关于“告警好用”这件事的个人体会实际把系统给几位朋友试用后,我最大的体会是:检测准不等于体验好。一个系统如果频繁误报,驾驶员在头几天之后就会彻底无视它。所以“决定什么时候不报警”和“决定什么时候报警”同等重要。我的最后一条经验是报警分级。把所有事件分为注意、警告、严重三个等级,只有严重等级才强制弹出语音和画面提示,注意和警告只在后台记录,不打扰驾驶。这样系统既保持了预警能力,又不会让驾驶员产生“狼来了”式的疲劳感。目前这个版本还不够完善,我后续打算加入更多上下文信息(比如车速、跟车距离)做联动判定,让告警更贴合真实驾驶场景。如果你也在做类似项目,建议从数据标注规范开始就认真打磨——这一块的工夫永远不会白费。