☰
基于OpenCV与关键点检测的疲劳驾驶检测系统实战解析
2026/10/10 1:33:11 网站建设 项目流程

简介:这是一份基于Python与OpenCV实现的疲劳驾驶检测项目完整工程,面向计算机相关专业正在准备毕业设计的学生,也适用于课程设计、期末大作业等实战场景。项目主要利用摄像头实时采集驾驶员面部图像,结合人脸关键点检测与眼部纵横比等指标,对瞌睡状态进行判断并给出提示,适合希望快速完成可运行Demo的开发者参考。压缩包共4个文件,包含主程序py脚本、人脸关键点模型dat文件、中文字体ttc及说明txt,整体大小74.52MB,结构简洁、便于直接导入使用。所有源码和数据经过严格调试,下载解压后即可运行,能够作为毕设核心模块或功能演示直接提交。目前已有1174人学习下载,适合急需完整方案、想要节约开发时间并快速理解疲劳检测实现思路的读者。

1. 疲劳驾驶检测不只是人脸识别:先想清楚你要在视频里“看”什么

拿到一个基于 Python + OpenCV 的疲劳驾驶检测毕业设计源码包,很多人的第一反应是打开摄像头看它能不能识别出人眼。实际跑起来你会发现,真正难的不是人脸检测,而是“怎么判断这个人困了”——眨眼太快会被漏掉,低头玩手机又会被误判成打瞌睡,夜间光线一变整个检测全乱,戴眼镜的同学一入镜,程序直接“看不见”他了。这个方向解决的是一类视频流里的状态估计问题:把人脸检测、关键点定位、眼睛纵横比、嘴部纵横比和连续帧统计串成一条链路,最终输出一个可报警的疲劳评分。它适合正在做毕业设计、课程设计,或者想用视觉技术完整走一遍图像采集、特征计算、逻辑判定和结果可视化流程的开发者。下面这些经验,按“能跑通、能答辩、能扩展”的顺序讲。

2. 方案选型与疲劳指标:为什么用 Python + OpenCV,而不从目标检测做起

疲劳驾驶检测在工程上有多种实现形态,车载设备、手机端、后台视频分析各有一套。但放到毕业设计或个人项目场景,最可靠的做法是用 Python 调 OpenCV 做基础图像处理,配合 dlib 做人脸关键点提取,再用简单的纵横比公式量化睁眼、闭眼和打哈欠。这套组合不依赖显卡,不要求大规模标注,单摄像头就能达到实用的判定精度。先把方案选型的理由说明白,后面调试代码时才不会反复换思路。

2.1 三条技术路线对比:传统关键点、深度目标检测、头部姿态估计

刚接触这个题目的人容易纠结:要不要用 YOLO 直接把眼睛状态检测出来?要不要用姿态估计模型算头部角度?我的建议是先看清三条路线的代价差异。

路线精度速度实现成本适合场景
OpenCV + dlib 关键点中高,CPU 实时可跑低,无需训练单摄像头、固定安装位、个人项目
深度目标检测直接回归眼睛状态高中低速,依赖显卡高,需要标注数据和训练多姿态、多遮挡、光线复杂的工业场景
头部姿态估计辅助指标中中配合点头检测,不适合单独做疲劳判定

传统关键点方案的核心优势是数据需求小。dlib 的 68 点人脸关键点模型是现成的,输入一张人脸灰度图,输出眉毛、眼睛、嘴巴、下巴共 68 个坐标点。疲劳判定只需要其中眼睛周围 12 个点和嘴巴周围 6 个点,不需要训模型、不需要手工标数据,这在毕设时间线下非常关键。深度检测方案听着高级,但数据标注和训练周期往往比写整个检测逻辑还长,答辩演示时还容易因为环境换一台机器就崩溃。我的建议是:先传统关键点方案把全流程跑通,如果时间和算力允许,再把其中某个模块替换成深度学习模型做改进点。

2.2 眼睛纵横比 EAR、嘴部纵横比 MAR 与 PERCLOS:三个指标的计算口径

疲劳判定不能只看一张图,要看一段时间内眼睛和嘴巴的变化。最常用的量化指标有三个。

眼睛纵横比 EAR 衡量眼睛睁开程度。取眼睛周围 6 个关键点,垂直方向两个距离的平均值除以水平方向一个距离。睁眼时 EAR 一般在 0.25 到 0.3 之间,闭眼时趋近于 0。工程上常取 0.21 作为闭眼判定阈值。嘴部纵横比 MAR 衡量嘴巴张开程度,嘴部 6 个关键点的垂直距离除以水平距离,打哈欠时 MAR 会明显超过 0.6。PERCLOS 是闭合时间占比,统计一个时间窗口内眼睛闭合帧数占总帧数的比例,例如 60 秒内闭眼帧占比超过 20% 就判定为疲劳。

这三个指标口径要提前定死,否则代码写一半会乱。我的做法是:EAR 和 MAR 管“单帧状态”,PERCLOS 管“时间窗口”。单帧判定容易抖动,所以 EAR 和 MAR 要先做平滑;PERCLOS 用于排除偶尔一下闭眼造成的误报。指标定义清楚,后面第 4 章的代码就是按这个口径落地的,改任何一个阈值都不会影响整体结构。

2.3 模块拆分先行:先画好数据流再动手写类

动手写代码前,先把数据流画出来:视频源 -> 人脸检测 -> 人脸关键点 -> EAR/MAR 计算 -> 时间窗口统计 -> 疲劳报警输出。这个数据流决定了工程的目录结构。人脸检测是一个模块,关键点提取是一个模块,指标计算是一个模块,统计判定是一个模块。好处是后续任何一个环节想换实现,都不会牵连其他部分。比如今天用 dlib 的检测器,明天想换成 OpenCV 的 DNN 人脸检测,只需要替换人脸检测模块,EAR、MAR、PERCLOS 的逻辑完全不动。我见过不少源码把检测、指标计算、报警全揉在一个循环里,最后想调一下闭眼判定逻辑,改一行代码要连带看十分钟,这是典型的翻车现场。

3. 环境搭建与数据准备:让源码在你机器上第一次跑起来

很多毕设源码包下载后跑不起来,多半不是代码问题,而是环境问题。疲劳驾驶检测最少涉及 OpenCV、dlib、NumPy、SciPy 四个依赖,其中 dlib 的安装最容易把新手卡住。这个项目的数据部分也别急着忽略:先搞懂原始视频、标注文件、样本图片分别放在哪,后面调试和答辩展示才能用上。

3.1 虚拟环境与依赖安装:OpenCV、dlib、SciPy 的版本搭配

打开终端,先建虚拟环境,避免把全局 Python 环境搞乱。Windows 下推荐用下面这套命令:

python -m venv venv venv\Scripts\activate python -m pip install --upgrade pip pip install opencv-python dlib scipy imutils

Linux 或 macOS 下把第二行换成source venv/bin/activate。依赖清单里 NumPy 会随 OpenCV 自动装上,但建议显式写进 requirements.txt,方便以后换机器复现。requirements.txt 里标最低版本就够用:

opencv-python>=4.5 dlib>=19.22 numpy>=1.21 scipy>=1.7 imutils>=0.5.4

逻辑说明:OpenCV 负责视频读取、图像预处理和画面绘制;dlib 提供人脸检测和人脸 68 点关键点模型;SciPy 用于计算欧氏距离,避免自己手写平方根;imutils 是工具库,后面做人脸区域裁剪时会省几行代码。我在 Windows 上遇到过很典型的一个坑:pip install dlib直接卡在编译阶段,日志里出现一堆 C++ 报错。原因是 dlib 在 Windows 上默认用源码编译,需要 Visual Studio 的 C++ 生成工具,很多人没装。解决路径有两条:一是先装 “Visual Studio Build Tools” 再重新 pip install;二是找与你 Python 版本匹配的预编译 whl 文件直接安装。毕业设计场景不推荐自己编译,浪费的时间够写一整个标注脚本了。

python -c "import cv2, dlib, numpy; print(cv2.__version__, dlib.__version__)"

这一条命令用来验证安装结果。能正常打印版本号,说明环境基本通了;如果 import dlib 报错 DLL load failed,多数是预编译包与 Python 版本不匹配,换个版本重新装。

3.2 数据目录怎么摆:视频样本、标签文件与模型文件

拿到“源码+全部数据”的包,先别一股脑把代码和数据混在一起。常见的工程组织方式是一个顶层项目目录,下面分代码区、模型区、数据区。我习惯按这个结构整理:

project/ ├── main.py # 主程序入口 ├── config.py # 阈值、路径等参数 ├── detectors/ │ ├── __init__.py │ ├── face_detector.py # 人脸检测封装 │ └── landmark_detector.py # 关键点提取封装 ├── metrics/ │ ├── __init__.py │ ├── ear.py # 眼睛纵横比 │ ├── mar.py # 嘴部纵横比 │ └── perclos.py # 时间窗口统计 ├── models/ │ └── shape_predictor_68_face_landmarks.dat # 关键点模型 └── data/ ├── raw/ # 原始视频 ├── labels/ # 人工标注 CSV └── samples/ # 睁眼/闭眼/哈欠样本图片

数据目录是“全部数据”的核心。raw 里通常放几段不同场景的视频,白天正面、戴眼镜、夜间低照度各一两条;labels 里是每段视频对应的状态标注文件,格式一般是一行一个状态:帧号或时间戳加状态编号;samples 里是从视频里截出来的单张人脸样本,用来验证分类器和调阈值。模型文件要单独放,不要丢在代码文件夹里,否则打包传送时体积大还容易漏。拿到数据后先做的事不是跑代码,而是把任意一段视频和它的标注文件打开对一遍,确认“闭眼”的标注标准和代码里的判定标准一致,否则后面所有评估都是白做。

3.3 给视频打时间戳标签:一个 30 分钟能写完的标注脚本

如果你拿到的数据里缺少部分标注,或者想补充自己的测试视频,需要一个小工具。我一般用 OpenCV 逐帧播放视频,按键盘数字键给当前帧打状态标签,输出到 CSV。下面这段代码够用:

# label_frames.py import cv2 VIDEO_PATH = "data/raw/sample_01.mp4" # 待标注视频 OUT_CSV = "data/labels/sample_01.csv" # 标注输出 cap = cv2.VideoCapture(VIDEO_PATH) fps = cap.get(cv2.CAP_PROP_FPS) # 视频帧率 frame_idx = 0 labels = [] # 每个元素是 (frame_idx, state) print("按键说明: 0=正常 1=闭眼 2=哈欠 空格=下一帧 q=退出") while True: ret, frame = cap.read() if not ret: break cv2.putText(frame, f"frame: {frame_idx}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("label", frame) key = cv2.waitKey(1) & 0xFF if key == ord(" "): frame_idx += 1 elif key in (ord("0"), ord("1"), ord("2")): labels.append((frame_idx, int(chr(key)))) frame_idx += 1 elif key == ord("q"): break else: frame_idx += 1 cap.release() cv2.destroyAllWindows() with open(OUT_CSV, "w", encoding="utf-8") as f: for idx, state in labels: f.write(f"{idx},{state}\n")

逻辑说明:每按一个数字键,就把当前帧号和状态写入内存列表,最后统一写文件。用帧号而不是时间戳,是为了后面与算法输出的帧号直接对齐,省去换算。参数说明:waitKey(1)里的 1 表示等待按键的毫秒数,值越大播放越慢;把“下一帧”和“打标签”分开按键,是为了防止手滑误标。标注时最怕状态定义不一致,所以我规定 0=正常、1=闭眼、2=哈欠,和后面 PERCLOS 统计代码里的状态编号保持一致。

4. 核心检测实现:从视频帧到疲劳报警的代码全流程

这一章进入正题:把疲劳检测算法写到能跑的程度。整个实现分四步:视频读入与人脸检测、关键点提取与纵横比计算、疲劳判定逻辑、主流程整合。你可以把第 4 章当成一套可复用的骨架,改改阈值就能用到自己的视频上。

4.1 视频读入与人脸检测:跳过帧的节奏控制

疲劳检测处理的是持续视频流,每一帧都做完整的人脸检测非常浪费。dlib 的人脸检测器在 CPU 上单帧耗时几十毫秒,看起来不快,但如果每帧都跑,帧率会被拖到个位数。我一般用一个跳帧策略:每隔 N 帧做一次全图人脸检测,中间帧直接沿用上一帧的人脸框,只在框内做关键点提取。

# detectors/face_detector.py import cv2 import dlib class FaceDetector: def __init__(self, model_path): # 使用 dlib 的 HOG 人脸检测器 self.detector = dlib.get_frontal_face_detector() def detect(self, gray, frame_idx, prev_rect=None): # 每 2 帧全图检测一次,其他帧用上一帧结果 if frame_idx % 2 == 0 or prev_rect is None: rects = self.detector(gray, 0) if len(rects) > 0: return rects[0] # 默认只跟踪最大人脸 return None return prev_rect

逻辑说明:frame_idx % 2 == 0就是跳帧开关,2 代表每两帧检测一次全图,中间一帧直接沿用上一帧结果。detector(gray, 0)里的 0 是上采样次数,0 表示不放大图像,检测速度最快;如果小尺寸人脸经常漏检,可以改成 1,但速度会变慢。参数说明:默认只取第一个检测框,也就是跟踪画面里最大的人脸,适合车内单人场景。如果视频里会同时出现多个人,后面就需要做人脸框跨帧匹配,这是第 6 章要讲的进阶点。

4.2 关键点提取与眼睛纵横比计算:睁眼闭眼只看 6 个点

人脸框有了以后,在这个框内提取 68 个关键点。dlib 的shape_predictor_68_face_landmarks.dat模型输入灰度人脸图和人脸框,输出 68 个坐标点。其中左眼是第 36 到 41 号点,右眼是第 42 到 47 号点,嘴部外轮廓是第 48 到 59 号点。计算 EAR 时只需要每只眼睛的 6 个点。

# metrics/ear.py from scipy.spatial import distance as dist def eye_aspect_ratio(eye_points): # eye_points 是包含 6 个 (x, y) 坐标的数组 # 垂直距离:第2-第6点、第3-第5点 vertical_a = dist.euclidean(eye_points[1], eye_points[5]) vertical_b = dist.euclidean(eye_points[2], eye_points[4]) # 水平距离:第1-第4点 horizontal = dist.euclidean(eye_points[0], eye_points[3]) ear = (vertical_a + vertical_b) / (2.0 * horizontal) return ear

逻辑说明:眼睛睁开时,上下眼睑距离大,EAR 值高;闭眼时上下眼睑几乎重合,EAR 值趋近 0。除以水平距离是为了归一化,不同人脸大小下 EAR 数值可比。参数说明:6 个点的顺序不能错,dlib 输出的 36 到 41 号点就是顺时针排列的眼角、上眼皮、下眼皮,直接用索引访问即可。第 15 行2.0 * horizontal里的 2.0 是 EAR 公式的固定分母,不要改成其他值,否则阈值全部要跟着变。同理 MAR 用嘴部外轮廓 48 到 53 号的 6 个点,垂直距离除以水平距离,闭着嘴时约 0.2,打哈欠时超过 0.6。

4.3 疲劳判定逻辑:连续帧计数、滑动窗口与报警条件

单帧的 EAR 浮动很大,不能看一眼小于阈值就报警。工程上要做两层处理:一是滑动窗口平滑 EAR,把偶尔的抖动过滤掉;二是连续帧计数,只有持续闭眼超过一定帧数才触发报警。

# metrics/ear.py 续 from collections import deque class FatigueCounter: def __init__(self, ear_threshold=0.21, mar_threshold=0.6, consec_frames=15, window=60): self.ear_threshold = ear_threshold self.mar_threshold = mar_threshold self.consec_frames = consec_frames self.ear_buffer = deque(maxlen=5) # EAR 滑动窗口 self.closed_eye_frames = 0 # 连续闭眼计数 self.total_frames = 0 # 时间窗口总帧数 self.closed_frames = 0 # 时间窗口内闭眼帧数 self.window = window # PERCLOS 窗口长度 def update(self, ear, mar): self.ear_buffer.append(ear) ear_smooth = sum(self.ear_buffer) / len(self.ear_buffer) is_closed = ear_smooth < self.ear_threshold is_yawning = mar > self.mar_threshold if is_closed: self.closed_eye_frames += 1 self.closed_frames += 1 else: self.closed_eye_frames = 0 self.total_frames += 1 if self.total_frames > self.window: self.total_frames = self.window perclos = self.closed_frames / self.total_frames fatigue_alarm = (self.closed_eye_frames >= self.consec_frames or perclos > 0.2) return ear_smooth, perclos, fatigue_alarm

逻辑说明:ear_buffer用deque(maxlen=5)只保留最近 5 帧的 EAR,取均值作为平滑后的 EAR,5 帧在 30 帧每秒的视频里对应约 0.17 秒,足够滤掉眨眼瞬间的抖动,又不会让真实闭眼被平均掉。closed_eye_frames记录连续闭眼帧数,连续 15 帧闭眼才触发一次报警,15 帧在 30 fps 下是 0.5 秒,符合正常人眨眼 0.2 秒左右的特征,能很好地区分眨眼和瞌睡。PERCLOS 窗口 60 帧,也就是 2 秒,占比超过 20% 即闭眼超过 12 帧,作为疲劳状态的一个辅助参考。参数说明:ear_threshold=0.21和mar_threshold=0.6是经验起始值,不同的人眼型、摄像头角度下需要微调,第 5 章会展开讲怎么调。

4.4 主流程整合:把检测、统计、报警串成一条线

前面的模块都是零散的,需要一条主循环把它们串起来。主程序负责打开摄像头或视频文件、调用人脸检测和关键点提取、计算指标、更新计数器、在画面上画框和文字、触发报警。

# main.py import cv2 import dlib from detectors.face_detector import FaceDetector from metrics.ear import eye_aspect_ratio, FatigueCounter LANDMARK_MODEL = "models/shape_predictor_68_face_landmarks.dat" # 左眼 36-41,右眼 42-47 LEFT_EYE = list(range(36, 42)) RIGHT_EYE = list(range(42, 48)) face_detector = FaceDetector(None) landmark_predictor = dlib.shape_predictor(LANDMARK_MODEL) counter = FatigueCounter() prev_rect = None frame_idx = 0 cap = cv2.VideoCapture(0) # 0 表示摄像头,也可以换成视频文件路径 while True: ret, frame = cap.read() if not ret: break frame_idx += 1 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) rect = face_detector.detect(gray, frame_idx, prev_rect) if rect is not None: shape = landmark_predictor(gray, rect) points = [(shape.part(i).x, shape.part(i).y) for i in range(68)] left_eye = points[LEFT_EYE[0]:LEFT_EYE[-1] + 1] right_eye = points[RIGHT_EYE[0]:RIGHT_EYE[-1] + 1] ear = (eye_aspect_ratio(left_eye) + eye_aspect_ratio(right_eye)) / 2.0 ear_smooth, perclos, alarm = counter.update(ear, 0.0) cv2.rectangle(frame, (rect.left(), rect.top()), (rect.right(), rect.bottom()), (0, 255, 0), 2) cv2.putText(frame, f"EAR: {ear_smooth:.2f}", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(frame, f"PERCLOS: {perclos:.2f}", (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if alarm: cv2.putText(frame, "FATIGUE!", (20, 100), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) prev_rect = rect cv2.imshow("fatigue_detector", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

逻辑说明:主循环里帧计数、灰度转换、人脸检测、关键点提取、EAR 计算、计数器更新是按数据流顺序执行的。LEFT_EYE和RIGHT_EYE用 range 取索引,保证传给eye_aspect_ratio的点顺序是连续的。报警这里先用画面文字提示,如果要声音报警,可以在 alarm 分支加一个系统蜂鸣调用,Windows 下用winsound.Beep(1000, 500)。参数说明:cv2.VideoCapture(0)里的 0 是设备索引,只有内置一个摄像头时用 0;外接摄像头可能要用 1 或 2。cv2.waitKey(1)控制每帧显示间隔,值越小播放越快,1 在 30fps 视频里接近正常速度。

5. 避坑与排查:疲劳检测里最常翻车的 5 个问题

这一章全部是我的血泪经验。疲劳检测代码写出来很容易,但放在真实环境里跑,各种问题会轮着出现。以下 5 个问题是我在不同项目、不同机器上反复遇到的,按频率从高到低排列。

5.1 睁着眼却报闭眼:EAR 跳变与滑动窗口

现象:测试者明明睁着眼睛,程序却偶尔弹一下疲劳报警,尤其侧脸或低头时频繁发生。原因:人脸关键点在人脸轻微转动时会抖动,眼角的点稍微偏移,EAR 就从 0.28 掉到 0.2 以下。解决:不要用单帧 EAR 直接判断,加滑动窗口均值。把 4.3 节里的deque(maxlen=5)改成maxlen=8,平滑力度更大,但如果闭眼持续时间很短,也会被平滑掉。折中值是 5 到 6 帧。另外可以把 EAR 低于阈值后必须持续至少 10 帧才能记为“闭眼”,这样偶发单帧跳变不会影响计数。

5.2 帧率低到不能看:每帧都做全图检测的代价

现象:程序跑起来画面一卡一卡,帧率只有个位数。原因:Windows 下 dlib 的 HOG 人脸检测器每帧全图扫描,CPU 占用居高不下。解决:跳帧策略是第一步,把frame_idx % 2改成frame_idx % 3,即每 3 帧全图检测一次。如果还卡,第二个手段是缩小检测图像,把灰度图缩到 320 宽再喂给人脸检测器,检测框坐标再映射回原图。实际测试里,这两步能省下一半以上的 CPU 时间。注意不要压缩得太狠,否则小尺寸人脸检测不到。

5.3 戴眼镜就失灵:关键点遮挡的应对

现象:测试者一戴上眼镜,关键点就乱飘,甚至人脸框都检测不到。原因:镜框反光把眼睛区域的关键点特征破坏,夜间或侧光时更明显。解决:先对灰度图做直方图均衡化,增强局部对比,代码是gray = cv2.equalizeHist(gray),这一步对反光有一定抵消作用。如果还是不行,改用人脸区域裁剪:先检测人脸框,框内放大 1.2 倍后再提关键点,让眼睛区域的像素面积更大。我在实际测试中发现,戴深色粗框眼镜时关键点基本不受影响,浅色镜框或反光严重的镜片会掉点,均衡化能救回来一半。

5.4 换了个人阈值全错:固定阈值的局限

现象:开发者自己测得好好的,换一个人坐进来报警频率完全不对,要么乱报,要么全程沉默。原因:EAR 的绝对值受眼型、双眼皮、眼妆影响,不同人的闭眼阈值能差出 20%。解决:做一个开机自适应标定。程序启动后先提示被测者保持睁眼状态 2 秒,采集 60 帧的 EAR 均值作为 baseline,再让被测者闭眼 2 秒,采集闭眼均值,阈值取两者中点。代码逻辑很直观:阈值不再是固定 0.21,而是每个用户动态算出来的个体值。这个功能写在毕设里也是加分项,答辩时直接演示“不同人自适应”。

5.5 启动就报错或中途退出:模型路径与设备占用问题

现象:双击运行马上抛出文件找不到,或者跑到一半提示摄像头被占用,程序直接退出。原因:模型文件路径写死成绝对路径,换台机器必然错;摄像头被其他软件占用,OpenCV 拿不到设备;视频路径带有中文名时部分平台会解码失败。解决:模型路径统一用相对路径,并在程序启动时加一段存在性校验,缺失就打印明确提示而不是让 traceback 刷屏。摄像头打开前先用cap.isOpened()判断,打不开就输出提示。项目里所有路径一律用英文命名,中文路径在 Windows 上小心出幺蛾子。我自己的习惯是启动时先打印一份配置摘要,把模型路径、视频路径、阈值全部输出一遍,确认无误再进入主循环。

6. 进阶:把“能跑”变成“可信”,以及我现在的习惯

能跑通只是第一步,答辩或实际部署需要证明它“可信”。我的做法是准备一段模拟疲劳视频:录制一个人正常驾驶 20 秒,然后做出几次明显闭眼和打哈欠动作,人工标注出“闭眼开始”和“闭眼结束”的时间点。跑完算法后,对比报警时刻与人工标注时间,报警延误在 3 秒以内,说明系统是可用的。评估代码可以这样写:

# evaluate.py import csv def load_labels(path): labels = [] with open(path) as f: for line in f: frame, state = line.strip().split(",") labels.append((int(frame), int(state))) return labels def main(): truth = load_labels("data/labels/sample_01.csv") pred = load_labels("data/labels/sample_01_pred.csv") # 按帧对齐,计算状态一致的帧占比 min_len = min(len(truth), len(pred)) hits = sum(1 for i in range(min_len) if truth[i][1] == pred[i][1]) print(f"准确率: {hits / min_len:.2f}")

注意评估时不能只看准确率,因为正常睁眼帧数远多于闭眼帧数,随便全标正常也能拿高分。要分开看闭眼帧的召回率,也就是真实闭眼帧里有多少被算法识别出来,这个数字才是疲劳报警的核心质量指标。

进阶方向有三条:一是疲劳评分分级,把 PERCLOS、EAR 均值、持续闭眼时长合并成一个 0 到 100 分的疲劳分,输出变化曲线;二是事件日志,每次报警记录时间戳和当时的 PERCLOS,方便回放;三是多目标跟踪,车内副驾也有人时,用检测框的欧氏距离做跨帧匹配,保证疲劳判定绑定到同一个人。我现在的习惯是拿到任何一段新视频和标注,先花十分钟人工过一遍状态变化,再调阈值,最后才看检测代码——标注口径和算法口径对齐,调试能省一半时间。这个习惯帮我躲开了无数次跑偏,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询