ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python卷积神经网络人脸识别驾驶员疲劳检测与预警系统实战

2026/10/7 13:32:31 拓冰建站 浏览量
Python卷积神经网络人脸识别驾驶员疲劳检测与预警系统实战 简介本资源是一套基于Python卷积神经网络的人脸识别驾驶员疲劳检测与预警系统面向计算机、人工智能相关专业的毕业设计、期末大作业与课程设计场景适合具备一定Python基础、希望快速完成高分项目的学生使用。压缩包共20个文件约78.33MB包含11个py源码文件、3个txt说明文档、2个xml人脸检测模型、1个hdf5训练权重、1个exe可执行程序及md说明等覆盖模型训练、人脸提取、疲劳判别与界面交互等模块。已有72人学习下载。项目代码注释完整新手也能看懂附带系统说明与运行说明部署后即可直接运行其中tkinter界面美观、操作简单CNN模型与检测流程清晰可作为毕设或课程设计的完整方案也便于在此基础上二次开发与功能扩展。1. 从一张打哈欠的抓拍说起这套疲劳检测系统到底在做什么凌晨两点跑长途的司机被车内摄像头抓拍到连续闭眼 1.8 秒仪表台立刻响起蜂鸣同时手机端收到一条预警——这不是概念视频而是基于 Python 卷积神经网络人脸识别驾驶员疲劳检测与预警系统最典型的落地场景。它要解决的核心问题很具体用普通摄像头替代昂贵的红外眼动仪靠 CNN 从人脸区域里判断眼睛开合、嘴巴张合再叠加时间窗口统计输出疲劳等级并触发预警。整套东西的技术栈是 Python OpenCV 卷积神经网络适合做毕业设计、课程设计也适合想入门深度学习落地的新手。它不追求工业级鲁棒性但胜在链路完整、可复现、能讲清楚每一环为什么这么设计。下面我按自己搭过一遍的顺序把选型、数据、模型、预警逻辑和踩过的坑讲透。2. 为什么选 CNN 而不是传统特征人脸疲劳检测的技术选型2.1 传统 HOGSVM 方案在疲劳场景下为什么容易翻车早期做疲劳检测很多人第一反应是 HOG 特征加 SVM 分类器或者用 dlib 的 68 点关键点算眼睛纵横比EAR。这套方法在实验室光照稳定、头部姿态固定的条件下能跑出不错的结果但一上车就原形毕露。原因有三第一HOG 对光照变化极其敏感隧道进出口那种明暗突变会让特征分布整体漂移第二关键点检测依赖人脸对齐司机侧脸、戴眼镜、低头看仪表盘时关键点直接丢失EAR 值就成了噪声第三EAR 是手工设计的几何比值它没法区分「正常眨眼」和「疲劳性微睡眠」因为两者在单帧上的眼睛开合度可能一样差别在时间维度的持续性和上下文。CNN 的价值在于它把特征提取和分类揉进一个可学习的网络里。卷积核在训练中自动学到对眼睛闭合、嘴角下拉、眉间皱起这些疲劳相关纹理的响应不需要你手写规则。更关键的是CNN 对局部形变和光照有更强的容忍度配合数据增强后侧脸和戴眼镜的漏检率能明显压下来。这不是说 CNN 一定比传统方法准而是在「摄像头位置不固定、光照不可控、司机姿态随机」的真实场景里CNN 的鲁棒性上限更高调参空间也更大。2.2 用 Python 搭 CNN 疲劳检测的最小依赖清单动手之前先把环境理清楚。我一般用 Python 3.8 到 3.10 之间的版本太新的版本有些库轮子还没跟上装起来会折腾。核心依赖就几个OpenCV 负责读摄像头和做人脸检测NumPy 做矩阵运算TensorFlow 或 PyTorch 二选一搭 CNNMatplotlib 用来画训练曲线。如果你只是想快速跑通建议先用 TensorFlow/Keras它的 Sequential API 写起来短调试直观。# 创建虚拟环境避免污染系统 Python python -m venv fatigue_env # Windows 激活 fatigue_env\Scripts\activate # Linux/Mac 激活 source fatigue_env/bin/activate # 安装核心依赖版本按自己环境微调 pip install opencv-python4.8.0.74 pip install numpy1.24.3 pip install tensorflow2.13.0 pip install matplotlib3.7.2 pip install scikit-learn1.3.0这段命令的逻辑是先隔离环境再按「图像采集 → 数值计算 → 模型训练 → 可视化 → 评估」的顺序装库。参数上opencv-python 选 4.8 是因为它的 DNN 模块对 Caffe 模型支持稳定后面做人脸检测会用到TensorFlow 2.13 是最后一个默认带 Keras 且对 CUDA 11.8 支持较好的版本如果你没有 GPU装 CPU 版也能跑只是训练慢。装完用python -c import cv2, tensorflow验证一下没报错就说明环境通了。提示如果你用的是 Apple Silicon 的 MacTensorFlow 要装 tensorflow-macos 和 tensorflow-metal否则跑起来会退回 CPU训练速度差好几倍。2.3 人脸检测和 CNN 分类的分工怎么划很多人一开始会把「人脸检测」和「疲劳分类」混在一起想用一个网络端到端搞定。理论上可行但实操里不划算。人脸检测用 OpenCV 自带的 Haar 级联或者 DNN 模块就够了它的任务是框出人脸区域把无关背景裁掉降低后续 CNN 的输入噪声。CNN 只负责在裁好的人脸小图上判断「睁眼/闭眼」「张嘴/闭嘴」这类二分类。这样分工的好处是人脸检测模型可以换CNN 分类器可以单独训练和调优两边解耦出问题好定位。我一般用 OpenCV 的 DNN 人脸检测器加载 res10_300x300 的 Caffe 模型它的速度和精度比 Haar 级联好不少尤其在侧脸和暗光下。检测到人脸后按比例裁出上半部分眼睛和嘴巴都在这个区域缩放到 64x64 或 96x96送进 CNN。这个尺寸是权衡后的结果太小会丢眼睛细节太大推理慢64x64 在树莓派上也能跑到 15 帧以上。3. 数据集怎么准备从公开数据到自采样本的完整流程3.1 公开疲劳数据集的选用和局限做毕业设计最省事的起点是公开数据集。常见的有 CEWClosed Eyes in the Wild和 ZJU 眨眼数据集前者有几千张闭眼和睁眼图后者是视频片段。CEW 的好处是干净、标注明确坏处是它只覆盖眼睛状态没有张嘴打哈欠的样本而且都是正面人脸缺少侧脸和戴眼镜的多样性。如果你只用 CEW 训练模型在真实车内场景的泛化会打折扣。我的做法是用 CEW 做预训练让 CNN 先学会区分睁眼闭眼的基本纹理然后再用自己采集的样本做微调。自采样本不需要多找个同学坐在工位上用笔记本摄像头录几段视频分别做「正常睁眼」「闭眼 2 秒」「打哈欠」「说话」这几个动作每段 30 秒左右总共几百帧就够。关键是覆盖不同光照和角度比如白天靠窗、晚上开台灯、侧脸 30 度这些多样性比数量更重要。3.2 用 OpenCV 批量裁剪人脸并生成训练集拿到视频后第一步是把帧拆出来检测人脸裁出眼睛和嘴巴区域按类别存到不同文件夹。下面这段脚本是我常用的批处理模板它会遍历视频文件用 DNN 检测器框人脸然后按比例裁上下两半分别存成睁眼/闭眼和张嘴/闭嘴的候选图。import cv2 import os import numpy as np # 加载 OpenCV DNN 人脸检测器 prototxt deploy.prototxt model res10_300x300_ssd_iter_140000.caffemodel net cv2.dnn.readNetFromCaffe(prototxt, model) def extract_faces(video_path, out_dir, label): cap cv2.VideoCapture(video_path) os.makedirs(out_dir, exist_okTrue) idx 0 while True: ret, frame cap.read() if not ret: break h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) net.setInput(blob) detections net.forward() for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence 0.5: # 置信度阈值低于 0.5 的框丢弃 box detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 box.astype(int) # 裁上半脸眼睛区域和下半脸嘴巴区域 face frame[max(0, y1):min(h, y2), max(0, x1):min(w, x2)] if face.size 0: continue fh face.shape[0] eye_region face[0:int(fh * 0.5), :] mouth_region face[int(fh * 0.5):fh, :] # 统一缩放到 64x64 eye_resized cv2.resize(eye_region, (64, 64)) mouth_resized cv2.resize(mouth_region, (64, 64)) cv2.imwrite(f{out_dir}/{label}_eye_{idx}.jpg, eye_resized) cv2.imwrite(f{out_dir}/{label}_mouth_{idx}.jpg, mouth_resized) idx 1 # 每 5 帧取一帧避免相邻帧太相似 cap.set(cv2.CAP_PROP_POS_FRAMES, cap.get(cv2.CAP_PROP_POS_FRAMES) 4) cap.release() print(f{video_path} 处理完成共输出 {idx} 组样本) # 按类别调用label 用 open/closed/fatigue 等 extract_faces(normal_drive.mp4, dataset/open, open) extract_faces(drowsy_drive.mp4, dataset/closed, closed)这段代码的关键参数有三个confidence 0.5是检测置信度阈值调高会漏掉侧脸调低会引入误检框0.5 是实测比较平衡的值int(fh * 0.5)是上下脸的分割比例眼睛大致在上半部分嘴巴在下半部分这个比例对大多数正面人脸够用侧脸时需要微调cap.set那行是跳帧采样因为相邻帧几乎一样全采会导致训练集冗余每 5 帧取一帧能让样本分布更均匀。跑完之后你会得到一堆 64x64 的小图接下来要人工过一遍把明显裁歪的、模糊的删掉这一步不能省脏数据对 CNN 的伤害比数据少更大。3.3 数据增强和类别不平衡的处理自采样本很容易出现类别不平衡比如正常睁眼的帧远多于闭眼帧。直接拿去训练模型会倾向于预测多数类闭眼召回率上不去。我的处理方式是两步先用数据增强把少数类扩增再用类别权重在损失函数里补偿。增强用 Keras 的 ImageDataGenerator 就行对眼睛区域做水平翻转、±10 度旋转、亮度微调不要做垂直翻转因为倒过来的眼睛在现实中不存在会引入噪声。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen ImageDataGenerator( rescale1./255, # 像素归一化到 0-1 rotation_range10, # 旋转 ±10 度 width_shift_range0.1, # 水平平移 10% height_shift_range0.1, # 垂直平移 10% brightness_range[0.8, 1.2], # 亮度扰动 horizontal_flipTrue, # 水平翻转 fill_modenearest # 填充边缘像素 ) train_generator train_datagen.flow_from_directory( dataset/train, target_size(64, 64), batch_size32, class_modebinary )参数上rotation_range10是保守值再大可能把眼睛转出合理范围brightness_range模拟车内明暗变化fill_modenearest保证旋转后边缘不留黑边。类别权重可以在model.fit里用class_weight参数传比如闭眼类权重设为 2.0睁眼类设为 1.0让损失函数更关注少数类。这两招配合下来闭眼召回率通常能从 70% 出头提到 85% 以上。4. 卷积神经网络模型怎么搭从 LeNet 到轻量级自定义结构4.1 为什么不用 ResNet50 这种大模型毕业设计里常见的一个误区是直接搬 ResNet50 或 VGG16觉得网络越深效果越好。但在疲劳检测这个任务上输入是 64x64 的小图类别只有两三类大模型的参数量严重过剩训练慢、容易过拟合部署到树莓派或手机端更是跑不动。我一般用一个 4 层卷积加 2 层全连接的自定义结构参数量控制在 50 万以内在 CPU 上单帧推理不到 10 毫秒精度和 ResNet50 微调后差不了两个点。这个结构的思路是前两层卷积核小一点3x3抓边缘和纹理后两层卷积核感受野大一些抓眼睛整体形状和嘴巴开合每层卷积后接 BatchNormalization 和 MaxPoolingBN 加速收敛Pooling 降维。全连接层前加 Dropout比例 0.5防止过拟合。输出层用 Sigmoid 做二分类如果要同时判断眼睛和嘴巴状态就改成两个分支各输出一个 Sigmoid。4.2 用 Keras 定义并编译疲劳分类网络from tensorflow.keras import layers, models, optimizers def build_fatigue_cnn(input_shape(64, 64, 3)): model models.Sequential([ # 第一层卷积32 个 3x3 核抓基础纹理 layers.Conv2D(32, (3, 3), activationrelu, paddingsame, input_shapeinput_shape), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), # 第二层卷积64 个 3x3 核抓局部形状 layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), # 第三层卷积128 个 3x3 核抓眼睛嘴巴整体模式 layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), # 第四层卷积128 个 3x3 核进一步抽象 layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dropout(0.5), # 丢弃 50% 神经元防过拟合 layers.Dense(256, activationrelu), layers.Dropout(0.3), layers.Dense(1, activationsigmoid) # 二分类输出 ]) return model model build_fatigue_cnn() model.compile( optimizeroptimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[accuracy] ) model.summary()这段代码里paddingsame保证卷积后尺寸不变方便堆叠BatchNormalization放在卷积和激活之后、Pooling 之前能让训练更稳Dropout(0.5)在全连接层前是防过拟合的主力输出层sigmoid把值压到 0 到 1大于 0.5 判为闭眼或疲劳。编译时用 Adam学习率 1e-3 是起点如果训练损失震荡就降到 1e-4。binary_crossentropy是二分类的标准损失如果你要三分类正常/闭眼/打哈欠就把输出层改成 3 个神经元的 softmax损失换成 categorical_crossentropy。4.3 训练时的回调配置和早停策略训练不是跑满 epoch 就好过拟合往往在验证损失开始上升时就发生了。我一般配三个回调ModelCheckpoint 保存验证集上最好的权重EarlyStopping 在验证损失连续 5 轮不降时停ReduceLROnPlateau 在学习停滞时把学习率砍半。这三个配合能省掉大量手动调参时间。from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau callbacks [ ModelCheckpoint(best_fatigue_model.h5, monitorval_loss, save_best_onlyTrue, verbose1), EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue, verbose1), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-6, verbose1) ] history model.fit( train_generator, epochs50, validation_dataval_generator, callbackscallbacks, class_weight{0: 1.0, 1: 2.0} # 闭眼类权重加倍 )patience5表示验证损失 5 轮不降就停这个值太小会早停太大会浪费算力5 是经验值。factor0.5是学习率衰减比例min_lr1e-6是下限防止学习率降到 0。class_weight里给闭眼类 2.0 的权重是因为闭眼样本通常少加权后模型会更重视漏检闭眼的代价。训练完把best_fatigue_model.h5留下来后面推理用这个不要用最后一轮的权重。5. 预警逻辑怎么设计从单帧判断到时间窗口统计5.1 单帧分类结果为什么不能直接触发预警CNN 输出的是单帧的闭眼概率但疲劳是一个时间累积的概念。正常眨眼也会闭眼持续 0.1 到 0.3 秒如果单帧闭眼就报警司机每眨一次眼就被吵一次系统根本没法用。所以必须加时间窗口统计。我的做法是维护一个长度为 30 帧的滑动窗口按 15 帧每秒算约 2 秒统计窗口内闭眼帧的占比超过 70% 才判定为疲劳性闭眼触发一级预警。如果连续闭眼帧数超过 45 帧约 3 秒直接触发二级预警蜂鸣加语音提示。打哈欠的判断类似但窗口更长因为一个哈欠持续 2 到 4 秒。我用 60 帧窗口统计张嘴帧占比超过 60% 判为哈欠连续 3 次哈欠触发预警。眼睛和嘴巴两个通道的预警可以叠加比如闭眼占比高且哈欠频繁就提升预警等级。这套逻辑不复杂但它是把 CNN 的单帧输出变成可用系统的关键一步少了这层模型再准也没法落地。5.2 用 OpenCV 串起摄像头、CNN 推理和蜂鸣预警下面这段是主循环的骨架它把摄像头采集、人脸检测、CNN 分类、滑动窗口统计和声音预警串在一起。实际跑的时候CNN 推理和图像显示要分线程否则显示会卡但为了讲清楚逻辑这里先写单线程版本。import cv2 import numpy as np from tensorflow.keras.models import load_model import winsound # Windows 蜂鸣Linux 用 os.system(beep) model load_model(best_fatigue_model.h5) face_net cv2.dnn.readNetFromCaffe(deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel) eye_window [] # 滑动窗口存 0/1 MOUTH_WINDOW_SIZE 60 EYE_WINDOW_SIZE 30 EYE_THRESHOLD 0.7 # 闭眼占比阈值 yawn_count 0 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) face_net.setInput(blob) detections face_net.forward() for i in range(detections.shape[2]): if detections[0, 0, i, 2] 0.5: box detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 box.astype(int) face frame[max(0, y1):min(h, y2), max(0, x1):min(w, x2)] if face.size 0: continue fh face.shape[0] eye cv2.resize(face[0:int(fh*0.5), :], (64, 64)) mouth cv2.resize(face[int(fh*0.5):fh, :], (64, 64)) # 归一化后送 CNN eye_input np.expand_dims(eye / 255.0, axis0) mouth_input np.expand_dims(mouth / 255.0, axis0) eye_pred model.predict(eye_input, verbose0)[0][0] mouth_pred model.predict(mouth_input, verbose0)[0][0] # 更新滑动窗口1 表示闭眼/张嘴 eye_window.append(1 if eye_pred 0.5 else 0) if len(eye_window) EYE_WINDOW_SIZE: eye_window.pop(0) # 疲劳判定 if len(eye_window) EYE_WINDOW_SIZE: closed_ratio sum(eye_window) / EYE_WINDOW_SIZE if closed_ratio EYE_THRESHOLD: cv2.putText(frame, FATIGUE ALERT, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) winsound.Beep(1000, 500) # 1000Hz 响 500ms cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(Fatigue Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里EYE_WINDOW_SIZE30对应约 2 秒窗口EYE_THRESHOLD0.7是闭眼占比阈值这两个值要根据实际帧率调。如果你的摄像头是 30 帧每秒窗口要翻倍到 60否则时间窗口会缩短一半。winsound.Beep是 Windows 专用Linux 下可以用os.system(echo -e \a)或者 pygame 播放音频。推理部分每帧调两次model.predict在 CPU 上大概 20 毫秒加上人脸检测整体能跑到 20 帧以上够实时用。注意model.predict每次调用有固定开销如果帧率上不去可以把眼睛和嘴巴的输入拼成一个 batch 一次推理能省一半时间。5.3 预警等级和误报抑制的实用参数预警不能只有「响」和「不响」两档实际用起来需要分级。我一般分三级一级是闭眼占比超阈值但未持续只在屏幕上标红二级是持续闭眼超 2 秒蜂鸣短响三级是持续闭眼超 3 秒或连续哈欠 3 次蜂鸣加语音。误报抑制上除了滑动窗口还可以加一个「冷却时间」触发预警后 5 秒内不再重复触发避免蜂鸣一直响。另外如果检测到人脸丢失超过 1 秒清空滑动窗口因为司机可能转头了旧数据不再有参考价值。这些参数没有标准答案要拿真人测试记录误报和漏报的次数再回头调阈值。6. 避坑与排查这套系统最容易翻车的五个地方6.1 摄像头帧率不稳导致时间窗口失真现象是预警时灵时不灵同样的闭眼动作有时触发有时不触发。原因是滑动窗口按帧数算但摄像头实际帧率会波动光线暗时自动降帧30 帧的窗口可能变成 4 秒而不是 2 秒。解决办法是不要按帧数改按时间戳统计记录每帧的time.time()窗口只保留最近 2 秒内的帧这样帧率变化不影响判定。改完之后预警一致性会好很多。6.2 戴眼镜反光让闭眼被误判成睁眼现象是戴眼镜的测试者闭眼时系统不报警。原因是眼镜片反光在眼睛区域形成高亮斑块CNN 把高亮当成睁眼的眼白。解决办法有两个一是在训练集里加入戴眼镜的样本让模型学会忽略反光二是在预处理时做自适应直方图均衡化CLAHE压一下高光。我一般两个都做CLAHE 用 OpenCV 的cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8))对灰度图处理后转回 RGB能明显改善反光场景。6.3 模型在训练集上 99% 但实车漏检严重现象是训练日志里准确率很高一上车就频繁漏检。原因是训练集和真实场景的分布差异太大公开数据集多是正面、均匀光照车内是侧光、阴影、头部晃动。解决办法是拿真实车内视频做微调哪怕只有几百帧冻结前面卷积层只训练全连接层学习率调到 1e-4跑 10 个 epoch泛化会明显提升。另外检查一下输入归一化是否一致训练时除以 255推理时忘了除这种低级错误也常导致漏检。6.4 多线程下 OpenCV 显示卡死现象是加了推理线程后cv2.imshow窗口无响应。原因是 OpenCV 的 GUI 必须在主线程调用推理放在子线程后主线程被cap.read()阻塞。解决办法是把读取摄像头也放子线程主线程只负责imshow和waitKey线程间用queue.Queue传帧。或者更简单不用多线程把 CNN 输入尺寸从 64 降到 48推理时间减半单线程也能跑顺。6.5 蜂鸣预警在 Linux 上没声音现象是 Windows 上winsound.Beep正常移到 Linux 或树莓派上没反应。原因是winsound是 Windows 专属模块。解决办法是换 pygame 播放 wav 文件或者用os.system(aplay alert.wav)。树莓派上还可以接一个 GPIO 蜂鸣器用 RPi.GPIO 控制比音频更可靠。跨平台的话建议一开始就用 pygame虽然多装一个库但省得后面移植时改代码。7. 把预警从「响一声」做到「可追溯」日志回放和阈值自整定系统能跑起来之后真正拉开差距的是可追溯性。我后来养成一个习惯每次预警触发时把前后各 5 秒的帧存成一个小视频片段文件名带上时间戳和触发等级同时把闭眼占比、哈欠次数、帧率写进 CSV。这样出问题可以回放看是误报还是漏报比盯着实时画面猜靠谱得多。存视频用cv2.VideoWriter编码用 MJPG5 秒 640x480 大概 2MB跑一天也就几百 MB完全可接受。import csv import time # 预警触发时记录 def log_alert(level, closed_ratio, yawn_count, fps): with open(alert_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([time.strftime(%Y-%m-%d %H:%M:%S), level, round(closed_ratio, 3), yawn_count, round(fps, 1)]) # 回放时按时间戳找对应视频片段 # 视频命名格式alert_20250101_143022_level2.avi阈值自整定是另一个进阶点。固定阈值在不同人身上表现不一样有人眼睛小正常睁眼时 CNN 输出的闭眼概率就偏高固定 0.5 会误报。我的做法是系统启动后先跑 30 秒「校准期」让司机正常睁眼统计这段时间 CNN 输出的均值加两倍标准差作为该用户的闭眼判定阈值。校准期结束后切换到正常检测。这个改动不大但能显著降低个体差异带来的误报实测误报率能降一半左右。最后说个我自己的教训一开始我追求模型精度花了两周调网络结构准确率从 92% 提到 94%但实车测试时漏检还是多。后来发现问题不在模型在数据——训练集里闭眼样本全是正面、均匀光照而司机实际是侧脸、顶光。我回头补了 300 帧真实车内样本做微调准确率只涨了 1 个点但漏检率降了 40%。这件事让我记住疲劳检测这种场景数据的场景覆盖度比模型深度重要得多。先把数据采对再谈调参。希望帮到你。本文还有配套的精品资源点击获取