1. 程式人生 > >重磅!頭部姿態估計「原理詳解 + 實戰程式碼」來啦!

重磅!頭部姿態估計「原理詳解 + 實戰程式碼」來啦!

寫在前面

經過兩週的文獻和部落格閱讀,CV_Life君終於欣(dan)喜(zhan)若(xin)狂(jing)地給各位帶來head pose estimation這篇文章,因為剛剛入手這個方向,如有疏漏請各位多多包涵,並多多指教。廢話少說,先放個Demo熱熱身。

0294e683706362cc43382955e26d7d16f448e5e7

Head Pose Estimation是幹啥的?

熱身完畢,有沒有對Demo上變化的數字費解呢?做過此方向的小夥伴,應該會比較容易理解,Head Pose Estimation 就是估計頭部的姿態。詳細道來:Head Pose Estimation 是通過一幅面部影象來獲得頭部的姿態角,跟飛機飛行有點類似,即計算 pitch,yaw 和 roll 三個尤拉角,分別學名俯仰角、偏航角和滾轉角,通俗講就是擡頭、搖頭和轉頭。百聞不如一見,上圖示意

21cf0fad81638ae88317242b6b75a49956a21b7d

Head Pose Estimation有啥用呢?

記得在群裡問“群裡有沒有做過 Head Pose Estimation 研究的小夥伴?”,有人問過“這個目的是什麼?”。其實 Head Pose Estimaion 的應用場景和目的挺豐富的,下面CV_Life君就跟小夥伴們分享幾個方向。

(1) 注意力檢測。CV_Life君目前就在做這個方向,通過判斷頭部姿態可以判斷人的注意力情況。比如可以檢測長途司機是不是在目視前方,長時間不目視前方的話,可以提前敲打,保證安全,減少事故;再比如監控學生上課時是否集中精力,以後再也不用擔心班主任在後窗偷窺了。

7f2f9456d13b8005bca515b440f8f43add29d11d

(2) 行為分析。和上面的有點類似,但還是有點不同。我家鄉方言裡有個詞叫“胡撒”,說的就是心虛的人容易左顧右盼,通過視訊監控分析再輔助其他演算法可以判斷一個人是否具有不軌行為,做到提前預警,防患於未然。

(3) 人機互動。人的頭部動作有時可以表示意義,傳遞資訊。搖頭在大多數人看來是否認,點頭表示同意(三哥表示不服),長時間低頭說不定你就是“地獄之門”的沉思者。如果機器人能理解這樣的行為,將提高人機互動的質量和有效性。

353900bda551155936d72bc8d80f9fd662860e1d

(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表示。 

如此看來必然少不了座標系轉換。講點人性,繼續上圖

957d48f34596144ee57a39cd02c88231e4003a04

於是世界座標系(UVW)、相機座標系(XYZ)、影象中心座標系(uv)和畫素座標系(xy)四兄弟閃亮登場。如果相機完美無瑕,老三可以回家洗洗睡覺,關係也相對簡單。

世界座標系到相機座標系:

feec02bdef28b2b6cc633a3dd4679f074f9714c4

相機座標系到畫素座標系:

3dd9a32db4a13787c61d9c82efc105c4b19fc18a

因此畫素座標系和世界座標系的關係如下:

c083f4e8957656eb521cb24f5b09df74a8d5c6ef

上式的求解可用DLT(Direct Linear Transform)演算法結合最小二乘進行迭代求解,最小二乘的目標函式可為

62b79b56a15631e92cc3d0000c1d813d64fb99c4

帶^的變數為預測值,其餘為測量值。

可是相機也很無奈,她不完美,總有點瑕疵,比如徑向和切向畸變,那關係就要稍微複雜一些,叫醒阿三繼續推導: 相機座標系要先轉換到影象中心座標系: 93fc47d16ce609de37877540498f99587cd55815

然後再被折磨一番(計算畸變): cdc0b064ebe8c9b8b441473937521abcd5b28e43

最後影象中心座標系到畫素座標系: cd2e8e46b6620b419e0863169fd0c843681bdb8a
看來只要知道世界座標系內點的位置、畫素座標位置和相機引數就可以搞定旋轉和平移矩陣,可上面的關係分明是非線性的,這可怎麼解啊?其實OpenCV已經給我們提供了求解PnP問題的函式solvePnp(),一步輕鬆到位。 得到旋轉矩陣後,就可以開心地去見尤拉角了: e855ed076f6085e01cef4f8fc329eb90f5335233
估計有些小夥伴犯嘀咕了:世界座標系中點的位置怎麼得到呢?一開始 CV_Life君也苦惱這個問題,總不能每時每刻都要測一下人臉各個點在空間的位置吧,後來CV_Life君從各種論文中發現,原來大牛們在演算法裡面內建了一個3D人臉模型,把關鍵點的空間位置都標出來,就充當真實臉的空間位置;可是大牛又覺得這樣不太合理,一個3D人臉模型不能表示所有人的臉,對所有人採用一個模型得到的精度肯定不好,於是便有了3DMM(3D Morphable Model),對不同人可以擬合出對應的3D臉模型,這樣關鍵點的空間位置就比較準確了,Head Pose Estimation 的精度提上去了。但代價還是有的,計算量變大了,處理起來也就慢了。蘿蔔青菜各有所愛,速度和精度不可兼得,看你口味選擇。一般的face Model如下所示,用一系列的點的座標構建mesh。 d67d02fdd771452062c6362869aec41428028865

可能又有小夥伴舉手了:2D關鍵點怎麼檢測啊?這個咱這裡就不討論了,有興趣自行google,因為CV_Life君目前也沒研究明白(捂臉),不過還好有大牛貢獻原始碼,咱們先行嚐鮮,後續再去慢慢品嚐。

人臉3D點和2D點的對應關係如下所示,目前的演算法可以檢測到更多的關鍵點,比如商湯科技的關鍵點檢測已經可以做到240,可謂行業佼佼者。下面程式碼用到的是人臉68點檢測演算法。

42fe781dc1b0174af405eb438bf696af6e8c1309

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君準備研究一下機器學習和深度學習的方法,有興趣的可以一起討論學習。


原文釋出時間為:2018-11-29 本文作者:老張本人 本文來自雲棲社群合作伙伴“ 計算機視覺life”,瞭解相關資訊可以關注“ 計算機視覺life”。