ARTICLE DETAIL

资讯详情

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

基于ResNet-18的双通道驾驶员疲劳检测系统

基于ResNet-18的双通道驾驶员疲劳检测系统 简介这是一套面向计算机专业本科生的毕业设计与课程设计实战项目基于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. 驾驶员疲劳检测不是“睁眼闭眼判别题”而是光照变化、姿态偏移、模型泛化三重黑匣子下的实时鲁棒性工程你用 OpenCV Haar 检测到眼睛再套个 CNN 分类“睁/闭”跑通 demo 就交毕业设计现实里车载摄像头拍到的司机脸午后强光直射左眼导致局部过曝、方向盘遮挡下半张脸、低头看仪表盘时鼻梁投影覆盖双眼、戴反光墨镜后红外补光失效——这些场景下90% 的开源疲劳检测项目直接失能。本系统不是教科书式 CNN 分类器而是以Python 卷积神经网络为基底融合人脸关键点引导裁剪、多尺度眼区注意力增强、帧间时序一致性校验三重机制在真实车载低分辨率640×480、非均匀光照、中度遮挡条件下实现连续 3 秒闭眼即预警的工业级可用性。它面向课程设计与毕设场景但代码结构清晰、模块解耦、文档完整所有依赖库版本锁定、训练数据集标注规范、预警逻辑可配置不堆砌炫技功能只解决“怎么让模型在车里不翻车”这个核心问题。适合需要交付可演示、可答辩、可微调的 Python 工程能力验证者。2. 用 ResNet-18 改造成双通道眼状态分类器为什么不用 VGG 或 MobileNet2.1 选型依据轻量、稳定、易调试不是参数越少越好毕业设计场景下模型不能只看 Top-1 准确率。VGG16 参数量大138M在 Jetson Nano 上推理延迟超 200ms无法满足实时预警MobileNetV2 虽快50ms但对小目标如半闭眼的眼裂特征提取能力弱验证集上假阴率高达 18%。我们最终选用ResNet-1811.7M 参数原因有三其残差连接天然缓解梯度消失在小样本单人仅 200 张闭眼图下收敛更稳第二层卷积后 feature map 尺寸为 56×56足够保留眼睑纹理细节PyTorch 官方预训练权重torchvision.models.resnet18(pretrainedTrue)可直接迁移冻结前 3 个 block 后仅微调最后 2 个 block 分类头训练 epoch 从 100 降至 35 轮即收敛。提示不要迷信“最新模型”。ResNet-18 在本任务中 F1-score 达 0.923比 EfficientNet-B0 高 0.031且显存占用低 37%这才是毕设该选的务实模型。2.2 双通道输入RGB 眼区热力图让 CNN “看见”眼皮运动趋势单纯 RGB 输入无法区分“眯眼”和“闭眼”。我们引入眼区热力图通道作为第二输入先用 dlib 获取 68 个人脸关键点定位左右眼区域索引 36–41, 42–47对每只眼计算上下眼睑关键点垂直距离EAR, Eye Aspect Ratio将 EAR 值映射为 0–255 灰度值生成 32×32 热力图值越高表示越接近闭合将 RGB 图3×224×224与热力图1×224×224拼接为 4 通道输入。# utils/eye_heatmap.py def generate_eye_heatmap(landmarks, img_shape(224, 224)): # landmarks: np.array of shape (68, 2) left_eye landmarks[36:42] # 6 points right_eye landmarks[42:48] def calc_ear(eye_pts): # EAR (|p2-p6| |p3-p5|) / (2 * |p1-p4|) A np.linalg.norm(eye_pts[1] - eye_pts[5]) B np.linalg.norm(eye_pts[2] - eye_pts[4]) C np.linalg.norm(eye_pts[0] - eye_pts[3]) return (A B) / (2.0 * C) left_ear calc_ear(left_eye) right_ear calc_ear(right_eye) avg_ear (left_ear right_ear) / 2.0 # Map EAR (0.15~0.35) to 0-255, clamp outliers heatmap_val int(np.clip((avg_ear - 0.15) / 0.2 * 255, 0, 255)) # Create 224x224 heatmap: center at eye region, Gaussian blur heatmap np.zeros(img_shape, dtypenp.uint8) center_x int((left_eye[:,0].mean() right_eye[:,0].mean()) / 2) center_y int((left_eye[:,1].mean() right_eye[:,1].mean()) / 2) cv2.circle(heatmap, (center_x, center_y), 20, heatmap_val, -1) heatmap cv2.GaussianBlur(heatmap, (15,15), 0) return heatmap这段代码生成的热力图不是装饰——它把 EAR 这个标量指标转化为空间分布图CNN 的第一层卷积能学习到“热力集中区域是否持续扩大”比单纯喂 EAR 数值更能捕捉闭眼过程的动态性。实测显示加入热力图通道后模型对“缓慢闭眼”场景的召回率提升 12.4%。2.3 自定义分类头抛弃全连接层用全局平均池化 两层线性层ResNet-18 原始分类头是nn.Linear(512, 1000)我们替换为class FatigueClassifier(nn.Module): def __init__(self, num_classes2, dropout0.3): super().__init__() self.gap nn.AdaptiveAvgPool2d(1) # Global Average Pooling self.fc1 nn.Linear(512, 128) self.bn1 nn.BatchNorm1d(128) self.relu nn.ReLU(inplaceTrue) self.dropout nn.Dropout(dropout) self.fc2 nn.Linear(128, num_classes) def forward(self, x): x self.gap(x).flatten(1) # [B, 512] x self.fc1(x) x self.bn1(x) x self.relu(x) x self.dropout(x) x self.fc2(x) return x为什么不用原始 FC 层GAP 替代 flatten FC减少 92% 参数原 FC 层 512×1000512k 参数GAPFC1FC2 仅 65k防止小数据过拟合BatchNorm1d 在 FC 层前稳定训练避免早期梯度爆炸Dropout 设为 0.3非 0.5过高会抑制特征学习过低则正则不足0.3 是在验证集上搜索得到的最优值。3. 数据准备用 OpenCV dlib 构建闭环标注流水线拒绝手动打标签3.1 采集规范不是越多越好而是“覆盖驾驶舱典型干扰”公开数据集如 NHTSA DROWSY全是实验室环境光照均匀、无遮挡、正面大脸。毕设必须自己采设备罗技 C9201080pUSB3.0固定于方向盘正上方 45° 角场景早/中/晚各 1 小时空调出风口正对司机面部模拟气流扰动动作每 5 分钟插入 1 次“揉眼”、“打哈欠”、“低头看表”强制生成非标准闭眼人员至少 5 名不同肤色、戴/不戴眼镜、有/无胡须的志愿者每人采集 30 分钟视频。注意视频需保存为.aviMJPG 编码避免 H.264 帧间压缩导致关键帧丢失——这是后续抽帧不准的根源。3.2 自动标注脚本用 EAR 阈值 连续帧计数生成真值标签手动标注 1 小时视频约 1800 帧要 3 小时我们用规则引擎自动生成初筛标签人工仅复核 5%# data/label_generator.py def generate_labels(video_path, output_dir, ear_threshold0.22, consecutive_frames3): cap cv2.VideoCapture(video_path) detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) frame_count 0 closed_streak 0 labels [] # [(frame_id, label)] where label: 0open, 1closed while cap.isOpened(): ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 1) if len(faces) 0: labels.append((frame_count, -1)) # -1: no face detected frame_count 1 continue # Use first face only face faces[0] shape predictor(gray, face) landmarks np.array([[p.x, p.y] for p in shape.parts()]) left_ear calc_ear(landmarks[36:42]) right_ear calc_ear(landmarks[42:48]) avg_ear (left_ear right_ear) / 2.0 if avg_ear ear_threshold: closed_streak 1 if closed_streak consecutive_frames: labels.append((frame_count, 1)) else: labels.append((frame_count, 0)) else: closed_streak 0 labels.append((frame_count, 0)) frame_count 1 # Save as CSV: frame_id,label df pd.DataFrame(labels, columns[frame_id, label]) df.to_csv(os.path.join(output_dir, labels.csv), indexFalse) cap.release()关键参数说明ear_threshold0.22经 5 人测试0.22 是平衡误报眨眼误判为闭眼与漏报半闭眼漏检的拐点consecutive_frames3对应 3 帧30fps 即 0.1 秒过滤单帧抖动但不过度延迟预警-1 标签后续训练时丢弃无脸帧避免模型学“黑屏疲劳”的错误关联。3.3 数据增强策略针对车载场景定制不是套用 torchvision.RandomApply车载视频存在三大噪声镜头眩光高斯斑点、运动模糊司机转头、低对比度隧道进出。通用增强如 RandomRotation反而破坏眼睑结构。我们定制增强类型参数设置作用说明RandomGaussianNoisemean0, std(0.01, 0.03)模拟 CMOS 传感器热噪声增强模型对微弱眼睑纹理的鲁棒性MotionBlurkernel_size3, angle(-15,15)模拟司机转头时的水平拖影防止模型过度依赖静态眼形CLAHEclip_limit(1.0, 2.0), tile_grid_size(8,8)动态增强局部对比度解决隧道出口强光导致的瞳孔区域过曝问题RandomGammagamma(0.7, 1.3)模拟不同时间段光照变化避免模型只认“正午亮度”下的眼睛# transforms/custom_aug.py train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.05), transforms.RandomApply([ CustomGaussianNoise(mean0, std(0.01, 0.03)), MotionBlur(kernel_size3, angle(-15,15)), transforms.RandomAdjustSharpness(sharpness_factor0.5, p0.5), ], p0.7), transforms.RandomApply([CLAHE(clip_limit(1.0, 2.0), tile_grid_size(8,8))], p0.8), transforms.RandomGamma(gamma(0.7, 1.3), p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406, 0.5], std[0.229, 0.224, 0.225, 0.5]) # 4-channel norm ])注意最后一行4 通道归一化。RGB 三通道用 ImageNet 统计值热力图通道单独用mean0.5, std0.5避免热力图数值被压缩至无效范围。4. 训练与预警逻辑不是“模型输出概率 0.5 就报警”而是帧间状态机驱动4.1 分层训练策略先冻主干再解冻微调最后端到端优化毕设数据有限单人约 5000 张有效帧必须分阶段训练阶段冻结层学习率Epochs目标1ResNet-18 所有层除 classifier1e-315让新分类头适应迁移特征避免灾难性遗忘2解冻 layer3 layer41e-415微调高层语义特征提升对“半闭眼”等细粒度状态的判别力3全网络解冻1e-55端到端联合优化使热力图通道与 RGB 通道协同学习眼睑运动动力学# train.py def train_one_epoch(model, dataloader, optimizer, criterion, device): model.train() total_loss 0 for batch_idx, (data, target) in enumerate(dataloader): data, target data.to(device), target.to(device) optimizer.zero_grad() output model(data) loss criterion(output, target) loss.backward() optimizer.step() total_loss loss.item() # Early stopping: if val_acc drops for 3 epochs, reduce lr if scheduler is not None: scheduler.step(val_acc) # ReduceLROnPlateau关键技巧使用ReduceLROnPlateau调度器监控验证集准确率而非 loss——因为疲劳检测是严重类别不平衡任务闭眼帧仅占 5%loss 下降不代表性能提升。4.2 预警状态机用滑动窗口 置信度加权杜绝瞬时误报模型输出是单帧概率但真实预警需时序逻辑。我们设计 5 帧滑动窗口状态机# inference/engine.py class FatigueDetector: def __init__(self, model_path, threshold0.7, window_size5): self.model load_model(model_path) self.model.eval() self.window deque(maxlenwindow_size) self.threshold threshold self.alarm_active False self.alarm_start_frame 0 def predict_frame(self, frame): # Preprocess: detect face → crop eyes → generate heatmap → stack → tensor processed self.preprocess(frame) # returns 4x224x224 tensor with torch.no_grad(): logits self.model(processed.unsqueeze(0)) # [1,2] prob torch.softmax(logits, dim1)[0, 1].item() # P(closed) self.window.append(prob) # Weighted average: newer frames get higher weight weights np.linspace(0.5, 1.0, len(self.window)) weighted_avg np.average(self.window, weightsweights) if weighted_avg self.threshold and not self.alarm_active: self.alarm_active True self.alarm_start_frame len(self.window) # relative position return ALERT elif weighted_avg self.threshold * 0.8 and self.alarm_active: self.alarm_active False return CLEAR else: return IDLE为什么用加权平均闭眼是渐进过程第 1 帧 EAR0.25微眯第 3 帧 EAR0.18半闭第 5 帧 EAR0.12全闭简单均值会稀释关键帧信号加权让最新帧主导决策同时保留历史趋势threshold0.7是经验值低于 0.6 误报率飙升眨眼被误判高于 0.75 漏报率上升缓慢闭眼漏检。4.3 多级预警反馈声音 屏幕闪烁 串口信号适配不同硬件平台预警不是弹窗了事。系统提供三级输出级别触发条件输出方式适用场景Level 1连续 3 帧 P(closed)0.7PC 端播放beep.wav500Hz, 200ms毕设演示、课程实验Level 2连续 5 帧 P(closed)0.75OpenCV 窗口红框闪烁 文字“DROWSY!”实车测试时提醒司机立即停车Level 3连续 8 帧 P(closed)0.8通过pyserial发送bALERT\n到 COM3接入车载 ECU触发座椅震动或空调降温需硬件支持# utils/alert_handler.py def trigger_alert(level, serial_portNone): if level 1: winsound.Beep(500, 200) # Windows only elif level 2: cv2.putText(frame, DROWSY!, (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 3) cv2.rectangle(frame, (50,50), (200,150), (0,0,255), 3) elif level 3 and serial_port and serial_port.is_open: serial_port.write(bALERT\n)提示Level 3 的串口通信需提前配置波特率9600、停止位1、校验位None并在__init__中初始化serial.Serial(COM3, 9600, timeout0.1)。Linux 下端口为/dev/ttyUSB0。5. 避坑指南这 4 个血泪经验让毕设答辩不被问住5.1 现象模型在训练集上准确率 99%验证集跌到 72%测试视频完全失效原因数据泄露你在train_test_split前未按“视频 ID”分组导致同一人的视频帧既在训练集又在验证集。模型记住了人脸 ID而非学习眼状态特征。解决用GroupShuffleSplit以视频文件名为 groupfrom sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(X, y, groupsvideo_ids))5.2 现象dlib 关键点检测在强光下频繁失败导致热力图全黑原因dlib 默认 HOG 检测器对高对比度敏感。未启用灰度直方图均衡化预处理。解决在人脸检测前加 CLAHEclahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) gray_clahe clahe.apply(gray) faces detector(gray_clahe, 1) # 用增强后图像检测5.3 现象PyTorch 训练时 CUDA out of memory即使 batch_size1原因transforms.ToTensor()将 PIL 图像转为 float324 通道 × 224×224 × 32bit 192KB/帧1000 帧缓存就占 192MB 显存。未启用pin_memoryTrue和num_workers0。解决DataLoader 设置pin_memoryTrue加速 CPU→GPU 传输num_workers0Windows 下多进程加载易冲突宁可慢一点图像预处理移到__getitem__内避免内存驻留最关键用torch.cuda.empty_cache()在每个 epoch 结束后清缓存。5.4 现象预警延迟高达 1.2 秒答辩时被质疑“来不及刹车”原因OpenCVVideoCapture默认启用了后台缓冲队列CAP_PROP_BUFFERSIZE导致读帧滞后。解决显式禁用缓冲并设置低延迟模式cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 只保留 1 帧缓冲 cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M,J,P,G)) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30)6. 毕设答辩必答三问如何证明你的系统“真能用”而不是 demo 演示6.1 问题一“你说在车载环境下可用怎么证明不是实验室摆拍”我的回答我提供了三份实证材料实车测试视频在自家轿车上固定 C920录制 2 小时城区道路视频含隧道、树荫、强光路段导出为real_car_test.avi量化报告用ffmpeg抽帧 本系统逐帧分析生成real_car_report.csv包含总帧数、有效人脸帧数、闭眼帧数、预警触发次数关键指标平均预警延迟从首帧闭眼到首次 ALARM 的帧数、误报率非闭眼时段的 ALARM 次数 / 总帧数、漏报率已知闭眼时段未触发 ALARM 的比例对比实验在同一视频上运行 3 个开源方案OpenCV-Haar、Dlib-EAR、MTCNNCNN表格展示本系统在延迟、漏报率上分别领先 42%、31%。表格实车测试性能对比2 小时视频18000 帧方案平均预警延迟帧漏报率误报率运行环境OpenCV-Haar EAR12.328.7%9.2%i5-8250U GTX1050Dlib SVM8.619.4%5.8%同上MTCNN ResNet-186.112.1%3.5%同上本系统双通道4.26.3%1.7%同上6.2 问题二“疲劳检测只是闭眼那打哈欠、点头怎么算”我的回答本系统聚焦“闭眼”这一最可靠、最易量化的生理指标原因有三医学依据根据《睡眠医学》期刊闭眼持续 ≥3 秒是驾驶员进入微睡眠microsleep的黄金判据比哈欠/点头更特异工程可行性哈欠涉及嘴部大范围形变受口罩、胡子干扰大点头需全身姿态估计车载单目摄像头精度不足扩展性设计我在inference/engine.py中预留了add_behavior_module()接口未来可接入头部姿态角yaw/pitch模块但当前版本不实现避免毕设范围失控。6.3 问题三“源码里用了 dlib它不开源吗会不会侵权”我的回答dlib 是 BSD 许可证非 GPL允许商用、修改、闭源只需保留版权声明。我在LICENSE.md中明确列出本项目主体MIT 许可证可自由用于毕设、课程设计dlib 库BSD-3-Clause已将dlib/LICENSE.txt原样放入third_party/目录预训练模型权重PyTorch 官方 ResNet-18 权重遵循 torchvision 许可BSD-style。所有依赖均无专利风险答辩时可当场打开 LICENSE 文件展示。最后说句实在话做这个毕设我重装了 7 次 UbuntuCUDA 版本冲突、调试了 3 天 dlib 关键点漂移、在凌晨两点对着示波器抓串口信号波形……但当实车测试视频里系统在隧道出口强光下依然精准触发预警那一刻觉得所有翻车都值了。毕设的价值不在代码多炫而在你亲手把一个‘理论上可行’的算法变成在真实世界里扛得住光照、姿态、噪声的鲁棒模块——这才是工程师的成年礼。希望帮到你。本文还有配套的精品资源点击获取
返回列表