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

使用 C/C++ 和 OpenCV 判断是否抬头

使用 C++ 和 OpenCV 判断是否抬头 🧐

本文将介绍一种简单有效的方法,使用 C++ 和 OpenCV 来判断图像或视频中的人物是否正在抬头。这个技术在驾驶员疲劳检测、人机交互或行为分析等领域非常有用。

我们的核心思路是分析面部关键点的相对位置。当一个人抬头时,在二维图像上,他/她的下巴尖端会向鼻子尖端靠近。我们可以利用这个几何变化来做出判断。

🧠 核心逻辑

  1. 人脸检测: 首先,在图像中定位出人脸的位置。
  2. 面部关键点检测: 在找到的人脸区域内,精确识别出多个关键点(Landmarks),如眼睛、鼻子、嘴巴和下巴轮廓。
  3. 距离计算与归一化:
    • 计算鼻子尖端下巴底端之间的垂直距离(Y 轴距离)。
    • 为了消除人脸大小和远近带来的影响,我们需要一个“基准”距离来进行归一化。两眼之间的距离是一个很好的选择,因为它在抬头或低头时变化不大。
    • 计算比率:Ratio = (鼻子到下巴的垂直距离) / (两眼之间的距离)
  4. 阈值判断: 当人抬头时,上述比率会明显变小。我们只需设定一个合适的阈值,当比率小于这个阈值时,就判定为“抬头”。

🛠️ 环境与模型准备

在开始之前,你需要准备好:

  • C++ 编译器: 如 G++, Clang, 或 MSVC。
  • OpenCV 库: 确保已正确安装,并包含 dnnface 模块。
  • 预训练模型文件:
    1. 人脸检测模型 (YuNet): 从 OpenCV’s GitHub 下载 face_detection_yunet_2023mar.onnx 文件。
    2. 面部关键点模型 (LBF): 从 这里 下载 lbfmodel.yaml 文件。

请将下载好的模型文件放在你的项目目录中。


💻 代码实现

我们将使用 OpenCV 的 DNN 模块加载 YuNet 进行人脸检测,使用 cv::face 模块加载 LBF 模型进行关键点检测。

1. 包含头文件与主函数框架

#include <iostream>
#include <vector>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/face.hpp> // 关键点检测模块// 68点模型中关键点的索引
const int NOSE_TIP_INDEX = 30;
const int CHIN_BOTTOM_INDEX = 8;
const int LEFT_EYE_CORNER_INDEX = 36;
const int RIGHT_EYE_CORNER_INDEX = 45;int main(int argc, char **argv) {// ---- 模型路径 ----std::string faceDetectorModel = "face_detection_yunet_2023mar.onnx";std::string facemarkModel = "lbfmodel.yaml";std::string imageFile = "person.jpg"; // 替换成你的图片if (argc > 1) {imageFile = argv[1];}// ---- 加载模型 ----cv::dnn::Net detector = cv::dnn::readNet(faceDetectorModel);cv::Ptr<cv::face::Facemark> facemark = cv::face::createFacemarkLBF();facemark->loadModel(facemarkModel);// ---- 读取图像 ----cv::Mat frame = cv::imread(imageFile);if (frame.empty()) {std::cerr << "Error: Could not read the image." << std::endl;return -1;}

2. 人脸检测

我们使用 YuNet 模型来检测图像中的人脸。

    // ---- 人脸检测 ----detector.setInputSize(frame.size());cv::Mat faces;detector.detect(frame, faces);if (faces.rows < 1) {std::cout << "No faces detected." << std::endl;return 0;}// 将检测结果转换为 std::vector<cv::Rect>std::vector<cv::Rect> faceRects;for (int i = 0; i < faces.rows; ++i) {faceRects.push_back(cv::Rect(faces.at<float>(i, 0), faces.at<float>(i, 1),faces.at<float>(i, 2), faces.at<float>(i, 3)));}

3. 关键点检测与姿态分析

在检测到的人脸矩形上运行关键点检测器。然后,提取我们需要的点并进行计算。

    // ---- 关键点检测 ----std::vector<std::vector<cv::Point2f>> landmarks;bool success = facemark->fit(frame, faceRects, landmarks);if (success) {for (size_t i = 0; i < landmarks.size(); ++i) {auto l = landmarks[i];// ---- 提取所需关键点 ----cv::Point2f noseTip = l[NOSE_TIP_INDEX];cv::Point2f chinBottom = l[CHIN_BOTTOM_INDEX];cv::Point2f leftEye = l[LEFT_EYE_CORNER_INDEX];cv::Point2f rightEye = l[RIGHT_EYE_CORNER_INDEX];// ---- 计算距离和比率 ----double noseChinDist = std::abs(noseTip.y - chinBottom.y);double eyeDist = cv::norm(leftEye - rightEye);// 避免除以零if (eyeDist == 0) continue; double ratio = noseChinDist / eyeDist;// ---- 阈值判断 ----// 这个阈值需要根据实际情况微调,0.7 是一个不错的起点double threshold = 0.7; std::string status = "Looking Forward";cv::Scalar color(0, 255, 0); // 绿色if (ratio < threshold) {status = "Looking Up";color = cv::Scalar(0, 0, 255); // 红色}// ---- 结果可视化 ----cv::face::drawFacemarks(frame, l, color); // 绘制所有关键点cv::putText(frame, status, cv::Point(faceRects[i].x, faceRects[i].y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.8, color, 2);std::cout << "Face " << i << " Ratio: " << ratio << " -> " << status << std::endl;}}// ---- 显示结果 ----cv::imshow("Head Pose Detection", frame);cv::waitKey(0);return 0;
}

🚀 编译与运行

  1. 保存代码: 将所有代码整合到一个 C++ 文件中,如 head_pose.cpp
  2. 编译: 使用以下命令编译代码。请确保 OpenCV 已正确配置。
    g++ -o head_pose_app head_pose.cpp $(pkg-config --cflags --libs opencv4)
    
    如果你的 OpenCV 不是版本 4,请相应修改 opencv4
  3. 运行:
    ./head_pose_app path/to/your/image.jpg
    
    程序会弹出一个窗口,显示检测结果,并在人脸上标注状态(“Looking Up” 或 “Looking Forward”)。

总结

这种基于面部关键点几何关系的方法,为判断头部姿态提供了一个简单、直观且相当可靠的方案。你可以通过调整阈值 (threshold) 来适应不同的光照和人脸特征,也可以轻松地将此逻辑嵌入到视频处理循环中,以实现实时检测。

http://www.xdnf.cn/news/14014.html

相关文章:

  • Spring 事务传播行为详解
  • 自己的服务器被 DDOS跟CC攻击了怎么处理,如何抵御攻击?
  • 公司内网远程访问配置教程:本地服务器(和指定端口应用)实现外网连接使用
  • 29-Oracle 23ai Flashback Log Placement(闪回日志灵活配置)
  • 进程控制
  • Trae Builder 模式:从需求到全栈项目的端到端实践
  • 书写时垂直笔画比水平笔画表现更好的心理机制分析
  • Android binder内核漏洞研究(一)——环境搭建
  • 【MySQL基础】表的约束的类型与使用指南
  • 从Apache OFBiz 17.12.01的反序列化漏洞到Docker逃逸的渗透之红队思路
  • GaussDB 分布式数据库调优(架构到全链路优化)
  • C#实战:解决NPOI读取Excel单元格数值0.00001显示为1E-05的问题
  • [特殊字符] Harmony OS Next里的Web组件:网页加载的全流程掌控手册
  • macOS 查看当前命令行的ruby的安装目录
  • 大语言模型的分类与top3
  • Spark 之 Subquery
  • matlab实现非线性Granger因果检验
  • 深度学习:PyTorch张量基本运算、形状改变、索引操作、升维降维、维度转置、张量拼接
  • 将后端数据转换为docx文件
  • 京东零售基于Flink的推荐系统智能数据体系 |Flink Forward Asia 峰会实录分享
  • 论文阅读:arxiv 2025 How Likely Do LLMs with CoT Mimic Human Reasoning?
  • 自动化模型管理:MediaPipe Android SDK 中的模型文件下载与加载机制
  • Flutter:步骤条组件
  • Wi-Fi 6 在 2.4GHz 频段的速率与优化分析
  • Unit 3 训练一个Q-Learning智能体 Frozen-Lake-v1
  • 基于springboot视频及游戏管理系统+源码+文档+应用视频
  • RTP MOS计算:语音质量的数字评估
  • STM32HAL库发送字符串,将uint8_t数据转为字符串发送,sprintf函数的使用方法
  • 声学成像仪在电力行业的应用品牌推荐
  • Java从入门到精通 - 面向对象高级(一)