「Python」Python基于OpenPose实现图片中人体姿态识别

背景

合作公司有个小伙伴提到之前要处理图片中对人物裁剪的效果不好,之前仅识别到人脸做一个大概的图片裁切,无法针对半身图,全身图进行。因此希望能够有方式能够识别到人体的关节,识别到人的肩膀, 腰部,腿部等等。

我第一时间想到的是识别到人脸之后,获取到人脸宽高,再按比例去分割图片。例如我们常说九头身的模特身材,就是按人头大小去划分身体。第一个人脸高度为头部,第二个人脸高度为胸口,第三个人脸高度为腰部,这样类推。

不过想到网上应该会有更加成熟的解决方案,所以去网上搜了一下,有一个叫 OpenPose 的开源项目能够很好的满足人体姿态识别的需求。

介绍

OpenPose人体姿态识别项目是美国卡耐基梅隆大学(CMU)基于卷积神经网络和监督学习并以caffe为框架开发的开源库。可以实现人体动作、面部表情、手指运动等姿态估计。适用于单人和多人,具有极好的鲁棒性。是世界上首个基于深度学习的实时多人二维姿态估计应用,基于它的实例如雨后春笋般涌现。人体姿态估计技术在体育健身、动作采集、3D试衣、舆情监测等领域具有广阔的应用前景。

Github地址: https://github.com/CMU-Perceptual-Computing-Lab/openpose

这个项目有新手提供了很大的便利,在 Github Install档中有提供 Windows 安装的版本,或者仅需运行 OpenPose Demo即可(openpose/doc/01_demo.md),该 Demo 提供了处理图片、视频或者网络摄像头的视频流,并展示和后处理结果。

在 OpenPose 中,输入是一个或多个人的图像或视频,在这些图像中,OpenPose 会检测每个人的所有身体部位,并生成一个基于骨架的表示。OpenPose 使用两个深度卷积神经网络来实现此目的:Part Confidence Maps(PCM)和 Part Affinity Fields(PAFs)。PCM 预测每个像素属于人体部件的概率。PAFs则用于预测不同人体部位之间的连接情况。这两个网络都是基于ResNet架构的变形版本。一旦完成了所有部位的识别,OpenPose 将这些部位连接成一个完整的人体骨架,最终呈现出一个基于骨架的表示,呈现出每个人的不同身体部位和位置。

OpenPose 的应用非常广泛,例如自动化驾驶、娱乐、运动分析、医疗等领域,它在这些领域中都有着非常重要的应用。

使用

安装

我们需要通过 Python 来使用 OpenPose 的 API,因此要有Python环境

然后安装 OpenCV 库

1
pip install opencv

下载模型

一般我们要加载OpenPose的本地模型来进行识别

模型分为 TensorFlow 模型(.pb 文件)和 Caffe 模型

TensorFlow 模型我没有找到下载和转换的方式,各位如果了解的可以补充一下

Caffe 模型需要拉取 Github 项目下来,在 Models 目录下执行 getModels.bat 或者 getModels.sh 来进行下载

caffemodel 文件

1
2
3
4
5
cd models
.\getModels.bat

# 或者
./getModels.sh

或者可以直接在网上搜索别人分享的文件,下载完成之后将模型中的 caffemodel 文件和 prototxt 文件一起放到自己项目目录中。

其中官方项目中的 models 目录下面 pose 文件夹,又分为 body_25、coco、MPI。

其中

body_25模型:这个模型是基于COCO数据集进行训练的,其中包含了25个关键点,可以检测出人体的各种姿势,如手臂、腿、头部等。它的训练数据集较大,适用于多种不同场景下的人体姿势估计任务。pose_iter_584000.caffemodel是该模型的网络权重文件。

coco模型:这个模型同样是基于COCO数据集进行训练的,但只包含了18个关键点,相对于body_25模型来说更简化了姿势表示。它的训练数据集中的标注数据是以COCO关键点标注为基础。pose_iter_440000.caffemodel是该模型的网络权重文件。

mpi模型:是基于 MPII 数据集进行训练的,该数据集包含了约 40,000 张单人姿势估计图像,检测出 15 个关键点,涵盖头部、躯干、手臂和腿部等主要部位。相对于COCO和body_25模型,MPI模型提供了更为简化的关键点表示。

我这边使用的是 COCO 模型,因此复制 COCO 目录下的 pose_deploy_linevec.prototxt 文件以及下载好的 pose_iter_440000.caffemodel 到项目目录中使用即可

示例代码

下面是一个示例代码,展示了如何使用 OpenPose 在 Python 中使用摄像头或视频文件获取图像实现多人姿态检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import cv2
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--video", help="path to video file. If empty, camera's stream will be used")
args = parser.parse_args()

cap = cv2.VideoCapture(args.video if args.video else 0)

# 导入 OpenPose 模型
#net = cv2.dnn.readNetFromTensorflow("models/pose/graph_opt.pb")
cv2.dnn.readNetFromCaffe("data//pose/coco/pose_deploy_linevec.prototxt",
"data/pose/coco/pose_iter_440000.caffemodel")

while True:
# 从视频流中读取帧
ret, frame = cap.read()

# 帧处理
if ret:
# 将帧转换为 Blob
blob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (368, 368), (0, 0, 0), swapRB=False, crop=False)
net.setInput(blob)

# 运行前向传递
out = net.forward()

# 处理输出
h, w, c = frame.shape
points = []
for i in range(18):
# 获取每个人体部位的可信度映射(PCM)
heatMap = out[0, i, :, :]
_, conf, _, point = cv2.minMaxLoc(heatMap)
x = int(w * point[0] / out.shape[3])
y = int(h * point[1] / out.shape[2])

# 将部位添加到列表中
points.append((x, y) if conf > 0.1 else None)

for pair in POSE_PAIRS:
partFrom = pair[0]
partTo = pair[1]
idFrom = BODY_PARTS_DICT[partFrom]
idTo = BODY_PARTS_DICT[partTo]

if points[idFrom] and points[idTo]:
# 绘制连线
cv2.line(frame, points[idFrom], points[idTo], (0, 255, 0), 2)
cv2.circle(frame, points[idFrom], 5, (0, 0, 255), thickness=-1, lineType=cv2.FILLED)

# 显示结果
cv2.imshow("Output-Keypoints", frame)
key = cv2.waitKey(1)
if key == 27:
break

cap.release()
cv2.destroyAllWindows()

单人物人体姿态识别

我在这里实现了一个单人梯姿态识别代码,并且返回上下左右最顶端的坐标数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import cv2
import numpy as np

# 加载模型和配置文件
models = ['models/pose/coco/pose_deploy_linevec.prototxt', 'models/pose/coco/pose_iter_440000.caffemodel']
net = cv2.dnn.readNetFromCaffe(*models)

# 定义人体各部位的名称、颜色、连接关系和顺序
body_parts = {'Nose': 0, 'Neck': 1, 'RShoulder': 2, 'RElbow': 3, 'RWrist': 4, 'LShoulder': 5,
'LElbow': 6, 'LWrist': 7, 'RHip': 8, 'RKnee': 9, 'RAnkle': 10, 'LHip': 11,
'LKnee': 12, 'LAnkle': 13, 'REye': 14, 'LEye': 15, 'REar': 16, 'LEar': 17}

colors = [[0, 255, 0], [0, 255, 255], [255, 255, 0], [255, 0, 255], [255, 0, 0], [100, 50, 0],
[255, 255, 255], [100, 100, 100], [0, 125, 0], [0, 0, 125], [0, 255, 125], [125, 0, 0],
[125, 0, 125], [125, 125, 0], [0, 0, 255], [125, 125, 125], [50, 100, 0], [0, 100, 50]]

POSE_PAIRS = [['Neck', 'RShoulder'], ['Neck', 'LShoulder'], ['RShoulder', 'RElbow'],
['RElbow', 'RWrist'], ['LShoulder', 'LElbow'], ['LElbow', 'LWrist'],
['Neck', 'RHip'], ['RHip', 'RKnee'], ['RKnee', 'RAnkle'], ['Neck', 'LHip'],
['LHip', 'LKnee'], ['LKnee', 'LAnkle'], ['Nose', 'REye'], ['REye', 'REar'],
['Nose', 'LEye'], ['LEye', 'LEar'], ['Neck', 'Nose']]


def pose_detection(image, inWidth=368, inHeight=368, scale=0.003922, mean=(0, 0, 0), swapRB=False, crop=False):
"""

:param image: 输入的图像数据
:param inWidth: 输入图像的宽度,默认为368
:param inHeight: 输入图像的高度,默认为368
:param scale: 图像缩放因子,默认为0.003922
:param mean: 图像均值,默认为(0, 0, 0)
:param swapRB: 是否交换图像通道顺序,默认为False
:param crop: 是否进行裁剪,默认为False
:return: key_points:关键点坐标数组,get_vertex_coordinates(key_points)的结果:顶点坐标数组
"""
# 读取输入的图像数据
image = cv2.imread(img_path)
if image.shape[0] > 800:
# 如果图像的高度大于800,按比例缩放到高度为800
image = cv2.resize(image, (int(image.shape[1] * 800 / image.shape[0]), 800))

# 对输入图像进行预处理,生成blob对象
blob = cv2.dnn.blobFromImage(image, scale, (inWidth, inHeight), mean, swapRB, crop)

# 输入blob到神经网络中进行推断
net.setInput(blob)
# 获取输出结果
output = net.forward()

# 显示检测结果并添加关键点名称
H = output.shape[2]
W = output.shape[3]

# 初始化关键点坐标数组
keypoints = [(0, 0)] * len(body_parts)

# 遍历输出结果,获取每个关键点的置信度和坐标
for i, part_name in enumerate(body_parts):
confidenceMap = output[0, i, :, :]
_, conf, _, point = cv2.minMaxLoc(confidenceMap)
x = int((image.shape[1] * point[0]) / W)
y = int((image.shape[0] * point[1]) / H)
if conf > 0.1 and (x, y) != (0, 0):
print((x, y))
# 将符合条件的关键点坐标保存到数组中
keypoints[i] = (x, y)

# 在图像上显示关键点
cv2.circle(image, (x, y), 5, (0, 255, 255), -1)
cv2.putText(image, "{}".format(part_name), (x, y + 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.putText(image, "({}, {})".format(x, y), (x, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)

# 将关键点两两相连并绘制到图像中
for pair in POSE_PAIRS:
part_a = pair[0]
part_b = pair[1]
if part_a in body_parts.keys() and part_b in body_parts.keys():
id_a = body_parts[part_a]
id_b = body_parts[part_b]
if keypoints[id_a][0] != 0 and keypoints[id_a][1] != 0 and keypoints[id_b][0] != 0 and keypoints[id_b][
1] != 0:
cv2.line(image, keypoints[id_a], keypoints[id_b], colors[id_a], 2)

# 去除关键点数组中的无效点(坐标为(0, 0))
keypoints = [item for item in keypoints if item != (0, 0)]
# 将关键点数组转换为NumPy数组
key_points = np.array(keypoints)

# 显示处理后的图像
cv2.imshow("output", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

# 打印关键点数组的类型
print(type(key_points))

# 返回关键点数组和顶点坐标数组
return key_points, get_vertex_coordinates(key_points)


def get_vertex_coordinates(arr):
"""
获取坐标数组中的四个顶点坐标

:param arr: 包含坐标点的二维数组,行表示点的数量,列表示每个点的坐标轴数量
:return: arr 一个按照顺时针方向排列的四个顶点的坐标数组
"""
# 找到最上、最下、最左、最右四个点的索引
# 找到y轴坐标最小的点的索引
top_idx = np.argmin(arr[:, 1])
# 找到y轴坐标最大的点的索引
bottom_idx = np.argmax(arr[:, 1])
# 找到x轴坐标最小的点的索引
left_idx = np.argmin(arr[:, 0])
# 找到x轴坐标最大的点的索引
right_idx = np.argmax(arr[:, 0])

# 输出最上、最下、最左和最右四个点的坐标
print("最上坐标为:({}, {})".format(arr[top_idx][0], arr[top_idx][1]))
print("最下坐标为:({}, {})".format(arr[bottom_idx][0], arr[bottom_idx][1]))
print("最左坐标为:({}, {})".format(arr[left_idx][0], arr[left_idx][1]))
print("最右坐标为:({}, {})".format(arr[right_idx][0], arr[right_idx][1]))

# 按上右下左顺时针方向创建一个包含四个顶点坐标的数组
coordinates = np.array([
[arr[top_idx][0], arr[top_idx][1]],
[arr[right_idx][0], arr[right_idx][1]],
[arr[bottom_idx][0], arr[bottom_idx][1]],
[arr[left_idx][0], arr[left_idx][1]]
])

return coordinates


if __name__ == '__main__':
# 读取输入图像
img_path = 'img/img.png'

# 进行人体姿态估计
key_points, vertex_coordinates = pose_detection(image=img_path)

print('关键点坐标:', key_points)
print('四个顶点坐标:', vertex_coordinates)

这个代码可以满足单个人物的姿态识别,并且能够获取到每个关节点的坐标,可以依据坐标进行裁剪图片,但是存在多个人物的时候,会出现识别有误,效果并不是很理想。

多人物人体姿态识别

然后再找到了 LearnOpenCV 的一个多人姿态检测实现的文章(译文可见基于OpenCV使用OpenPose进行多个人体姿态估计 )。

主要实现方式是识别多个人物的关键点(例如鼻子),再通过关键点查找有效连接点,例如某人鼻子的左肩通常为这个人的左肩,他右边识别到的左肩则应该为另外一个人的左肩。通过亲和性方向进行识别和连接然后组合就能识别出来不同人物的姿态组,再通过姿态组绘制骨骼图就得到最终我们想要的多人物姿态识别。感兴趣的朋友可以自行查阅原文。

以下是我实际可用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
import argparse
import time

import cv2
import numpy as np

"""
该文件主要用于识别人体姿态并输出最边缘坐标
主要函数为 detect_pose
不需要预览结果请注释 cv2.imshow 等相关代码即可
依赖库
opencv-python~=4.7.0.72
numpy~=1.24.2
"""

# 设定模型文件路径和关键点数量等信息
# 模型结构文件
protoFile = "data//pose/coco/pose_deploy_linevec.prototxt"
# 训练好的参数文件
weightsFile = "data/pose/coco/pose_iter_440000.caffemodel"
# COCO 数据集中人体关键点的数量
nPoints = 18

# COCO 数据集中的人体关键点名称列表
keypointsMapping = ['Nose', 'Neck', 'R-Shoulder', 'R-Elbow', 'R-Wrist', 'L-Shoulder', 'L-Elbow', 'L-Wrist', 'R-Hip',
'R-Knee', 'R-Ankle', 'L-Hip', 'L-Knee', 'L-Ankle', 'R-Eye', 'L-Eye', 'R-Ear', 'L-Ear']

# 定义连接不同关键点之间的线段,即人体的姿势
# 在 COCO 输出格式中,关键点的编号从 0 开始,即第 0 个点表示 Nose,最后一个点是 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]]

# 定义每个 POSE_PAIRS 对应的 PAF 在输出中的索引
# pafs与POSE_PAIRS的索引,例如,对于POSE_PAIR(1,2),PAF位于输出的指数(31,32),类似,(1,5)->(39,40)等。
# PAF 表示 Part Affinity Fields,即用于描述关键点之间连接情况的向量场
# 在这里,使用了 COCO 数据集中提供的预训练模型,其输出结果包括关键点坐标和 PAF
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]]

# # 定义用于绘制不同连接线段的颜色,根据定义的姿势连接线段,每个连接线段对应一种颜色
colors = [[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]]

# 存储检测出来的所有关键点坐标列表,用于后续计算顶点
points = []


def detect_pose(image_path):
"""
检测姿势
:param image_path: 图片路径
:return: 返回 pose, points
其中 pose 为
points 为 按照顺时针方向排列(即上右下左)的四个最顶点的坐标数组
调用函数例子如下:
path = "img/test.png"
pose, points = detect_pose(path)
print(points)
"""

# 创建一个解析器
parser = argparse.ArgumentParser(description='运行关键点检测')

# 添加参数
parser.add_argument("--device", default="cpu", help="推理设备")
parser.add_argument("--image_file", default=image_path, help="输入图像")

# 解析参数
args = parser.parse_args()
# 读取输入图像
image1 = cv2.imread(args.image_file)

def getKeypoints(probMap, threshold=0.1):
"""
从输入的概率图(即 probMap)中提取关键点信息
:param probMap: 概率图
:param threshold: 二值化概率图时所采用的阈值,默认为 0.1
值较小时,可以提取出更多的关键点,但可能会包含一些噪声或冗余信息
值较大时,可以减少关键点的数量,但可能会漏掉一些有用信息
:return: 关键点列表
"""
# 对概率图进行高斯模糊,以去除噪声。
mapSmooth = cv2.GaussianBlur(probMap, (3, 3), 0, 0)

# 二值化概率图,生成一个二值掩模
mapMask = np.uint8(mapSmooth > threshold)

# 寻找轮廓
contours, _ = cv2.findContours(mapMask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

keypoints = []
# 针对每个轮廓寻找最大值
for cnt in contours:
# 构造一个与原图大小一致的全黑图像
blobMask = np.zeros(mapMask.shape)
# 在全黑图像上填充轮廓,轮廓内部的像素值为 1,其余像素值为 0
blobMask = cv2.fillConvexPoly(blobMask, cnt, 1)
# 计算概率图中对应区域的最大值和坐标, 将概率图与二值图像相乘,得到关键点所在区域的图像
maskedProbMap = mapSmooth * blobMask
# 在关键点所在区域的图像中,寻找最大值及其坐标,即关键点的位置
_, maxVal, _, maxLoc = cv2.minMaxLoc(maskedProbMap)
# 在关键点列表中加入当前关键点的坐标和对应概率
keypoints.append(maxLoc + (probMap[maxLoc[1], maxLoc[0]],))
# 冗余存储各关键点坐标,用于下面计算顶点坐标
points.append(maxLoc)
# 返回关键点列表
return keypoints

def getValidPairs(output):
"""
在所有检测到的人中,寻找有效连接关系
:param output: 检测到的人体内容
:return: 有效连接关系列表,无效连接关系列表
"""
# 存储有效连接关系
valid_pairs = []
# 存储无效连接关系
invalid_pairs = []
# 插值采样点数目
n_interp_samples = 10
# PAF 对齐阈值
paf_score_th = 0.1
# 有效连接容忍度阈值
conf_th = 0.7

# 对于每个 POSE_PAIR 进行如下处理
for k in range(len(mapIdx)):
# 获取该连线关系相关联的两个 PAF
pafA = output[0, mapIdx[k][0], :, :]
pafB = output[0, mapIdx[k][1], :, :]
# 调整 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]]
# 连线关系的关键点数目
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):
# 计算两个关键点之间的向量 d_ij
d_ij = np.subtract(candB[j][:2], candA[i][:2])
# 计算 d_ij 的模长
norm = np.linalg.norm(d_ij)
if norm:
# 归一化处理
d_ij = d_ij / norm
else:
continue
# # 对连接中介进行插值,生成 n_interp_samples 个采样点 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)))
# # 查询 PAF 值 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]))]])
# 计算连接得分 E
paf_scores = np.dot(paf_interp, d_ij)
avg_paf_score = sum(paf_scores) / len(paf_scores)

# 如果插值采样点中对齐 PAF 向量的比例高于阈值,则判定为有效连接关系
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

# 将有效连接信息添加到列表中
if found:
valid_pair = np.append(valid_pair, [[candA[i][3], candB[max_j][3], maxScore]], axis=0)

# 将有效连接信息存入全局列表中
valid_pairs.append(valid_pair)
else:
# 没有检测到关键点,说明连接无效
print("没有连接 : k = {}".format(k))
invalid_pairs.append(k)
valid_pairs.append([])
# 返回有效连接关系列表和无效连接关系列表
return valid_pairs, invalid_pairs

def getPersonwiseKeypoints(valid_pairs, invalid_pairs):
"""
遍历所有有效连接关系,将其对应的关键点分配给不同的人,并计算出每个人在当前姿态下的得分。
这样可以更为合适地去描绘人体姿态,减少出现 A 的左眼连结到 B 的右眼的情况
:param valid_pairs: 有效连接关系列表
:param invalid_pairs: 无效连接关系列表
:return: 个性化关键点数组
"""
# 每一行最后一个元素是总得分
personwiseKeypoints = -1 * np.ones((0, 19))

# 遍历所有有效连接关系
for k in range(len(mapIdx)):
if k not in invalid_pairs:
# partAs 和 partBs 分别是相互连接的两个关节点
partAs = valid_pairs[k][:, 0]
partBs = valid_pairs[k][:, 1]
indexA, indexB = np.array(POSE_PAIRS[k])
# 将 B 的分数加到 A 所在行的总得分中,或创建一个新行
for i in range(len(valid_pairs[k])):
found = 0
person_idx = -1
# 在已有的姿态中查找 partA。
for j in range(len(personwiseKeypoints)):
if personwiseKeypoints[j][indexA] == partAs[i]:
person_idx = j
found = 1
break

# 如果在当前姿态中找到了与 partA 相关联的关键点,则将 partB 添加到该行。
if found:
personwiseKeypoints[person_idx][indexB] = partBs[i]
# 在该姿态下,添加 partB 的关键点分数以及连接得分到该行的总得分中。
personwiseKeypoints[person_idx][-1] += keypoints_list[partBs[i].astype(int), 2] + \
valid_pairs[k][i][
2]
# 如果当前姿态中不存在与 partA 相关联的关键点,则创建一个新姿态
elif not found and k < 17:
row = -1 * np.ones(19)
row[indexA] = partAs[i]
row[indexB] = partBs[i]
# 在该姿态下,将两个关键点的关键点分数 scores 以及连接得分加起来作为该行的总得分。
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

# 获取图像的宽度和高度
frameWidth = image1.shape[1]
frameHeight = image1.shape[0]

t = time.time()
# 从磁盘上读取预训练模型
net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)
# 指定运行模型的设备类型,如果使用CPU则设置为CPU,否则设置为GPU
if args.device == "cpu":
net.setPreferableBackend(cv2.dnn.DNN_TARGET_CPU)
print("使用 CPU")
elif args.device == "gpu":
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
print("使用 GPU")

# 固定输入图像的高度,并根据图像的宽高比来计算输入的宽度
inHeight = 368
inWidth = int((inHeight / frameHeight) * frameWidth)

# 将输入图像转换为Blob格式,并进行归一化和缩放
inpBlob = cv2.dnn.blobFromImage(image1, 1.0 / 255, (inWidth, inHeight),
(0, 0, 0), swapRB=False, crop=False)
# 将输入Blob传递给网络
net.setInput(inpBlob)
# 运行前馈传递,可以获取到识别的人脸
output = net.forward()
print("前馈传递所需时间 = {}".format(time.time() - t))

# 定义一个列表用于存储检测出的所有关键点
detected_keypoints = []
# 创建一个 shape 为 (0, 3) 的 numpy 数组,用于保存关键点的位置和 id
keypoints_list = np.zeros((0, 3))
# 初始化关键点 id
keypoint_id = 0
# 设置概率阈值
threshold = 0.1

# 遍历每个关键点
for part in range(nPoints):
# 获取关键点对应的概率图,并将其 resize 到与输入图像相同的大小
probMap = output[0, part, :, :]
probMap = cv2.resize(probMap, (image1.shape[1], image1.shape[0]))

# 根据阈值获取该关键点的位置
keypoints = getKeypoints(probMap, threshold)
# 输出该关键点的位置信息
print("Keypoints - {} : {}".format(keypointsMapping[part], keypoints))
# 存储关键点的位置和 id
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)

# 在原始图像上绘制所有检测出的关键点和 id
frameClone = image1.copy()
for i in range(nPoints):
for j in range(len(detected_keypoints[i])):
# 输出关键点对应的坐标和名称
print(detected_keypoints[i][j][0:2])
cv2.putText(frameClone, "{}".format(keypointsMapping[i]), detected_keypoints[i][j][0:2],
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.putText(frameClone, "({})".format(detected_keypoints[i][j]), detected_keypoints[i][j][0:2],
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.circle(frameClone, detected_keypoints[i][j][0:2], 5, colors[i], -1, cv2.LINE_AA)

# 在原始图像上绘制每条相邻关键点之间的连线,描绘人体的姿态
valid_pairs, invalid_pairs = getValidPairs(output)
personwiseKeypoints = getPersonwiseKeypoints(valid_pairs, invalid_pairs)

for i in range(17):
for n in range(len(personwiseKeypoints)):
index = personwiseKeypoints[n][np.array(POSE_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(frameClone, (B[0], A[0]), (B[1], A[1]), colors[i], 3, cv2.LINE_AA)

# 可视化显示检测结果,仅供预览使用,这部分可注释
cv2.imshow("关键点", frameClone)
cv2.imshow("姿态检测", frameClone)
cv2.waitKey(0)

# 返回检测到的人体关键点和身体姿态的信息
return personwiseKeypoints[0] if len(personwiseKeypoints) > 0 else None, get_vertex_coordinates(points)


def get_vertex_coordinates(arr):
"""
获取坐标数组中的四个顶点坐标
:param arr: 包含坐标点的二维数组,行表示点的数量,列表示每个点的坐标轴数量
:return: arr 一个按照顺时针方向排列的四个顶点的坐标数组
"""

arr = np.array(arr)

# 找到最上、最下、最左、最右四个点的索引
# 找到y轴坐标最小的点的索引
top_idx = np.argmin(arr[:, 1])
# 找到y轴坐标最大的点的索引
bottom_idx = np.argmax(arr[:, 1])
# 找到x轴坐标最小的点的索引
left_idx = np.argmin(arr[:, 0])
# 找到x轴坐标最大的点的索引
right_idx = np.argmax(arr[:, 0])

# 输出最上、最下、最左和最右四个点的坐标
print("最上坐标为:({}, {})".format(arr[top_idx][0], arr[top_idx][1]))
print("最下坐标为:({}, {})".format(arr[bottom_idx][0], arr[bottom_idx][1]))
print("最左坐标为:({}, {})".format(arr[left_idx][0], arr[left_idx][1]))
print("最右坐标为:({}, {})".format(arr[right_idx][0], arr[right_idx][1]))

# 按上右下左顺时针方向创建一个包含四个顶点坐标的数组
coordinates = np.array([
[arr[top_idx][0], arr[top_idx][1]],
[arr[right_idx][0], arr[right_idx][1]],
[arr[bottom_idx][0], arr[bottom_idx][1]],
[arr[left_idx][0], arr[left_idx][1]]
])

return coordinates


if __name__ == '__main__':
path = "../data/result.png"
pose, points = detect_pose(path)
print('------------')
print(points)

结果返回了多个关键点和四个方向顶点坐标数组,可以看到效果还是很好的,可以满足业务需求了。

参考

Github开源人体姿态识别项目OpenPose中文文档 - 简书

基于OpenCV使用OpenPose进行多个人体姿态估计_qq_27158179的CSDN博客

Multi Person Pose Estimation in OpenCV using OpenPose (learnopencv.com)