原文链接:重磅!头部姿态估计「原理详解 + 实战代码」来啦!
大牛阅读本文需要5-10分钟,小牛可能10分钟以上
写在前面
经过两周的文献和博客阅读,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
#include
添加相机内参:doubleK[ 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]
doubleD[ 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:: vectorcv::Point3d object_pts; // 空间点坐标集合 model referenced from http://aifi.isr.uc.pt/Downloads/OpenGL/glAnthropometric3DModel.cpp
std:: vectorcv::Point2d image_pts; // 像素坐标集合
人脸关键点检测:std:: vectordlib::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图片。
文章来源于 云栖社区合作伙伴“计算机视觉life”,了解相关信息可以关注“计算机视觉life”,侵删。