尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenPose骨骼点与dlib结合的疲劳驾驶检测系统实践

OpenPose骨骼点与dlib结合的疲劳驾驶检测系统实践 简介基于深度学习骨骼点OpenPose的司机驾驶状态检测告警系统Python源码面向计算机视觉与深度学习方向的毕设、课程设计和期末大作业适合学生与开发者快速构建完整的疲劳驾驶告警原型。资源共28个文件涵盖13个Python脚本、OpenPose/dlib/随机森林模型、说明文档、CSV特征数据、Jupyter示例、MP3语音提示和运行效果图压缩包约147.66MB。项目入口脚本整合骨骼点提取、姿态识别到疲劳和异常姿态告警的完整流程特征提取脚本可基于Kaggle的State Farm公开数据集生成姿态特征函数支持库提供详细中文注释并配有轻量化OpenPose模块方便理解算法细节与二次开发。模型文件与语音提示均已内置安装依赖后即可在主流Python环境中运行也便于替换摄像头输入、扩展检测类别。已有237人学习下载适合学术展示、项目答辩和快速验证相关算法。1. 为什么驾驶状态检测要先看骨骼点疲劳驾驶的早期信号从来不只是闭眼。头部低垂、肩膀前倾、单手脱离方向盘这些姿态变化往往比 PERCLOS 指标出现得更早。OpenPose 把人体姿态压缩成 17 个带置信度的坐标点让「姿态」变成可量化的向量这正是这套系统把骨骼点作为核心输入的原因。项目由三部分拼成Light-OpenPose 提取骨骼关键点dlib 的 68 点人脸模型计算眼睛和嘴部开合度随机森林对姿态特征分类最后按帧窗口触发语音告警。它适合两类人一类是做毕设或课程设计、需要完整可演示链路的学生另一类是已在做图像检测、想看骨骼点特征如何与传统分类器结合的工程师。下面按数据流动的顺序拆开讲。2. 系统拆解Light-OpenPose、dlib 与随机森林如何分工2.1 项目结构与模型文件职责拿到压缩包先别急着跑把目录过一遍就能看出数据流。main.py 是程序入口get_train.py 负责离线生成训练数据func.py 是带中文注释的共享函数库modules 和 datasets 目录放的是 Light-OpenPose 网络定义与 COCO 关键点读取逻辑val.py 是模型验证入口。文件/目录职责main.py程序入口双路视频推理与告警触发get_train.py用 OpenPose 从 State Farm 图片提取姿态特征输出 CSVfunc.py共享函数库含中文注释modules/、datasets/Light-OpenPose 网络与 COCO 关键点处理val.py模型验证脚本models/3 个已训练模型文件sound/eyes.mp3、yawn.mp3 两种告警音video/前置、侧置摄像头视频样本models 目录下三个文件各管一件事checkpoint_iter_370000.pth 是 Light-OpenPose 在 COCO 上迭代 37 万步的权重负责输出 17 个骨骼点shape_predictor_68_face_landmarks.dat 是 dlib 的人脸关键点模型输出眼睛、嘴巴等 68 个点RandomForestClassifier_model.pkl 是训练好的随机森林把骨骼点特征映射到驾驶姿态类别。理解这三个文件的边界后面配置环境时就知道为什么缺一个都跑不起来。2.2 为什么姿态提取选 Light-OpenPose 而不是原版原版 OpenPose 用 VGG 前 10 层做骨干单帧推理在 CPU 上要几百毫秒。驾驶检测是视频流任务帧率直接决定告警延迟。Light-OpenPose 把骨干换成 MobileNet用深度可分离卷积替换普通卷积在 COCO AP 下降有限的前提下把单帧推理压到几十毫秒。这个取舍对驾驶场景很关键司机姿态是连续变化的帧率低于 10fps 时低头、打哈欠这类快动作会被抽帧丢掉。Heatmap 分辨率也是选型要看的指标。Light-OpenPose 输出的热图在 45x80 左右对 17 点 COCO 关键点来说够用因为后续不是做像素级姿态估计而是把坐标喂给分类器。如果你的场景要精确测量关节角度做康复评估就得换分辨率更高的网络这是后话。2.3 dlib 与随机森林在链路中的位置OpenPose 管躯干和四肢dlib 管脸二者互补。dlib 68 点模型在正脸和小角度侧脸下表现可靠眼睛 6 点、嘴巴 20 点EAR 和 MAR 都从这些点直接算。随机森林则接收 OpenPose 的 17 点坐标做姿态分类。为什么姿态分类不用深度学习分类头样本量不够。State Farm 训练集按类别分有几千到上万张但按帧提特征后会遇到类别不平衡随机森林对表格型特征和小样本更耐受训练快还能输出特征重要性帮你判断哪些骨骼点对「分心」判定贡献最大。这种多模型混合管线的好处是每个模型只做自己最擅长的事坏处是要维护三套依赖第 5 章会讲怎么处理。# 三个模型的初始化顺序对应它们在链路中的位置 from modules.pose import Pose import joblib, dlib pose_net Pose(model_pathmodels/checkpoint_iter_370000.pth) face_detector dlib.get_frontal_face_detector() landmark_predictor dlib.shape_predictor( models/shape_predictor_68_face_landmarks.dat) clf joblib.load(models/RandomForestClassifier_model.pkl)这段初始化逻辑说明pose_net 必须在视频循环外创建加载 37 万步的权重有几百毫秒开销放循环里等于每帧重新加载。dlib 的检测器和预测器是分离设计检测器负责找脸框预测器负责在框内定位 68 点两步都要做。随机森林的 pkl 用 joblib 加载比 pickle 在 numpy 数组场景下更快。3. 特征提取用 OpenPose 把驾驶姿态落成 CSV 样本3.1 State Farm 数据集与类别映射get_train.py 的数据来源是 Kaggle 的 State Farm Distracted Driver Detection 竞赛。每个样本是车内摄像头拍的司机照片标签从 c0 到 c9 共 10 类c0 安全驾驶c1/c2 右手发消息和打电话c3/c4 左手发消息和打电话c5 操作收音机c6 喝水c7 伸手到后排c8 化妆梳头c9 和乘客说话。这个类别设计有个特点多数类别是「单手离开方向盘 视线偏移」这正是骨骼点特征最敏感的动作。3.2 get_train.py 的提取流程提取逻辑不复杂遍历图片跑一遍 Light-OpenPose把 17 个骨骼点坐标和置信度按固定顺序写进 CSV。关键在归一化和置信度处理。import cv2 import pandas as pd from modules.pose import Pose pose_net Pose(model_pathmodels/checkpoint_iter_370000.pth) rows [] for img_path, label in sample_list: img cv2.imread(img_path) if img is None: continue keypoints pose_net.inference(img) # shape: (17, 3) feat [] for x, y, conf in keypoints: # 坐标按图像宽高归一化防止分辨率不一致导致特征漂移 feat.extend([x / img.shape[1], y / img.shape[0], conf]) rows.append(feat [label]) df pd.DataFrame(rows) df.to_csv(openpose_train_data.csv, indexFalse, headerFalse)逻辑说明pose_net.inference 返回 (17, 3) 数组三列分别是 x、y、置信度。归一化必须放在写 CSV 之前因为随机森林不知道原图尺寸训练集和测试集若来自不同分辨率视频未归一化的坐标会让分类器学到「位置」而不是「姿态」。置信度这一维很多人会丢掉其实很有用某点置信度长期偏低说明被遮挡保留它等于告诉分类器「这个部位没看见」也是一种模式。参数上只需要关心 inference 时的 heatmap 阈值默认 0.1 左右即可。太低会引入噪声点太高会把低置信度的关键点直接置 0导致 CSV 里大量空行。提示如果提取出的 CSV 里某类样本大量出现全零行优先检查 heatmap 阈值是不是设太高把低置信度关键点清掉了。3.3 CSV 字段设计与后续训练openpose_train_data.csv 的结构是 52 列前 51 列是 17 个关键点的 x、y、conf最后一列是类别标签。按 COCO 顺序第 0 点鼻子、第 1 点脖子、第 2 到第 4 点是右肩右肘右腕以此类推。列区间内容说明0-2鼻子 x、y、confCOCO 关键点第 0 号3-5脖子 x、y、confCOCO 关键点第 1 号6-50其余 15 个关键点按 COCO 顺序排列51label0-9 类别编号openposeRandomForest.ipynb 读取这个 CSV 训练随机森林保存为 RandomForestClassifier_model.pkl。如果分类效果不好优先加角度特征而不是换网络。肘部夹角、肩颈连线与垂直方向的夹角、躯干倾斜角这几个角度对 c5 操作收音机和 c6 喝水这类动作区分度很高。def angle_between(p1, p2, p3): # 计算 p1-p2-p3 三点夹角输入是骨骼点坐标 import math v1 (p1[0] - p2[0], p1[1] - p2[1]) v2 (p3[0] - p2[0], p3[1] - p2[1]) dot v1[0] * v2[0] v1[1] * v2[1] n1 math.hypot(*v1) n2 math.hypot(*v2) return math.degrees(math.acos(max(-1.0, min(1.0, dot / (n1 * n2)))))这里说明一下angle_between 是向量夹角的经典实现acos 的输入要 clamp 到 [-1, 1]否则浮点误差会让 acos 返回 NaN这个坑在骨骼点特征工程里很常见。加入角度特征后重新训练随机森林特征重要性排名里通常能看到肘部角度进前三位。4. 骨骼点推理链路从视频帧坐标到告警触发4.1 main.py 的双路检测流程main.py 处理两个视频流前置摄像头视频送给 dlib 做疲劳判定侧置摄像头视频送给 OpenPose 和随机森林做姿态判定两路结果在状态机里融合。cap_front cv2.VideoCapture(video/front.mp4) cap_side cv2.VideoCapture(video/side.mp4) while cap_front.isOpened() and cap_side.isOpened(): ok_f, frame_front cap_front.read() ok_s, frame_side cap_side.read() if not (ok_f and ok_s): break # 前置摄像头dlib 68 点 - EAR / MAR landmarks get_face_landmarks(frame_front, face_detector, landmark_predictor) ear eye_aspect_ratio(landmarks) mar mouth_aspect_ratio(landmarks) # 侧置摄像头OpenPose 骨骼点 - 随机森林姿态类别 keypoints pose_net.inference(frame_side) pose_label clf.predict(pose_feature(keypoints))[0] if fatigue_state(ear, mar, pose_label): trigger_alarm()逻辑说明两个 VideoCapture 各自 read帧率不一致时以较慢的一路为准否则状态窗口的时间基准是乱的。实际部署建议用线程分别读帧并用最新帧同步演示场景逐帧读取就够。get_face_landmarks 返回 68 个点ear 和 mar 是两个标量pose_label 是 0-9 的整数融合逻辑就是这三个值的时序判断。4.2 One Euro Filter 抑制骨骼点抖动OpenPose 单帧输出的坐标有抖动尤其是手腕、脚踝这类末端点。直接拿抖动坐标算姿态随机森林的类别输出会在相邻帧之间跳变。项目里的 one_euro_filter.py 就是干这个的它参数少、延迟小。from modules.one_euro_filter import OneEuroFilter # 17 个点每个点 x、y 各一个滤波器 filters [OneEuroFilter(min_cutoff1.2, beta0.007) for _ in range(17 * 2)] def smooth_keypoints(keypoints): out [] for i, (x, y, conf) in enumerate(keypoints): sx filters[i * 2].filter(x) sy filters[i * 2 1].filter(y) out.append([sx, sy, conf]) return outOne Euro Filter 的原理是自适应低通目标移动快时减弱平滑移动慢时加强平滑核心参数就两个。min_cutoff 控制低频时的平滑基线数值越小越平beta 控制对速度的响应beta 越大越跟手但容易把噪声放大。0.007 是姿态跟踪场景常用的起点值如果骨骼点在原地轻微抖动先把 min_cutoff 降到 1.0 以下试试。4.3 疲劳指标计算与姿态判定疲劳判定分两路。眼睛用 EAR嘴巴用 MAR计算公式一样只是取点不同。from scipy.spatial import distance as dist def eye_aspect_ratio(eye): # eye 是 dlib 68 点中眼睛的 6 个点顺序固定 A dist.euclidean(eye[1], eye[5]) B dist.euclidean(eye[2], eye[4]) C dist.euclidean(eye[0], eye[3]) return (A B) / (2.0 * C) def mouth_aspect_ratio(mouth): A dist.euclidean(mouth[2], mouth[10]) B dist.euclidean(mouth[4], mouth[8]) C dist.euclidean(mouth[0], mouth[6]) return (A B) / (2.0 * C)逻辑说明EAR 是眼睛垂直距离和水平距离的比值睁眼时约 0.25 到 0.35闭眼时趋近 0.1 以下对个体差异不敏感所以不需要按人头标定。MAR 同理打哈欠时嘴部垂直距离变大比值会超过 0.6。判定策略用连续帧计数而不是单帧阈值因为单帧误检无法避免EAR_THRESH 0.22 CLOSE_FRAMES 48 # 30fps 下约 1.6 秒 MAR_THRESH 0.65 YAWN_FRAMES 15 # 约 0.5 秒 if ear EAR_THRESH: close_count 1 else: close_count 0 if mar MAR_THRESH: yawn_count 1 else: yawn_count 0 fatigue close_count CLOSE_FRAMES or yawn_count YAWN_FRAMES参数说明CLOSE_FRAMES 取 48因为正常眨眼持续 200 到 400 毫秒也就是 6 到 12 帧1.6 秒的连续闭眼只可能是疲劳或睡着不会误伤正常眨眼。YAWN_FRAMES 取 15因为哈欠的嘴部张开时间通常在 0.5 秒以上说话时 MAR 也会有尖峰但很少连续 15 帧超阈值。4.4 姿态告警与语音输出姿态告警的判定核心是「持续性」。随机森林单帧分类错误率不低不能一帧非 c0 就报警。常见的做法是滑动窗口统计最近 30 帧里非安全驾驶类别占比超过 70% 才触发。触发后播放 sound 目录下的告警音eyes.mp3 对应疲劳闭眼yawn.mp3 对应哈欠用独立线程播放避免阻塞主循环。def trigger_alarm(kind): # kind1 疲劳闭眼告警kind2 哈欠告警 now time.time() if now - last_alarm_time 5.0: return # 5 秒冷却防止重复轰炸 last_alarm_time now sound_file sound/eyes.mp3 if kind 1 else sound/yawn.mp3 threading.Thread(targetlambda: playsound(sound_file), daemonTrue).start()冷却时间这段值得多说一句持续告警会让司机直接关掉系统这在产品里叫告警疲劳。5 秒冷却意味着最低报警间隔 5 秒如果检测到连续 30 秒疲劳只会在 0 秒、5 秒、10 秒各响一声而不是每帧都响。5. 跑通与调优Python 环境、模型路径与阈值细节5.1 Anaconda 环境与依赖安装顺序建议用 Anaconda 建独立环境Python 版本锁 3.8太高会出现 torch 和 dlib 的预编译包对不上的情况。conda create -n driver python3.8 -y conda activate driver pip install torch1.8.1 torchvision0.9.1 pip install opencv-python pandas numpy scikit-learn imutils scipy conda install -c conda-forge dlib -y pip install playsound joblib安装顺序有讲究torch 先装因为 Light-OpenPose 的 load_state.py 依赖 torch 的版本 API装新版 torch 可能遇到 checkpoint 参数名不兼容的问题。dlib 用 conda-forge 装而不是 pipWindows 上 pip 装 dlib 大概率要现场编译容易卡在 CMake。装完跑一遍 main.py缺什么库看报错补什么func.py 顶部 import 的库就是完整清单。5.2 模型文件与路径检查三个模型文件必须放在 models 目录下文件名不能改因为代码里是硬编码路径。启动前用一段断言检查最省事import os required [ models/checkpoint_iter_370000.pth, models/shape_predictor_68_face_landmarks.dat, models/RandomForestClassifier_model.pkl, ] for path in required: assert os.path.exists(path), fmissing model: {path} print(all models ready)注意dlib 的 shape_predictor 只接受 RGB 输入OpenCV 读进来是 BGR直接喂会得到错位的人脸关键点转换用 cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)。这段检查的价值在于区分两类报错路径错误和模型损坏。assert 让你在启动第一时间发现文件缺失而不是等 torch 或 dlib 加载到一半抛出晦涩的格式错误。checkpoint_iter_370000.pth 体量不小下载后先看文件大小太小说明下载被截断。5.3 阈值自校准与调参技巧阈值不要照抄不同摄像头安装角度下 EAR 和 MAR 的分布不一样。一个不需要标注数据的自校准方法先录一段自己正常驾驶、正常眨眼的视频跑推理统计 EAR 的均值和标准差把阈值设为均值减两倍标准差。这个值能覆盖 95% 的正常睁眼状态闭眼必低于它。参数默认值调低效果调高效果EAR_THRESH0.22更不容易误报疲劳检出变迟钝更敏感眨眼可能误触CLOSE_FRAMES48响应快误报多响应慢漏报风险MAR_THRESH0.65说话容易误判哈欠真哈欠可能漏掉min_cutoff1.2骨骼点更平滑动作滞后更跟手抖动明显调参时把参数全部集中到 func.py 顶部的配置区不要散落在 main.py 各函数里。每调一轮把叠加了骨骼点、人脸框和判定结果的视频存成 mp4对比不同阈值下告警触发时刻的差异这个对比文件同时也是答辩时最有说服力的材料。本文还有配套的精品资源点击获取
返回列表