网站主机地址,东莞网站建设多少钱,wordpress主菜单,客户说做网站价格高前言
之前出过一个关于openpose配置的博客#xff0c;不过那个代码虽然写的很好#xff0c;而且是官方的#xff0c;但是分析起来很困难#xff0c;然后再opencv相关博客中找到了比较清晰的实现#xff0c;这里分析一波openpose的推断过程。
国际惯例#xff0c;参考博…前言
之前出过一个关于openpose配置的博客不过那个代码虽然写的很好而且是官方的但是分析起来很困难然后再opencv相关博客中找到了比较清晰的实现这里分析一波openpose的推断过程。
国际惯例参考博客
opencv官方文档只有单人
大佬的实现包括多人
解读
直接使用opencv的dnn库调用openpose的caffe模型然后对输出进行后处理。重点是代表关节连接亲密度的亲和场的解析。
网络输出解析
推断阶段的模型结构(pose/coco)戳openpose官网点这里跳转可以使用netscope可视化。
最后一层的结构如下
layer {name: concat_stage7type: Concatbottom: Mconv7_stage6_L2bottom: Mconv7_stage6_L1# top: concat_stage7top: net_outputconcat_param {axis: 1}可以发现拼接了两个层 Mconv7_stage6_L2 layer {name: Mconv7_stage6_L2type: Convolutionbottom: Mconv6_stage6_L2top: Mconv7_stage6_L2param {lr_mult: 4.0decay_mult: 1}param {lr_mult: 8.0decay_mult: 0}convolution_param {num_output: 19pad: 0kernel_size: 1weight_filler {type: gaussianstd: 0.01}bias_filler {type: constant}}
}Mconv7_stage6_L1 layer {name: Mconv7_stage6_L1type: Convolutionbottom: Mconv6_stage6_L1top: Mconv7_stage6_L1param {lr_mult: 4.0decay_mult: 1}param {lr_mult: 8.0decay_mult: 0}convolution_param {num_output: 38pad: 0kernel_size: 1weight_filler {type: gaussianstd: 0.01}bias_filler {type: constant}}
}可以发现被拼接的两个层分别具有19和38个特征图。对照的网络结构图 两个stage每个stage有两个branch第一个branch输出191919个特征图分别代表18个人体关键点和背景第二个branch有38个特征图代表文章所提出来亲和场(Part Affinity Fileds,PAF)代表关节与关节之前的联系。
代码里面对应关系
keypointsMapping [Nose, Neck, R-Sho, R-Elb, R-Wr, L-Sho, L-Elb, L-Wr, R-Hip, R-Knee, R-Ank, L-Hip, L-Knee, L-Ank, R-Eye, L-Eye, R-Ear, L-Ear]POSE_PAIRS [[1,2], [1,5], [2,3], [3,4], [5,6], [6,7],[1,8], [8,9], [9,10], [1,11], [11,12], [12,13],[1,0], [0,14], [14,16], [0,15], [15,17],[2,17], [5,16] ]# index of pafs correspoding to the POSE_PAIRS
# e.g for POSE_PAIR(1,2), the PAFs are located at indices (31,32) of output, Similarly, (1,5) - (39,40) and so on.
mapIdx [[31,32], [39,40], [33,34], [35,36], [41,42], [43,44], [19,20], [21,22], [23,24], [25,26], [27,28], [29,30], [47,48], [49,50], [53,54], [51,52], [55,56], [37,38], [45,46]]POSE_PAIRS分别代表keypointsMapping里面同一根骨骼两端的两个人体关节(关键点)。
mapIdx代表与POSE_PAIRS对应的亲和场特征图索引
【注】这里很容易出现疑问为什么同一个关节在向量场里面有不同的特征图索引呢比如[1,2],[1,5]里面的关节1在PAF特征图里面是索引31,39。这是因为一个关节可以被其它多个关节连接而一个向量场PAF特征图只指向一个关节到另一个关节的链接无法指向其它所有关节的链接。后面会可视化解释。
这里贴一下coco的人体人体关键点 调用模型
直接用opencv的dnn.readNetFromCaffe来调用模型
protoFile ./models/pose/coco/pose_deploy_linevec.prototxt
weightsFile ./models/pose/coco/pose_iter_440000.caffemodel
net cv2.dnn.readNetFromCaffe(protoFile,weightsFile)然后输入一张图
img cv2.imread(./examples/media/COCO_val2014_000000000328.jpg)
frameWidth img.shape[1]
frameHeight img.shape[0]
inHeight 368
inWidth int((inHeight/frameHeight)*frameWidth)
inBlob cv2.dnn.blobFromImage(img,1.0/255.0,(inWidth,inHeight),(0,0,0),swapRBFalse,cropFalse)
net.setInput(inBlob)
outputnet.forward()
print(output.shape)#(1, 57, 46, 60)可以发现输出的特征图个数和前面分析的相同。接下来随便可视化看看
#可视化
plt.figure(figsize[20,20])
plt.subplot(141)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 10, :, :], (frameWidth, frameHeight)), alpha0.6)
plt.axis(off)
plt.subplot(142)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 18, :, :], (frameWidth, frameHeight)), alpha0.6)
plt.axis(off)
plt.subplot(143)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 31, :, :], (frameWidth, frameHeight)), alpha0.6)
plt.axis(off)
plt.subplot(144)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.imshow(cv2.resize(output[0, 39, :, :], (frameWidth, frameHeight)), alpha0.6)
plt.axis(off)从左到右分别是第10个关节点的特征图背景特征图(1,2)(1,2)(1,2)关节的关节111的亲和场PAF特征图(1,5)(1,5)(1,5)关节的关节111的亲和场PAF特征图。
提取关键点
接下来就可以利用前面的18个特征图把肢体关键点提取出来。
对于某个关节的特征图调用
def getKeypoints(probMap,threshold0.1):mapSmooth cv2.GaussianBlur(probMap,(3,3),0,0)mapMask np.uint8(mapSmooththreshold)keypoints []_,contours,_ cv2.findContours(mapMask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)for cnt in contours:blobMask np.zeros(mapMask.shape)blobMask cv2.fillConvexPoly(blobMask,cnt,1)maskProbMap mapSmooth*blobMask_,maxVal,_,maxLoc cv2.minMaxLoc(maskProbMap)keypoints.append(maxLoc(probMap[maxLoc[1],maxLoc[0]],))#位置和置信度return keypoints就可以将当前关节的位置和对应的置信度提取出来。
提取所有的关节的位置和置信度就相当于把每个关节的特征图遍历一遍
nPoints 18
def get_joint_kps(output):detected_keypoints []keypoints_list np.zeros((0,3))keypoints_id 0threshold 0.1for part in range(nPoints):probMapoutput[0,part,:,:]probMap cv2.resize(probMap,(img.shape[1],img.shape[0]))keypoints getKeypoints(probMap,threshold)keypoints_with_id []for i in range(len(keypoints)):keypoints_with_id.append(keypoints[i](keypoints_id,)) #所有人的18个关节位置、置信度、idkeypoints_list np.vstack([keypoints_list,keypoints[i]])keypoints_id 1detected_keypoints.append(keypoints_with_id)return detected_keypoints,keypoints_list调用方法也很简单
detected_keypoints,keypoints_list get_joint_kps(output)简单地看一下输出
detected_keypoints[[(325, 165, 0.84138775, 0),(442, 143, 0.8589974, 1),(196, 133, 0.8166057, 2)],[(473, 176, 0.7320131, 3),(337, 165, 0.73004884, 4),(197, 133, 0.8598474, 5)],[(420, 176, 0.6951778, 6),(293, 154, 0.76514935, 7),(154, 133, 0.7135527, 8)],[(420, 261, 0.7520779, 9),(262, 218, 0.4267502, 10),(134, 197, 0.7333843, 11)],[(314, 251, 0.23509319, 12),(165, 228, 0.59333, 13),(453, 196, 0.6519662, 14)],[(388, 176, 0.62505144, 15),(518, 176, 0.6421095, 16),(240, 134, 0.6540677, 17)],[(549, 262, 0.73827094, 18),(389, 251, 0.71131617, 19),(240, 207, 0.6886268, 20)],[(495, 293, 0.62819993, 21),(357, 252, 0.7374373, 22),(207, 219, 0.560498, 23)],[(442, 282, 0.6578402, 24),(293, 252, 0.52459615, 25),(165, 228, 0.5512052, 26)],[(410, 283, 0.7377036, 27),(261, 272, 0.69384813, 28),(123, 239, 0.6885635, 29)],[(378, 431, 0.70034677, 30),(251, 411, 0.59873545, 31),(101, 356, 0.6479251, 32)],[(505, 283, 0.54467636, 33),(356, 271, 0.50983644, 34),(208, 229, 0.57463825, 35)],[(569, 314, 0.7413026, 36),(324, 293, 0.774911, 37),(228, 250, 0.7241578, 38)],[(538, 455, 0.58486414, 39),(282, 400, 0.46120968, 40),(207, 369, 0.56457037, 41)],[(314, 154, 0.8159541, 42),(433, 133, 0.72613055, 43),(186, 122, 0.80552864, 44)],[(335, 155, 0.8006719, 45),(453, 133, 0.8574599, 46),(206, 122, 0.80626, 47)],[(304, 133, 0.10505505, 48), (166, 111, 0.5242959, 49)],[(485, 144, 0.76806116, 50),(357, 143, 0.738666, 51),(218, 112, 0.73427236, 52)]]可以看出来这个数据是被分组的总共18个组分别代表18个关节每组涵盖了当前图像所有人的这个关节的坐标和置信度以及当前数据的编号依次往下排主要是为了索引keypoints_list里面的数据。这个keypoints_list里面是将所有的关键点的坐标和置信度不分组地塞到一起所以维度是(53,3)(53,3)(53,3)
把关键点可视化瞅瞅呗
#可视化关键点
img_show img.copy()
for i in range(nPoints):for j in range(len(detected_keypoints[i])):cv2.circle(img_show,detected_keypoints[i][j][0:2],3,[0,255,0],-1,cv2.LINE_AA)
plt.figure(figsize[8,8])
plt.imshow(img_show)
plt.axis(off)区分关键点
上面提取了所有的关键点但是没有计算哪个关键点属于哪个人此时就需要根据亲和场计算各关键点之间的联系。比如第一个人大臂到三个人各自的小臂关键点的亲和场肯定不同它只到属于自己的小臂关键点亲和场特征比较明显。知道这个道理接下来分析一波。
下面就是找到与当前关键点最可能连接的肢体关键点是哪个。
根据mapIdx里面定义的亲和场索引
先找到亲和场特征图
pafA output[0,mapIdx[k][0],:,:] #第k组连接关节的第一个关节PAF
pafB output[0,mapIdx[k][1],:,:] #第k组连接关节的第二个关节PAF
pafA cv2.resize(pafA,(frameWidth,frameHeight))
pafB cv2.resize(pafB,(frameWidth,frameHeight))再找到对应的肢体关键点索引
#找到这两个关节的位置
candA detected_keypoints[POSE_PAIRS[k][0]] #找到第一个关节的位置(所有人)
candB detected_keypoints[POSE_PAIRS[k][1]] #找到第二个关节的位置(所有人)再看看亲和场怎么算的先看论文的图 就是直接把两个关键点连起来中间做一条线计算亲和场上这条线上的值。
公式表达为 先计算dj2−dj1∥dj2−dj1∥2\frac{d_{j_2}-d_{j_1}}{\parallel d_{j_2}-d_{j_1} \parallel}_2∥dj2−dj1∥dj2−dj12
d_ij np.subtract(candB[j][:2],candA[i][:2])
norm np.linalg.norm(d_ij)
if(norm):d_ij d_ij/norm #公式(10的d部分)
else:continue画一条直线过去得到PAF上每个点的值
n_interp_samples 10
interp_coord list(zip(np.linspace(candA[i][0],candB[j][0],numn_interp_samples),np.linspace(candA[i][1],candB[j][1],numn_interp_samples)))
paf_interp []
for k in range(len(interp_coord)):paf_interp.append([pafA[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))], pafB[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))]])计算PAF得分和平均得分
paf_scores np.dot(paf_interp,d_ij)
avg_paf_score sum(paf_scores)/len(paf_scores)使用PAF分数进行阈值筛选
paf_score_th 0.1
conf_th 0.7
if(len(np.where(paf_scorespaf_score_th)[0])/n_interp_samples)conf_th:if(avg_paf_scoremaxScore):max_j jmaxScore avg_paf_scorefound 1找到关键点以后就可以把当前关键点对儿的索引和得分保存
valid_pair np.append(valid_pair,[[candA[i][3],candB[max_j][3],maxScore]],axis0) #被连接的肢体的关键点索引这里的整块代码写成函数如下主要是额外加了一些得分不够和没有关键点的情况
def get_valid_pairs(output,detected_keypoints):valid_pairs []invalid_pairs []n_interp_samples 10paf_score_th 0.1conf_th 0.7for k in range(len(mapIdx)):#两个可能连接的关节pafA output[0,mapIdx[k][0],:,:] #第k组连接关节的第一个关节PAFpafB output[0,mapIdx[k][1],:,:] #第k组连接关节的第二个关节PAFpafA cv2.resize(pafA,(frameWidth,frameHeight))pafB cv2.resize(pafB,(frameWidth,frameHeight))#找到这两个关节的位置candA detected_keypoints[POSE_PAIRS[k][0]] #找到第一个关节的位置(所有人)candB detected_keypoints[POSE_PAIRS[k][1]] #找到第二个关节的位置(所有人)nA len(candA)nB len(candB)#使用公式计算亲和场的得分if(nA!0 and nB!0): #如果有这两个关节valid_pair np.zeros((0,3))for i in range(nA): #对于第一个关节的所有人遍历max_j -1maxScore -1found 0for j in range(nB): #第二个关节的所有人遍历d_ij np.subtract(candB[j][:2],candA[i][:2])norm np.linalg.norm(d_ij)if(norm):d_ij d_ij/norm #公式(10的d部分)else:continueinterp_coord list(zip(np.linspace(candA[i][0],candB[j][0],numn_interp_samples),np.linspace(candA[i][1],candB[j][1],numn_interp_samples)))paf_interp []for k in range(len(interp_coord)):paf_interp.append([pafA[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))],pafB[int(round(interp_coord[k][1])),int(round(interp_coord[k][0]))]])paf_scores np.dot(paf_interp,d_ij)avg_paf_score sum(paf_scores)/len(paf_scores)if(len(np.where(paf_scorespaf_score_th)[0])/n_interp_samples)conf_th:if(avg_paf_scoremaxScore):max_j jmaxScore avg_paf_scorefound 1if found:valid_pair np.append(valid_pair,[[candA[i][3],candB[max_j][3],maxScore]],axis0) #被连接的肢体的关键点索引valid_pairs.append(valid_pair)else:#如果关节被遮挡等原因导致不存在invalid_pairs.append(k)valid_pairs.append([]) return valid_pairs,invalid_pairs稍微看一下这个函数的调用方法和返回的数据结构
#valid_pairs存储可成对的关节索引所有人的每个关节成一组比如3个人的第一个关节组成一个3*3的矩阵
valid_pairs,invalid_pairs get_valid_pairs(output,detected_keypoints)[array([[3. , 6. , 0.9164666 ],[4. , 7. , 0.85875524],[5. , 8. , 0.88577998]]), array([[ 3. , 16. , 0.90284936],[ 4. , 15. , 0.77933996],[ 5. , 17. , 0.80140835]]), array([[ 6. , 9. , 0.89909419],[ 7. , 10. , 0.52857684],[ 8. , 11. , 0.71177599]]), array([[ 9. , 14. , 0.8581396 ],[10. , 12. , 0.38934133],[11. , 13. , 0.8797749 ]]), array([[15. , 19. , 0.81766873],[16. , 18. , 0.78573793],[17. , 20. , 0.67746843]]), array([[18. , 21. , 0.63336505],[19. , 22. , 0.88562933],[20. , 23. , 0.7300858 ]]), array([[ 3. , 24. , 0.7975674 ],[ 4. , 25. , 0.53436182],[ 5. , 26. , 0.79336061]]), array([[24. , 27. , 0.80693887],[25. , 28. , 0.59622135],[26. , 29. , 0.80041958]]), array([[27. , 30. , 0.78664207],[28. , 31. , 0.73021965],[29. , 32. , 0.6312245 ]]), array([[ 3. , 33. , 0.90471435],[ 4. , 34. , 0.75671906],[ 5. , 35. , 0.75167511]]), array([[33. , 36. , 0.68868005],[34. , 37. , 0.86412876],[35. , 38. , 0.71096365]]), array([[36. , 39. , 0.82994086],[37. , 40. , 0.86046369],[38. , 41. , 0.9100325 ]]), array([[3. , 1. , 0.96472907],[4. , 0. , 0.97379622],[5. , 2. , 0.42410478]]), array([[ 0. , 42. , 0.8114687 ],[ 1. , 43. , 0.72544987],[ 2. , 44. , 0.90721482]]), array([[44. , 49. , 0.65025106]]), array([[ 0. , 45. , 0.7345252 ],[ 1. , 46. , 0.74511886],[ 2. , 47. , 0.83590513]]), array([[45. , 51. , 0.72804518],[46. , 50. , 0.90572883],[47. , 52. , 0.66244994]]), array([], shape(0, 3), dtypefloat64), array([], shape(0, 3), dtypefloat64)]同样是被分组了总共有mapIdx对应19种连接方法因为考虑到多人情况所以每个连接方法又对应多条连接线。我们把这些边全连起来看看
img_show img.copy()
for pair in valid_pairs:for i in range(pair.shape[0]):conA keypoints_list[int(pair[i][0])].astype(int)conB keypoints_list[int(pair[i][1])].astype(int)cv2.line(img_show, (conA[0], conA[1]), (conB[0], conB[1]), colors[i], 3, cv2.LINE_AA)plt.imshow(img_show)
plt.axis(off)看着基本没连错第一个人的肩膀不会连到第二个人的胳膊肘其它关键点一样。
分开存储
原理很简单就是把有连接的边放到一个集合里面
# 根据获得的能被连接的关键点对把坐标也对应好
def getPersonwiseKeyPoints(valid_pairs,invalid_pairs,keypoints_list):personwiseKeypoints -1 * np.ones((0,19))for k in range(len(mapIdx)): #遍历有效的关节连接if(k not in invalid_pairs): #当前关节存在partAs valid_pairs[k][:,0] #所有人第一个关节索引partBs valid_pairs[k][:,1] #所有人第二个关节索引indexA,indexB np.array(POSE_PAIRS[k]) #对应肢体的关键点索引for i in range(len(valid_pairs[k])): #当前关节有多少个数据点(人)found 0person_idx -1for j in range(len(personwiseKeypoints)):#遍历人 if(personwiseKeypoints[j][indexA]partAs[i]):person_idx jfound1breakif(found):personwiseKeypoints[person_idx][indexB] partBs[i]personwiseKeypoints[person_idx][-1] keypoints_list[partBs[i].astype(int),2]valid_pairs[k][i][2]elif not found and k17:row -1*np.ones(19)row[indexA] partAs[i]row[indexB] partBs[i]row[-1] sum(keypoints_list[valid_pairs[k][i,:2].astype(int),2]) valid_pairs[k][i][2]personwiseKeypoints np.vstack([personwiseKeypoints,row])return personwiseKeypoints这块代码自己实现也行反正能连接的边都在上一步知道了。这里只需要先执行后面的not found构建几个personwiseKeypoints然后再执行上面的found不断把上一个节点能连的下一个节点塞到对应位置。
输出
personwiseKeypoints getPersonwiseKeyPoints(valid_pairs,invalid_pairs,keypoints_list)
print(personwiseKeypoints)[[ 1. 3. 6. 9. 14. 16.18. 21. 24. 27. 30. 33.36. 39. 43. 46. -1. 50.25.16836102][ 0. 4. 7. 10. 12. 15.19. 22. 25. 28. 31. 34.37. 40. 42. 45. -1. 51.22.83992412][ 2. 5. 8. 11. 13. 17.20. 23. 26. 29. 32. 35.38. 41. 44. 47. 49. 52.25.00522498]]从结果上来看是三个人可视化看看
for i in range(17):for n in range(len(personwiseKeypoints)):index personwiseKeypoints[n][np.array(POSE_PAIRS[i])]if -1 in index:continueB np.int32(keypoints_list[index.astype(int), 0])A np.int32(keypoints_list[index.astype(int), 1])cv2.line(img_show, (B[0], A[0]), (B[1], A[1]), colors[i], 3, cv2.LINE_AA)plt.figure(figsize[15,15])
plt.imshow(img_show[:,:,[2,1,0]])后记
本博客对应代码 链接https://pan.baidu.com/s/1ywFPXyTr-9vWbQnUIdjT2g 提取码ajcl
后面有机会再解读一下openpose的网络搭建理论吧。
有兴趣的可以先看看
《Convolutional Pose Machines》 《OpenPose: Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields》 Stacked Hourglass Networks for Human Pose Estimation
本文以同步到微信公众号中代码也在公众号简介的GitHub中有兴趣可以关注一波