当前位置: 首页 > news >正文

使用 C++ 和 OpenCV 构建驾驶员疲劳检测软件

使用 C++ 和 OpenCV 构建驾驶员疲劳检测软件

重要声明: 本文所描述的软件是一个概念验证的原型,绝对不能用作现实世界中的安全系统。真正的车载安全系统需要经过大量的测试、具备冗余设计并通过专业认证,以确保其绝对可靠。

驾驶疲劳是全球范围内引发交通事故的主要原因之一。当驾驶员感到困倦时,他们的反应时间会变慢,决策能力会下降,而在方向盘后睡着的风险则会急剧增加。为了解决这一关键问题,计算机视觉技术提供了一个充满希望的解决方案。通过使用摄像头实时监控驾驶员,我们可以开发算法来检测疲劳迹象,并及时发出警报。

本文将指导您完成一个使用 C++ 和流行的计算机视觉库 OpenCV 开发疲劳驾驶检测软件的全过程。我们将重点关注从面部特征中提取最常用且最可靠的指标:眼部纵横比 (EAR) 用于检测长时间闭眼和眨眼,嘴部纵横比 (MAR) 用于检测哈欠,以及对头部姿态的监控。

核心概念:如何检测疲劳?

我们的方法基于对三个核心疲劳指标的分析:

  1. 眼部闭合 (EAR): 这是最可靠的疲劳指标。当人变得昏昏欲睡时,他们的眨眼会变得更慢、持续时间更长。我们将使用一个称为 眼部纵横比 (Eye Aspect Ratio, EAR) 的度量标准来量化眼睛的睁开程度。EAR 是眼睑之间垂直距离与眼睛水平距离的比值。当眼睛睁开时,这个比值相对恒定,但当眼睛闭合时,它会迅速下降到接近零。通过持续监控 EAR,我们可以检测到驾驶员的眼睛是否长时间闭合。

  2. 打哈欠 (MAR): 打哈欠是另一个明显的疲劳迹象。与 EAR 类似,我们可以计算 嘴部纵横比 (Mouth Aspect Ratio, MAR) 来衡量嘴巴的张开程度。MAR 的显著增加可以标志着一次哈欠的发生。

  3. 头部姿态: 低头是极度困倦的另一个标志。通过追踪面部关键点的移动,可以估算头部的姿态。当检测到头部长时间处于前倾或侧倾状态时,可以将其作为疲劳的辅助判断依据。

环境与依赖配置

在开始编码之前,请确保您已经配置好了 C++ 开发环境,并安装了以下库:

  • OpenCV: 开源计算机视觉库。您可以从 OpenCV 官网 下载。请确保为其正确配置了您的 C++ 编译器(如 g++, MinGW, MSVC)。
  • Dlib: 一个包含机器学习算法的现代 C++ 工具包。我们将使用它进行人脸检测和面部关键点预测。请从 Dlib 官网 下载。
  • 面部关键点预测模型: 您需要一个预训练模型来检测面部关键点。Dlib 提供了一个优秀的68点模型,您可以从这里下载:shape_predictor_68_face_landmarks.dat。下载后解压,并将 .dat 文件放置在您的项目目录中。

项目结构

您的项目目录结构应如下所示:

fatigue_detection/
|-- main.cpp
`-- shape_predictor_68_face_landmarks.dat

C++/OpenCV 实现步骤详解

现在,让我们一步步编写 main.cpp 文件中的代码。

1. 包含头文件与初始化

首先,我们需要包含 OpenCV 和 Dlib 的必要头文件,并初始化检测器和预测器。

#include <opencv2/opencv.hpp>
#include <dlib/opencv.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <iostream>using namespace cv;
using namespace std;
using namespace dlib;// 计算两点之间的欧几里得距离
double euclidean_dist(dlib::point p1, dlib::point p2) {return sqrt(pow(p1.x() - p2.x(), 2) + pow(p1.y() - p2.y(), 2));
}// 计算眼部纵横比 (EAR)
double get_ear(const std::vector<dlib::point>& eye_landmarks) {// 计算垂直距离double vert_dist1 = euclidean_dist(eye_landmarks[1], eye_landmarks[5]);double vert_dist2 = euclidean_dist(eye_landmarks[2], eye_landmarks[4]);// 计算水平距离double horz_dist = euclidean_dist(eye_landmarks[0], eye_landmarks[3]);// 计算EARdouble ear = (vert_dist1 + vert_dist2) / (2.0 * horz_dist);return ear;
}// 计算嘴部纵横比 (MAR)
double get_mar(const std::vector<dlib::point>& mouth_landmarks) {// 计算垂直距离 (点62到点66)double vert_dist = euclidean_dist(mouth_landmarks[3], mouth_landmarks[9]);// 计算水平距离 (点60到点64)double horz_dist = euclidean_dist(mouth_landmarks[0], mouth_landmarks[6]);// 计算MARdouble mar = vert_dist / horz_dist;return mar;
}

2. 主函数 main()

主函数将处理视频流,执行检测,并显示结果。

int main() {try {// 初始化摄像头VideoCapture cap(0);if (!cap.isOpened()) {cerr << "错误: 无法打开摄像头。" << endl;return -1;}// 加载Dlib的人脸检测器frontal_face_detector detector = get_frontal_face_detector();// 加载形状预测器 (面部关键点)shape_predictor sp;deserialize("shape_predictor_68_face_landmarks.dat") >> sp;// 定义阈值和计数器const double EAR_THRESH = 0.21; // EAR阈值const int EAR_CONSEC_FRAMES = 20; // 连续帧数,眼睛必须低于阈值const double MAR_THRESH = 0.5;  // MAR阈值const int MAR_CONSEC_FRAMES = 15; // 连续帧数,嘴巴必须张开int ear_counter = 0;int mar_counter = 0;bool drowsiness_alert = false;bool yawn_alert = false;cout << "疲劳监测已启动... 按 'q' 键退出。" << endl;// 主循环,处理来自摄像头的每一帧while (true) {Mat frame;cap >> frame;if (frame.empty()) {break;}// 将OpenCV帧转换为Dlib图像格式cv_image<bgr_pixel> cimg(frame);// 在图像中检测人脸std::vector<dlib::rectangle> faces = detector(cimg);// 遍历每个检测到的人脸for (const auto& face : faces) {// 获取面部关键点full_object_detection shape = sp(cimg, face);// 提取眼睛和嘴巴的关键点std::vector<dlib::point> left_eye_landmarks;std::vector<dlib::point> right_eye_landmarks;std::vector<dlib::point> mouth_landmarks;// 68个面部关键点的索引// 左眼: 36-41for (int i = 36; i <= 41; ++i) left_eye_landmarks.push_back(shape.part(i));// 右眼: 42-47for (int i = 42; i <= 47; ++i) right_eye_landmarks.push_back(shape.part(i));// 嘴巴内部轮廓: 60-67for (int i = 60; i <= 67; ++i) mouth_landmarks.push_back(shape.part(i));// 计算双眼的平均EARdouble left_ear = get_ear(left_eye_landmarks);double right_ear = get_ear(right_eye_landmarks);double avg_ear = (left_ear + right_ear) / 2.0;// 计算MARdouble mar = get_mar(mouth_landmarks);// 为了可视化,在帧上绘制关键点for (unsigned long i = 0; i < shape.num_parts(); ++i) {circle(frame, Point(shape.part(i).x(), shape.part(i).y()), 2, Scalar(0, 255, 0), -1);}// 基于EAR检查是否瞌睡if (avg_ear < EAR_THRESH) {ear_counter++;if (ear_counter >= EAR_CONSEC_FRAMES) {if (!drowsiness_alert) {drowsiness_alert = true;// 在帧上显示警报putText(frame, "!!! KUNLENG JINGGAO !!!", Point(10, 30),FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 255), 2);}}} else {ear_counter = 0;drowsiness_alert = false;}// 基于MAR检查是否打哈欠if (mar > MAR_THRESH) {mar_counter++;if (mar_counter >= MAR_CONSEC_FRAMES) {if(!yawn_alert) {yawn_alert = true;// 显示哈欠警报putText(frame, "!!! DAHAQIAN JINGGAO !!!", Point(10, 60),FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 255, 255), 2);}}} else {mar_counter = 0;yawn_alert = false;}// 在帧上显示EAR和MAR的值putText(frame, "EAR: " + to_string(avg_ear), Point(frame.cols - 200, 30),FONT_HERSHEY_SIMPLEX, 0.7, Scalar(255, 0, 0), 2);putText(frame, "MAR: " + to_string(mar), Point(frame.cols - 200, 60),FONT_HERSHEY_SIMPLEX, 0.7, Scalar(255, 0, 0), 2);}// 显示处理后的帧imshow("Fatigue Detection", frame);// 如果按下 'q' 键,则退出循环if (waitKey(1) == 'q') {break;}}} catch (const exception& e) {cerr << "\n程序异常: " << e.what() << endl;return -1;}return 0;
}

代码解析

  • 初始化: 代码首先初始化摄像头捕获。然后,它从磁盘加载 Dlib 的 frontal_face_detector(人脸检测器)和 shape_predictor(关键点预测模型)。
  • 阈值定义:
    • EAR_THRESH: 眼部纵横比的阈值。如果 EAR 低于此值,我们认为眼睛是闭合的。这个值可能需要根据不同的人、光照和摄像头位置进行微调。一个好的初始值在 0.2 到 0.3 之间。
    • EAR_CONSEC_FRAMES: EAR 必须连续低于阈值的帧数,才会触发瞌睡警报。这有助于避免因正常眨眼而产生的误报。
    • MAR_THRESHMAR_CONSEC_FRAMES: 用于哈欠检测的类似阈值和帧计数器。
  • 帧处理循环:
    1. 捕获帧: 从摄像头获取一帧图像。
    2. 人脸检测: 使用 Dlib 的人脸检测器在帧中定位所有的人脸。
    3. 关键点预测: 对于每个检测到的人脸,调用形状预测器来定位68个面部关键点。
    4. 提取关键点: 根据68点模型的标准索引,提取左眼、右眼和嘴巴的坐标。
    5. 计算EAR和MAR: 调用我们自定义的 get_earget_mar 函数来计算当前帧的指标。
    6. 检查疲劳状态:
      • EAR 的逻辑检查平均 EAR 是否低于 EAR_THRESH。如果是,则增加计数器 ear_counter。如果计数器达到 EAR_CONSEC_FRAMES,则显示瞌睡警报。如果 EAR 恢复到阈值以上,则重置计数器。
      • 对 MAR 应用类似的逻辑来检测哈欠。
    7. 可视化: 将当前的 EAR 和 MAR 值以及任何警报信息绘制到帧上,以提供视觉反馈。关键点也以绿色小圆圈的形式绘制出来。
    8. 显示帧: 在窗口中显示带有注释的帧。
    9. 退出: 当用户按下 ‘q’ 键时,循环终止。

编译与运行

您在编译时需要链接 OpenCV 和 Dlib 库。一个在 Linux 上使用 g++ 的示例编译命令可能如下所示:

g++ main.cpp -o fatigue_detector `pkg-config --cflags --libs opencv4 dlib-1` -lpthread

注意: pkg-config 是管理编译标志的推荐方式。如果未安装,您需要手动指定包含路径和库链接,例如:
g++ main.cpp -o fatigue_detector -I/path/to/dlib/include -I/path/to/opencv/include -L/path/to/dlib/lib -L/path/to/opencv/lib -lopencv_core -lopencv_highgui -lopencv_videoio -lopencv_imgproc -ldlib -lpthread

编译成功后,运行程序:

./fatigue_detector

一个窗口将会弹出,显示您的摄像头画面。尝试长时间闭上眼睛或张大嘴巴,看看警报是否会触发。

改进方向与未来展望

这个软件提供了一个基础框架。对于一个更强大的系统,可以考虑以下几点:

  • 校准: EAR 和 MAR 的基准值因人而异。一个更高级的系统可以从一个校准阶段开始,为每个驾驶员确定个性化的基准阈值。
  • 头部姿态估计: 使用 cv::solvePnP 来实现头部姿态估计算法。通过跟踪头部的旋转,您可以检测到驾驶员是否点头,这是一个强烈的疲劳信号。
  • 光照鲁棒性: 在光照条件不佳的情况下(例如夜晚),性能会显著下降。使用红外摄像头可以极大地提高可靠性。
  • 警报集成: 系统可以不仅仅是显示文本,还可以连接到触发声音警报或触觉警报(如座椅振动),以更有效地吸引驾驶员的注意。
  • 性能优化: 对于嵌入式系统,代码的性能优化至关重要。这可能包括使用更轻量级的人脸检测器(如OpenCV的Haar级联分类器,尽管精度较低)或加速图像处理。

结论

在本文中,我们使用 C++、OpenCV 和 Dlib 构建了一个功能性的软件,用于实时检测驾驶员的疲劳状态。通过分析如眼部纵横比和嘴部纵横比等指标,我们创建了一个能够有效预防事故的预警系统。尽管此实现作为一个优秀的概念验证,但从原型到可部署的安全系统还有很长的路要走,这需要精心的工程设计、严格的测试以及对各种现实世界场景的考量。然而,对于任何对计算机视觉在安全领域的实际应用感兴趣的人来说,这个项目都是一个绝佳的起点。

http://www.dtcms.com/a/263083.html

相关文章:

  • Java设计模式之结构型模式(外观模式)介绍与说明
  • jenkins集成sonarqube(使用token进行远程调用)
  • 使用Python进行数据库交互:从SQL查询到ORM操作的安全实践指南
  • 【王阳明代数讲义】二十四史语料库与意气实体过程学说导引
  • 大学专业科普 | 云计算、大数据
  • 将 h264+g711a存为 mp4文件,记录
  • FreePDFv3.0.0:颠覆你的文献阅读习惯
  • 【RTSP从零实践】3、实现最简单的传输H264的RTSP服务器
  • GORM 高级查询实战:从性能优化到业务场景解决方案
  • 华为云Flexus+DeepSeek征文 | ​​接入华为云ModelArts Studio大模型 —— AI智能法务解决方案革新法律实践​
  • x86-64架构和aarch64架构的区别解读
  • 【ARM】解决ArmDS的工程没有生成Map文件的问题
  • 使用 Kafka 优化物流系统的实践与思考
  • 信息安全工程师考试架构相关说明
  • C语言之文件操作详解(文件打开关闭、顺序/随机读写)
  • Python Ai语音识别教程
  • 2 大语言模型基础-2.2 生成式预训练语言模型GPT-2.2.2 有监督下游任务微调-Instruct-GPT强化学习奖励模型的结构改造与维度转换解析
  • Spring生态:云原生与AI的革新突破
  • Linux 系统管理:高效运维与性能优化
  • FastAPI—学习1
  • 本地服务器部署后外网怎么访问不了?内网地址映射互联网上无法连接问题的排查
  • CppCon 2018 学习:A Little Order! Delving into the STL sorting algorithms
  • MySQL索引原理-主键索引与普通索引
  • 【软考高项论文】论信息系统项目的干系人管理
  • ACT-R 7.28
  • pbootcms程序运行异常: Modulo by zero,位置:/www/wwwroot/****/core/function/helper.php
  • 链表题解——设计链表【LeetCode】
  • langchain从入门到精通(二十四)——RAG优化策略(二)多查询结果融合策略及 RRF
  • [特殊字符]️ Hyperf框架的数据库查询(ORM组件)
  • iOS 接口频繁请求导致流量激增?抓包分析定位与修复全流程