基于Python的疲劳驾驶检测系统:CNN人脸识别与实时预警实战解析
发布时间:2026/10/2 2:42:08来源:尧图网络
简介基于Python卷积神经网络的人脸识别驾驶员疲劳检测与预警系统是一套面向毕业设计、课程设计与项目开发的完整可运行源码包。系统以打哈欠、眨眼、点头三类面部疲劳特征为切入点结合人脸朝向、瞳孔朝向、眼睛开合度、眨眼频率、瞳孔收缩率等数据实时计算驾驶员注意力集中程度并给出安全提示。压缩包共20个文件约78.33MB核心为11个Python脚本覆盖人脸检测、数据预处理、模型训练、检测推理与界面交互另含2个XML人脸/眼睛检测器、1个预训练hdf5模型、1个可直接运行的exe及3个说明文档方便快速启动与二次开发。已有723人学习浏览源码已通过测试适合作为毕业设计或课程设计参考便于快速上手项目实践也可在此基础上扩展更多疲劳预警策略或接入实车场景。1. 基于Python的疲劳检测系统打开压缩包后先看什么疲劳驾驶是交通事故里最隐蔽的诱因也正因为这个痛点太真实基于Python卷积神经网络的人脸识别驾驶员疲劳检测与预警系统才会成为毕业设计和课程设计里的常客。这套源码包把“摄像头实时检测 CNN分类 预警输出”整个链路都配齐了detect_class.py负责实时推理baojin.py负责报警tkinter_UI.py做成可视化界面还附带一个训练好的_mini_XCEPTION.102-0.66.hdf5权重和一个可直接双击的exe。拆完这个包我的第一印象是结构清晰、能跑出结果但坑也不少。适合正在做毕业设计、课程设计或者想快速搭一套视觉检测Demo的开发者。下面我按原理、环境、训练、避坑、交付这条线拆开讲。2. 系统架构与判定原理文件怎么分工疲劳指标如何量化2.1 源码包文件清单每个脚本的职责边界拿到压缩包之后我建议先不要急着运行而是花五分钟把文件关系捋一遍。这个包的模块分层很明确用一个表格就能看清楚文件/目录职责detect_class.py实时检测主程序负责调用摄像头、人脸检测和模型推理baojin.py预警模块判定疲劳后触发声音或界面提示cnn.pyCNN模型的定义和训练入口evaluate.py读取训练好的hdf5权重在测试集上评估准确率和损失data_provider.py数据生成器把图片分批喂给模型load_and_process.py图像加载与预处理包括缩放、灰度化、归一化convert.py数据格式转换比如把csv或原始视频帧转成图片目录split_train_test.py按比例划分训练集与测试集extract_face.py从视频流中提取人脸区域并保存为样本check.py环境检查确认摄像头和依赖库是否就绪haarcascade_frontalface_default.xmlOpenCV的Haar人脸检测模型haarcascade_eye.xmlOpenCV的Haar眼睛检测模型models/_mini_XCEPTION.102-0.66.hdf5训练好的模型权重102表示训练轮数0.66是准确率tkinter_UI.py/tkinter_UI.exeTkinter图形界面及打包版本系统说明.txt/运行说明.txt项目文档与运行说明这里有个细节值得注意模型文件名自带了关键信息——102是epoch数0.66是验证准确率。对于疲劳检测这种二分类或三分类场景0.66的准确率听起来不算高但在实际使用中系统还会结合眨眼频率、哈欠持续性等时序判断做二次决策单帧分类准确率不足是可以用阈值和连续帧补偿的。所以不要看到0.66就认为模型不可用重点是它能不能给时序逻辑提供稳定的“闭眼概率”。整个包的关键链路是extract_face.py负责从视频里切人脸convert.py统一数据格式split_train_test.py切分训练集和测试集data_provider.py负责把样本喂给cnn.py训练训练产出hdf5后由evaluate.py评估最后detect_class.py把模型接回摄像头画面。如果只是做演示可以跳过训练环节直接加载现成的hdf5如果你要复现训练并改进准确率那就得从数据准备这一步开始走。2.2 人脸与眼睛定位为什么是Haar级联模型只对裁剪后的人脸区域做分类因此先要稳定拿到人脸框。这个项目用的是OpenCV自带的Haar级联分类器也就是那两个xml文件。Haar级联的核心思路是滑动窗口特征阈值判断其优点是模型体积小、推理速度快在普通CPU上也能跑到每秒几十帧对于需要现场演示的课程设计或答辩场景非常合适。相比用CNN直接做目标检测如YOLO、SSDHaar级联在50到200像素的人脸上表现足够稳定而且不需要GPU。常见用法是这样一段代码import cv2 face_cascade cv2.CascadeClassifier(haarcascade_files/haarcascade_frontalface_default.xml) eye_cascade cv2.CascadeClassifier(haarcascade_files/haarcascade_eye.xml) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48) ) for (x, y, w, h) in faces: roi_gray gray[y:y h, x:x w] eyes eye_cascade.detectMultiScale(roi_gray) # eyes 用于定位眼睛区域为后续CNN分类提供输入参数说明scaleFactor1.1表示每次窗口缩放比例越小检测越精细但越慢minNeighbors5表示候选区域被多少个相邻窗口确认才算有效调大可以减少误检但可能漏检minSize(48,48)是避免把远处微小区域误认成人脸。实际场景里侧脸和暗光最容易导致人脸框抖动我一般会加一个简单的帧间跟踪对检测框坐标做移动平均平滑效果立竿见影。选择Haar而不是dlib的人脸关键点还有一个现实原因两个xml文件加一起不到1MB而dlib的shape_predictor模型动辄几十MB部署成本高。毕业设计答辩现场经常需要临时换电脑轻量方案容错率更高。2.3 CNN模型与疲劳判定眨眼、哈欠、点头的量化方式模型在这里的角色是“区域状态分类器”。以眼睛为例haarcascade_eye.xml把人眼区域框出来之后模型判断这个区域是“睁眼”还是“闭眼”同样嘴巴区域也可以判断是“闭合”还是“张开”。判断结果再送入时序逻辑得到三个疲劳指标眨眼频率统计单位时间内闭眼帧占总帧数的比例以及持续闭眼的时间长度。连续多帧闭眼也就是眨眼间隔过长是疲劳的重要信号。哈欠频率与时长当嘴巴张开的帧持续超过一个阈值常见做法是连续20到30帧判定为一次哈欠。点头特征通过人脸框中心点的垂直位移来估算。如果头部在短时间内反复上下移动且人脸检测没有丢失就认为是点头动作。项目简介里还提到“瞳孔朝向、瞳孔收缩率”这是更高阶的指标在纯图像方案里可以通过瞳孔中心相对眼框的位置变化来近似估计——瞳孔位置偏移量可以用来判断视线方向如果驾驶员长时间没有明显视线移动说明注意力集中度在下降。这些指标不一定要单独训练一个模型用既有CNN输出加几何计算就能得到近似值。为了说明判断逻辑下面是一个经典的眨眼判定片段# 基于闭眼帧计数判断眨眼 EYE_CLOSE_THRESH 0.60 # 模型认为闭眼的概率阈值 CLOSE_FRAME_LIMIT 3 # 连续闭眼帧数达到3帧记为一次眨眼 close_counter 0 blink_count 0 for frame in video_frames: eye_closed cnn_predict(eye_roi)[0] EYE_CLOSE_THRESH if eye_closed: close_counter 1 else: if 3 close_counter 15: blink_count 1 close_counter 0这里的关键是给close_counter设置上下限。上限15的意义是排除“闭眼时间过长”这种疑似瞌睡的情况因为长时间闭眼不是眨眼而是疲劳风险极高的表现。评分时如果在一分钟内blink_count明显低于正常值正常人是每分钟15到20次同时检测到长时间闭眼系统就会判定疲劳并触发baojin.py。到这里应该能理解整个系统的架构了Haar负责定位CNN负责单帧状态判断时序逻辑负责把单帧结果变成行为特征最后报警模块做决策输出。接下来就是把这些环节逐一落到环境与代码里。3. 环境搭建与数据预处理从裸Python到能跑训练的完整步骤3.1 安装依赖版本匹配是第一道坎这个项目基于Python3.6依赖中最容易出问题的是TensorFlow和Keras。压缩包里的requirements.txt如果直接pip install -r requirements.txt在现在的机器上大概率会失败原因是旧版本包已经不在默认源里或者与当前Python版本冲突。我建议用一个专门的虚拟环境conda create -n fatigue python3.6 conda activate fatigue pip install tensorflow2.6.0 pip install keras2.6.0 pip install opencv-python4.5.4.60 pip install numpy1.19.5 pip install pandas scikit-learn matplotlib pillow参数说明把TensorFlow固定在2.6.0而不是最新的2.x是因为Keras在2.6以后对Python3.6的支持已经变得很勉强numpy1.19.5则是为了兼容旧版OpenCV和TF的底层接口。如果不想折腾版本也可以直接把模型推理脚本放到TensorFlow 2.10 Python 3.9的环境里跑但训练脚本里的某些老API需要手动改。提示不要在同一环境里混装多个版本TensorFlow依赖冲突是最常见的翻车源头。运行代码前先执行一次环境自检check.py的作用就在于此。它是一个很简单的探测脚本逐个import依赖并打印版本号顺便测试摄像头能否打开。建议拿到包后先跑它再决定要不要重装环境。3.2 数据准备convert.py、extract_face.py与数据格式训练CNN前需要有标注好的人脸、闭眼、张嘴样本。convert.py的作用是把原始数据可能是视频帧或raw数据统一成模型能读取的图片目录。extract_face.py则负责用Haar级联从视频里自动切出人脸区域减少手动标注的工作量。这个项目的模型输入是64x64灰度图所以所有样本最终都要落到这个尺寸。常见的目录组织方式是这样data/ train/ open/ closed/ test/ open/ closed/extract_face.py里的核心逻辑import cv2 import os face_cascade cv2.CascadeClassifier(haarcascade_files/haarcascade_frontalface_default.xml) output_dir data/train/closed cap cv2.VideoCapture(raw_video.mp4) frame_idx 0 saved_cnt 0 while True: ret, frame cap.read() if not ret: break if frame_idx % 5 ! 0: # 每5帧抽1帧减少重复样本 frame_idx 1 continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) for (x, y, w, h) in faces: face gray[y:y h, x:x w] face cv2.resize(face, (64, 64)) cv2.imwrite(os.path.join(output_dir, fimg_{saved_cnt:06d}.jpg), face) saved_cnt 1 frame_idx 1这个脚本有几个容易出错的地方一是如果视频光线变化大抽帧得到的人脸可能大量重复或模糊我一般会在保存前用cv2.Laplacian计算梯度方差低于阈值的直接丢弃二是类别目录要分开建open和closed不要混在一起否则data_provider.py读取时会乱。3.3 数据划分与批次生成split_train_test.py和data_provider.pysplit_train_test.py做的是train_test_split核心参数是测试集比例和随机种子import os import shutil from sklearn.model_selection import train_test_split source_dir data/all train_dir data/train test_dir data/test for cls in [open, closed]: cls_path os.path.join(source_dir, cls) files [f for f in os.listdir(cls_path) if f.endswith(.jpg)] tr, te train_test_split(files, test_size0.2, random_state42) for out_dir, subset in [(train_dir, tr), (test_dir, te)]: target os.path.join(out_dir, cls) os.makedirs(target, exist_okTrue) for f in subset: shutil.copy(os.path.join(cls_path, f), os.path.join(target, f))random_state42固定后别人复现你的实验时得到的数据划分完全一致这在毕业设计答辩里很重要因为评估结果可以被精确复现。data_provider.py我一般会写成生成器而不是一次性把全部图片载入内存常见做法是配合ImageDataGenerator做数据增强from keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rescale1.0 / 255.0, rotation_range5, width_shift_range0.1, height_shift_range0.1, zoom_range0.1, horizontal_flipTrue ) train_flow datagen.flow_from_directory( data/train, target_size(64, 64), color_modegrayscale, batch_size32, class_modecategorical )参数说明rotation_range5只是轻微旋转太大反而会让闭眼和睁眼特征混乱batch_size32在CPU上训练也能接受显存不够就降到16。数据增强的目的是把64x64小图的过拟合压下去如果发现验证准确率比训练准确率低很多优先加大zoom_range而不是堆训练轮数。4. 训练、评估与实时检测从权重文件到摄像头推理的一整条链路4.1 cnn.py小尺寸XCEPTION模型与训练参数_mini_XCEPTION是这个项目模型的名字它是XCEPTION网络的迷你版。XCEPTION的核心是深度可分离卷积和残差连接在参数规模远小于VGG、ResNet的同时对局部纹理特征的提取能力很强。对于64x64的灰度人脸图这种结构很合适——图片小、类别少不需要把网络做得太深。训练时的模型定义常见写法如下from keras.models import Model from keras.layers import Input, Conv2D, SeparableConv2D, BatchNormalization from keras.layers import Activation, MaxPooling2D, Add, GlobalAveragePooling2D from keras.layers import Dense, Dropout def mini_xception(input_shape(64, 64, 1), num_classes2): inputs Input(shapeinput_shape) x Conv2D(32, (3, 3), paddingsame, use_biasFalse)(inputs) x BatchNormalization()(x) x Activation(relu)(x) x SeparableConv2D(64, (3, 3), paddingsame, use_biasFalse)(x) x BatchNormalization()(x) x Activation(relu)(x) x MaxPooling2D(pool_size(2, 2))(x) # 残差块输入和输出做加法缓解梯度消失 residual x x SeparableConv2D(128, (3, 3), paddingsame, use_biasFalse)(x) x BatchNormalization()(x) x Activation(relu)(x) x SeparableConv2D(128, (3, 3), paddingsame, use_biasFalse)(x) x BatchNormalization()(x) x Add()([x, residual]) x Activation(relu)(x) x MaxPooling2D(pool_size(2, 2))(x) x GlobalAveragePooling2D()(x) x Dropout(0.5)(x) x Dense(num_classes, activationsoftmax)(x) model Model(inputsinputs, outputsx) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) return model参数说明use_biasFalse配合BatchNormalization是XCEPTION的标准组合因为BN层本身带有偏置项再保留卷积偏置是冗余的dropout0.5放在全连接之前可以显著降低小数据集上的过拟合。训练时用ModelCheckpoint保存权重这就是_mini_XCEPTION.102-0.66.hdf5的来历from keras.callbacks import ModelCheckpoint, EarlyStopping checkpoint ModelCheckpoint( models/_mini_XCEPTION.{epoch:02d}-{val_acc:.2f}.hdf5, monitorval_acc, save_best_onlyTrue, verbose1 ) early_stop EarlyStopping(monitorval_loss, patience10) model.fit_generator( train_flow, steps_per_epoch200, epochs120, validation_dataval_flow, validation_steps50, callbacks[checkpoint, early_stop] )EarlyStopping的patience10表示连续10轮验证损失不下降就停止可以避免后段白白消耗时间。这里的steps_per_epoch要根据训练样本数除以batch_size来算如果数据量只有几千张steps_per_epoch200会导致一个epoch重复读好几轮数据属于正常现象。4.2 evaluate.py用hdf5权重做评估训练完成后evaluate.py读取hdf5文件并在测试集上计算指标from keras.models import load_model from keras.preprocessing.image import ImageDataGenerator model load_model(models/_mini_XCEPTION.102-0.66.hdf5) val_datagen ImageDataGenerator(rescale1.0 / 255.0) val_flow val_datagen.flow_from_directory( data/test, target_size(64, 64), color_modegrayscale, batch_size32, class_modecategorical, shuffleFalse ) loss, acc model.evaluate(val_flow) print(fTest loss: {loss:.4f}, Test acc: {acc:.4f})这里shuffleFalse很关键它让验证集保持原始顺序方便后续用predict时把预测标签和文件名一一对应起来。如果要在答辩里展示更多细节可以用sklearn.metrics.classification_report输出每一类别的精确率和召回率这比只报一个准确率更有说服力。4.3 detect_class.py与baojin.py实时推理与预警联动实时检测脚本把第2章提到的Haar定位和CNN分类串在一起并调用报警模块import cv2 import numpy as np from keras.models import load_model import baojin face_cascade cv2.CascadeClassifier(haarcascade_files/haarcascade_frontalface_default.xml) eye_cascade cv2.CascadeClassifier(haarcascade_files/haarcascade_eye.xml) model load_model(models/_mini_XCEPTION.102-0.66.hdf5) cap cv2.VideoCapture(0) fatigue_score 0 closed_frames 0 while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(48, 48)) for (x, y, w, h) in faces: face gray[y:y h, x:x w] face cv2.resize(face, (64, 64)) face face.astype(float32) / 255.0 pred model.predict(face.reshape(1, 64, 64, 1))[0] if pred[0] 0.6: # 假设类别0为闭眼 closed_frames 1 else: if closed_frames 10: fatigue_score 20 baojin.trigger(持续闭眼疑似瞌睡) closed_frames 0 cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(Fatigue Detector, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码把核心逻辑说清楚了出现closed_frames 10视为一次持续闭眼事件每次事件给fatigue_score加20分当分数累计到某个阈值比如60分时baojin.py就会被触发弹出警告或播放提示音。实际项目中建议对每帧加入时间戳用真实时间窗口代替简单的帧计数这样在不同帧率的摄像头下结果才一致。比如用time.time()记录闭眼起始和结束时刻眨眼时长按实际秒数算而不是按帧数算。5. 常见问题与避坑记录五个最容易翻车的现场5.1 TensorFlow与Keras版本冲突现象运行cnn.py或evaluate.py时直接报AttributeError: module tensorflow has no attribute placeholder还有人遇到cannot import name keras from tensorflow。原因项目代码是Python3.6 TensorFlow 1.x时代的写法系统默认装的是TensorFlow 2.xTF2.0把placeholder、Session等API移除了旧代码自然找不到这些符号。解决不要硬改代码直接建虚拟环境并用pip install tensorflow2.6.0 keras2.6.0固定版本。如果必须用TF2.9以上就在脚本开头加import tensorflow.compat.v1 as tf; tf.disable_v2_behavior()让旧接口重新生效。5.2 摄像头索引不对导致黑屏现象VideoCapture(0)打开成功但画面全黑或者直接返回False。换了外接摄像头后更明显。原因笔记本自带摄像头和外接USB摄像头的设备索引不是固定的有的机器索引是0有的则是1还有的摄像头被其他程序占用后索引会偏移。解决启动前先用一个小脚本探测import cv2 for idx in range(5): cap cv2.VideoCapture(idx) if cap.isOpened(): print(fcamera index {idx} works) cap.release()找到可用索引后把detect_class.py里的VideoCapture(0)改成对应数字。另外演示前先拔掉不用的摄像头避免索引被抢占。5.3 眨眼误报率极高现象人明明睁着眼睛系统却频繁触发闭眼报警或者正常眨几下眼就被判定为疲劳。原因两个常见根源——阈值设得太低导致模型预测概率稍微超过0.6就判定闭眼另一个是Haar检测框抖动把眉毛或额头区域错当成眼睛区域送进模型。解决把阈值提到0.7以上增加连续帧确认闭眼判定必须是连续3帧以上才生效对眼睛区域做裁剪时适当缩小roi高度减少眉毛干扰。我一般还会加一个“眨眼间隔过滤”两次眨眼间隔小于200毫秒的不计先滤掉抖动产生的假眨眼。5.4 tkinter_UI.exe双击没有反应现象在Windows上双击exe后没有任何窗口出现任务管理器里进程一闪而过或者运行后又弹出一个缺少DLL的报错。原因Tkinter打包成的exe依赖Python解释环境和OpenCV等运行库如果打包时没有带上models目录或haarcascade_files目录启动后找不到资源就会崩溃退出。解决第一优先的做法是直接用源码跑——确保虚拟环境里依赖齐全后执行python tkinter_UI.py如果一定要用exe把exe和models、haarcascade_files放在同一目录下并安装最新的Visual C运行库。杀毒软件也可能拦截加入白名单再试。5.5 模型准确率上不去现象训练十几个epoch后准确率停在0.5到0.6之间再训练就过拟合验证集掉点。原因最常见的是数据集太小、类别不平衡或者两类样本差异不够明显。另一个原因是64x64输入尺寸下原本就容易丢失细节如果没有做数据增强模型很快就把训练集背下来了。解决优先做数据增强增大rotation_range到15并加brightness_range检查数据分布如果open样本是closed的三倍就在data_provider.py里设置class_weight平衡如果还是不行减少模型宽度把第一层通道数从32降到16增强正则化效果。6. 开箱即用用Tkinter把检测系统封装成可演示的界面如果想在答辩现场稳定演示命令行窗口是不够的。tkinter_UI.py用Python自带的Tkinter做成了桌面界面把摄像头画面、检测状态和报警信息放在一个窗口里。它的结构并不复杂核心是在Tkinter的Label组件里实时刷新视频帧import tkinter as tk import cv2 from PIL import Image, ImageTk from keras.models import load_model class FatigueUI: def __init__(self, root): self.root root self.root.title(驾驶员疲劳检测系统) self.video_label tk.Label(root) self.video_label.pack() self.status_label tk.Label(root, text正常, font(微软雅黑, 16)) self.status_label.pack() self.cap cv2.VideoCapture(0) self.model load_model(models/_mini_XCEPTION.102-0.66.hdf5) self.update_frame() def update_frame(self): ret, frame self.cap.read() if ret: # 在这里复用detect_class.py中的检测逻辑 # 并将结果画到frame上 frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img Image.fromarray(frame_rgb) imgtk ImageTk.PhotoImage(imageimg) self.video_label.imgtk imgtk self.video_label.config(imageimgtk) self.root.after(30, self.update_frame) if __name__ __main__: root tk.Tk() ui FatigueUI(root) root.mainloop()要特别注意self.video_label.imgtk imgtk这一行如果不保存PhotoImage对象的引用Python的垃圾回收机制会在下一次刷新时把图片清掉画面就会闪烁变黑。这是一个很容易被忽略的细节论坛里问“Tkinter视频刷新闪黑屏”的帖子八成都是栽在这里。关于演示流程我建议在答辩前固定好一条测试路径先打开UI正对摄像头保持正常状态10秒让界面显示“正常”然后缓慢闭上眼睛3秒以上此时系统应当触发预警并在状态栏变色最后转向侧脸或降低光照演示人脸检测丢失后的容错提示。整个流程控制在30秒以内比临场发挥要稳妥得多。如果想顺便展示模型的泛化能力可以准备一段网上下载的驾驶场景短视频用VideoCapture(video.mp4)替换摄像头索引效果和实时画面一样。我拆这个项目的习惯是每改一个阈值、每动一个模块都把当时的参数和现象记录在系统说明.txt旁边。不要相信“改成0.7就一定好”这种经验因为你的摄像头、光照和数据集跟原作者的都不一样。从那以后我每次拿到这类源码包都会强制自己走一遍从环境检查到单步运行的完整流程确认所有边界参数都摸过一遍再交给别人演示。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网