ARTICLE DETAIL

资讯详情

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

DMS驾驶员监控系统实战:从瞳孔检测到CBAM分类的完整视觉推理链路

DMS驾驶员监控系统实战:从瞳孔检测到CBAM分类的完整视觉推理链路 简介面向计算机类毕业设计或课程作业的完整项目聚焦智能座舱中的驾驶员分心行为监测系统DMS融合人工智能与计算机视觉技术实现对注意力分散行为的识别与预警适合需要完成毕设、课程项目或学习检测算法实际落地的开发者。压缩包共39个文件主要包含Python源码、预训练模型权重pth、XML与JSON配置、说明文档等其中py/pyc文件覆盖视线估计、模型训练与实时预测逻辑pth权重可直接用于推理整体包体83.45MB便于快速搭建运行环境。已有305人学习下载。通过源码可深入理解 gaze_tracking 视线跟踪模块、CBAM注意力机制及DMS整体流程并可基于已有模型权重进行二次训练或迁移优化对提升智能驾驶安全领域的AI实战能力具有明显价值。1. 为什么一个0.978精度的DMS模型落到智能座舱里仍然要先解决跑不跑得动的问题model-70_0.978_fix.pth这个权重文件验证集准确率0.978初看相当亮眼但它是固定的摄像头角度、固定光照、同一批驾驶姿态数据里测出来的不是真实路测的统计口径。这套工程真正值钱的部分在于gaze_tracking模块瞳孔检测、头部姿态解算、视线向量计算被完整串成了一条推理链后端再挂一个带CBAM注意力机制的分类模型做分心状态判定。对做毕设和课程作业的人来说它给出了人脸关键点检测→视线估计→行为分类→报警输出全链路而智能座舱测试工程师也能拿它当纯视觉DMS基线方案不依赖红外专用摄像头。整个工程是本地实时推理架构MAIN.py是功能完整版MAIN_fast.py是加速优化版predict_cam.py可直接接USB摄像头配合requirements.txt就能把环境拉起来。2. 拆解gaze_tracking模块瞳孔定位与头部姿态是分心判断的地基2.1 文件清单决定了系统边界先把这个工程的源码结构理清楚因为一个压缩包里哪些东西被当作核心交付物本身就说明了项目的设计倾向。文件/目录职责关键依赖gaze_tracking/init.py模块入口对外导出GazeTracking类gaze_tracking/pupil.py单眼区域瞳孔中心定位OpenCVgaze_tracking/eye.py眼睛几何特征计算睁闭眼状态gaze_tracking/gaze_tracking.py头部姿态解算与视线向量合成dlib、OpenCVgaze_tracking/calibration.py屏幕注视点标定model_cbam.py带CBAM注意力机制的分类模型PyTorch、torchvisionMAIN.py完整推理主循环含标定与报警MAIN_fast.py优化后的加速推理循环predict_cam.py摄像头实时预测独立脚本model-70_0.978_fix.pth训练完成的模型权重class_indices.json类别索引到标签名的映射requirements.txt运行环境依赖清单这个文件分布很明显不是训练脚本测试脚本的常规毕设结构训练逻辑被压缩到只剩model_cbam.py一个文件而推理侧拆出了两个主入口和一个独立预测脚本。也就是说系统默认你已经有训练好的权重剩余的问题全在如何在一个新场景里稳定地跑起来。这也是智能座舱测试里最容易被低估的环节——模型在离线数据集上精度做得再高摄像头安装角度一变、驾驶员座椅高度一调检测率会出现肉眼可见的回落。2.2 瞳孔中心与眼睛几何pupil.py和eye.py分工gaze_tracking这套模块的底层依赖dlib的68点人脸关键点检测。pupil.py做的事情是在关键点框出的眼睛区域里通过二值化把最暗的虹膜区域分离出来然后计算轮廓质心作为瞳孔中心。核心逻辑可以还原成如下形式。import cv2 import numpy as np def detect_pupil(eye_region): # eye_region 是从人脸关键点裁剪出的单眼灰度图 gray cv2.cvtColor(eye_region, cv2.COLOR_BGR2GRAY) # 瞳孔比虹膜更暗用低阈值把最暗区域切出来 _, binary cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY_INV) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 取最大连通域的质心作为瞳孔近似中心 largest max(contours, keycv2.contourArea) M cv2.moments(largest) if M[m00] 0: return None cx int(M[m10] / M[m00]) cy int(M[m01] / M[m00]) return cx, cy这段代码的关键在阈值60这个值它是经验值。我实测过驾驶舱偏暗环境下这个阈值基本稳定但迎光行驶时瞳孔区域会被高光打穿二值化直接失败这时需要换成cv2.adaptiveThreshold做自适应分割。这类固定阈值实现进不了量产但对毕设演示完全够用——它把一个视线估计问题降维成了图像分割加质心计算。eye.py则在此基础上提取眼睑几何特征典型的是眼睛长宽比EAR用来判断睁眼闭眼状态。分心检测里有一个信号值得关注眼睛被判定为闭合后端的分类模型却输出正常驾驶这种矛盾通常意味着人脸框发生了漂移。2.3 头部姿态与视线向量从solvePnP到注意力方向gaze_tracking.py比pupil.py更深一层。它做的事可以概括为两步第一用dlib输出的人脸关键点和标准3D人脸模型做solvePnP求解得到头部旋转向量第二把瞳孔中心相对眼睛中心的像素偏移叠加到旋转向量上合成最终的视线向量。这个向量的水平分量和垂直分量一旦持续超出某个角度范围系统就认为驾驶员没有在看前方道路。实际使用里有几个容易忽略的细节。分心检测真正依赖的不是看哪里的绝对坐标而是头部姿态加视线偏移的组合是否持续偏离前方。当座椅位置调整后头部姿态的基准值会整体变化所以calibration.py才存在——它引导用户依次注视屏幕的四个角和中心点在视线向量和屏幕坐标之间建立映射关系。演示时标定环节的意义很大因为答辩评委通常会上手试坐标定顺不顺直接影响第一印象。我做二次开发时会把标定点从5个扩展到9个边界区域的映射误差能够显著降低。3. CBAM注意力机制与0.978权重分心分类模型在通道和空间维度上关注什么3.1 model_cbam.py的结构通道注意力与空间注意力串联model_cbam.py定义的分心分类模型核心在于CBAM模块。CBAM由通道注意力模块和空间注意力模块串行组成先让模型知道看什么特征再告诉模型去哪里看。在分心行为识别场景里背景座椅、车窗外的景物很容易干扰分类结果CBAM的作用就是让特征图聚焦到人脸、手部和方向盘区域的响应上。它的标准实现如下。import torch import torch.nn as nn class CBAMBlock(nn.Module): def __init__(self, channel, reduction16): super().__init__() # 通道注意力全局平均池化和最大池化并行提取描述符 self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Conv2d(channel, channel // reduction, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(channel // reduction, channel, 1, biasFalse) ) self.sigmoid_channel nn.Sigmoid() # 空间注意力沿通道维度聚合后用一个7x7卷积生成空间掩码 self.conv_spatial nn.Conv2d(2, 1, kernel_size7, padding3, biasFalse) self.sigmoid_spatial nn.Sigmoid() def forward(self, x): # 通道注意力两种池化结果共享同一个MLP相加后做sigmoid avg_out self.mlp(self.avg_pool(x)) max_out self.mlp(self.max_pool(x)) channel_att self.sigmoid_channel(avg_out max_out) x x * channel_att # 空间注意力通道维平均和最大聚合后拼接过7x7卷积 avg_spatial torch.mean(x, dim1, keepdimTrue) max_spatial, _ torch.max(x, dim1, keepdimTrue) spatial_att self.sigmoid_spatial(self.conv_spatial(torch.cat([avg_spatial, max_spatial], dim1))) return x * spatial_att这段代码里有三个细节值得展开。reduction取16意味着通道数先压缩16倍再过两个卷积层这个倍数控制的是MLP的参数量空间注意力部分不用MLP而用7x7卷积是因为空间注意力需要保留位置信息全连接会破坏空间结构两种池化并行是因为平均池化保留全局背景响应最大池化突出响应最强的局部区域两者互补。在分心分类这个任务上通道注意力会倾向于放大手部接近面部这类判别性特征的权重空间注意力则把高响应区域约束在图像中部的人脸及周围区域。3.2 class_indices.json与标签体系四类分心行为定义class_indices.json是模型输出索引到类别名的映射文件。这个工程的分心行为类别划分符合目前智能座舱测试中最常见的数据集组织方式一个正常驾驶类加上若干种典型分心行为。推理侧解码逻辑如下。import json import torch import torch.nn.functional as F with open(class_indices.json, r, encodingutf-8) as f: idx_to_class {int(k): v for k, v in json.load(f).items()} def predict_single_frame(model, tensor_image): model.eval() with torch.no_grad(): logits model(tensor_image) # 形状 [1, num_classes] probs F.softmax(logits, dim1) score, idx torch.max(probs, dim1) return idx_to_class[idx.item()], score.item()注意第一行的int(k)转换。JSON字典的键在序列化后是字符串不转int会导致索引错位这种隐蔽bug。softmax输出的概率和等于1torch.max同时返回最大值和对应索引score就是这个帧的置信度——后面的报警阈值决策就是拿这个值和预设阈值比较。每个类别在训练前都要保证视频样本里出现的场景足够多样否则模型会学到这个座椅纹理属于正常驾驶这种伪特征。3.3 0.978这个精度数字的真实含义model-70_0.978_fix.pth这个文件名本身包含三层信息70很可能是训练轮数epoch0.978是验证集准确率fix后缀表示这是修正过某类错误后的版本。但要从这个数字判断模型能不能用于智能座舱测试还缺一个角色混淆矩阵。分心检测中存在两类错误安全行为被报成分心行为的假阳性会打扰驾驶员分心行为漏报的假阴性则是安全底线。0.978的总体准确率无法回答这两类错误各自占比多少必须自己录制测试视频做统计。我测试这类分心模型时有一个固定做法每一类危险行为准备80到100个片段正常驾驶准备200个片段跑完后单独计算每一类的召回率和正常类的误报率这个评估口径与智能座舱测试规范里对DMS误报率的要求是对齐的。4. 实时推理链路与关键参数从摄像头帧到报警输出的完整过程4.1 主循环调度关键点检测与分类推理异步执行MAIN.py和predict_cam.py共享同一套推理逻辑区别在于入口组织和输出形式。主循环的骨架可以抽象成下面的伪代码结构它反映了分心检测推理链路的真实调度方式。import cv2 import torch from gaze_tracking import GazeTracking gaze GazeTracking() model torch.load(model-70_0.978_fix.pth, map_locationcpu) model.eval() cap cv2.VideoCapture(0) # 默认摄像头智能座舱里通常是A柱或仪表盘上方 frame_interval 2 # 每隔2帧做一次人脸关键点检测 frame_count 0 vote_buffer [] # 保存最近N帧的分类结果 while True: ret, frame cap.read() if not ret: break frame cv2.resize(frame, (640, 360)) # 统一输入分辨率降低后续计算量 frame_count 1 if frame_count % frame_interval 0: gaze.refresh(frame) # dlib关键点检测 视线向量更新 face gaze.face_region() # 按关键点裁剪人脸区域 if face is None: vote_buffer.append(unknown) continue category, confidence predict_single_frame(model, preprocess(face)) vote_buffer.append(category if confidence 0.75 else unknown) # 滑动窗口多数投票窗口内危险类占多数才触发报警 if len(vote_buffer) 10: vote_buffer.pop(0) dangerous_ratio vote_buffer.count(phone) / len(vote_buffer) # 示例统计 if dangerous_ratio 0.6: trigger_alarm()这段逻辑里gaze.refresh和模型推理是串行执行的关系因此每一帧的耗时是两者的和。dlib的68点检测是关键路径上的瓶颈而frame_interval控制了它的执行频率。滑动窗口投票的作用是抑制单帧抖动比如图像噪声导致一帧误判为phone单个异常值不会触发报警连续多帧都判定为phone才真正报警。0.75置信度阈值和0.6危险占比这两个值解释一下0.75是模型单帧输出的取舍点置信度低时宁可选unknown避免把不确定帧当作证据0.6则控制投票窗口内的触发比例窗口长度10帧意味着至少6帧判定为危险类才报警。4.2 推理链路的关键参数与调参顺序把上一节出现过的参数汇总成一张表其中调参方向是实际测试中摸索出来的。参数默认值作用调整方向frame_interval2每隔N帧执行一次dlib关键点检测调大降低CPU占用但快速转头时会跟丢人脸推理分辨率640x360送入分类模型的图像尺寸降到320x180提速但小目标特征会丢失置信度阈值0.75低于阈值的帧标记为unknown调高减少误报调低降低漏报率投票窗口长度10参与多数投票的帧数越长越稳定但报警响应延迟增加危险类占比0.6窗口内危险类触发报警的比例调低更灵敏适合对漏报零容忍的场景调参有一个最常见的错误顺序一上来就调置信度阈值。但置信度阈值的作用范围只在分类模型输出之后前端人脸检测如果已经丢失目标后面的阈值再合理也无济于事。我推荐的顺序是先固定摄像头位置和角度跑一遍标定然后用一段30秒的连续驾驶视频测试统计人脸丢失帧占比丢失率高于5%先降低推理分辨率或调大frame_interval最后再根据剩余的误报情况调整0.75和0.6这两个值。还有一个端到端指标比单帧准确率更关键——从分心动作发生到报警触发的时间差。行业里这个值通常要求小于800毫秒如果超时优先缩减投票窗口而不是优化模型。4.3 predict_cam.py与MAIN.py的工程分工predict_cam.py做的事情与MAIN.py主循环基本一致但它是独立脚本不依赖完整工程的状态管理。我在验证一个模型权重是否好用时更倾向于直接跑predict_cam.py因为它的输出更精简只有类别标签和置信度方便在终端里肉眼确认结果。MAIN.py则集成了标定流程、状态叠加显示和报警输出面向的是完整演示场景。两个入口分开维护符合实际工程里验证脚本和业务主程序的分离原则改报警逻辑时不需要担心弄坏模型验证链路。5. 用MAIN_fast.py做推理加速跳帧、跟踪器与降分辨率的具体收益MAIN_fast.py相对MAIN.py的四个优化手段逐一拆开看收益来源。第一dlib关键点检测从每帧执行改成隔帧执行中间帧用OpenCV的跟踪器接手人脸框因为跟踪器的计算量远小于重新检测第二模型输入分辨率从640x360降到320x180推理耗时大约能降一半第三人脸框区域使用上一帧的位置做局部裁剪避免整帧缩放第四模型权重加载后转成半精度浮点。这四个手段里收益最大的是第一项dlib正脸检测单帧需要30到80毫秒跟踪器一轮只需要2到5毫秒代价是快速转头时人脸框可能偏移所以跟踪器判定丢失时要立即回退到dlib重新检测。帧率测量和延迟统计用time.perf_counter打点这是验证优化效果的第一步。import time start time.perf_counter() result process_frame(frame) # 这里放MAIN_fast.py的处理函数 elapsed time.perf_counter() - start fps 1.0 / elapsed print(fframe latency: {elapsed * 1000:.1f} ms, fps: {fps:.1f})process_frame包含检测、推理、后处理全流程elapsed是完整处理一帧的耗时。fps要在无人脸、有人脸、快速转头三种场景下分别记录因为MAIN_fast.py的优化手段在不同场景下的表现差异很大。端到端验证用智能座舱测试里常见的动作序列回放法。录一段3分钟驾驶视频包含正常驾驶、手持手机、喝水、转头看侧窗和操作中控五个动作每个动作持续8到12秒动作之间留5秒正常驾驶间隔。录制时环境光保持稳定跑完回放后统计每个动作段内系统输出正确类别帧数的占比得到检出度和误报数。这个流程可以放在云资源上批量执行也可以本地循环播放。与对着摄像头实拍相比录制回放的优势在于条件可控每次跑结果可复现。比较有性价比的一个验证技巧是把投票窗口长度从10改为15或20观察误报数和报警延迟的变化。如果误报数几乎不变说明误报来源是前端人脸检测丢帧或跟踪器漂移而非分类模型本身此时应回头调整frame_interval和跟踪器的丢失回退策略而不是继续拉长投票窗口。这个判断方法能把调参方向引到真正的瓶颈环节比反复调置信度阈值高效得多。本文还有配套的精品资源点击获取
返回列表