opencv相机标定和人头姿态估计案例
前言
頭部驅(qū)動除了之前關(guān)注的表情驅(qū)動外,還有眼球驅(qū)動和頭部方向驅(qū)動。本博客基于opencv官方文檔和部分開源代碼來研究如何基于人臉關(guān)鍵點獲取頭部的朝向。
國際慣例,參考博客:
opencv:Camera Calibration and 3D Reconstruction
opencv:Real Time pose estimation of a textured object
cv.solvePnP位姿估計旋轉(zhuǎn)向量精度分析
頭部姿態(tài)估計原理及可視化
重磅!頭部姿態(tài)估計「原理詳解 + 實戰(zhàn)代碼」來啦!
相機(jī)矩陣(Camera Matrix)
Python cv2.decomposeProjectionMatrix方法代碼示例
face_landmark
head-pose-estimation
Face-Yaw-Roll-Pitch-from-Pose-Estimation-using-OpenCV
talking-head-anime-demo
OpenVtuber
相機(jī)標(biāo)定理論
幾種坐標(biāo)系
先看從opencv官網(wǎng)中扒下來的兩幅圖,代表針孔相機(jī)模型(pinhole camera model)
其中涉及到幾種坐標(biāo)系:
- 世界坐標(biāo)系:一個固定不變的坐標(biāo)系,原點通常固定不變,右圖的www坐標(biāo)系
- 相機(jī)坐標(biāo)系:相機(jī)在世界坐標(biāo)系下的姿態(tài),右圖的ccc坐標(biāo)系
- 圖像坐標(biāo)系:成像平面,圖中的x-y坐標(biāo)軸,其原點是相機(jī)的光軸與成像平面的膠墊
- 像素坐標(biāo)系:最終圖像,圖中的u-v坐標(biāo)軸,原點在左上角,就跟opencv輸出的圖片一樣,左上角代表(0,0)(0,0)(0,0)像素位置。
圖像坐標(biāo)系和像素坐標(biāo)系橫軸和縱軸方向一致,但是單位不同,一個是物理單位,一個是像素單位,一般有一個對應(yīng)的縮放關(guān)系,代表一個像素在成像平面上的大小。
針孔相機(jī)的目標(biāo)就是把3D坐標(biāo)點PwP_wPw?利用透視變換(perspective transformation)投影到圖像平面上,得到對應(yīng)像素ppp。其中PwP_wPw?和ppp都在齊次坐標(biāo)系下表示。
無畸變情況下,針孔相機(jī)的投影變換可以表示為:
sp=A[R∣t]Pws\ p=A[R|t]P_w s?p=A[R∣t]Pw?
其中PwP_wPw?為世界坐標(biāo)系下的3D坐標(biāo)點,ppp是圖像平面上的2D像素點,AAA是相機(jī)內(nèi)參矩陣,RRR和ttt分別描述了世界坐標(biāo)系到相機(jī)坐標(biāo)系的旋轉(zhuǎn)和平移變換,sss是任意尺度的投影變換(非相機(jī)模型的參數(shù),其實就是圖像坐標(biāo)系到像素坐標(biāo)系的變換系數(shù))。
世界坐標(biāo)系到相機(jī)坐標(biāo)系
旋轉(zhuǎn)-平移矩陣[R∣t][R|t][R∣t]是投影變換(projective transformation)和齊次變換(homogeneous transformation)的乘積。
維度為(3,4)(3,4)(3,4)投影變換可以將相機(jī)坐標(biāo)系里的3D坐標(biāo)映射到成像平面的2D坐標(biāo),并且在歸一化的相機(jī)坐標(biāo)系x′=XcZcx'=\frac{X_c}{Z_c}x′=Zc?Xc??和y′=YcZcy'=\frac{Y_c}{Z_c}y′=Zc?Yc??下表示出來
Zc[x′y′1]=[100001000010][XcYcZc1]Z_c\begin{bmatrix} x'\\ y'\\1 \end{bmatrix}=\begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&1&0 \end{bmatrix}\begin{bmatrix} X_c\\Y_c\\Z_c\\1 \end{bmatrix} Zc????x′y′1????=???100?010?001?000?????????Xc?Yc?Zc?1??????
而齊次變換通常在相機(jī)外參RRR和ttt中體現(xiàn)出來,代表世界坐標(biāo)系到相機(jī)坐標(biāo)系的變換,因此給定一個世界坐標(biāo)系下的點PwP_wPw?,那么相機(jī)坐標(biāo)系下的對應(yīng)點為:
Pc=[Rt01]PwP_c=\begin{bmatrix} R&t\\0&1 \end{bmatrix}P_w Pc?=[R0?t1?]Pw?
這個齊次變換一般就是由一個(3,3)的旋轉(zhuǎn)矩陣和一個(3,1)的平移向量組成:
[Rt01]=[r11r12r13txr21r22r23tyr31r32r33tz0001]\begin{bmatrix} R&t\\0&1 \end{bmatrix}=\begin{bmatrix} r_{11}&r_{12}&r_{13}&t_x\\ r_{21}&r_{22}&r_{23}&t_y\\ r_{31}&r_{32}&r_{33}&t_z\\ 0&0&0&1 \end{bmatrix} [R0?t1?]=?????r11?r21?r31?0?r12?r22?r32?0?r13?r23?r33?0?tx?ty?tz?1??????
因此
[XcYcZc1]=[r11r12r13txr21r22r23tyr31r32r33tz0001][XwYwZw1]\begin{bmatrix} X_c\\Y_c\\Z_c\\1 \end{bmatrix}=\begin{bmatrix} r_{11}&r_{12}&r_{13}&t_x\\ r_{21}&r_{22}&r_{23}&t_y\\ r_{31}&r_{32}&r_{33}&t_z\\ 0&0&0&1 \end{bmatrix}\begin{bmatrix} X_w\\Y_w\\Z_w\\1 \end{bmatrix} ?????Xc?Yc?Zc?1??????=?????r11?r21?r31?0?r12?r22?r32?0?r13?r23?r33?0?tx?ty?tz?1???????????Xw?Yw?Zw?1??????
結(jié)合投影變換和齊次變換,就可以得到將世界坐標(biāo)系下3D點映射到歸一化相機(jī)坐標(biāo)系下的成像平面下2D點的變換:
Zc[x′y′1]=[R∣t][XwYwZw1]=[r11r12r13txr21r22r23tyr31r32r33tz][XwYwZw1]Z_c\begin{bmatrix} x'\\y'\\1 \end{bmatrix}=[R|t]\begin{bmatrix} X_w\\Y_w\\Z_w\\1 \end{bmatrix}=\begin{bmatrix} r_{11}&r_{12}&r_{13}&t_x\\ r_{21}&r_{22}&r_{23}&t_y\\ r_{31}&r_{32}&r_{33}&t_z \end{bmatrix}\begin{bmatrix} X_w\\Y_w\\Z_w\\1 \end{bmatrix} Zc????x′y′1????=[R∣t]?????Xw?Yw?Zw?1??????=???r11?r21?r31??r12?r22?r32??r13?r23?r33??tx?ty?tz??????????Xw?Yw?Zw?1??????
其中x′=XcZcx'=\frac{X_c}{Z_c}x′=Zc?Xc??,y′=YcZcy'=\frac{Y_c}{Z_c}y′=Zc?Yc??
相機(jī)坐標(biāo)系到像素坐標(biāo)系
相機(jī)內(nèi)參矩陣AAA通常用K表示,用于將相機(jī)坐標(biāo)系下的3D坐標(biāo)點投影到像素坐標(biāo)系中。
p=APcp=AP_c p=APc?
通常相機(jī)內(nèi)參矩陣AAA包含了以像素為單位的焦距fxf_xfx?和fyf_yfy?,以及靠近圖像中心的原點(cx,cy)(c_x,c_y)(cx?,cy?):
A=[fx0cx0fycy001]A= \begin{bmatrix} f_x & 0 & c_x \\ 0&f_y&c_y\\ 0&0&1 \end{bmatrix} A=???fx?00?0fy?0?cx?cy?1????
所以
s[uv1]=[fx0cx0fycy001][XcYcZc]s\begin{bmatrix} u\\v\\1 \end{bmatrix}=\begin{bmatrix} f_x & 0 & c_x \\ 0&f_y&c_y\\ 0&0&1 \end{bmatrix}\begin{bmatrix} X_c\\Y_c\\Z_c \end{bmatrix} s???uv1????=???fx?00?0fy?0?cx?cy?1???????Xc?Yc?Zc?????
相機(jī)內(nèi)參,顧名思義只與相機(jī)自身有關(guān),與外部環(huán)境無關(guān),所以一次標(biāo)定以后,只要你不動焦距,就可以永久使用。
總結(jié):世界坐標(biāo)系到像素坐標(biāo)系
將內(nèi)外參矩陣放在一起就能把sp=A[R∣t]Pws\ p=A[R|t]P_ws?p=A[R∣t]Pw?重寫成:
s[uv1]=[fx0cx0fycy001][r11r12r13txr21r22r23tyr31r32r33tz][XwYwZw1]s\begin{bmatrix} u\\v\\1 \end{bmatrix}=\begin{bmatrix} f_x&0&c_x\\ 0&f_y&c_y\\ 0&0&1\\ \end{bmatrix}\begin{bmatrix} r_{11}&r_{12}&r_{13}&t_x\\ r_{21}&r_{22}&r_{23}&t_y\\ r_{31}&r_{32}&r_{33}&t_z \end{bmatrix}\begin{bmatrix} X_w\\Y_w\\Z_w\\1 \end{bmatrix} s???uv1????=???fx?00?0fy?0?cx?cy?1???????r11?r21?r31??r12?r22?r32??r13?r23?r33??tx?ty?tz??????????Xw?Yw?Zw?1??????
如果Zc≠0Z_c\neq0Zc??=0,那么
[uv]=[fxXc/Zc+cxfyYc/Zc+cy]\begin{bmatrix} u\\v \end{bmatrix}= \begin{bmatrix} f_xX_c/Z_c+c_x\\ f_yY_c/Z_c+c_y \end{bmatrix} [uv?]=[fx?Xc?/Zc?+cx?fy?Yc?/Zc?+cy??]
其中
[XcYcZc]=[R∣t][XwYwZw1]\begin{bmatrix} X_c\\Y_c\\Z_c \end{bmatrix}=[R|t]\begin{bmatrix} X_w\\Y_w\\Z_w\\1 \end{bmatrix} ???Xc?Yc?Zc?????=[R∣t]?????Xw?Yw?Zw?1??????
就得到最開始描述的左圖中的u-v坐標(biāo)系映射模型了。
【注】上述理論是基于畸變參數(shù)為0的情況下,關(guān)于不為零的時候,請自行查閱opencv官方文檔描述或者其他資料。
頭部姿態(tài)估計
理論
通過內(nèi)外參矩陣可以將世界坐標(biāo)系下的3維點映射到成像平面,那么同理,可以利用相機(jī)內(nèi)參、世界坐標(biāo)系的3D點、成像平面的2D點,找到世界坐標(biāo)系到相機(jī)坐標(biāo)系的旋轉(zhuǎn)和平移(外參矩陣)。
在做頭部姿態(tài)估計的時候,我們僅僅知道人臉關(guān)鍵點,其它信息一無所知,那么應(yīng)該怎么求解呢?
通過后五篇參考博客的源碼分析,大致流程就是:
- 建立一個虛假的3D頭模,找到幾個人臉關(guān)鍵點的3D坐標(biāo)
- 假定當(dāng)前相機(jī)的內(nèi)參矩陣和畸變系數(shù)
- 利用solvePnP求解平移向量和旋轉(zhuǎn)向量
- 利用decomposeProjectionMatrix將旋轉(zhuǎn)向量轉(zhuǎn)換為歐拉角
其中solvePnP的函數(shù)描述如下:
retval, rvec, tvec = cv.solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs[, rvec[, tvec[, useExtrinsicGuess[, flags]]]] )輸入?yún)?shù):
- objectPoints:世界坐標(biāo)系下的3D坐標(biāo)
- imagePoints:2D投影坐標(biāo)
- cameraMatrix:相機(jī)內(nèi)參矩陣
- distCoeffs:畸變系數(shù)
輸出:
- rvec:旋轉(zhuǎn)向量,可使用Rodrigues轉(zhuǎn)換為旋轉(zhuǎn)矩陣
- tvec:平移向量
其中decomposeProjectionMatrix的函數(shù)描述如下:
cameraMatrix, rotMatrix, transVect, rotMatrixX, rotMatrixY, rotMatrixZ, eulerAngles = cv.decomposeProjectionMatrix( projMatrix[, cameraMatrix[, rotMatrix[, transVect[, rotMatrixX[, rotMatrixY[, rotMatrixZ[, eulerAngles]]]]]]] )輸入?yún)?shù):
- projMatrix:維度為(3,4)(3,4)(3,4)的投影矩陣PPP
返回參數(shù):
- cameraMatrix:內(nèi)參矩陣
- rotMatrix:外部旋轉(zhuǎn)矩陣
- transVect:外部平移矩陣
- rotMatrixX:繞x軸旋轉(zhuǎn)的矩陣
- rotMatrixY:繞y軸旋轉(zhuǎn)的矩陣
- rotMatrixZ:繞z軸旋轉(zhuǎn)的矩陣
- eulerAngles:旋轉(zhuǎn)歐拉角
實現(xiàn)
例如最后一個參考博客中的源碼解析分別為:
-
預(yù)加載3D人臉關(guān)鍵點模型
首先預(yù)加載一個3D人臉關(guān)鍵點模型,源碼提供了人臉的39個關(guān)鍵點,我提取了其中12個關(guān)鍵點,關(guān)鍵點坐標(biāo)如下:
array([[ 29.64766 , 10. , 66.01275 ],[126.870285, 10. , 66.01275 ],[ 60.359673, 34.85047 , 44.13414 ],[ 25.144653, 33.933437, 39.87654 ],[ 96.15827 , 34.85047 , 44.13414 ],[131.37329 , 33.933437, 39.87654 ],[ 78.25897 , 88.78672 , 67.6343 ],[ 50.51882 , 109.59447 , 50.48531 ],[ 78.25897 , 105.25116 , 67.04956 ],[105.99912 , 109.59447 , 50.48531 ],[ 78.25897 , 119.950806, 60.976673],[ 78.25897 , 162.94363 , 40.70434 ]], dtype=float32)原始39個關(guān)鍵點與對應(yīng)提取的12個關(guān)鍵點在2D圖像上的位置關(guān)系如下:
-
提取真實人臉關(guān)鍵點
利用opencv或者HRNet模型,提取真實圖像中的2D人臉關(guān)鍵點,可參考之前換臉的博客,或者去我github上找源碼也可以,效果如下:
-
計算朝向
H,W = img.shape[0],img.shape[1] matrix = np.array([[W,0,W/2.0],[0,W,H/2.0],[0,0,1]])
首先創(chuàng)建內(nèi)參矩陣:然后求解外參矩陣,調(diào)用solvPnP函數(shù)求解旋轉(zhuǎn)向量和平移向量
_,rot_vec,trans_vec = cv2.solvePnP(obj[pick_model,...].astype("float32"),points[pick_dlib,...].astype("float32"),matrix,None,flags=cv2.SOLVEPNP_DLS)將旋轉(zhuǎn)向量和平移向量組合成外參矩陣的形式
rot_mat = cv2.Rodrigues(rot_vec)[0] pose_mat = cv2.hconcat((rot_mat, trans_vec))最后將旋轉(zhuǎn)向量轉(zhuǎn)換為歐拉角:
euler_angle = cv2.decomposeProjectionMatrix(pose_mat)[-1]可視化效果如下:
后記
結(jié)果有時候受到你初始模型的影響,所以建議多找些源碼測試一下,找到一個合適的3D模型使用。而且在驅(qū)動卡通角色時候,由于建模和游戲引擎的原因,坐標(biāo)系可能不同,因而歐拉角也要做適當(dāng)?shù)淖儞Q,比如我的項目基于python和unity交互的卡通角色肢體和表情驅(qū)動(深度學(xué)習(xí))中關(guān)于表情驅(qū)動部分的實驗。
完整的python實現(xiàn)放在微信公眾號的簡介中描述的github中,有興趣可以去找找。同時文章也同步到微信公眾號中,有疑問或者興趣歡迎公眾號私信。
總結(jié)
以上是生活随笔為你收集整理的opencv相机标定和人头姿态估计案例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 广发银行信用卡临时额度有效期多久?可以最
- 下一篇: 卡通角色表情驱动系列二