news 2026/9/28 8:07:39

疲劳检测大作业:Python+OpenCV+Dlib实现眨眼与哈欠识别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
疲劳检测大作业:Python+OpenCV+Dlib实现眨眼与哈欠识别

简介:这是数字图像处理课程大作业的完整实现包,基于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), dtype=np.uint8) faces = detector(test_img, 0) print(f"Dlib环境正常,检测到人脸数量: {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, key=lambda 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代替Dlib,468个点对眼镜的鲁棒性更好,但代码复杂度会上升,大作业不建议临时换方案。

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在正常和疲劳状态下的分布曲线,这张图放文档里,比任何文字都管用。这个方向值得投入的核心价值在于,你既完成了数字图像处理课程的要求,又完整走了一遍“信号采集、特征提取、阈值决策、可视反馈”的工程链路,这套方法论后续无论是做驾驶分心检测、课堂专注度分析还是其他视觉任务,都能直接迁移。希望帮到你,做完之后记得回来调试阈值,那才是真正的开始。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:07:23

麻栗坡做网站避坑速查手册:从被黑挂马到费用拆解全解析

麻栗坡做网站避坑速查手册:从被黑挂马到费用拆解全解析 昨天凌晨三点,一位在麻栗坡做边境贸易的老张给我打电话,声音都在抖。他的外贸独立站突然打不开了,浏览器提示“不安全”,打开全是乱七八糟的赌博广告和色情链接。他慌了,问能不能马上修好。我说别急,先查日志,再查后台,最后查服务器环境。这不仅是技术故障,…

作者头像 李华
网站建设 2026/9/28 8:07:07

避坑flash网站案例:3步搞定保姆级建站教程

避坑flash网站案例:3步搞定保姆级建站教程 找建站公司最怕什么?怕被坑高价,更怕交付一堆过时的技术垃圾。 别急着掏钱,先看看这篇保姆级建站教程,用真实flash网站案例拆解底层逻辑。 很多老板还在问“现在做Flash网站行不行”,其实这就像问“现在骑马送信快不快”。…

作者头像 李华
网站建设 2026/9/28 8:06:39

交通仿真数据输入输出实战解析:以Paramics为例

1. 数据输入输出的全景认知做交通仿真项目这几年&#xff0c;我用过VISSIM&#xff0c;也碰过TransModeler&#xff0c;但Paramics一直是我项目里的主力之一。尤其是做到中后期&#xff0c;你会有个很直观的感受&#xff1a;仿真软件里最能拉开效率差距的&#xff0c;往往不是建…

作者头像 李华
网站建设 2026/9/28 8:06:14

AI编程前如何用Grill-Me、BFS、AFK建立需求闭环

开头我先讲一个让人血压升高的场景&#xff1a;你把需求栏里那段话复制给 AI&#xff0c;让它写个功能&#xff0c;它不到一分钟就甩出几百行代码&#xff0c;你心里想“AI 写代码真方便”&#xff0c;结果一跑&#xff0c;报错&#xff0c;改了半小时&#xff0c;业务场景又对…

作者头像 李华
网站建设 2026/9/28 8:06:14

5个原因导致固定链接wordpress不起作用一文搞懂

5个原因导致固定链接wordpress不起作用一文搞懂 网站被黑挂马不知道怎么办?别慌,先检查你的固定链接设置。很多站长发现WordPress固定链接失效后,直接以为是服务器问题,结果排查半天没头绪。其实,90%的固定链接wordpress不起作用案例,根源都在配置细节上。本文结合10年实操经验,帮…

作者头像 李华
网站建设 2026/9/28 8:06:11

娱乐建设网站新手入门避坑指南3种方案报价

娱乐建设网站新手入门避坑指南3种方案报价 域名服务器搞不懂?别慌,我是做了十年站点的安徽老张。很多新手一上来就问“做个娱乐站要多少钱”,却连域名解析和服务器配置都分不清。这就像装修房子还没量尺寸,先问油漆工要多少钱。 做娱乐类建设网站,核心不是代码多炫,而是 访问速度 和 合规安全…

作者头像 李华