3D姿态估计——ThreeDPose项目简单易用的模型解析
前言
之前寫(xiě)過(guò)tensorflow官方的posenet模型解析,用起來(lái)比較簡(jiǎn)單,但是缺點(diǎn)是只有2D關(guān)鍵點(diǎn),本著易用性的原則,當(dāng)然要再來(lái)個(gè)簡(jiǎn)單易用的3D姿態(tài)估計(jì)。偶然看見(jiàn)了ThreeDPose的項(xiàng)目,感覺(jué)很強(qiáng)大的,所以把模型扒下來(lái)記錄一下調(diào)用方法。
參考博客:
ThreeDPose官方代碼
微軟的ONNX模型解析庫(kù)
ONNX解析庫(kù)的pythonAPI文檔
3D姿態(tài)估計(jì)最大的好處就是卡通角色的肢體驅(qū)動(dòng)了,其實(shí)就是單目攝像頭的動(dòng)捕方法。
理論和代碼解析
可以從官方去下載模型,戳這里,或者在文末的百度網(wǎng)盤(pán)下載。
模型結(jié)構(gòu)
模型已經(jīng)被作者轉(zhuǎn)換成ONNX的模型文件了,所以下載netron軟件去可視化模型,打開(kāi)以后可以發(fā)現(xiàn)模型的網(wǎng)絡(luò)結(jié)構(gòu)和輸入輸出
這里說(shuō)明一下各部分含義:
有三個(gè)input,其實(shí)都是一樣,都要輸入(448,448,3)的圖片
有四個(gè)output,解析模型即提取關(guān)鍵點(diǎn)的時(shí)候,只需要第3和4個(gè)輸出,具體解析方法看下面代碼解析。
代碼解析
在windows上為了讀取ONNX的模型文件,可以使用微軟提供的onnxruntime這個(gè)庫(kù),直接pip安裝即可,這個(gè)庫(kù)的文檔見(jiàn)上面參考博客的2和3。
先引入相關(guān)的庫(kù)文件
import numpy as np import onnxruntime as rt import cv2import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D首先就是加載模型,輸入圖片,推斷當(dāng)前圖片的四個(gè)輸出
#加載模型 sess = rt.InferenceSession("Resnet34_3inputs_448x448_20200609.onnx") inputs = sess.get_inputs() #讀取圖片 img = cv2.imread("D:/photo/pose/5.jpg") img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) img = cv2.resize(img,(448,448)) img = img.astype(np.float32)/255.0 img = img.transpose(2,1,0) img = img[np.newaxis,...] img.shape #輸入到網(wǎng)絡(luò)結(jié)構(gòu)中 pred_onx = sess.run(None,{inputs[0].name:img,inputs[1].name:img,inputs[2].name:img })獲取輸出
offset3D = np.squeeze(pred_onx[2]) heatMap3D = np.squeeze(pred_onx[3]) print(offset3D.shape)#(2016, 28, 28) print(heatMap3D.shape)#(672, 28, 28) print(offset3D.shape[0]/heatMap3D.shape[0])#3.0按照參考博客1的unity代碼解析輸出,可以發(fā)現(xiàn)這個(gè)命名和posenet的解析一模一樣,使用heatmap粗略定位關(guān)節(jié)位置,然后使用offset在heatmap結(jié)果上精確調(diào)整關(guān)節(jié)位置。
heatmap的 (672,28,28)(672,28,28)(672,28,28) 代表 24個(gè)關(guān)節(jié)的28個(gè)大小為(28,28)(28,28)(28,28)的特征圖。而offset比heatmap的特征圖多三倍,很明顯就是剛才說(shuō)的精確定位,只不過(guò)需要在offset中定位到x,y,z三個(gè)坐標(biāo),所以就是三倍關(guān)系了。
定位原理就是:heatmap相當(dāng)于把原圖劃分為(28,28)(28,28)(28,28)的網(wǎng)格點(diǎn),每個(gè)網(wǎng)格點(diǎn)代表附近有一個(gè)關(guān)節(jié)的概率,通過(guò)找到某個(gè)關(guān)節(jié)最可能在heatmap哪一副特征圖的哪個(gè)網(wǎng)格,我們根據(jù)heatmap的索引在offset中找到對(duì)應(yīng)的3個(gè)精確矯正xyz坐標(biāo)的特征圖,這三個(gè)特征圖的值就是對(duì)應(yīng)關(guān)節(jié)的xyz坐標(biāo)相對(duì)于當(dāng)前heatmap網(wǎng)格位置的精確偏移量。
在寫(xiě)代碼之前,著重關(guān)注一下heatmap和offset的特征圖相對(duì)于關(guān)節(jié)是一個(gè)怎樣的順序。
- heatmap的順序是第1個(gè)關(guān)節(jié)的第1個(gè)特征圖、第1個(gè)關(guān)節(jié)的第2個(gè)特征圖、…、第2個(gè)關(guān)節(jié)的第1個(gè)特征圖、第二個(gè)關(guān)節(jié)的第2個(gè)特征圖、…、第24個(gè)關(guān)節(jié)的第28個(gè)特征圖
- offsetmap的順序是第1個(gè)關(guān)節(jié)的第1個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第2個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第3個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、…、第1個(gè)關(guān)節(jié)的第28個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、…、第2個(gè)關(guān)節(jié)的第1個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、…、第24個(gè)關(guān)節(jié)的第28個(gè)特征圖對(duì)應(yīng)的x坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第1個(gè)特征圖對(duì)應(yīng)的y坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第2個(gè)特征圖對(duì)應(yīng)的y坐標(biāo)偏移、…、第24個(gè)關(guān)節(jié)的第28個(gè)特征圖對(duì)應(yīng)的y坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第1個(gè)特征圖對(duì)應(yīng)的z坐標(biāo)偏移、第1個(gè)關(guān)節(jié)的第2個(gè)特征圖對(duì)應(yīng)的z坐標(biāo)偏移、…、第24個(gè)關(guān)節(jié)的第28個(gè)特征圖對(duì)應(yīng)的z坐標(biāo)偏移。
說(shuō)了一堆,不如看代碼簡(jiǎn)單明了:
比如提取第j個(gè)關(guān)節(jié)的3D坐標(biāo)位置,對(duì)應(yīng)的特征圖是[j?28,(j+1)?28?1][j*28, (j+1)*28-1][j?28,(j+1)?28?1] ,然后從這里面找到最大值的位置就是當(dāng)前關(guān)節(jié)最可能在哪個(gè)特征圖的哪個(gè)網(wǎng)格位置上。
# 找到第j個(gè)關(guān)節(jié)的28個(gè)特征圖,并找到最大值的索引 joint_heat = heatMap3D[j*28:(j+1)*28,...] [x,y,z] = np.where(joint_heat==np.max(joint_heat)) # 避免有多個(gè)最大值,所以取最后一組 x=int(x[-1]) y=int(y[-1]) z=int(z[-1])然后按照找到的heatmap索引去查詢(xún)offset,找到精確的矯正值
pos_x = offset3D[j*28+x,y,z] + x pos_y = offset3D[24*28+j*28+x,y,z] + y pos_z = offset3D[24*28*2+j*28+x,y,z] + z每個(gè)坐標(biāo)都是在當(dāng)前網(wǎng)格的位置上加上精確的偏移量,因?yàn)閤yz分別在offset上分別隔了24?2824*2824?28個(gè)特征圖,所以出現(xiàn)了y和z的第一個(gè)索引有變化。
如果還有疑問(wèn),建議去看看前面的2D姿態(tài)估計(jì)的解析,然后自己手寫(xiě)一遍解析算法就理解了。
把所有關(guān)節(jié)的位置存到kps然后可視化看看
%matplotlib inlinefig = plt.figure() ax = fig.gca(projection='3d') ax.scatter3D(kps[:,0],-kps[:,1],-kps[:,2],'red') parent = np.array([0,1,2,3,3, 1,6,7,8,8, 12,15,14,15,24, 24,16,17,18, 24,20,21,22, 0])-1; for i in range(24):if(parent[i]!=-1):ax.plot3D(kps[[i,parent[i]],0], -kps[[i,parent[i]],1], -kps[[i,parent[i]],2], 'gray')ax.xaxis.set_tick_params(labelsize=10) ax.yaxis.set_tick_params(labelsize=10) ax.zaxis.set_tick_params(labelsize=10)ax.view_init(elev=10., azim=180)其實(shí)把關(guān)鍵點(diǎn)保存下來(lái)用matlab畫(huà)更好看
%test clear;clc;close all % a=dlmread("D:\code\python\ThreeDPose\unity_data\kps100.txt"); a=[0.292807,14.994286,6.560671; -0.123055,15.480020,8.367174; -2.666008,19.526012,9.689341; -3.994198,19.631011,9.902868; -3.537779,20.058028,9.978683; 1.467720,11.799177,6.442903; -1.372830,10.170244,5.793396; -2.116019,9.653587,3.315798; -1.332626,9.906492,1.420080; -2.152191,9.461523,2.985400; 0.378545,12.237494,4.861950; -0.739344,12.452946,4.971550; -0.678598,14.203241,5.388392; -0.671332,13.130285,4.690326; -1.256000,12.918788,5.623796; 0.216525,13.818989,12.736559; 0.793380,13.383041,17.217848; -0.301103,13.578390,22.771406; -0.406112,13.623824,23.280164; 0.380849,11.442492,12.643937; -2.110893,11.706800,18.199155; -1.205901,12.321800,22.871755; -2.116101,12.384523,24.476315; -0.500211,12.706436,11.490892];parent =[15,1,2,3,3, 15,6,7,8,8, 12,15,14,15,24, 24,16,17,18, 24,20,21,22, 0]; plot3(a(:,1),-a(:,2),-a(:,3),'ro','MarkerSize',2,'MarkerFaceColor','r') for i=1:24if(parent(i)~=0)line([a(i,1) a(parent(i),1)],[-a(i,2) -a(parent(i),2)],[-a(i,3) -a(parent(i),3)])end end axis equal axis off24個(gè)關(guān)節(jié)的名稱(chēng)分別標(biāo)注一下說(shuō)一下吧,從unity代碼中的VNectModel.cs能找到,玩過(guò)骨骼動(dòng)畫(huà)的基本知道每個(gè)單詞的代表的關(guān)節(jié),這里就不畫(huà)骨骼結(jié)構(gòu)圖了,自己對(duì)應(yīng)到上面的關(guān)鍵點(diǎn)圖中即可。
rShldrBend, rForearmBend, rHand, rThumb2, rMid1, lShldrBend, lForearmBend, lHand, lThumb2, lMid1, lEar, lEye, rEar, rEye, Nose, rThighBend, rShin, rFoot, rToe, lThighBend, lShin, lFoot, lToe, abdomenUpper,后記
其實(shí)如果要做肢體驅(qū)動(dòng),還需要
- 根據(jù)上述關(guān)節(jié)計(jì)算額外的一些關(guān)節(jié)坐標(biāo),這一塊不在本博文的學(xué)習(xí)范圍內(nèi),博文只關(guān)注怎么簡(jiǎn)單的使用這個(gè)模型去解析關(guān)節(jié)位置。
- 將關(guān)節(jié)坐標(biāo)映射到原圖,這個(gè)根據(jù)比例原圖比例縮放一下即可,與posenet一樣,也不做闡述。
模型文件網(wǎng)盤(pán)地址:
鏈接:https://pan.baidu.com/s/1TsvALWJRIoCAtQ9ffcno7w
提取碼:rfow
本博文同步更新到微信公眾號(hào)中,有興趣可關(guān)注一波,代碼在微信公眾號(hào)簡(jiǎn)介的github找得到,有問(wèn)題直接公眾號(hào)私信。
總結(jié)
以上是生活随笔為你收集整理的3D姿态估计——ThreeDPose项目简单易用的模型解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Vue组件化之插槽
- 下一篇: 简易的素描图片转换流程与实现