
简介这是数字图像处理课程大作业的完整实现包基于Python编写疲劳检测系统面向计算机、人工智能、自动化等专业学生及课程设计、毕业设计场景。代码已测试运行成功配套文档说明详细可远程教学指导适合快速上手或在此基础上二次开发。压缩包共254个文件包含240个avi测试/结果视频、6个py源代码、1个pdf文档、1个docx说明、1个dat模型文件等整体约300MB其中视频直观展示了拨打手机、打哈欠、闭眼、侧视等检测结果py文件为核心算法实现便于对照学习。已有543人浏览学习资源具备完整的代码、文档与演示素材可帮助理解疲劳检测流程并用于答辩或项目演示。1. 疲劳检测大作业从选题到答辩一套能跑的Python方案如果你正在为数字图像处理课程的大作业发愁疲劳检测是一个几乎不会出错的选题它把图像处理里的经典操作——人脸检测、特征点定位、几何特征计算——串成了一条完整链路而且结果可视化直观答辩时不用费口舌解释“这东西到底有什么用”。基于Python实现的疲劳检测方案本质上就是拿摄像头帧流做人脸关键点追踪再用眼睛开合程度和嘴部状态去推断疲劳程度最终输出一个可量化的“疲劳分数”。这套方案适合三类人一是数字图像处理课程需要交大作业的本科生二是想快速在OpenCV生态里练手的人脸项目初学者三是打算把视觉方案接到驾驶预警或专注度分析里的开发者。它不需要GPU也不依赖深度学习框架一台带摄像头的普通笔记本就能跑。接下来我会把完整的技术选型、代码实现、参数调试和翻车经验一次性讲透让你照着做就能交出一份“能演示、能讲原理、能扛住提问”的大作业。2. 疲劳检测的原理与选型为什么用眨眼频率加眼部纵横比而不是深度学习2.1 数字图像处理里最朴素的那条路人脸关键点与眼部纵横比疲劳检测在数字图像处理课程里最常见的落地方式是用Dlib的68点人脸关键点模型定位眼睛轮廓再通过眼部纵横比Eye Aspect Ratio, EAR判断眼睛开合程度。这个方法的妙处在于它完全绕开了“训练一个分类器判断是否疲劳”的黑匣子思路而是把问题拆解成几个可解释的图像处理步骤灰度化、人脸检测、关键点回归、几何比例计算。EAR的计算非常直观。人眼有六个关键点分别标记左右眼的内眼角、外眼角和上下眼睑当眼睛睁开时上下眼睑的纵向距离相对较大当眼睛闭合时这个距离趋近于零。EAR就是纵向距离和横向距离的比值公式为EAR (||p2 - p6|| ||p3 - p5||) / (2 * ||p1 - p4||)其中p1到p6是眼睛关键点的坐标索引。这个比值基本不受人脸缩放、与摄像头距离远近的干扰因为它是一个归一化的几何量。你要做的就是把连续帧里的EAR值和阈值比较统计闭眼帧的时间和频率。2.2 技术选型对比OpenCV加Dlib加哈欠检测为什么是最稳组合做疲劳检测有三条主流路线选型直接决定大作业的工作量和答辩深度。第一条是深度学习路线比如用YOLO检测人脸再加OpenFace或自建CNN分类眼睛状态。优点是精度高、像真实产品缺点是你需要数据集、标注和时间对课程大作业来说严重超配。第二条是MediaPipe路线它提供Face Mesh的468点人脸网格眼睛关键点比Dlib的68点更密但关键问题是它提取的坐标系是归一化的没有直接给出像素级的关键点坐标你得自己换算而且版本迭代快答辩时被问到“关键点怎么来的”容易答不上来。第三条是OpenCV加Dlib路线也是我推荐的做法。Dlib提供的是shape_predictor_68_face_landmarks.dat模型输入一张人脸ROI输出68个像素坐标点第36到41点是右眼、第42到47点是左眼、第48到67点是嘴巴轮廓。这个模型文件约60MB需要从Dlib官网下载或从GitHub仓库获取但注意不要把这个模型和Dlib库本身的安装混淆。OpenCV负责摄像头读取、灰度化、画框和显示Dlib负责最核心的人脸检测和关键点回归。之所以说这是最稳组合是因为每个环节都是可解释的经典算法你可以在答辩时把每一步的原理写清楚而不用面对“你为什么不解释一下你那个黑盒神经网络在干什么”的尴尬提问。2.3 判定逻辑怎么定单帧阈值、连续帧计数与疲劳指数的关系疲劳判定不是一个EAR低于阈值就立刻报警的简单事因为眨眼本身就是眼睛短暂闭合的过程。正常人的眨眼持续约100到150毫秒如果摄像头帧率是30帧每秒那么一次眨眼大约占据3到4帧。疲劳的早期表现是两种一是单次眨眼持续时间变长超过200到300毫秒二是单位时间内眨眼次数增多或频繁出现持续数秒的闭眼。因此判定逻辑要做两级处理。第一级是单帧判断EAR小于阈值EAR_THRESH就认为当前帧眼睛闭合但这个状态只维持一帧可能是音乐节拍那种级别的瞬间干扰不做处理。第二级是连续判断维护一个连续闭眼帧数计数器连续闭眼超过阈值CLOSED_FRAMES就触发一次“闭眼事件”之后再累加疲劳指数。哈欠检测同理用嘴部纵横比Mouth Aspect Ratio, MAR做判断即上下嘴唇关键点的纵向距离与横向距离的比值。当MAR大于阈值MAR_THRESH且连续保持20帧以上认为是一次哈欠。疲劳的最终判定是综合逻辑单位时间内的闭眼次数过多、或单次闭眼持续过长、或哈欠次数过多三个条件任何一个命中就输出疲劳警告。这套逻辑的优点是每个参数都能在答辩时解释清楚而且可以现场调整阈值演示效果变化。3. 用Python实现疲劳检测从代码结构到参数调优3.1 项目目录与依赖安装跑通前先把环境钉死我一般会建议大作业项目采用这样的目录结构它会让答辩演示时更规范也方便后续扩展。目录里包含主程序、配置文件、文档说明和模型文件完全对应题目要求的“源代码文档说明”fatigue_detection/ ├── main.py # 主程序摄像头读取、帧处理与判定 ├── config.py # 参数配置文件阈值、帧数、显示开关 ├── fatigue_detector.py # 核心检测类封装人脸检测与关键点提取 ├── requirements.txt # 依赖清单 ├── README.md # 文档说明使用说明原理实验数据 └── models/ └── shape_predictor_68_face_landmarks.dat # Dlib关键点模型依赖安装有几个常见坑。Dlib在Windows上用pip直接装容易报错因为需要CMake和Visual Studio构建工具我建议你用pip install dlib前先装好CMake或者直接用pip install dlib-bin这个预编译轮子。OpenCV用pip install opencv-python安装即可注意不要同时装opencv-contrib-python两者会冲突。NumPy是OpenCV和Dlib的共同依赖但版本不能太低建议用pip install numpy --upgrade确保最新。装完依赖后把模型文件放到models目录下然后运行一个最小测试脚本验证环境import cv2 import dlib import numpy as np # 测试Dlib人脸检测器是否正常工作 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) # 生成一张假的测试图像黑色背景避免真实图片路径问题 test_img np.zeros((480, 640, 3), dtypenp.uint8) faces detector(test_img, 0) print(fDlib环境正常检测到人脸数量: {len(faces)})这段代码的核心作用是验证Dlib的人脸检测器和关键点模型能否正常加载。如果你遇到RuntimeError说明模型文件路径错误如果遇到加载库失败的错误说明Dlib编译有问题需要重新安装。跑通这一步后整个项目的地基就算打好了。3.2 核心代码人脸检测、眼部纵横比计算与疲劳判定主程序的核心是一个循环读取摄像头帧、检测人脸、提取关键点、计算EAR和MAR、判定疲劳状态、显示结果。我在fatigue_detector.py里把检测逻辑封装成类这样代码结构清晰也方便你做单元测试。import cv2 import dlib import numpy as np from config import EAR_THRESH, MAR_THRESH, CLOSED_FRAMES class FatigueDetector: def __init__(self, model_path): self.detector dlib.get_frontal_face_detector() self.predictor dlib.shape_predictor(model_path) self.closed_counter 0 # 连续闭眼帧数 self.blink_counter 0 # 总眨眼次数 self.yawn_counter 0 # 哈欠次数 self.fatigue_score 0 # 疲劳指数 staticmethod def compute_ear(eye_points): 计算眼部纵横比返回浮点数 A np.linalg.norm(eye_points[1] - eye_points[5]) B np.linalg.norm(eye_points[2] - eye_points[4]) C np.linalg.norm(eye_points[0] - eye_points[3]) return (A B) / (2.0 * C) staticmethod def compute_mar(mouth_points): 计算嘴部纵横比返回浮点数 A np.linalg.norm(mouth_points[1] - mouth_points[7]) B np.linalg.norm(mouth_points[3] - mouth_points[5]) C np.linalg.norm(mouth_points[0] - mouth_points[4]) return (A B) / (2.0 * C) def process_frame(self, frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces self.detector(gray, 1) # 只处理检测到的第一张脸多脸场景下取最大面积 if len(faces) 0: self.closed_counter 0 return None face max(faces, keylambda r: r.width() * r.height()) landmarks self.predictor(gray, face) # 提取右眼关键点索引36-41、左眼42-47、嘴巴48-67 right_eye np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in range(36, 42)]) left_eye np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in range(42, 48)]) mouth np.array([(landmarks.part(i).x, landmarks.part(i).y) for i in range(48, 68)]) ear (self.compute_ear(right_eye) self.compute_ear(left_eye)) / 2.0 mar self.compute_mar(mouth) # 连续闭眼帧计数 if ear EAR_THRESH: self.closed_counter 1 else: if self.closed_counter CLOSED_FRAMES: self.blink_counter 1 self.closed_counter 0 # 哈欠检测MAR超阈值且持续20帧以上 if mar MAR_THRESH: self.yawn_counter 1 return ear, mar这段代码里最值得注意的参数是self.detector(gray, 1)中的第二个参数1它表示对图像做一次上采样后再检测能提高小脸检测率但代价是速度变慢。如果摄像头分辨率是640x480这个参数保持1是可以的如果跑1080P建议改成0以省时间。FAST人脸检测模式会对画面里的大脸更敏感对侧脸和小脸不太友好这个参数具体调要看你的使用场景。EAR和MAR的计算函数里用np.linalg.norm算欧氏距离这个逻辑是纯几何的答辩时可以直接写在公式推导里。3.3 参数配置与文档说明怎么写让大作业从“能跑”升级到“能讲”配置参数我坚持单独放一个config.py文件不直接散落在主程序里。这样答辩时你可以说“阈值是集中管理、可动态调整的”比把魔法数字写在代码里高级一个档次。基础参数设置如下# config.py EAR_THRESH 0.21 # 眼部纵横比阈值低于此值认为闭眼 MAR_THRESH 0.6 # 嘴部纵横比阈值高于此值认为张嘴 CLOSED_FRAMES 3 # 连续闭眼多少帧算一次眨眼 YAWN_FRAMES 20 # 嘴部持续张开多少帧算一次哈欠 BLINK_RATE_THRESH 15 # 每分钟眨眼次数超过该值视为疲劳倾向 YAWN_COUNT_THRESH 3 # 连续三分钟哈欠次数超过该值视为疲劳倾向这些参数不是随手填的。EAR阈值在0.2到0.25之间是大多数文献使用的区间我建议你做一个简单实验拿摄像头拍自己正常睁眼和闭眼各100帧统计EAR的均值和标准差把闭眼均值与睁眼均值的中间值作为阈值这比直接抄参数更有说服力。CLOSED_FRAMES参数决定眨眼判定的灵敏度设置为3意味着在30FPS下闭眼超过100毫秒才算一次眨眼基本过滤掉了瞬间闪烁。如果测试中发现眨眼计数偏高或偏低优先调整这个参数而不是改EAR阈值。README.md文档说明是大作业评分的重要部分。我建议结构按四个部分写项目介绍、技术原理、使用说明、实验结果与分析。技术原理部分重点写EAR公式推导和PERCLUS指标的定义。实验结果部分一定要放一张你自己测的表格记录正常状态、疲劳状态、戴眼镜状态下各测一分钟的眨眼次数和EAR均值用数据证明检测有效。很多同学只贴代码不贴数据答辩时被问“你的准确率是多少”就答不上来提前做了实验数据现场演示和提问环节都能稳很多。4. 避坑指南疲劳检测大作业常见的5个翻车现场4.1 翻车现场一Dlib安装失败卡在Python环境半天出不来现象是pip install dlib在Windows上长时间卡在构建中或直接报CMake must be installed to build dlib。原因在于Dlib的源码需要用CMake和C编译器编译很多同学的Python环境是刚装好的Anaconda没装Visual Studio Build Tools。解决方法是换安装源和预编译包。先执行pip install cmake再执行pip install dlib-bin这步绕开了源码编译。如果dlib-bin也失败就检查Python版本是否在3.7到3.10之间Dlib预编译包对Python 3.11以上的支持还不完善建议直接新建一个Python 3.9的conda环境避免浪费半天时间。4.2 翻车现场二摄像头打不开或画面全黑现象是代码运行后窗口闪现就崩溃或者窗口出现但画面是黑的。原因通常有两个一是OpenCV的摄像头索引写错笔记本内置摄像头索引是0外接摄像头可能是1或2二是摄像头被其他软件占用比如你开着腾讯会议没退出。解决方法是写一个三行探测脚本遍历索引0到2读取摄像头能读到就打印索引号然后到驱动设置里确保没有其他软件锁定摄像头。如果连续读取20帧失败就报错退出。还有一个小概率事件是驱动用到了MJPEG格式OpenCV读取失败这种情况下要手动设置cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G))。4.3 翻车现场三戴眼镜同学的EAR值整体偏高导致漏检这是最典型的血泪经验。戴眼镜时眼镜边框和反光会干扰关键点回归结果就是眼睛明明闭上了EAR值仍然在0.25以上阈值0.21怎么都触发不了。现象就是系统对戴眼镜的人几乎不做疲劳判断。原因是Dlib的关键点模型在眼镜遮挡区域的特征提取不稳定关键点会漂移到眼镜框边缘。解决方法是给EAR阈值设置一个偏移量比如戴眼镜的同学阈值从0.21下调到0.18同时把CLOSED_FRAMES从3下调到2来补偿灵敏度。这种做法虽然牺牲了一点点误报率但能保证戴眼镜场景不失效。另一种做法是直接用MediaPipe的Face Mesh代替Dlib468个点对眼镜的鲁棒性更好但代码复杂度会上升大作业不建议临时换方案。4.4 翻车现场四侧脸检测失败一转头就丢失人脸现象是正面坐着没问题稍微向左转头人脸框就追踪丢失了然后是连续闭眼计数直接清零。原因是Dlib的人脸检测器基于HOG和线性SVM训练对正面人脸表现最好侧脸超过45度就很容易漏检。解决办法分两层。第一层是代码层面可以把detector(gray, 1)里的上采样参数从1提到2提高小脸和侧脸的检测灵敏度代价是高分辨率下帧率会掉到15FPS左右。第二层是运行环境层面在答辩演示时提醒被测者尽量正对摄像头这是最实际的方案。如果你的大作业想展示更强的追踪能力可以考虑换用dlib.correlation_tracker做关键点追踪在检测到人脸后用追踪器锁住这样可以撑住更大角度的偏转但代码量会增加约80行。4.5 翻车现场五眨眼计数爆炸一根头发丝飘过都算眨眼现象是正常盯着屏幕一分钟系统报眨了30多次眼明显高于正常人每分钟15到20次的水平。原因大多数时候不是算法错了而是阈值太敏感或摄像头帧率太低。当帧率只有10FPS左右时一次真实的眨眼只占用1到2帧CLOSED_FRAMES 3就永远不满足于是计数逻辑走到了“单帧闭眼但不足3帧也算一次”的歧路。解决方法是把帧率因素纳入计数逻辑。我在实际项目中会先测一下cap.get(cv2.CAP_PROP_FPS)如果低于15就自动把CLOSED_FRAMES改成2。同时把眨眼判定加一个消抖窗口即连续出现“闭眼-睁眼”模式才算眨眼单纯一帧闭眼后没有平滑过渡到睁眼的认为是噪声。代码里加一个last_ear_state变量只在状态从低变高时计数能去掉大半毛刺。5. 让检测更鲁棒光照归一化、座椅姿态提示与答辩演示技巧最后这一章要解决的是“同样的代码别人演示效果比你好”的问题核心在于三个进阶处理光照归一化、姿态自适应性、以及答辩时怎么说清楚系统的边界。光照归一化的推荐做法是在进入人脸检测前对灰度图做一次直方图均衡化用OpenCV的cv2.equalizeHist实现。这个操作的原理是把图像的灰度分布拉开让暗部的脸部特征更明显。实现时注意只对灰度图做不要对RGB三通道分别做再合成否则会引入色偏。加这个操作后在背光或侧光环境下的检测稳定性会明显提升代码成本只有一行。另一个做法是CLAHE自适应直方图均衡化对局部对比度保护更好但如果答辩时间有限普通均衡化足够讲清楚。姿态自适应方面可以做一个简单的固定逻辑如果连续30帧检测不到人脸就在画面上提示“请正对摄像头”而不是静默丢帧。这个逻辑虽然简单但能在答辩现场避免尴尬的“老师我转个头就检测不到了”事故。实现上就是维护一个no_face_counter变量超过阈值就把提示文本画到画面上。同时可以把脸部检测区域裁剪后做个水平翻转来增加数据多样性但这部分属于锦上添花不建议在课业压力大的时候强行加。演示技巧上我建议准备一条“阈值调整”的现场演示路径。在config.py里把阈值参数通过argparse暴露成命令行参数演示时先用默认参数跑一段正常状态然后现场把EAR_THRESH从0.21改成0.35系统立刻会判定“持续闭眼”再改回0.21恢复正常。这个操作能直观显示你理解这个系统怎么工作、参数怎么影响输出比干讲一大堆原理更有说服力。同时也准备好一次“戴帽子戴眼镜侧脸”的失败演示案例主动说明系统在这种情况下的局限性反而比藏着掖着更让答辩老师认可。我自己的血泪教训是疲劳检测大作业的分数四成在代码能不能跑通六成在你能不能讲清楚每个参数为什么这么设。拿自己的视频录一段测试数据统计EAR在正常和疲劳状态下的分布曲线这张图放文档里比任何文字都管用。这个方向值得投入的核心价值在于你既完成了数字图像处理课程的要求又完整走了一遍“信号采集、特征提取、阈值决策、可视反馈”的工程链路这套方法论后续无论是做驾驶分心检测、课堂专注度分析还是其他视觉任务都能直接迁移。希望帮到你做完之后记得回来调试阈值那才是真正的开始。本文还有配套的精品资源点击获取