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

发布于:2025-06-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

使用 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) 来适应不同的光照和人脸特征,也可以轻松地将此逻辑嵌入到视频处理循环中,以实现实时检测。


网站公告

今日签到

点亮在社区的每一天
去签到