尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于CNN的驾驶员疲劳检测系统:Python源码详解与预警实现

发布时间:2026/9/27 23:13:45

资讯中心
01
ARTICLE

基于CNN的驾驶员疲劳检测系统:Python源码详解与预警实现

基于CNN的驾驶员疲劳检测系统:Python源码详解与预警实现
简介这是一套基于Python与卷积神经网络的驾驶员疲劳检测与预警系统完整源码面向计算机、人工智能等相关专业准备毕业设计或大作业的学生也适合希望实战图像识别项目的开发者。项目通过面部关键点与眼睛状态识别判断疲劳程度内置Tkinter图形界面与可直接运行的exe程序配套CNN模型文件整体难度适中且经评审达到95分以上。资源共19个文件压缩包78.33MB主要包含11个Python源码、hdf5模型文件、人脸与眼睛检测xml、依赖说明txt及可视化界面所需的exe等Python脚本覆盖数据预处理、模型训练、检测分类与UI交互等环节目录结构清晰。已有789人学习/下载下载后可直接对照运行快速复现检测流程便于在此基础上改进算法或调整界面是高效完成毕业设计的优质参考资料。1. 把卷积神经网络搬进驾驶舱这套疲劳检测源码到底解决什么问题凌晨两点的货运司机、连续赶路的网约车司机、盯着监控屏的调度员——疲劳驾驶是事故率最高的场景之一而绝大多数车队目前仍然靠人工盯防。传统的疲劳检测方案要么依赖昂贵的红外眼动仪要么靠方向盘传感器间接推断都没能大规模落地。这几年卷积神经网络成熟以后用普通USB摄像头实时捕捉人脸、识别眼睛开合与打哈欠状态、再算出疲劳评分已经成为成本最低也最容易复现的技术路线。这套基于 Python CNN 的驾驶员疲劳检测与预警系统源码做的正是这样一件事摄像头画面进去疲劳等级和预警信号出来。它的核心价值在于把“采集人脸 → 训练模型 → 实时推理 → 触发报警”整条链路做成了一套可以跑通的毕业设计级代码适合正在做相关课题的学生、想快速搭原型验证的算法工程师以及想给车队做低成本预警方案的技术人员。2. 整体架构与数据准备先搞定人脸检测和样本标注再谈 CNN 训练2.1 疲劳检测选型为什么是 CNN 而不是传统图像处理早期疲劳检测常见做法是用 OpenCV 的人脸关键点检测比如 dlib 的 68 点模型直接算眼睛纵横比EAR和嘴巴纵横比MAR阈值一卡就判断闭眼或打哈欠。这个方案在实验室环境里表现尚可但一遇到侧脸、遮挡、暗光、戴眼镜关键点检测就开始飘误报率直线上升。CNN 方案的不同在于它不再手工设计“眼睛长宽比”这类特征而是让卷积核自己去学“什么样子算闭眼”“什么动作算打哈欠”。以本源码使用的轻量 CNN 分类模型为例输入是一张对齐后的人脸 ROI输出是“正常 / 闭眼 / 打哈欠 / 分心”之类的类别概率。这样做的好处是鲁棒性更强——训练数据里覆盖了足够的戴眼镜、暗光、低头样本模型就能在真实摄像头画面上稳定工作。代价是需要先准备一批标注好的样本这也是很多人拿到源码后第一个卡住的地方。2.2 数据集准备与标注人脸 ROI 提取与三类样本组织源码默认的训练数据组织方式是典型的图像分类目录结构dataset/ ├── train/ │ ├── normal/ # 正常驾驶状态人脸图 │ ├── eye_closed/ # 闭眼状态人脸图 │ └── yawn/ # 打哈欠状态人脸图 └── val/ ├── normal/ ├── eye_closed/ └── yawn/我一般会建议按 8:2 切分训练集和验证集每类样本量尽量不少于 1500 张。如果原始视频素材不够不要硬撑后面的数据增强能帮你把样本量翻几倍。预处理流水线的关键代码在源码的preprocess.py里。它会先从视频帧中用 OpenCV 的级联分类器或 MTCNN 检测人脸再把检测框裁出来缩放到统一尺寸常见做法是 64×64 或 128×128本源码默认 64×64训练速度快但精度稍低想提精度可以改成 96×96 重训。import cv2 import os import numpy as np def extract_face_roi(video_path, output_dir, target_size(64, 64)): 从视频中逐帧检测人脸裁出人脸区域并统一尺寸 video_path: 原始视频路径 output_dir: 保存人脸图的目录 target_size: 输出图片尺寸默认64x64 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(video_path) count 0 while True: ret, frame cap.read() if not ret: break # 转为灰度图降低光照影响也减小计算量 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 检测人脸scaleFactor越小检测越细但越慢 faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48) ) for (x, y, w, h) in faces: # 稍微往外扩一点边缘避免裁掉额头和下巴 margin int(0.1 * w) x0 max(0, x - margin) y0 max(0, y - margin) x1 min(frame.shape[1], x w margin) y1 min(frame.shape[0], y h margin) roi frame[y0:y1, x0:x1] roi cv2.resize(roi, target_size, interpolationcv2.INTER_AREA) cv2.imwrite( os.path.join(output_dir, fface_{count:05d}.jpg), roi ) count 1 cap.release() print(f提取完成共保存 {count} 张人脸图)这段代码的逻辑很直白逐帧读视频灰度化后人脸检测每个检测框外扩 10% 裁图缩放后落盘。scaleFactor1.1每轮按 10% 缩小搜索窗口检测更细但慢minNeighbors5表示至少 5 个邻域窗口确认才算人脸减少误检。INTER_AREA在缩小时效果好能避免明显锯齿。2.3 数据增强策略把 1500 张样本用成 9000 张CNN 训练最怕样本量不足尤其是“闭眼”和“打哈欠”这两类状态录制成本高、样本天然稀缺。源码里的augment.py实现了几个低成本高收益的增强操作水平翻转、小角度旋转、亮度抖动、高斯噪声。from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range10, # 随机旋转 ±10 度 width_shift_range0.05, # 水平平移 5% height_shift_range0.05, # 垂直平移 5% brightness_range(0.8, 1.2), # 亮度随机变化 80%~120% horizontal_flipTrue, # 水平翻转 fill_modenearest # 平移后空白的填充方式 ) # 配合 flow_from_directory 读取目录结构数据集 train_generator datagen.flow_from_directory( dataset/train, target_size(64, 64), batch_size32, class_modecategorical )一个容易忽略的细节是fill_modenearest——旋转和平移后图像边缘会出现空白区域nearest模式用边缘像素填充不会产生突兀的黑色边框干扰模型。brightness_range的范围控制在 0.8~1.2 比较稳妥调太大模型会把“明暗变化”误学成关键特征实拍暗光反而影响真实表现。3. CNN 模型构建与训练从 LeNet 到轻量化网络的实战取舍3.1 模型结构设计为什么不用大网络跑这种小任务疲劳检测本质上是一个图像分类任务输入尺寸小、类别少通常 3~4 类不需要 ResNet 这种上百层的深度网络。源码里采用的是类似 LeNet 的轻量 CNN 结构两层卷积 两层池化 两层全连接。得益于卷积核的权值共享特性参数量只有几万在纯 CPU 上也能跑到每秒 20 帧以上推理速度这对实时检测很关键。网络结构的关键参数如下层输出尺寸卷积核/参数激活函数Conv164×64×323×3×32步长1ReLUMaxPool132×32×322×2 池化-Conv232×32×643×3×64步长1ReLUMaxPool216×16×642×2 池化-Flatten16384--Dense112816384×128ReLU DropoutDense23128×3Softmax这个结构在分类精度和推理速度之间取得了不错的平衡。第一层 3×3 卷积核负责提取边缘、纹理等低级特征第二层 64 个卷积核提取眼睛开合度、嘴巴张合度这类更高阶的模式。Flatten 之后接 128 维全连接加 Dropout最后 Softmax 输出三个类别的概率。3.2 训练脚本与核心超参数直接照着跑也行改两个数更稳源码附带的训练脚本train.py核心逻辑如下import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_cnn(input_shape(64, 64, 3), num_classes3): model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shapeinput_shape), MaxPooling2D(pool_size(2, 2)), Conv2D(64, (3, 3), activationrelu), MaxPooling2D(pool_size(2, 2)), Flatten(), Dropout(0.5), Dense(128, activationrelu), Dropout(0.3), Dense(num_classes, activationsoftmax) ]) return model model build_cnn() model.compile( optimizerAdam(learning_rate0.001), losscategorical_crossentropy, metrics[accuracy] ) history model.fit( train_generator, steps_per_epoch200, # 每个 epoch 迭代 200 个 batch epochs30, # 训练轮数 validation_dataval_generator, validation_steps50 ) model.save(driver_fatigue_cnn.h5)几个容易踩坑的参数learning_rate0.001是 Adam 的常用默认值但如果 loss 震荡不下降可以先降到 0.0003 试一下Dropout(0.5)放在 Flatten 之后是防止全连接层过拟合的经典位置steps_per_epoch200配合 batch_size32相当于每个 epoch 看 6400 张增强后样本如果自己的数据集不够可以把它改小到 100避免每个 epoch 反复重复读图造成过拟合。3.3 训练监控与过拟合控制准确率高不等于能用训练阶段最常见的翻车是训练准确率 99%、验证准确率也到了 97%但一到真实摄像头画面就各种误判。原因通常是训练数据太“干净”——全部是正脸、光线均匀、无遮挡。解决办法是上面提到的增强策略里加入随机遮挡Random Erasing或者直接混入一些真实摄像头采集的难例。我这里建议每次训练后都做一次坏样本收集把测试视频喂给模型把所有预测置信度低于 0.85 的帧保存下来看一眼是模型错了还是标注本身就有问题。这一步虽然费时间但却是模型从“能跑通”到“能上线”的必经之路。源码的predict.py里留了置信度输出接口可以改造成自动保存低置信度帧的功能。4. 实时检测与预警实现摄像头取流、EAR 辅助判断与防误报4.1 实时视频流处理为什么先检测人脸再分类源码的实时检测部分detect.py是一个独立的线程循环从摄像头逐帧读取画面每隔一帧做一次人脸检测检测到人脸后裁出 ROI 送进 CNN 分类器。跳帧这个细节很关键——完整跑 30fps 的人脸检测加分类CPU 占用会到 80% 以上而隔一帧处理一次每秒处理 15 帧卡顿感基本无感知CPU 占用能降一半。import cv2 import numpy as np from tensorflow.keras.models import load_model class FatigueDetector: def __init__(self, model_pathdriver_fatigue_cnn.h5): self.model load_model(model_path) self.face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 类别顺序要与训练时的 class_indices 保持一致 self.class_names [normal, eye_closed, yawn] self.frame_skip 1 # 每隔1帧处理一次 def predict_frame(self, frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces self.face_cascade.detectMultiScale(gray, 1.1, 5) results [] for (x, y, w, h) in faces: roi frame[y:yh, x:xw] roi cv2.resize(roi, (64, 64)) roi roi.astype(float32) / 255.0 roi np.expand_dims(roi, axis0) pred self.model.predict(roi, verbose0)[0] cls_idx int(np.argmax(pred)) confidence float(pred[cls_idx]) results.append({ box: (x, y, w, h), label: self.class_names[cls_idx], confidence: confidence }) return results这里有个新手很容易翻车的点模型输入需要归一化到 [0,1]但很多人在预处理阶段把像素值除以了 255训练时做了预测时忘了结果模型输出全是垃圾概率。另一个坑是类别顺序——Keras 的flow_from_directory按目录名排序训练时class_indices是{eye_closed: 0, normal: 1, yawn: 2}预测脚本里如果写死[normal, eye_closed, yawn]输出全错。源码用注释明确了这一点实际跑的时候最好打印model.class_indices对一下。4.2 疲劳评分机制单帧分类不够时间窗口才是关键单帧判断闭眼没有意义——正常人本来就会眨眼眨眼持续 0.1~0.3 秒属于生理现象。源码的预警逻辑采用滑动窗口统计连续 N 秒内闭眼帧占比超过阈值就触发预警。from collections import deque import time class FatigueScorer: def __init__(self, window_size30, closed_threshold0.4): window_size: 统计窗口帧数 closed_threshold: 闭眼帧占比阈值 self.window deque(maxlenwindow_size) self.closed_threshold closed_threshold def add_frame(self, label): # 入队1 表示闭眼或打哈欠0 表示正常 self.window.append(1 if label in (eye_closed, yawn) else 0) def is_fatigue(self): if len(self.window) self.window.maxlen: return False # 窗口未填满不判断 ratio sum(self.window) / len(self.window) return ratio self.closed_thresholdwindow_size30按 15fps 处理速度算大约是 2 秒的滑动窗口。closed_threshold0.4意味着 2 秒内超过 40% 的帧是闭眼/哈欠状态才会触发疲劳预警。这个阈值一开始可以设 0.5误报多了再调低实车测试时我一般从 0.4 起步。4.3 预警触发策略声音报警与界面提示的最小实现源码的报警模块用pygame.mixer播放一段 WAV 音频同时用 OpenCV 在实时画面上画红色警告框。之所以没用winsound是为了跨平台兼容——pygame.mixer在 Windows 和 Linux 上都能正常播放 WAV 文件。import pygame class Alarm: def __init__(self, sound_pathalarm.wav): pygame.mixer.init() self.sound pygame.mixer.Sound(sound_path) self.last_trigger_time 0 def trigger(self, cooldown_seconds5): 触发报警cooldown_seconds 防止同一状态反复报警 now time.time() if now - self.last_trigger_time cooldown_seconds: self.sound.play() self.last_trigger_time now防重复报警这个逻辑在实际场景里非常重要。如果没有冷却时间司机打了个哈欠系统在 2 秒窗口内持续判定疲劳报警声会连续响 5~6 次烦到司机直接把系统关了。加了 5 秒冷却情况会好很多。5. 避坑指南训练到部署最常见的五个问题从环境配置到模型加载5.1 环境安装翻车TensorFlow 装完导入报错现象按 README 执行pip install tensorflow后Python 里import tensorflow直接报 DLL 加载失败或提示找不到指定模块。原因绝大多数情况是 Python 版本太新3.11或 32 位环境TensorFlow 官方预编译的 wheel 只覆盖有限版本组合。还有一部分是 GPU 版装了没有对应 CUDA 的机器上。解决先无脑降级到 Python 3.8~3.10然后执行pip install tensorflow-cpu2.10.*Windows或tensorflow2.10.*Linux。如果是 Apple Silicon Mac用tensorflow-metal插件。装完后跑一句print(tf.__version__)确认导入成功再继续。提示强烈建议为这个项目单独建虚拟环境不要在系统 Python 里混装。python -m venv fatigue_env然后激活干净环境能省掉一半玄学报错。5.2 摄像头读取失败笔记本内置摄像头也能翻车现象cv2.VideoCapture(0)返回 True但read()拿到的画面是黑屏或者直接报错。原因摄像头被其他软件占用微信、OBS、浏览器视频会议OpenCV 打开设备时拿不到帧数据部分联想笔记本的摄像头需要先物理开关打开。解决先关掉所有占用摄像头的程序再用cap.isOpened()判断。如果编号 0 不行试着改成cv2.VideoCapture(1)或cv2.VideoCapture(-1)让 OpenCV 自动枚举可用设备。还是不行就在系统相机应用里确认摄像头本身能出图。5.3 模型训练 loss 不下降学习率和数据归一化的双重坑现象训练了 10 多个 epochloss 一直在 1.0 左右晃准确率没有上升迹象。原因最常见的是输入没做归一化像素值 0~255 直接喂进网络激活函数输出饱和其次是学习率设置过高loss 在震荡中无法收敛。解决检查数据流里是否执行了/255.0归一化确认后用更小的学习率重新训练。我一般会把 Adam 的learning_rate从 0.001 降到 0.0003同时把 batch_size 从 32 提到 64稳定性会好很多。5.4 预测结果和训练时类别对不上Keras 类别索引的暗坑现象训练准确率 97%但预测时“闭眼”图片被分成“哈欠”“正常”图片被分成“闭眼”而且概率还挺高。原因验证集用的是同一个ImageDataGenerator类别索引一致但预测脚本里如果手写了class_names列表顺序没有和flow_from_directory的class_indices对齐就会张冠李戴。解决预测前打印模型的class_indices属性把真实索引打印出来然后以此为准写映射表。不要靠猜测目录顺序。# 加载后先确认类别映射防止搞错顺序 print(detector.model.class_indices) # 输出示例: {eye_closed: 0, normal: 1, yawn: 2}5.5 画面上画框卡顿每一帧都做完整 CNN 推理的代价现象画面像幻灯片一样一卡一卡的明显掉帧严重。原因实时循环里每一帧都跑人脸检测 模型推理。人脸检测本身比较耗时CNN 虽然是轻量网络但逐帧跑在 CPU 上依然吃紧。解决把frame_skip从 1 改成 2 或 3即每 3 帧才做一次完整检测。另外可以缩小检测窗口先用小分辨率图识别人脸定位后再裁剪原图区域送分类器。实测这样处理15fps 的画面流畅度基本够用。6. 进阶模型量化压缩与疲劳指数调优让这套系统真正跑进量产门槛如果你已经跑通了上面的完整流程下一步值得做的事情是模型量化和疲劳评分逻辑的细调。H5 格式的 Keras 模型体积通常在几百 KB 到 1MB 之间对于树莓派或 Jetson Nano 这类端侧设备来说推理延迟还是偏高。用 TensorFlow Lite 做量化压缩模型体积能缩小到原来的四分之一推理速度提升 2~3 倍。import tensorflow as tf # 加载训练好的 Keras 模型 model tf.keras.models.load_model(driver_fatigue_cnn.h5) # 转换为 TFLite 格式默认 float32 converter tf.lite.TFLiteConverter.from_keras_model(model) tflite_model converter.convert() # 保存量化前后的模型方便对比 with open(driver_fatigue_cnn.tflite, wb) as f: f.write(tflite_model) # 量化版int8 动态范围量化体积更小、速度更快 converter_quant tf.lite.TFLiteConverter.from_keras_model(model) converter_quant.optimizations [tf.lite.Optimize.DEFAULT] tflite_quant_model converter_quant.convert() with open(driver_fatigue_cnn_quant.tflite, wb) as f: f.write(tflite_quant_model)量化后的模型需要在推理脚本中换用tf.lite.Interpreter加载输入输出张量的访问方式也随之变化。第一版量化模型跑起来精度通常会有一两个百分点的波动这是正常现象如果出现明显退化优先调整训练阶段的数据增强强度而不是回退到 float32 版本。疲劳评分逻辑的调优核心在于 PERCLOS单位时间内眼睛闭合时间占比标准的落地。学术研究常用 P80 标准闭眼超过 80% 才报警但实际驾驶场景里 2 秒窗口 40% 闭眼已经具备预警价值。你可以把多个窗口长度做成可配置参数实测时以司机真实反馈为准调整——阈值太紧司机嫌吵太松起不到预警作用没有万能数值。从那以后我每接触一套检测类源码都会先跑通最小闭环再去做数据集扩充和参数量化而不是一上来就追求最高精度。毕竟疲劳检测这种系统的核心指标不是“准”而是“在噪声环境里稳定可信”。源码给的是一个经过验证的起点真正能落地的部分恰恰是你根据自己的驾驶场景一点点磨出来的。希望这篇拆解能帮你在复现时少走几步弯路。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。