使用 C++ 和 OpenCV 判断是否抬头 🧐
本文将介绍一种简单有效的方法,使用 C++ 和 OpenCV 来判断图像或视频中的人物是否正在抬头。这个技术在驾驶员疲劳检测、人机交互或行为分析等领域非常有用。
我们的核心思路是分析面部关键点的相对位置。当一个人抬头时,在二维图像上,他/她的下巴尖端会向鼻子尖端靠近。我们可以利用这个几何变化来做出判断。
🧠 核心逻辑
- 人脸检测: 首先,在图像中定位出人脸的位置。
- 面部关键点检测: 在找到的人脸区域内,精确识别出多个关键点(Landmarks),如眼睛、鼻子、嘴巴和下巴轮廓。
- 距离计算与归一化:
- 计算鼻子尖端和下巴底端之间的垂直距离(Y 轴距离)。
- 为了消除人脸大小和远近带来的影响,我们需要一个“基准”距离来进行归一化。两眼之间的距离是一个很好的选择,因为它在抬头或低头时变化不大。
- 计算比率:
Ratio = (鼻子到下巴的垂直距离) / (两眼之间的距离)
。
- 阈值判断: 当人抬头时,上述比率会明显变小。我们只需设定一个合适的阈值,当比率小于这个阈值时,就判定为“抬头”。
🛠️ 环境与模型准备
在开始之前,你需要准备好:
- C++ 编译器: 如 G++, Clang, 或 MSVC。
- OpenCV 库: 确保已正确安装,并包含
dnn
和face
模块。 - 预训练模型文件:
- 人脸检测模型 (YuNet): 从 OpenCV’s GitHub 下载
face_detection_yunet_2023mar.onnx
文件。 - 面部关键点模型 (LBF): 从 这里 下载
lbfmodel.yaml
文件。
- 人脸检测模型 (YuNet): 从 OpenCV’s GitHub 下载
请将下载好的模型文件放在你的项目目录中。
💻 代码实现
我们将使用 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;
}
🚀 编译与运行
- 保存代码: 将所有代码整合到一个 C++ 文件中,如
head_pose.cpp
。 - 编译: 使用以下命令编译代码。请确保 OpenCV 已正确配置。
如果你的 OpenCV 不是版本 4,请相应修改g++ -o head_pose_app head_pose.cpp $(pkg-config --cflags --libs opencv4)
opencv4
。 - 运行:
程序会弹出一个窗口,显示检测结果,并在人脸上标注状态(“Looking Up” 或 “Looking Forward”)。./head_pose_app path/to/your/image.jpg
总结
这种基于面部关键点几何关系的方法,为判断头部姿态提供了一个简单、直观且相当可靠的方案。你可以通过调整阈值 (threshold) 来适应不同的光照和人脸特征,也可以轻松地将此逻辑嵌入到视频处理循环中,以实现实时检测。