madobet / webooru

Web Danbooru
32 stars 6 forks source link

重磅!头部姿态估计「原理详解 + 实战代码」来啦! - 知乎 #203

Open madobet opened 4 years ago

madobet commented 4 years ago

本文首发于公众号:计算机视觉 life。原文链接

重磅!头部姿态估计「原理详解 + 实战代码」来啦!​mp.weixin.qq.com

写在前面

经过两周的文献和博客阅读,CV_Life 君终于欣 (dan) 喜 (zhan) 若 (xin) 狂 (jing) 地给各位带来 head pose estimation 这篇文章,因为刚刚入手这个方向,如有疏漏请各位多多包涵,并多多指教。废话少说,先放个 Demo 热热身。

Head Pose Estimation 是干啥的?

热身完毕,有没有对 Demo 上变化的数字费解呢?做过此方向的小伙伴,应该会比较容易理解,Head Pose Estimation 就是估计头部的姿态。详细道来:Head Pose Estimation 是通过一幅面部图像来获得头部的姿态角,跟飞机飞行有点类似,即计算 pitch,yaw 和 roll 三个欧拉角,分别学名俯仰角、偏航角和滚转角,通俗讲就是抬头、摇头和转头。百闻不如一见,上图示意

Head Pose Estimation 有啥用呢?

记得在群里问 “群里有没有做过 Head Pose Estimation 研究的小伙伴?”,有人问过 “这个目的是什么?”。其实 Head Pose Estimaion 的应用场景和目的挺丰富的,下面 CV_Life 君就跟小伙伴们分享几个方向。
(1) 注意力检测。CV_Life 君目前就在做这个方向,通过判断头部姿态可以判断人的注意力情况。比如可以检测长途司机是不是在目视前方,长时间不目视前方的话,可以提前敲打,保证安全,减少事故;再比如监控学生上课时是否集中精力,以后再也不用担心班主任在后窗偷窥了。

(2) 行为分析。和上面的有点类似,但还是有点不同。我家乡方言里有个词叫 “胡撒”,说的就是心虚的人容易左顾右盼,通过视频监控分析再辅助其他算法可以判断一个人是否具有不轨行为,做到提前预警,防患于未然。
(3) 人机互动。人的头部动作有时可以表示意义,传递信息。摇头在大多数人看来是否认,点头表示同意 (三哥表示不服),长时间低头说不定你就是 “地狱之门” 的沉思者。如果机器人能理解这样的行为,将提高人机交互的质量和有效性。

(4) 视线追踪,也可以称为眼球跟踪。准确的 Head Pose Estimation 能够提高视线追踪的精度。视线追踪可以用在游戏领域,也许有一天你打开手游后,用眼睛就可以控制游戏内人物的移动了 (体验如何暂且不管,要的是黑科技),让体感操作更上一层楼。

说完了 Head Pose Estimation 的八卦,既然这玩意这么有用,小伙伴们是不是已经迫不及待地想去试试手呢?下面 CV_Life 君就说说 Head Pose Estimation 的原理之一。

Head Pose Estimation 如何理解?

如果你对相机标定熟悉的话,就比较好理解,因为 Head Pose Estimation 比较有难度的部分已经被大牛们搞定了,CV_Life 君普及一下比较基本的原理。一种比较经典的 Head Pose Estimation 算法的步骤一般为:2D 人脸关键点检测;3D 人脸模型匹配;求解 3D 点和对应 2D 点的转换关系;根据旋转矩阵求解欧拉角。Bingo!就是这么简单。
下面是原理时间。众所周知一个物体相对于相机的姿态可以使用旋转矩阵和平移矩阵来表示。
平移矩阵:物体相对于相机的空间位置关系矩阵,用 T 表示;
旋转矩阵:物体相对于相机的空间姿态关系矩阵,用 R 表示。
如此看来必然少不了坐标系转换。讲点人性,继续上图

于是世界坐标系 (UVW)、相机坐标系 (XYZ)、图像中心坐标系 (uv) 和像素坐标系 (xy) 四兄弟闪亮登场。如果相机完美无瑕,老三可以回家洗洗睡觉,关系也相对简单。
世界坐标系到相机坐标系:

相机坐标系到像素坐标系:

因此像素坐标系和世界坐标系的关系如下:

上式的求解可用 DLT(Direct Linear Transform) 算法结合最小二乘进行迭代求解,最小二乘的目标函数可为

带 ^ 的变量为预测值,其余为测量值。
可是相机也很无奈,她不完美,总有点瑕疵,比如径向和切向畸变,那关系就要稍微复杂一些,叫醒阿三继续推导:
相机坐标系要先转换到图像中心坐标系:

然后再被折磨一番 (计算畸变):

最后图像中心坐标系到像素坐标系:

看来只要知道世界坐标系内点的位置、像素坐标位置和相机参数就可以搞定旋转和平移矩阵,可上面的关系分明是非线性的,这可怎么解啊?其实 OpenCV 已经给我们提供了求解 PnP 问题的函数 solvePnp(),一步轻松到位。
得到旋转矩阵后,就可以开心地去见欧拉角了:

估计有些小伙伴犯嘀咕了:世界坐标系中点的位置怎么得到呢?一开始 CV_Life 君也苦恼这个问题,总不能每时每刻都要测一下人脸各个点在空间的位置吧,后来 CV_Life 君从各种论文中发现,原来大牛们在算法里面内置了一个 3D 人脸模型,把关键点的空间位置都标出来,就充当真实脸的空间位置;可是大牛又觉得这样不太合理,一个 3D 人脸模型不能表示所有人的脸,对所有人采用一个模型得到的精度肯定不好,于是便有了 3DMM(3D Morphable Model),对不同人可以拟合出对应的 3D 脸模型,这样关键点的空间位置就比较准确了,Head Pose Estimation 的精度提上去了。但代价还是有的,计算量变大了,处理起来也就慢了。萝卜青菜各有所爱,速度和精度不可兼得,看你口味选择。一般的 face Model 如下所示,用一系列的点的坐标构建 mesh。

可能又有小伙伴举手了:2D 关键点怎么检测啊?这个咱这里就不讨论了,有兴趣自行 google,因为 CV_Life 君目前也没研究明白 (捂脸),不过还好有大牛贡献源代码,咱们先行尝鲜,后续再去慢慢品尝。

人脸 3D 点和 2D 点的对应关系如下所示,目前的算法可以检测到更多的关键点,比如商汤科技的关键点检测已经可以做到 240,可谓行业佼佼者。下面代码用到的是人脸 68 点检测算法。

Head Pose Estimation 玩玩何妨?

本段代码来自 github 上 KwanHua Lee 的项目,可点击下面链接查看项目:https://github.com/lincolnhard/head-pose-estimation。CV_Life 君是用 VS2017 来实现的,需要 OpenCV 库和 dlib 库,建议不要用 debug 模式,选择 release 模式编译。dlib 库主要用来检测人脸关键点,关于 dlib 库的使用可以参考其官方网站http://dlib.net/。另外友情提醒一下:opencv 需要设置编译器包含路径,但是 dlib 一定要用 #include 搜索路径的方式,记得在项目里添加 dlib 的 source.cpp 文件。

添加 OpenCV 包含路径:

默认预装了 OpenCV3.0 以上版本,并在 VS 内设置完成

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/imgproc.hpp>

添加 dlib 包含路径:

不用像 OpenCV 那样需要在 VS 内提前设置,直接将 dlib 库拷贝到项目文件内,CV_Life 君下载的是 dlib-19.16 版本,记得在项目内添加 source.cpp 文件

#include "../dlib-19.16/dlib/opencv.h"
#include "../dlib-19.16/dlib/image_processing/frontal_face_detector.h"
#include "../dlib-19.16/dlib/image_processing/render_face_detections.h"
#include "../dlib-19.16/dlib/image_processing.h"

其他包含路径:

#include <iostream>
#include <vector>

添加相机内参:

double K[9] = { 6.5308391993466671e+002, 0.0, 3.1950000000000000e+002, 0.0, 6.5308391993466671e+002, 2.3950000000000000e+002, 0.0, 0.0, 1.0 };  // 等价于矩阵[fx, 0, cx; 0, fy, cy; 0, 0, 1]
double D[5] = { 7.0834633684407095e-002, 6.9140193737175351e-002, 0.0, 0.0, -1.3073460323689292e+000 };  // 相机畸变参数[k1, k2, p1, p2, k3]

调用 dlib 库,创建人脸检测和关键点检测模型:

dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();  // 人脸检测模型
dlib::shape_predictor predictor;  // 关键点检测模型
dlib::deserialize("shape_predictor_68_face_landmarks.dat") >> predictor; // 68关键点检测

定义空间点和图像点:

std::vector<cv::Point3d> object_pts; // 空间点坐标集合 model referenced from http://aifi.isr.uc.pt/Downloads/OpenGL/glAnthropometric3DModel.cpp
std::vector<cv::Point2d> image_pts; // 像素坐标集合

人脸关键点检测:

std::vector<dlib::rectangle> faces = detector(cimg); // 检测人脸,cimg为摄像头拍摄的图像或视频帧图像
if(faces.size() > 0) {
    dlib::full_object_detection shape = predictor(cimg, faces[0]); // 检测第一个人脸的关键点
    ......
}

求解旋转和平移矩阵:

cv::solvePnP(object_pts, image_pts, cam_matrix, dist_coeffs, rotation_vec, translation_vec);  // cam_matrix与K矩阵对应,dist_coeffs与D矩阵对应,rotation_vec表示旋转矩阵,translation_vec表示平移矩阵

求解欧拉角:

cv::Rodrigues(rotation_vec, rotation_mat);
cv::hconcat(rotation_mat, translation_vec, pose_mat);
cv::decomposeProjectionMatrix(pose_mat, out_intrinsics, out_rotation, out_translation, cv::noArray(), cv::noArray(), cv::noArray(), euler_angle);

说了这么多,CV_Life 君终于啰嗦完毕,希望有心读完的小伙伴对 Head Pose Estimation 能有一定的理解。当然 Head Pose Estimation 的算法还有很多,后续 CV_Life 君准备研究一下机器学习和深度学习的方法,有兴趣的可以一起讨论学习。

对了,上述代码和. dat 文件 CV_Life 君已经给按奈不住想要试试的小伙伴们准备好了,在计算机视觉公众号菜单栏里回复:“头” 就能下载啦!

参考文献

https://www.learnopencv.com/head-pose-estimation-using-opencv-and-dlib/
https://github.com/lincolnhard/head-pose-estimation
http://dlib.net/
Paper1: Robust Head Pose Estimation Using a 3D Morphable Model
Paper2: Robust and Accurate 3D Head Pose Estimation Through 3DMM and Online Head Model Reconstruction
Paper3: A Pose-Adaptive Constrained Local Model for Accurate Head Pose Tracking
Paper4: Landmark Based Head Pose Estimation Benchmark and Method
注:以上部分图片来自 google 图片。

https://zhuanlan.zhihu.com/p/51208197