简介文档为一种基于计算机视觉的驾驶员疲劳检测方法及系统的发明专利申请公开文本由扬州大学科研团队提出面向智能驾驶、交通安全及计算机视觉方向的研究者与开发者解决行车过程中驾驶员疲劳状态难以实时监测的问题。资源包仅含1个docx文件大小约15KB完整呈现专利公开文本涵盖权利要求书、说明书、摘要及附图。已有155人学习下载。专利内容从摄像头采集驾驶员脸部视频出发详述了基于类Harr特征的AdaBoost分类器人脸定位、landmark驱动的眨眼与打哈欠检测以及图片预处理结合PERCLOS算法进行疲劳判定的完整技术路线并给出了可融合心率、脑电图等信号构建综合检测系统的扩展方向。读者可借助文档快速理解非接触式疲劳检测的系统架构、核心算法流程及专利文本撰写范式。1. 货车司机在方向盘上睡着前几秒眼睛闭合时间会比正常状态长出一截基于计算机视觉的驾驶员疲劳检测方案盯的就是这个信号。这个专利描述的链路并不复杂摄像头采集脸部视频、类 Haar 特征的 AdaBoost 分类器定位人脸、landmark 做眨眼和打哈欠检测、再按 PERCLOS 准则判定疲劳等级并预警。整套流程完全非接触不需要驾驶员佩戴任何传感器实时性也能满足车载工况。不管你是正在做计算机视觉大作业的高校学生还是想在驾驶监控项目里嵌入一个 DMS 模块的从业者这条技术路线都是最容易被复现的切入点。这类方案的价值在于它没有把“疲劳检测”当成一个单独的分类问题而是拆成了人脸检测、关键点检测、眼睛状态统计三段每一段都有现成算法和成熟库支撑。换句话说你不需要从头训练任何模型按这条链路拼起来就能得到一个可运行的原型。接下来我会按专利的实际流程把每一步的实现方式、参数调法和踩坑经验都过一遍最后讲清楚怎么用一段离线视频验证这套系统到底靠不靠谱。2. 人脸定位类 Haar 特征与 AdaBoost 分类器的落地取舍2.1 类 Haar 特征为什么能当人脸探测器人脸检测是这个系统里的第一道闸门它的稳定性直接决定后面所有步骤的输入质量。在深度学习还没有完全统治目标检测的年代Viola-Jones 检测器就是人脸检测的事实标准而它用的正是类 Haar 特征配合 AdaBoost 分类器。类 Haar 特征的本质是一组黑白矩形模板计算某个矩形区域内像素和与另一个区域的差值用来表达图像的灰度对比关系。人脸结构天然适合这种表达眼睛区域通常比脸颊暗鼻梁区域比两侧亮这些固定的灰度规律不需要大量计算就能捕获。AdaBoost 在其中的角色是从成千上万个候选矩形特征里迭代选出少量“弱分类器”每一个弱分类器只负责判断一个特征是否匹配叠加上百个之后就变成一个强分类器。OpenCV 里封装好的级联分类器 Cascade Classifier 正是这套思想的工程实现。它先在大尺寸窗口上快速筛选绝大多数窗口在早期阶段就被淘汰只有少数候选窗口进入后续的多级验证因此速度非常快在 CPU 上跑实时视频流毫无压力。从选型角度看这套方案在嵌入式设备上的优势非常明显。疲劳检测系统要装在车里面对的往往不是高性能 GPU而是一块 ARM 芯片或者旧款笔记本。深度学习人脸检测精度更高但模型推理的算力开销和内存占用都不小。类 Haar 特征检测器虽然模型精度略有上限但它的推理本质上是积分图加比较运算没有卷积和矩阵乘几百毫瓦级功耗的处理器都能流畅运行。专利选择这个算法在当时的技术条件下是合理且高效的工程决策。2.2 用 OpenCV 跑通人脸检测代码与参数含义无论你最后要部署到什么平台第一步都是在本地把检测链路跑通。OpenCV 内置了基于 AdaBoost 训练好的级联模型文件加载方式非常简单。以下代码是完整的单帧人脸检测函数可以直接套到视频循环里使用。import cv2 # 加载 OpenCV 自带的正面人脸级联分类器 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) def detect_face(frame): # 转灰度图级联分类器只接受单通道输入 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 直方图均衡化把过亮或过暗区域的对比度拉回来 gray cv2.equalizeHist(gray) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, # 每轮检测图像缩小到原来的 1/1.1 minNeighbors5, # 候选框周边至少要有 5 个邻近框才保留 minSize(64, 64), # 人脸最小尺寸小于这个值的窗口直接丢弃 flagscv2.CASCADE_SCALE_IMAGE ) return faces这段代码里有几个关键点需要解释。转灰度是必须的类 Haar 特征只分析亮度关系不需要颜色信息直方图均衡化是我额外加的因为驾驶舱的光照变化非常大逆光时脸部对比度会被压缩均衡化能显著提高检测召回率。detectMultiScale 返回的是一个人脸矩形列表每个矩形是 (x, y, w, h)后面关键点检测就是在这些矩形框内进行的。参数的作用按优先级来说minNeighbors 是影响精度的第一号参数。它要求每个候选窗口周围至少有多少个邻居窗口也通过检测数值越小误检越多数值越大越容易漏检。minSize 则是影响性能的第一号参数——检测窗口越小意味着要在图像上滑动更多次计算量成倍上升。如果把 minSize 从 64 改成 32单帧处理时间可能翻几倍。2.3 尺度、邻域与人脸尺寸三个参数的工程经验这里说一下实际项目中我怎么设参数。驾驶座摄像头一般固定在中控台或后视镜附近驾驶员人脸距离镜头约 40 到 60 厘米普通 720p 画面里人脸宽度大约在 150 到 250 像素之间。这种情况下 minSize 设成 80 到 100 比较合理既能过滤远处乘客的脸又能保证不会因为驾驶员偶尔探头而丢框。如果摄像头是鱼眼或者广角画面边缘人脸会变小那就把 minSize 降到 64同时接受一定的误检。scaleFactor 这个参数经常被误认为是检测精度参数其实它控制的是检测尺度金字塔的步长。设成 1.1 表示每轮检测把图像缩小 0.9 倍相当于检测窗口逐渐变大匹配尺度更精细但帧数会下降。设成 1.3 时速度明显提升但可能错过某些关键尺度上的正脸窗口。在驾驶场景中我建议保留 1.1 到 1.2因为这关系到眨眼检测的稳定性。人脸框如果在尺度上抖动框的大小忽大忽小后续关键点坐标也会跟着波动EAR 曲线就会出现毛刺。还有一个常被忽略的点级联分类器对侧脸和人脸剧烈旋转基本无能为力。驾驶员转头看右侧后视镜时检测框可能会短暂丢失这是算法本身的边界。缓解办法是加一个跟踪器比如 OpenCV 里的 CSRT 或 KCF在分类器连续几帧检测到人脸后用跟踪器接管中间帧检测器只负责周期性校正。这样能把人脸框丢失的概率降下来为后续关键点检测创造稳定输入。3. 人脸关键点提取从人脸框到眨眼和打哈欠的判定3.1 关键点模型怎么选68 点方案与轻量替代拿到人脸框之后下一个任务是定位眼睛和嘴巴的关键点。专利文档里的表述是“利用 landmark 进行眨眼和打哈欠的检测”这里的 landmark 指的就是人脸关键点。最常见的是 dlib 的 68 点模型它把脸分成眉毛、眼睛、鼻子、嘴巴、下颌五个区域每只眼睛周围有 6 个点嘴巴外沿有 12 个点。用这些点的坐标就能算出眼睛的开合度和嘴巴的开合度。选用 dlib 主要有三个理由。第一模型文件是开放下载的不需要自己标注训练第二它提供了清晰的 Python 接口加载模型后直接传人脸框就能返回 68 个点第三模型推理速度在普通 CPU 上能做到每帧 10 到 20 毫秒满足实时要求。缺点也很明显模型文件接近 100MB对嵌入式部署不太友好。如果是资源受限场景可以考虑 MediaPipe 的 Face Mesh它基于 TensorFlow Lite模型更小输出 468 个点但接口和输出格式与 dlib 差异较大代码不能直接复用。从工程稳定性角度我推荐先用 dlib 把流程跑通再根据需要替换成轻量模型。因为 dlib 的输出格式是固定的后续的 EAR、MAR 计算逻辑完全基于点坐标理论上任何能输出对应 6 个眼睛点和嘴部点的模型都可以无缝替换。只要保证点的索引顺序与计算逻辑一致即可。3.2 眨眼检测EAR 的计算与连续帧判决眨眼检测的核心指标叫 EAR即 Eye Aspect Ratio。它利用眼睛周围 6 个特征点计算垂直方向点到水平方向点的比例。眼睛睁开时两组垂直距离相对较大EAR 接近 0.3眼睛闭合时垂直距离趋近于零EAR 可能掉到 0.1 以下。这个指标的优点是方向无关且尺度无关人脸靠近或远离摄像头时EAR 值基本稳定。EAR 的公式可以写成EAR (dist(P2, P6) dist(P3, P5)) / (2 * dist(P1, P4))。其中 P1、P4 是眼睛内眼角和外眼角P2、P3 是上眼睑两个等分点P5、P6 是下眼睑对应的两个点。实现代码非常简单import numpy as np def eye_aspect_ratio(eye_points): 计算眼睛纵横比 EAR eye_points: 按顺序传入 6 个关键点坐标顺序为 [P1, P2, P3, P4, P5, P6]对应眼角/上下眼睑 p1, p4 eye_points[0], eye_points[3] p2, p3 eye_points[1], eye_points[2] p5, p6 eye_points[4], eye_points[5] vertical_a np.linalg.norm(p2 - p6) vertical_b np.linalg.norm(p3 - p5) horizontal np.linalg.norm(p1 - p4) return (vertical_a vertical_b) / (2.0 * horizontal 1e-6)上面最后加了个 1e-6防止水平距离为零时除零报错。实际项目中 alert 使用 1e-6 或更小值常规点检测不会出现这类情况但不妨加上。单帧的 EAR 不足以判断眨眼因为眼睛是一个动态过程EAR 从正常值掉到低值再回到正常值才算完成一次完整眨眼。实现时需要一个状态机上一帧 EAR 大于阈值当前帧 EAR 小于阈值记录眼睛闭合当后续帧 EAR 重新大于阈值时判定完成一次眨眼。这个过程中至少要连续记录 2 到 3 帧闭合才能排除单帧噪声或者眼睑抖动造成的误判。3.3 打哈欠检测MAR 与时间窗口打哈欠用类似的思路但指标换成 MAR即 Mouth Aspect Ratio。嘴部也有 6 个可用关键点分别是左右嘴角和上下嘴唇的等分点。MAR 的计算方式与 EAR 一致只是点的索引不同。正常闭嘴时 MAR 在 0.2 到 0.3 之间嘴巴张开到较大程度时MAR 会超过 0.6 甚至更高。单独看 MAR 瞬态值很容易误报因为说话、喝水、咳嗽都会导致嘴巴张大。关键在于持续时间长度。普通说话时嘴巴张开时间一般不超过半秒而哈欠的张口动作通常会持续 1 秒以上。因此我在判断哈欠时要求连续多帧 MAR 超过阈值并且累计张口时间超过 1000 毫秒才输出一次哈欠事件。这个逻辑用帧计数实现即可因为每帧之间的时间间隔是固定的帧数乘以帧间隔就是持续时间。这里要特别提一下数据来源的质量。如果用的是普通 RGB 摄像头驾驶员嘴巴区域容易受光照阴影影响上下嘴唇关键点在暗光下会抖动MAR 曲线会出现突然的尖峰。一种缓解方式是对连续帧的 MAR 做中值滤波取最近 5 帧的中位数作为当前值能有效抹掉单帧噪声。滤波窗口不宜过大否则会吞掉真实哈欠的起始时刻对后续 PERCLOS 统计造成额外延迟。4. PERCLOS 指标与预警状态机从闭眼比例到疲劳判定4.1 PERCLOS 是什么闭眼时间占比与三个标准PERCLOS 是 Percentage of Eyelid Closure 的缩写字面意思就是单位时间内闭眼时间所占的比例。疲劳检测领域用得最多的是 P80 标准即当眼睑遮住瞳孔面积超过 80% 时认为该时刻眼睛处于闭合状态。统计一段固定时间内闭合帧数占总帧数的比例就得到 PERCLOS 值。为什么要用闭眼比例而不是瞬时眨眼次数来判定疲劳因为疲劳的视觉特征是一段时间内闭眼行为累积而不是某一次眨眼。正常人每分钟眨眼 15 到 20 次每次眨眼持续约 100 到 150 毫秒这个过程中眼睛闭合时间占比大约在 4% 到 6%。当驾驶员疲劳时眨眼频率可能下降但单次闭眼时间拉长到 500 毫秒甚至更长有些接近微睡眠的状态下闭眼时间会达到 3 到 5 秒PERCLOS 值可以轻松超过 20%。因此PERCLOS 天然是一个有累积意义的统计量比单帧状态可靠得多。4.2 用滑动窗口统计 PERCLOS 的代码实现PERCLOS 的实现需要一个统计缓存我一般用 Python 的 deque 做固定长度滑动窗口。窗口大小取 600 帧假设视频帧率 30fps正好对应 20 秒统计时长。更新流程是每一帧计算 EAR 后判断当前眼睛是否处于闭合状态把布尔值写入队列每次取队列中闭眼布尔值占比即可。from collections import deque class PerclosCounter: def __init__(self, window_size600, ear_threshold0.2): self.window deque(maxlenwindow_size) self.ear_threshold ear_threshold def update(self, ear_value): # 判断当前帧是否闭眼写入滑动窗口 is_closed ear_value self.ear_threshold self.window.append(is_closed) def get_perclos(self): # 窗口未满时返回 0避免冷启动阶段误报警 if len(self.window) self.window.maxlen: return 0.0 closed_count sum(self.window) return closed_count / len(self.window) # 每帧调用示例 counter PerclosCounter() counter.update(ear) ratio counter.get_perclos()这个实现的优势是计算复杂度为 O(1)不会随着窗口长度增加而变慢。sum 操作虽然要遍历整个 deque但 600 帧的布尔求和耗时小于 0.1 毫秒完全可以接受。需要注意冷启动问题如果窗口未满就计算比例实际有效样本不足得出的 PERCLOS 值偏低所以返回 0.0 作为占位。实际操作中系统启动后的前 20 秒内不会有任何预警输出这也是合理的安全策略。PERCLOS 的阈值需要结合统计窗口时长来设定。窗口 20 秒时常见的建议阈值在 0.35 到 0.4 之间即 20 秒内有 7 到 8 秒闭眼才触发预警。窗口拉长到 60 秒阈值可以相应下调到 0.3 左右。阈值设太高容易漏报真正疲劳的场景设太低又会在正常眨眼时频繁报警。最稳妥的方法是采集 10 分钟正常驾驶和 10 分钟模拟疲劳状态的数据画出两条 PERCLOS 曲线取两条曲线之间的中间值作为初始阈值。4.3 预警状态机疲劳等级与输出策略单靠一个 PERCLOS 阈值直接决定是否报警在实际项目中不够稳健。我习惯把预警拆成两级PERCLOS 连续 5 秒超过 0.25 时输出“轻度疲劳”提示PERCLOS 连续 10 秒超过 0.4 时输出“深度疲劳”报警。这背后是一个简单的状态机每个状态都有独立的持续时间计数避免因为单次峰值触发报警。下表是一组我自己调试后比较稳的参数可以作为初始配置参考参数项建议值作用EAR 闭眼阈值0.2低于此值判为眼睛闭合眨眼最小闭合帧数3 帧低于此帧数忽略视为噪声MAR 哈欠阈值0.6超过此值判为嘴巴张开哈欠最小持续时长1000 毫秒持续时间超过才计为哈欠PERCLOS 正常阈值0.25轻度疲劳预警触发线PERCLOS 严重阈值0.4深度疲劳报警触发线统计窗口600 帧约 20 秒滑动窗口这里还要考虑一个实际问题帧率波动会影响“帧数”和“时间”之间的对应关系。如果检测算法在部分帧上耗时过大实际帧率会从 30fps 掉到 15fps600 帧窗口对应的真实时间就从 20 秒变成了 40 秒PERCLOS 值的物理意义就会变模糊。所以我在实际工程里会给每个帧打上时间戳统计时用时间戳计算闭眼时长总和再除以总时长而不用简单的帧数比。这样即使帧率有波动PERCLOS 值也始终是时间维度的准确比例。5. 避坑指南疲劳检测系统跑不稳的几个真实问题5.1 驾驶员侧脸看后视镜人脸框直接消失现象驾驶员转头看左侧或右侧后视镜人脸角度超过 45 度级联分类器的人脸框开始一帧有一帧无关键点检测跟着乱跳PERCLOS 统计被大量无意义数据污染。原因类 Haar 特征分类器本质上是正面人脸检测器对侧脸和大幅度旋转不敏感这也是 AdaBoost 级联模型的已知边界。遗传用车场景中驾驶员看后视镜是高频动作几乎每 10 秒就会发生一次。解决在分类器后面加一个固定在框上的跟踪器比如 OpenCV 的 CSRT 跟踪器。分类器检测到人脸后用跟踪器锁定该区域跟踪器连续丢失超过 30 帧才重新启动检测。这样即使侧脸瞬间检测不到跟踪器也能维持框的位置传给关键点检测的输入仍保持稳定。5.2 驾驶员戴墨镜EAR 判断全部失效现象墨镜把眼睛区域完全遮盖关键点模型在墨镜上找不出瞳孔边缘的特征点输出的 EAR 值要么异常偏大要么不断抖动眨眼检测完全失灵。原因dlib 的 68 点模型是在可见光人脸图像上训练的它依赖眉毛、眼睑、虹膜之间的灰度差来定位眼睛点。墨镜会抹掉这些自然特征模型只能凭训练数据里的“平均脸先验”硬猜位置结果当然不靠谱。解决这是视觉方案的物理边界软件层面几乎无法绕过。可行方案有两种一是换用红外摄像头红外光照下墨镜镜片通常不会完全遮挡眼睛轮廓二是在检测策略里做降级处理当双眼 EAR 连续出现异常值时自动降低眨眼和 PERCLOS 的权重转而依靠打哈欠和头部姿态判断疲劳。降级逻辑至少要保证系统不会因为一个墨镜就把整个预警功能关掉。5.3 视频帧率波动PERCLOS 统计结果失真现象代码在本地电脑上跑 CPU 占用率接近 90%帧率从 30fps 掉到平均 17fps日志里的 PERCLOS 值比实际驾驶员状态偏高一倍有时候驾驶员闭眼一秒也会触发严重报警。原因基于帧数比例的 PERCLOS 假设每帧时间相同但检测耗时波动导致帧与帧之间的时间间隔不一致。正常眨眼那几帧恰好落在计算压力大的时间段闭眼帧被相对拉长比例自然偏高。解决每帧计算后记录当前时间戳以真实时间窗口做统计。按“闭眼时长总和 / 总时长”取代“闭眼帧数 / 总帧数”相当于把 PERCLOS 变成一个时间维度指标。代码改动不大但物理意义更准确推荐所有项目都这么做。5.4 夜间低照度环境人脸检测率明显下降现象夜间行车时车内光源不足画面整体偏暗检测器输出的人脸框变小变松关键点模型输出的 EAR 噪声大眨眼检测准确率从日间 95% 掉到 70% 以下。原因类 Haar 特征依赖图像的灰度差异光线不足时脸部和背景的对比度被压缩特征响应变弱。同时摄像头自动增益会把噪点放大关键点在暗部区域的定位抖动加剧。解决在摄像头选型阶段就用红外摄像头或者采用内置红外补光的方案驾驶员脸部在近红外波段下会有稳定反射特征这是车载 DMS 行业的通行做法。如果手里只有普通 RGB 摄像头可以在预处理阶段加强直方图均衡化的强度并且把关键点输出的 EAR 序列做更激进的滤波比如滑动中位数窗口从 5 帧增大到 9 帧。5.5 眨眼和闭眼混在一起报警时机变得“玄学”现象测试者只是眨了一下眼系统报警了测试者确实闭眼两秒系统反而没报警报警触发完全没有可预测的边界。原因EAR 低于阈值时系统只记录“闭眼”但没有区分这是一次时长 150 毫秒的正常眨眼还是一次持续 2 秒的闭眼。如果判定逻辑只看单帧没有要求最低连续帧数眨眼瞬间也会被当成闭眼累积进 PERCLOS报警时间点自然飘忽。解决在闭眼判定中加入“最少连续帧数”约束比如连续 3 帧 EAR 低于阈值才把当前时刻计入闭眼状态。这样正常眨眼的 150 毫秒宽度会被过滤掉而真正的闭眼因为持续超过 3 帧会被完整捕获。类似地从闭眼恢复到睁眼的边界也要等连续 2 帧 EAR 高于阈值后才切换状态防止在阈值边界来回抖动。6. 进阶验证用离线视频与帧级回放把一套疲劳检测系统调可信系统能跑起来只是第一步难的是证明它在不同光照、不同人脸、不同疲劳程度下都稳定。我的习惯是先做一轮离线视频回放验证再谈参数优化。具体做法是录制三段驾驶模拟视频一段正常驾驶、一段轻度疲劳、一段明显疲劳时长各 10 分钟。然后把检测代码跑在每段视频上输出每一帧的 EAR、MAR、PERCLOS 值到 CSV 文件。下一步是手工标注每一帧的闭眼状态正样本是“帧内眼睛闭合超过 80%”负样本是“眼睛张开”。最后把标注结果和检测结果逐帧比对能直观看到误检集中在哪个阶段。评估指标不用太复杂。先看每帧闭眼判定的准确率再看每分钟 PERCLOS 曲线的趋势是否和实际状态一致。一个实用的做法是把三张 PERCLOS 曲线画在同一张图里正常驾驶的曲线应该稳定在 0.1 以下轻度疲劳可能偶尔冲到 0.3明显疲劳应该持续停留在 0.3 到 0.5 区间。如果曲线区分度不够优先调整 EAR 闭眼阈值而不是调 PERCLOS 报警阈值。对于判别阈值的选择我会在标注结果上画一条简单 ROC 曲线遍历从低到高的所有候选阈值找到误报率和漏报率都相对较低的点。通常在PERCLOS 上精度优先项目选 0.35召回优先项目选 0.25。这套系统里另外还有哈欠检测做辅助所以我可以把 PERCLOS 阈值适当调高一点用哈欠事件来兜住漏报整体预警效果反而更好。这个项目如果要扩到真实产品还可以把心率检测、脑电图等信号做融合输入但那段路涉及更多硬件不是单纯软件工程能覆盖的。先把视觉这条链路做扎实已经能覆盖大部分疲劳场景。从那以后我每次接疲劳检测项目都强制走一遍离线视频的帧级回放全程不跳过中间任何一帧这个方法虽然土但比任何可视化监控都更能暴露问题。希望帮到你。本文还有配套的精品资源点击获取