2021.2.23更新: 没想到自己闲下来完成的测试代码,看的人挺多的。平时工作也忙,看到评论和私信有时没及时法就忘了。我已经把工程上传到我的资源,需要的自行获取,不再邮箱发送工程。
openpose的简单介绍
该篇内容是在对openpose的内容已经了解清楚的情况下,记录如何使用opencv中的dnn进行openpose的人体姿势的估计。 openpose的详细内容,已经在之前的openpose论文翻译中介绍。
在下载模型权重文件后,预测过程主要分一下几步:
- (1) 读取模型、预测的图片,进行神经网络的预测,获取预测结果
- (2) 关键点的检测
- (3) 利用PAFs,找到有效的关键点对
- (4) 将点对组合成正确的人体骨骼图
![]()
// {0, "Nose"}, // {1, "Neck"}, // {2, "RShoulder"}, // {3, "RElbow"}, // {4, "RWrist"}, // {5, "LShoulder"}, // {6, "LElbow"}, // {7, "LWrist"}, // {8, "MidHip"}, // {9, "RHip"}, // {10, "RKnee"}, // {11, "RAnkle"}, // {12, "LHip"}, // {13, "LKnee"}, // {14, "LAnkle"}, // {15, "REye"}, // {16, "LEye"}, // {17, "REar"}, // {18, "LEar"}, // {19, "LBigToe"}, // {20, "LSmallToe"}, // {21, "LHeel"}, // {22, "RBigToe"}, // {23, "RSmallToe"}, // {24, "RHeel"}, // {25, "Background"}
![]()
// {0, "Nose"},// {1, "Neck"},// {2, "RShoulder"},// {3, "RElbow"},// {4, "RWrist"},// {5, "LShoulder"},// {6, “LElbow”},// {7, "LWrist"},// {8, "RHip"},// {9, "RKnee"},// {10, "RAnkle"},// {11, "LHip"},// {12, "LKnee"},// {13, "LAnkle"},// {14, "REye"},// {15, "LEye"},// {16, "REar"},// {17, "LEar"}
2
config.py
config.py
中保存着18个关节点和25个关节点的相关信息prototxt_25 = "./models/body_25/pose_deploy.prototxt" caffemodel_25 = "./models/body_25/pose_iter_584000.caffemodel" point_names_25 = ['Nose', 'Neck', 'RShoulder', 'RElbow', 'RWrist', 'LShoulder', 'LElbow', 'LWrist', 'MidHip', 'RHip', 'RKnee', 'RAnkle', 'LHip', 'LKnee', 'LAnkle', 'REye', 'LEye', 'REar', 'LEar', 'LBigToe', 'LSmallToe', 'LHeel', 'RBigToe', 'RSmallToe' , 'RHeel'] point_pairs_25 = [[1, 8], [1, 2], [1, 5], [2, 3], [3, 4], [5, 6], [6, 7], [8, 9], [9, 10], [10, 11], [8, 12], [12, 13], [13, 14], [1, 0], [0, 15], [15, 17], [0, 16], [16, 18], [2, 17], [5, 18], [14, 19], [19, 20], [14, 21], [11, 22], [22, 23], [11, 24]] map_idx_25 = [[26, 27], [40, 41], [48, 49], [42, 43], [44, 45], [50, 51], [52, 53], [32, 33], [28, 29], [30, 31], [34, 35], [36, 37], [38, 39], [56, 57], [58, 59], [62, 63], [60, 61], [64, 65], [46, 47], [54, 55], [66, 67], [68, 69], [70, 71], [72, 73], [74, 75], [76, 77]] colors_25 = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0], [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255], [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85], [255, 170, 85], [255, 170, 170], [255, 170, 255], [255, 85, 85], [255, 85, 170], [255, 85, 255], [170, 170, 170]] prototxt_18 = "./models/coco/pose_deploy_linevec.prototxt" caffemodel_18 = "./models/coco/pose_iter_440000.caffemodel" point_names_18 = ['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'] point_pairs_18 = [[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]] map_idx_18 = [[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]] colors_18 = [[0, 100, 255], [0, 100, 255], [0, 255, 255], [0, 100, 255], [0, 255, 255], [0, 100, 255], [0, 255, 0], [255, 200, 100], [255, 0, 255], [0, 255, 0], [255, 200, 100], [255, 0, 255], [0, 0, 255], [255, 0, 0], [200, 200, 0], [255, 0, 0], [200, 200, 0], [0, 0, 0]]
3
predict.py
3.1 代码概述
import cv2 import time import numpy as np import matplotlib.pyplot as plt from config import * class general_mulitpose_model(object): def __init__(self): """ 初始化""" def get_model(self): """ 加载openpose的模型""" def getKeypoints(self): """ 获取关键点""" def getValidPairs(self): """ 获取有效点对""" def getPersonwiseKeypoints(self): """ 连接有效点对,获取完整的人体骨骼图""" def predict(self): """ 整体的预测流程""" def vis_pose(self): """ 预测结果可视化"""
3.2 初始化、模型加载
3.2.1
def __init__()
def __init__(self, keypoint_num): self.point_names = point_names_25 if keypoint_num==25 else point_names_18 self.point_pairs = point_pairs_25 if keypoint_num==25 else point_pairs_25 self.map_idx = map_idx_25 if keypoint_num==25 else map_idx_25 self.colors = colors_25 if keypoint_num==25 else colors_25 self.num_points = 25 if keypoint_num==25 else 18 self.prototxt = prototxt_25 if keypoint_num==25 else prototxt_18 self.caffemodel = caffemodel_25 if keypoint_num==25 else caffemodel_18 self.pose_net = self.get_model()
3.2.2
def get_model()
def get_model(self): coco_net = cv2.dnn.readNetFromCaffe(self.prototxt, self.caffemodel) # coco_net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) # coco_net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) return coco_net
注释掉的两句代码,是用于调用GPU进行预测。使用GPU预测,就要注意opencv的环境的配置,这里不做讲述
3.3 前向计算
def predict()
3.3.1 【step1】读取图片并处理,进行CNN预测,获取预测结果
def predict(self, imgfile): img_cv2 = cv2.imread(imgfile) img_width, img_height = img_cv2.shape[1], img_cv2.shape[0] net_height = 368 net_width = int((net_height / img_height) * img_width) start = time.time() #将读取的图片转化成神经网络在dnn的api中所需形式 in_blob = cv2.dnn.blobFromImage( img_cv2, 1.0 / 255, (net_width, net_height), (0, 0, 0), swapRB=False, crop=False) # 进行神经网络的前向计算,并获取预测结果output self.pose_net.setInput(in_blob) output = self.pose_net.forward() print("[INFO]Time Taken in Forward pass: {}".format(time.time() - start))
我们可以可视化一下预测的关节点的信息。当i=0时,我们可视化的是第0个关节点鼻子。具体操作为:首先把输出的大小调整到与输入一样,然后在图像上进行Alpha混合proMap。
"""========== for testing ========""" i = 0 probMap = output[0, i, :, :] probMap = cv2.resize(probMap, (img_width, img_height)) plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)) plt.imshow(probMap, alpha=0.6) plt.show()
3.3.2 【step 2】解析预测结果,获取预测的关节点
我们针对每个关键点,对相应的置信图设置一个阈值(这里采用0.1)生成二值图
mapSmooth = cv2.GaussianBlur(probMap, (3, 3), 0, 0) mapMask = np.uint8(mapSmooth > threshold) plt.imshow(mapMask) plt.show()
probMap为预测结果数关键点特征图中的每一通道的热量图。
mapMask为probMap的mask,其中存在这当前关键点的多个blob。第0个通道的mapMask(也就是鼻子的关键点的热量图的mask)可视化为
寻找关键点的确切位置,我们需要找到当前关键点中的所有blob的极大值。具体步骤如下:
- 使用阈值,找到当前关节点的所有的blob的mask
- 迭代每个blob,利用掩码获取每个blob的像素值,然后求取最大位置的坐标和数值。
上面的过程实现的函数为
def getKeypoints()
def getKeypoints(self, probMap, threshold=0.1): mapSmooth = cv2.GaussianBlur(probMap, (3, 3), 0, 0) mapMask = np.uint8(mapSmooth > threshold) keypoints = [] # find the blobs contours, hierarchy = cv2.findContours(mapMask, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) # len(contours)=2,因为图中存在2个鼻子,contours列表中元素为每一个blob的所有坐标值 # for each blob find the maxima for cnt in contours: blobMask = np.zeros(mapMask.shape) blobMask = cv2.fillConvexPoly(blobMask, cnt, 1) # 将blobMask中的cnt坐标位置填充1 maskedProbMap = mapSmooth * blobMask # 获取当前blob位置上的像素值 _, maxVal, _, maxLoc = cv2.minMaxLoc(maskedProbMap) # 获取maskedProbMap中的最大最小值、以及相应的坐标信息 keypoints.append(maxLoc + (probMap[maxLoc[1], maxLoc[0]],)) return keypoints
这里第0个热量图,获取的keyspoints为
- [(400, 198, 0.8132073), (148, 180, 0.784292)]
getKeypoints()
在def predict()
中被调用的方式如下## def predict() 中 detected_keypoints = [] keypoints_list = np.zeros((0, 3)) keypoint_id = 0 threshold = 0.1 for part in range(self.num_points): probMap = output[0, part, :, :] probMap = cv2.resize(probMap, (img_cv2.shape[1], img_cv2.shape[0])) keypoints = self.getKeypoints(probMap, threshold) print("Keypoints - {} : {}".format(self.point_names[part], keypoints)) keypoints_with_id = [] # 记录的是当前置信图中获取到的关键点,并且给同类关键点增加了索引号 for i in range(len(keypoints)): keypoints_with_id.append(keypoints[i] + (keypoint_id,)) keypoints_list = np.vstack([keypoints_list, keypoints[i]]) keypoint_id += 1 detected_keypoints.append(keypoints_with_id)
这里需要注意:一个人总共有的关节点个数,这里称之为关节点序号。将图片中所有人关节点进行一个编号,这里称之为编号
- detected_keypoints:子元素为,某一种关节点的所有检测位置的信息
- keypoints_list:子元素为,所有被检测的关键点的相关信息
后面会使用到的数据为 detected_keypoints、keypoints_list。之所以分成两种形式记录检测点的信息,是为了更好代码编写和管理
for i in range(len(detected_keypoints)): print(detected_keypoints[i])
运行完该函数打印detected_keypoints如下,其中每一行为一张热量图中获取到对应关键的所有可能位置。[(坐标x, 坐标y, 置信度, 编号),…]
[(400, 198, 0.8132073, 0), (148, 180, 0.784292, 1)]
[(383, 245, 0.8672323, 2), (178, 229, 0.77064836, 3)]
[(321, 244, 0.713973, 4), (118, 230, 0.6964537, 5)]
[(305, 340, 0.83444226, 6), (117, 310, 0.73989004, 7)]
[(321, 418, 0.7464948, 8), (86, 261, 0.77350193, 9)]
[(431, 246, 0.7677428, 10), (226, 228, 0.6995288, 11)]
[(415, 340, 0.74716604, 12), (242, 324, 0.71403414, 13)]
[(415, 419, 0.62556756, 14), (195, 387, 0.7372464, 15), (416, 261, 0.46505377, 16)]
[(178, 387, 0.5826589, 17), (351, 372, 0.6017035, 18)]
[(133, 388, 0.62657654, 19), (306, 372, 0.56834227, 20)]
[(117, 500, 0.67122287, 21), (273, 482, 0.6239272, 22)]
[(101, 626, 0.74809325, 23), (272, 595, 0.6482342, 24)]
[(211, 387, 0.6102824, 25), (383, 373, 0.59796363, 26)]
[(383, 483, 0.6995035, 27), (210, 483, 0.5740754, 28)]
[(225, 625, 0.6215888, 29), (399, 626, 0.7091607, 30)]
[(399, 182, 0.89564496, 31), (132, 167, 0.83662105, 32)]
[(429, 197, 0.8141045, 33), (148, 165, 0.84335065, 34)]
[(117, 182, 0.7798492, 35), (384, 167, 0.7490212, 36)]
[(431, 213, 0.8018406, 37), (164, 150, 0.8991325, 38)]
[(383, 674, 0.74414533, 39), (210, 674, 0.62340456, 40)]
[(399, 673, 0.7276634, 41), (225, 673, 0.5719849, 42)]
[(399, 640, 0.53600895, 43), (226, 626, 0.6315871, 44)]
[(102, 688, 0.6469026, 45), (273, 641, 0.55285895, 46)]
[(87, 673, 0.62596595, 47), (258, 641, 0.5646609, 48)]
[(101, 640, 0.44578055, 49), (273, 609, 0.5640535, 50)]for i in range(len(keypoints_list)): print(keypoints_list[i])
运行完该函数打印detected_keypoints如下,其中每一行为一张热量图中获取到对应关键的所有可能位置。(坐标x, 坐标y, 置信度),是按照关键点的在keypoints_list的索引号,即该关键点的编号
[400. 198. 0.81320733]
[148. 180. 0.78429198]
[383. 245. 0.86723232]
[178. 229. 0.77064836]
[321. 244. 0.71397299]
[118. 230. 0.69645369]
[305. 340. 0.83444226]
[117. 310. 0.73989004]
[321. 418. 0.74649483]
[ 86. 261. 0.77350193]
[431. 246. 0.76774281]
[226. 228. 0.69952881]
[415. 340. 0.74716604]
[242. 324. 0.71403414]
[415. 419. 0.62556756]
[195. 387. 0.73724639]
[416. 261. 0.46505377]
[178. 387. 0.58265889]
[351. 372. 0.60170352]
[133. 388. 0.62657654]
[306. 372. 0.56834227]
[117. 500. 0.67122287]
[273. 482. 0.62392718]
[101. 626. 0.74809325]
[272. 595. 0.64823419]
[211. 387. 0.61028242]
[383. 373. 0.59796363]
[383. 483. 0.69950348]
[210. 483. 0.5740754]
[2.25000000e+02 6.25000000e+02 6.21588826e-01]
[399. 626. 0.70916069]
[399. 182. 0.89564496]
[132. 167. 0.83662105]
[429. 197. 0.8141045]
[148. 165. 0.84335065]
[117. 182. 0.77984917]
[384. 167. 0.74902117]
[431. 213. 0.8018406]
[164. 150. 0.89913249]
[383. 674. 0.74414533]
[2.10000000e+02 6.74000000e+02 6.23404562e-01]
[399. 673. 0.7276634]
[2.25000000e+02 6.73000000e+02 5.71984887e-01]
[3.99000000e+02 6.40000000e+02 5.36008954e-01]
[226. 626. 0.63158709]
[1.02000000e+02 6.88000000e+02 6.46902621e-01]
[2.73000000e+02 6.41000000e+02 5.52858949e-01]
[8.70000000e+01 6.73000000e+02 6.25965953e-01]
[2.58000000e+02 6.41000000e+02 5.64660907e-01]
[1.01000000e+02 6.40000000e+02 4.45780545e-01]
[2.73000000e+02 6.09000000e+02 5.64053476e-01]
3.3.3 【step3】获取有效连接点对
1 计算算法回顾
E = ∫ u = 0 u = 1 L c ( p ( u ) ) ⋅ d j 2 − d j 1 ∣ ∣ d j 2 − d j 1 ∣ ∣ 2 d u E=\int_{u=0}^{u=1}L_{c}(p(u)) \cdot \frac{d_{j2}-d_{j1}}{||d_{j2}-d_{j1}||_2}du E=∫u=0u=1Lc(p(u))⋅∣∣dj2−dj1∣∣2dj2−dj1du 这里 p ( u ) p(u) p(u) 是肢体两端关键点 d j 1 , d j 2 d_{j1},d_{j2} dj1,dj2 的之间的点集(两个关键点之间的插值点)。 p ( u ) = ( 1 − u ) d j 1 + u d j 2 p(u)=(1-u)d_{j1}+ud_{j2} p(u)=(1−u)dj1+udj2实际中,我们通过取样u的等间距值,求和对应L值来计算积分。
- L c ( p ( u ) ) L_c(p(u)) Lc(p(u)) 是在点p位置上的PAF数值;
- d j 2 − d j 1 ∣ ∣ d j 2 − d j 1 ∣ ∣ 2 \frac{d_{j2}-d_{j1}}{||d_{j2}-d_{j1}||_2} ∣∣dj2−dj1∣∣2dj2−dj1 是两个关键点之间的单位向量
- p ( u ) p(u) p(u) 是两个关键点之间的插值
- E E E 是通过计算PAF L 和向量 d i j d_{ij} dij之间的点积得到的。
2 计算流程为:
- step1:获取每个肢体的的两个PAFs,已经肢体两端的关键点
- step2:迭代两个关键点的点集,两两计算方向向量 d i j = d j 2 − d j 1 ∣ ∣ d j 2 − d j 1 ∣ ∣ 2 d_{ij} = \frac{d_{j2}-d_{j1}}{||d_{j2}-d_{j1}||_2} dij=∣∣dj2−dj1∣∣2dj2−dj1
- step3:在连线两点的线段上创建一个10个插值点的数组 p ( u ) p(u) p(u)
- step4:并对这些点的PAF和单位向量进行点乘运算 e = L c ( p ( u ) ) ⋅ d i j e = L_{c}(p(u)) \cdot d_{ij} e=Lc(p(u))⋅dij
- step5:如果这些点中有70%满足标准,则把这一对当成有效
3 进行代码分析:
getValidPairs()
在def predict()
中被调用的方式如下,返回的为有效的连接点对和无效的连接点对valid_pairs, invalid_pairs = self.getValidPairs(output, detected_keypoints,img_width,img_height)
- 具体定义如下
(其中output为神经网络的输出,detect_keypoints为上面讲解的检测出的关键点,img_width为图片的宽,img_height为图片的高)def getValidPairs(self, output, detected_keypoints, img_width, img_height): valid_pairs = [] invalid_pairs = [] n_interp_samples = 10 paf_score_th = 0.1 conf_th = 0.7
step1:获取每个肢体的的两个PAFs,已经肢体两端的关键点
"""迭代每一个肢体的PAFs,进行处理""" for k in range(len(self.map_idx)): # A->B constitute a limb pafA = output[0, self.map_idx[k][0], :, :] pafB = output[0, self.map_idx[k][1], :, :] pafA = cv2.resize(pafA, (img_width, img_height)) pafB = cv2.resize(pafB, (img_width, img_height)) # 肢体的两端的关节点 的所有可能位置 candA = detected_keypoints[self.point_pairs[k][0]] candB = detected_keypoints[self.point_pairs[k][1]]
这里获取到了当前肢体的PAFs,可视化后
# plt.imshow(cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)) # plt.imshow(pafA, alpha=0.4) # plt.show()
step2:迭代两个关键点的点集,两两计算方向向量 d i j = d j 2 − d j 1 ∣ ∣ d j 2 − d j 1 ∣ ∣ 2 d_{ij} = \frac{d_{j2}-d_{j1}}{||d_{j2}-d_{j1}||_2} dij=∣∣dj2−dj1∣∣2dj2−dj1
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 = -1 maxScore = -1 found = 0 for j in range(nB): # 获取关节点A中的第i个,与关节点B中的第j个,计算两个点之间的单位向量 d_ij = np.subtract(candB[j][:2], candA[i][:2]) norm = np.linalg.norm(d_ij) if norm: d_ij = d_ij / norm else: continue
step3: 在连线两点的线段上创建一个10个插值点的数组 p ( u ) p(u) p(u)
# Find p(u) interp_coord = list( zip(np.linspace(candA[i][0], candB[j][0], num=n_interp_samples), np.linspace(candA[i][1], candB[j][1], num=n_interp_samples))) # Find L(p(u)) 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]))]])
step4:并对这些点的PAF和单位向量进行点乘运算 e = L c ( p ( u ) ) ⋅ d i j e = L_{c}(p(u)) \cdot d_{ij} e=Lc(p(u))⋅dij
paf_scores = np.dot(paf_interp, d_ij) avg_paf_score = sum(paf_scores) / len(paf_scores)
step5:如果这些点中有70%满足标准,则把这一对当成有效
if (len(np.where(paf_scores > paf_score_th)[ 0]) / n_interp_samples) > conf_th: if avg_paf_score > maxScore: max_j = j maxScore = avg_paf_score found = 1 # Append the connection to the list if found: valid_pair = np.append(valid_pair, [[candA[i][3], candB[max_j][3], maxScore]], axis=0) # Append the detected connections to the global list valid_pairs.append(valid_pair) else: # If no keypoints are detected print("No Connection : k = {}".format(k)) invalid_pairs.append(k) valid_pairs.append([]) return valid_pairs, invalid_pairs
运行完该函数
【invalid_pairs】 中存放的为图片中没有找到的肢体的序号
【valid_pairs】 存放得到数据如下 (有效点对中点A的 编号, 有效点对中点B的 编号,连接的置信度)for i in range(len(valid_pairs)): print(valid_pairs[i],"===")
[[ 2. 18. 0.90824686]
[ 3. 17. 0.91445097]] ===
[[2. 4. 0.94886258]
[3. 5. 0.79143293]] ===
[[ 2. 10. 0.92424058]
[ 3. 11. 0.92916565]] ===
[[4. 6. 0.8333285 ]
[5. 7. 0.87003183]] ===
[[6. 8. 0.90968755]
[7. 9. 0.87161859]] ===
[[10. 12. 0.76567687]
[11. 13. 0.90720761]] ===
[[12. 16. 0.51112171]
[13. 15. 0.94733449]] ===
[[17. 19. 0.64863247]
[18. 20. 0.649182 ]] ===
[[19. 21. 0.70886038]
[20. 22. 0.7336272 ]] ===
[[21. 23. 0.84220238]
[22. 24. 0.92282994]] ===
[[17. 25. 0.64012247]
[18. 26. 0.59312024]] ===
[[25. 28. 0.91612404]
[26. 27. 0.91176482]] ===
[[27. 30. 0.92497094]
[28. 29. 0.89947163]] ===
[[2. 0. 0.73280045]
[3. 1. 0.95625335]] ===
[[ 0. 31. 0.89538064]
[ 1. 32. 0.79193573]] ===
[[31. 36. 0.78094221]
[32. 35. 0.59810466]] ===
[[ 0. 33. 0.91020581]
[ 1. 34. 0.95672418]] ===
[[33. 37. 0.75157858]
[34. 38. 0.8036731 ]] ===
[[ 4. 36. 0.57039805]
[ 5. 35. 0.74548511]] ===
[[10. 37. 0.8626399 ]
[11. 38. 0.89889973]] ===
[[29. 40. 0.81810908]
[30. 39. 0.77516393]] ===
[[39. 41. 0.75241477]
[40. 42. 0.59358489]] ===
[[29. 44. 0.20911491]
[30. 43. 0.21324079]] ===
[[23. 45. 0.70849594]
[24. 46. 0.8847764 ]] ===
[[45. 47. 0.6372949 ]
[46. 48. 0.64648099]] ===
[[23. 49. 0.21661166]
[24. 50. 0.32713855]] ===
3.3.3 【step 3】连接有效点对,获取完整的人体骨骼图
完成有效关键点对的获取,然后就可以将有效点对组合起来,得到完整且独立的人体骨架。
getPersonwiseKeypoints()
在def predict()
中被调用的方式如下,personwiseKeypoints = self.getPersonwiseKeypoints(valid_pairs, invalid_pairs, keypoints_list)
- 具体定义为:
def getPersonwiseKeypoints(self, valid_pairs, invalid_pairs, keypoints_list):
【step 1】 针对每类肢体,遍历其有效点对(当肢体存在有效的连接点对时),点对至少存在一组。代码中用partAs、partBs表示。
# 创建空列表,用来存放每个人的关键点 personwiseKeypoints = -1 * np.ones((0, self.num_points+1)) # 遍历每类肢体,并且该肢体存在有效的连接点对 for k in range(len(self.map_idx)): if k not in invalid_pairs: partAs = valid_pairs[k][:, 0] #获取了当前肢体两端,的关节点的序号A的所有位置的编号 partBs = valid_pairs[k][:, 1] #获取了当前肢体两端,的关节点的序号B的所有位置的编号 indexA, indexB = np.array(self.point_pairs[k]) #获取了当前肢体,两端所对应的两个关节点的序号 # 遍历当前肢体的 所有有效的连接点对 for i in range(len(valid_pairs[k])): found = 0 person_idx = -1
【step 2】 针对该类肢体当前有效点对中的点partA,判断其是否在在已有的 personwiseKeypoints 中的第 person_idx人 的关键点中
# 判断肢体起始端点partAs,,设置 found、person_idx 的信息 for j in range(len(personwiseKeypoints)): if personwiseKeypoints[j][indexA] == partAs[i]: person_idx = j found = 1 break
【step 3】 如果是,记录点partA对应的点partB,以及计算相应的置信度
if found: personwiseKeypoints[person_idx][indexB] = partBs[i] personwiseKeypoints[person_idx][-1] += keypoints_list[ partBs[i].astype(int), 2] + \ valid_pairs[k][i][2]
【step 4】 如果不是,重新建立一个列表,记录该类肢体当前有效点对中的点partA、partB,以及计算相应的置信度。然后将该列表添加到personwiseKeypoints中。
# if find no partA in the subset, create a new subset elif not found and k < self.num_points-1: row = -1 * np.ones(self.num_points+1) row[indexA] = partAs[i] row[indexB] = partBs[i] # add the keypoint_scores for the two keypoints and the paf_score 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
3.4 预测结果画图
def vis_pose(self, img_file, personwiseKeypoints, keypoints_list): img_cv2 = cv2.imread(img_file) for i in range(self.num_points-1): for n in range(len(personwiseKeypoints)): index = personwiseKeypoints[n][np.array(self.point_pairs[i])] if -1 in index: continue B = np.int32(keypoints_list[index.astype(int), 0]) A = np.int32(keypoints_list[index.astype(int), 1]) cv2.line(img_cv2, (B[0], A[0]), (B[1], A[1]), self.colors[i], 3, cv2.LINE_AA) plt.figure() plt.imshow(img_cv2[:, :, ::-1]) plt.title("Results") plt.axis("off") plt.show()
评价
可以明显的看到,上面的预测结果某些关节点并不准确,比如右边人物的左脚踝,就发生了明显的错误。
为了对比实验,使用openpose官网发布的exe进行测试,使用相同的网络模型(即官网提供的模型),效果要优于该篇博客讲解的基于opencv的openpose的预测结果,大家可以自行测试。
分析原因,排除了网络模型的因素,剩预测时数值精度以及后处理的影响了。具体细节可以阅读官网提供的代码进行算法细节比较。实际使用,看自己项目对速度和精度的要求。