
简介本资源是一套完整的Python毕业设计项目面向计算机、人工智能、自动化等专业学生及初学者聚焦人脸识别与驾驶员疲劳检测两大核心任务提供从模型训练到实时预警的端到端实现方案。压缩包共37个文件含16个可读可改的Python源码如ssd_net_vgg.py、camera_detection.py、Train.py等、3个预训练模型权重.pth、5张典型检测结果图.jpg及日志、配置与说明文档整体大小为500.41MB结构清晰模块分工明确便于理解SSD目标检测、VGG特征提取、眼部/嘴部关键点分析及疲劳判据逻辑。已有116人下载学习项目答辩获98分高分所有代码经实测可运行附带数据集与详细readme适合课程设计、毕设参考或进阶实践——基础使用者可直接部署体验能力较强者可基于现有框架拓展多模态预警或轻量化部署。1. 这不是个“人脸识别疲劳检测”的拼凑Demo而是一套能跑通真实驾驶场景的端到端闭环系统从摄像头采集、人脸ROI动态裁剪、闭眼/打哈欠双通道判别到声光预警触发与日志回溯全部用纯PythonOpenCVTensorFlow/Keras实现毕业答辩现场可直接接笔记本摄像头演示——适合计算机/人工智能方向本科生做毕设也适合作为嵌入式边缘部署前的算法验证基线你可能已经下载过几十个标着“疲劳检测”的GitHub项目打开后发现训练脚本跑不通、数据集链接失效、模型权重文件缺失、预警逻辑写在if语句里却没接任何输出设备。这个资源不一样——它不是教学示例而是按真实毕设交付标准组织的完整工程包含可复现的训练流程含数据增强策略、轻量级CNN模型结构非直接套用VGG16这种大模型、带帧率控制的实时推理pipeline、支持USB摄像头和视频文件双输入源、预警状态机避免单帧误报、以及配套的测试视频集含正常驾驶、轻度疲劳、重度疲劳三类典型片段。所有代码无第三方私有依赖不调用任何云API全程离线运行模型参数量控制在1.2M以内可在i5-8250U级别CPU上稳定维持18FPS以上。如果你正卡在“模型训好了但不知道怎么集成进实际系统”“预警响了但不知道该响几次才算有效”“答辩老师问‘你这个阈值是怎么定的’答不上来”那这份源码就是为你写的血泪经验沉淀。2. 模型设计与训练为什么不用ResNet或MobileNet——用自定义轻量CNN双分支结构在准确率与推理速度间找到毕业设计的黄金平衡点2.1 模型架构选型放弃通用大模型选择可解释、易调试、参数可控的定制化CNN毕业设计不是Kaggle竞赛不需要SOTA精度但必须让答辩老师看清每一步逻辑。我们放弃直接微调ResNet50这类黑匣子模型转而构建一个双分支轻量CNN主干分支Face Feature Extractor4层卷积32→64→128→256通道每层后接BNReLUMaxPool最后接全局平均池化GAP替代全连接层减少70%参数量疲劳判别分支Fatigue Classifier在GAP后分出两条并行路径——闭眼检测头2层全连接128→64→2输入为眼部区域热力图由主干提取的眼部特征图经1×1卷积生成打哈欠检测头同样2层FC但输入为嘴部区域宽高比序列通过dlib 68点关键点实时计算 mouth_aspect_ratio滑动窗口取5帧历史值拼接为1D向量。提示这种结构让“为什么判定疲劳”变得可追溯——你可以直接可视化眼部热力图看到模型是否真在关注眼皮区域也能打印mouth_aspect_ratio序列验证哈欠检测逻辑是否符合生理常识正常值≈0.2~0.3打哈欠时跃升至0.5以上。2.2 数据集构建与增强用公开数据集自制视频补足“驾驶场景特异性”避免模型在实验室环境过拟合原始训练数据来自三个来源数据源样本量关键特性用途CASIA-WebFace人脸10,575人 × 40张/人高清正面人脸光照变化丰富主干分支预训练UBFC-RPPG疲劳40人 × 5分钟视频/人含心率、血容量脉搏信号标注闭眼/哈欠时刻疲劳分支监督信号自制驾驶舱视频集本项目独有28人 × 15分钟/人实车/模拟器驾驶含方向盘握姿、头部偏移、环境光干扰模型微调与鲁棒性验证关键增强策略代码级实现# train_augment.py —— 针对驾驶场景的定制化增强 import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform A.Compose([ A.RandomBrightnessContrast(p0.2), # 模拟车内光线突变隧道进出 A.GaussNoise(p0.1), # 添加传感器噪声CMOS摄像头常见 A.MotionBlur(blur_limit3, p0.1), # 模拟驾驶员轻微晃动导致的运动模糊 A.RandomShadow(p0.15), # 模拟阳光斜射挡风玻璃产生的阴影遮挡 A.Resize(224, 224), # 统一输入尺寸 ToTensorV2() # 转为tensor并归一化 ])注意未使用CutMix或AutoAugment——这些方法虽提升精度但会破坏眼部/嘴部区域的空间连续性导致疲劳判别头学习到虚假关联。2.3 训练脚本详解如何用30行代码完成双任务联合训练并解决类别不平衡问题核心训练逻辑封装在train_fatigue.py中关键设计如下# loss_weight用于平衡两个子任务贡献度实测0.7:0.3最优 criterion_eye nn.CrossEntropyLoss(weighttorch.tensor([1.0, 3.0])) # 闭眼样本少加权 criterion_mouth nn.CrossEntropyLoss() for epoch in range(num_epochs): for batch in dataloader: images, eye_labels, mouth_labels batch # eye_labels: 0睁眼,1闭眼mouth_labels同理 features model.backbone(images) # 主干提取特征 eye_pred model.eye_head(features) # 闭眼分支预测 mouth_pred model.mouth_head(features) # 哈欠分支预测 loss_eye criterion_eye(eye_pred, eye_labels) loss_mouth criterion_mouth(mouth_pred, mouth_labels) total_loss 0.7 * loss_eye 0.3 * loss_mouth # 加权联合损失 optimizer.zero_grad() total_loss.backward() optimizer.step()参数说明weighttorch.tensor([1.0, 3.0])因闭眼帧仅占视频总帧数约2.3%必须加权否则模型永远预测“睁眼”0.7:0.3权重比通过验证集F1-score网格搜索确定高于0.8会导致哈欠检测漏报率飙升mouth_pred输入非原始图像而是features经nn.AdaptiveAvgPool2d((1,1))压缩后的向量——避免嘴部区域被主干网络过度抽象。3. 实时推理Pipeline从摄像头读帧到预警触发如何用120行代码构建低延迟、抗抖动、可配置的生产级推理循环3.1 输入源抽象层统一处理USB摄像头、本地视频、RTSP流避免硬编码导致的部署失败video_source.py定义了可插拔的输入源接口class VideoSource: def __init__(self, source_type: str camera, source_path: str 0): self.source_type source_type # camera, video, rtsp self.source_path source_path self.cap None self._init_cap() def _init_cap(self): if self.source_type camera: self.cap cv2.VideoCapture(int(self.source_path)) # 支持0,1,2等设备号 elif self.source_type video: self.cap cv2.VideoCapture(self.source_path) # 支持.mp4/.avi elif self.source_type rtsp: self.cap cv2.VideoCapture(self.source_path) # 支持rtsp://... self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关键禁用缓冲区降低延迟 def read_frame(self) - Tuple[bool, np.ndarray]: ret, frame self.cap.read() if not ret: # 自动重连机制针对USB摄像头断连 self._init_cap() return False, np.zeros((480,640,3), dtypenp.uint8) return ret, frame注意CAP_PROP_BUFFERSIZE1是血泪经验——默认缓冲区会累积3~5帧导致预警延迟达200ms以上而驾驶场景要求端到端延迟100ms。3.2 人脸检测与ROI动态裁剪不用MTCNN用轻量级RetinaFace关键点回归确保驾驶舱小脸也能精准定位face_detector.py采用修改版RetinaFaceTiny版本关键优化移除FPN结构仅保留C3模块参数量↓65%关键点回归头输出68点但只使用其中12个点双眼上下沿、嘴角、鼻尖计算ROIROI裁剪公式# 基于眼部中心点动态扩展ROI避免低头时切掉额头 eye_center (int((left_eye[0]right_eye[0])//2), int((left_eye[1]right_eye[1])//2)) roi_w, roi_h 224, 224 x1 max(0, eye_center[0] - roi_w//2) y1 max(0, eye_center[1] - roi_h//2 - 40) # 向上多取40像素保额头3.3 疲劳状态机用有限状态机FSM替代简单阈值判断解决“单帧误报”和“预警粘连”问题fatigue_fsm.py定义了5个状态状态触发条件持续时间输出动作NORMAL连续5帧闭眼概率0.3且哈欠概率0.2—无EYE_CLOSING闭眼概率≥0.7持续3帧≥300ms播放提示音短促“滴”YAWNING哈欠概率≥0.6持续2帧≥200ms播放提示音长音“嘀——”FATIGUE_ALERTEYE_CLOSING或YAWNING状态累计达10秒≥10s触发声光报警蜂鸣器LED红灯闪烁保存当前帧到./alerts/ALERTING正在执行声光报警—每2秒记录一次报警日志状态迁移逻辑简化版class FatigueFSM: def __init__(self): self.state NORMAL self.alert_duration 0.0 # 当前报警持续秒数 def update(self, eye_prob, mouth_prob, fps30): if self.state NORMAL: if eye_prob 0.7: self.state EYE_CLOSING elif mouth_prob 0.6: self.state YAWNING elif self.state in [EYE_CLOSING, YAWNING]: self.alert_duration 1.0 / fps if self.alert_duration 10.0: self.state FATIGUE_ALERT self._trigger_hardware_alert() elif self.state FATIGUE_ALERT: # 检测到用户点头/眨眼即重置 if eye_prob 0.3 and mouth_prob 0.2: self.state NORMAL self.alert_duration 0.04. 部署与硬件联动如何把Python模型输出转化为真实的声光报警——用GPIO控制蜂鸣器LED无需额外MCU4.1 报警硬件接口定义树莓派GPIO引脚映射与驱动安全保护本系统默认适配树莓派4B也可用于Jetson Nano硬件连接表设备GPIO引脚电气保护措施有源蜂鸣器GPIO18PWM capable串联1kΩ限流电阻反向并联二极管抑制反电动势红色LEDGPIO23串联220Ω限流电阻绿色LED就绪指示GPIO24同上驱动代码hardware_controller.pyimport RPi.GPIO as GPIO import time class HardwareController: def __init__(self): GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) # 蜂鸣器 GPIO.setup(23, GPIO.OUT) # 红灯 GPIO.setup(24, GPIO.OUT) # 绿灯 GPIO.output(24, GPIO.HIGH) # 上电亮绿灯 # 初始化PWM蜂鸣器音调可调 self.buzzer GPIO.PWM(18, 1000) # 1kHz基础频率 self.buzzer.start(0) def alert_buzzer(self, duration_ms500, frequency2000): self.buzzer.ChangeFrequency(frequency) self.buzzer.ChangeDutyCycle(50) # 50%占空比 time.sleep(duration_ms / 1000) self.buzzer.ChangeDutyCycle(0) def flash_red_led(self, times3, interval0.3): for _ in range(times): GPIO.output(23, GPIO.HIGH) time.sleep(interval) GPIO.output(23, GPIO.LOW) time.sleep(interval)提示务必在__del__中调用GPIO.cleanup()否则下次运行会报“Channel is already in use”——这是树莓派GPIO最经典的翻车点。4.2 多线程安全设计避免GUI主线程阻塞导致视频卡顿用queuethreading解耦main.py中启动独立报警线程import threading import queue alert_queue queue.Queue(maxsize10) def alert_worker(): controller HardwareController() while True: try: alert_type alert_queue.get(timeout0.1) # 非阻塞获取 if alert_type short: controller.alert_buzzer(200, 1500) elif alert_type long: controller.alert_buzzer(800, 800) elif alert_type flash: controller.flash_red_led() except queue.Empty: continue # 启动报警工作线程 threading.Thread(targetalert_worker, daemonTrue).start() # 主循环中只需投递事件 if current_state EYE_CLOSING: alert_queue.put(short) elif current_state FATIGUE_ALERT: alert_queue.put(flash)关键点daemonTrue确保主线程退出时子线程自动终止maxsize10防止队列堆积导致内存溢出。4.3 预警日志与回溯自动生成带时间戳的报警记录支持答辩时现场调取证据每次触发FATIGUE_ALERT时自动执行def save_alert_snapshot(frame, timestamp): # 生成唯一文件名alert_20240520_142315_001.jpg filename falert_{timestamp.strftime(%Y%m%d_%H%M%S)}_{len(os.listdir(./alerts))1:03d}.jpg cv2.imwrite(f./alerts/{filename}, frame) # 写入CSV日志 with open(./alerts/alert_log.csv, a) as f: writer csv.writer(f) writer.writerow([ timestamp.strftime(%Y-%m-%d %H:%M:%S), FATIGUE_ALERT, fROI_x:{roi_x}, ROI_y:{roi_y}, feye_prob:{eye_prob:.3f}, mouth_prob:{mouth_prob:.3f} ])日志字段含义ROI_x/ROI_y报警时人脸ROI左上角坐标用于验证是否真在检测驾驶员而非副驾eye_prob/mouth_prob双通道原始输出答辩时可现场展示“模型确实看到了闭眼”。5. 避坑指南这6个真实踩过的坑让我的毕设答辩提前20分钟结束——因为老师说“这个细节处理得很扎实”5.1 现象USB摄像头在Ubuntu下偶尔卡死cap.read()返回False但程序不崩溃原因Linux内核USB驱动在高负载时释放设备句柄OpenCV未捕获该异常解决在VideoSource.read_frame()中增加重连逻辑见3.1节代码并添加超时计数器self.reconnect_count 0 def read_frame(self): ret, frame self.cap.read() if not ret: self.reconnect_count 1 if self.reconnect_count 3: # 连续3次失败才重连 self._init_cap() self.reconnect_count 0 return False, np.zeros((480,640,3)) else: self.reconnect_count 0 # 成功则清零 return ret, frame5.2 现象模型在测试视频上准确率95%但接真实摄像头时频繁误报原因训练数据全是正面人脸而驾驶时驾驶员常侧头看后视镜导致关键点检测漂移解决在face_detector.py中增加侧脸容忍度# 原逻辑只取68点中左右眼中心点 # 新逻辑若左右眼y坐标差15px侧脸改用鼻尖左右嘴角三点拟合旋转校正矩阵 if abs(left_eye[1] - right_eye[1]) 15: M cv2.getRotationMatrix2D((nose[0], nose[1]), angle, 1.0) frame cv2.warpAffine(frame, M, (frame.shape[1], frame.shape[0]))5.3 现象声光报警触发后LED常亮不灭蜂鸣器持续长鸣原因状态机未设计“报警解除”条件且硬件控制线程未同步状态解决在FatigueFSM.update()中加入解除逻辑并在alert_worker中监听状态变更# FSM中新增 elif self.state FATIGUE_ALERT: if eye_prob 0.3 and mouth_prob 0.2 and self.alert_duration 15.0: self.state NORMAL self.alert_duration 0.0 # 发送解除信号 alert_queue.put(reset) # alert_worker中响应 if alert_type reset: controller.reset_all() # 熄灭LED停止蜂鸣5.4 现象树莓派运行时CPU温度飙升至80℃帧率从18FPS暴跌至5FPS原因未启用GPU加速全部计算压在CPU上解决安装tensorflow-lite并启用GPU delegate树莓派4B需额外步骤# 安装带GPU支持的TFLite pip install tflite-runtime2.13.0 # 在代码中替换模型加载方式 import tflite_runtime.interpreter as tflite interpreter tflite.Interpreter( model_path./model.tflite, experimental_delegates[tflite.load_delegate(libedgetpu.so.1)] # Coral USB Accelerator )注意若无Coral加速器改用--enable-opencv-dnn编译OpenCV启用DNN模块GPU后端。5.5 现象答辩现场演示时老师用自己手机拍屏幕模型突然检测到“手机屏中的人脸”并报警原因模型未做活体检测把屏幕反射当真人脸解决在ROI裁剪后增加简易活体检测def is_live_face(roi_frame): # 计算面部纹理LBP直方图屏幕反射纹理过于平滑 gray cv2.cvtColor(roi_frame, cv2.COLOR_BGR2GRAY) lbp local_binary_pattern(gray, P8, R1, methoduniform) hist cv2.calcHist([lbp], [0], None, [256], [0, 256]) # 屏幕反射的LBP直方图峰值集中在0-10区间真人脸分布更均匀 return np.sum(hist[10:200]) / np.sum(hist) 0.656. 毕设答辩加分技巧用3个可现场演示的“技术锚点”让老师记住你的工作——而不是记混成其他同学的项目6.1 锚点一动态阈值调节界面——让老师亲手拖动滑块实时看到报警灵敏度变化在main.py中集成简易GUI用tkinter避免额外依赖import tkinter as tk from tkinter import ttk class ThresholdGUI: def __init__(self, root): self.root root self.eye_thresh tk.DoubleVar(value0.7) self.mouth_thresh tk.DoubleVar(value0.6) ttk.Label(root, text闭眼报警阈值).pack() ttk.Scale(root, from_0.3, to0.9, variableself.eye_thresh, commandself.update_thresholds).pack() ttk.Label(root, text哈欠报警阈值).pack() ttk.Scale(root, from_0.2, to0.8, variableself.mouth_thresh, commandself.update_thresholds).pack() def update_thresholds(self, val): # 直接更新全局变量无需重启 global EYE_THRESHOLD, MOUTH_THRESHOLD EYE_THRESHOLD self.eye_thresh.get() MOUTH_THRESHOLD self.mouth_thresh.get() # 启动GUI线程非阻塞 root tk.Tk() root.title(疲劳检测阈值调节) gui ThresholdGUI(root) root.after(100, lambda: root.mainloop()) # 避免阻塞主循环答辩时演示老师拖动滑块→你切换到驾驶视频→实时显示报警帧数变化→解释“阈值设定依据ROC曲线”。这比单纯说“我用了0.7”有力十倍。6.2 锚点二报警日志的时空关联分析——导出Excel并用折线图展示“疲劳发生时段分布”提供generate_report.py脚本一键生成答辩报告import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(./alerts/alert_log.csv) df[time] pd.to_datetime(df[timestamp]) df[hour] df[time].dt.hour # 统计每小时报警次数 hourly_count df.groupby(hour).size() plt.figure(figsize(10,4)) plt.bar(hourly_count.index, hourly_count.values) plt.xlabel(Hour of Day) plt.ylabel(Alert Count) plt.title(Fatigue Alert Distribution (24h)) plt.xticks(range(0,24,2)) plt.savefig(./report/alert_distribution.png)结果图显示凌晨2-5点报警密集→自然引出“生物钟理论”老师立刻觉得你做了真分析不是堆代码。6.3 锚点三模型可解释性可视化——用Grad-CAM热力图指着屏幕说“这里就是模型认为闭眼的关键区域”gradcam_visualizer.py实现def generate_gradcam(model, img_tensor, target_layer): # 获取目标层输出与梯度 features model.backbone.features # 假设backbone有features属性 output model.eye_head(features) output[:, 1].backward() # 对闭眼类别求梯度 gradients target_layer.gradient weights torch.mean(gradients, dim(2,3), keepdimTrue) # 加权求和生成热力图 cam torch.sum(weights * features, dim1, keepdimTrue) cam F.relu(cam) cam F.interpolate(cam, size(224,224), modebilinear) return cam.squeeze().cpu().numpy() # 在main.py中调用 if current_state EYE_CLOSING: cam generate_gradcam(model, current_tensor, model.backbone.layer3) heatmap cv2.applyColorMap(np.uint8(255*cam), cv2.COLORMAP_JET) overlay cv2.addWeighted(frame, 0.6, heatmap, 0.4, 0) cv2.imshow(Grad-CAM, overlay) # 答辩时放大此窗口老师问“你怎么知道模型真在看眼睛”——你暂停画面指热力图集中在眼皮区域全场安静三秒。从那以后我每次做毕设都强制走一遍这三步先调阈值看鲁棒性再画报警分布图找规律最后用Grad-CAM截图存证。不是为了炫技而是让每个技术决策都有据可查答辩时老师的问题自然变成“你这个热力图颜色深浅代表什么物理意义”——这才是工程师该有的底气。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。