【问题标题】:Calculating angles of body skeleton in video using OpenPose使用 OpenPose 计算视频中身体骨骼的角度
【发布时间】:2021-09-12 20:18:26
【问题描述】:

免责声明:这个问题是关于OpenPose,但这里的关键实际上是弄清楚如何使用输出(存储在JSON中的坐标)而不是如何使用OpenPose,所以请考虑将其阅读到最后。

我有一个骑自行车的人的视频(他坐着的侧面,所以我们看到了右侧)。我使用 OpenPose 来提取骨架的坐标。 OpenPose 在 JSON 文件中提供坐标,如下所示(请参阅 explanation 的文档):

{
  "version": 1.3,
  "people": [
    {
      "person_id": [
        -1
      ],
      "pose_keypoints_2d": [
        594.071,
        214.017,
        0.917187,
        523.639,
        216.025,
        0.797579,
        519.661,
        212.063,
        0.856948,
        539.251,
        294.394,
        0.873084,
        619.546,
        304.215,
        0.897219,
        531.424,
        221.854,
        0.694434,
        550.986,
        310.036,
        0.787151,
        625.477,
        339.436,
        0.845077,
        423.656,
        319.878,
        0.660646,
        404.111,
        321.807,
        0.650697,
        484.434,
        437.41,
        0.85125,
        404.13,
        556.854,
        0.791542,
        443.261,
        319.801,
        0.601241,
        541.241,
        370.793,
        0.921286,
        502.02,
        494.141,
        0.799306,
        592.138,
        198.429,
        0.943879,
        0,
        0,
        0,
        562.742,
        182.698,
        0.914112,
        0,
        0,
        0,
        537.25,
        504.024,
        0.530087,
        535.323,
        500.073,
        0.526998,
        486.351,
        500.042,
        0.615485,
        449.168,
        594.093,
        0.700363,
        431.482,
        594.156,
        0.693443,
        386.46,
        560.803,
        0.803862
      ],
      "face_keypoints_2d": [],
      "hand_left_keypoints_2d": [],
      "hand_right_keypoints_2d": [],
      "pose_keypoints_3d": [],
      "face_keypoints_3d": [],
      "hand_left_keypoints_3d": [],
      "hand_right_keypoints_3d": []
    }
  ]
}

据我了解,每个 JSON 都是视频的一帧。

我的目标是检测特定坐标的角度,如右膝、右臂等。例如:

openpose_angles = [(9, 10, 11, "right_knee"),
                   (2, 3, 4,   "right_arm")]

这是基于以下 OpenPose 骨架假人:

我所做的是计算三个坐标之间的角度(使用 Python):

temp_df = json.load(open(os.path.join(jsons_dir, file)))
listPoints = list(zip(*[iter(temp_df['people'][person_number]['pose_keypoints_2d'])] * 3))

count = 0
lmList2 = {}
for x,y,c in listPoints:
    lmList2[count]=(x,y,c)
    count+=1

p1=angle_cords[0]
p2=angle_cords[1]
p3=angle_cords[2]
x1, y1 ,c1= lmList2[p1]
x2, y2, c2 = lmList2[p2]
x3, y3, c3 = lmList2[p3]

# Calculate the angle
angle = math.degrees(math.atan2(y3 - y2, x3 - x2) -
                     math.atan2(y1 - y2, x1 - x2))
if angle < 0:
    angle += 360

我在一些博客上看到的这种方法(我忘了在哪里),但与 OpenCV 而不是 OpenPose 相关(不确定是否有所不同),但看到的角度没有意义。我们向老师展示了它,他建议我们使用向量来计算角度,而不是使用math.atan2。但是我们对如何实现这一点感到困惑。

总而言之,这里有一个问题 - 计算角度的最佳方法是什么?如何使用向量计算它们?

【问题讨论】:

  • atan2 非常适合计算角度,而“使用向量代替”并不是一个特别有用的建议,因为 math.atan2(y3 - y2, x3 - x2) - math.atan2(y1 - y2, x1 - x2) 是在计算两个向量之间的定向角度((x2, y2 ), (x1, y1)) 和 ((x2, y2), (x3, y3))。
  • 但是,我不知道您的 json 文件中 "pose_keypoints_2d" 中的数字列表代表什么?这只是一个数字列表。你如何识别该列表中右膝的 (x, y) 坐标和右臂的 (x, y) 坐标等?
  • 所有的魔法显然都发生在listPoints = list(zip(*[iter(temp_df['people'][person_number]['pose_keypoints_2d'])] * 3))。你了解那行代码及其作用吗?
  • 你不能这样计算。 我确定你不知道骑自行车的人的关节范围。所以对你来说不是魔术功能!

标签: python math openpose


【解决方案1】:

你的老师是对的。我怀疑问题是 3 个点可以根据顺序组成 3 个不同的角度。只需考虑三角形中的角度。你似乎也忽略了第三个坐标。

重建骨架

在您的图片中,您指出骨骼的边缘/骨骼是

edges = {(0, 1), (0, 15), (0, 16), (1, 2), (1, 5), (1, 8), (2, 3), (3, 4), (5, 6), (6, 7), (8, 9), (8, 12), (9, 10), (10, 11), (11, 22), (11, 24), (12, 13), (13, 14), (14, 19), (14, 21), (15, 17), (16, 18), (19, 20), (22, 23)}

我从你的 json 文件中得到分数

np.array(pose['people'][0]['pose_keypoints_2d']).reshape(-1,3)

现在我忽略第三个组件来绘制它以了解我正在使用什么。请注意,这不会改变比例,因为第三个分量与其他分量相比非常小。

一个人肯定会认出一个颠倒的人。我注意到似乎有某种伪影,但我怀疑这只是识别错误,在其他框架中会更好。

计算角度

回想一下,点积除以范数的积给出了角度的余弦值。见the wikipedia article on dot product。我将包括那篇文章中的相关图片。 所以现在我可以像这样得到两个连接边的角度。

def get_angle(edge1,  edge2):
    assert tuple(sorted(edge1)) in edges
    assert tuple(sorted(edge2)) in edges
    edge1 = set(edge1)
    edge2 = set(edge2)
    mid_point = edge1.intersection(edge2).pop()
    a = (edge1-edge2).pop()
    b = (edge2-edge1).pop()
    v1 = points[mid_point]-points[a]
    v2 = points[mid_point]-points[b]

    angle = (math.degrees(np.arccos(np.dot(v1,v2)
                                    /(np.linalg.norm(v1)*np.linalg.norm(v2)))))
    return angle

例如,如果您想要肘部角度,您可以这样做

get_angle((3, 4), (2, 3))
get_angle((5, 6), (6, 7))

给你

110.35748420197164
124.04586139643376

当我看到我的骨架照片时,这对我来说是有意义的。比直角多一点。

如果我必须计算两个不共享一点的向量之间的角度怎么办?

在这种情况下,您必须更加小心,因为在这种情况下,矢量方向很重要。首先是代码

def get_oriented_angle(edge1,  edge2):    
    assert tuple(sorted(edge1)) in edges
    assert tuple(sorted(edge2)) in edges
    
    v1 = points[edge1[0]]-points[edge1[1]]
    v2 = points[edge2[0]]-points[edge2[1]]
    angle = (math.degrees(np.arccos(np.dot(v1,v2)   
                              /(np.linalg.norm(v1)*np.linalg.norm(v2)))))
    return angle

如您所见,代码要容易得多,因为我不为您排序。但这很危险,因为两个向量之间有两个角度(如果您不考虑它们的方向)。确保两个向量都指向您正在考虑的角度的点的方向(两个相反的方向也可以)。

这里是和上面一样的例子

get_oriented_angle((3, 4), (2, 3)) -> 69.64251579802836

如您所见,这与get_angle((3, 4), (2, 3)) 不一致!如果您想要相同的结果,则必须在两种情况下都将 3 放在首位(或最后)。

如果你这样做

get_oriented_angle((3, 4), (3, 2)) -> 110.35748420197164

和上面的角度一样。

【讨论】:

  • 朋友,这是一个虚拟计算,别忘了human is real
  • @dsgdfg 您好,感谢您的评论,但您能再用几句话解释一下吗?我不明白你想说什么。
  • 您描述的工件是原始值吗?查看原始数据集,节点 16 和 18 似乎是 (0,0,0),这表明它们可能不存在。我认为您可以安全地删除这两个节点。另外,我认为您的角度函数中的算法非常优雅,尽管我不知道在函数中赋予变量与函数相同的做法。这是好习惯吗?无论如何,关键是点的顺序;我怀疑使用 cos vs atan2 应该是个问题。
  • @wikikikitiki 我必须不同意 atan。该操作忽略了第三个组件,使其成为二维计算。我认为 atan 计算不像 3d 中的 cos 那样直接。不过,我愿意开悟。
  • @dsgdfg 所有这些观点似乎都不是关于操作员询问的“这个问题是关于 [...] 如何使用输出 [...] 而不是如何使用使用 OpenPose。”我可能错了。如果是这样,请随时添加您自己的答案来解决您的一个或所有观点,或者指出 cmets 中的一个或多个具体内容。我只是尽力回答手头的问题。
猜你喜欢
  • 2016-07-31
  • 1970-01-01
  • 2013-11-03
  • 2018-04-24
  • 1970-01-01
  • 2014-12-15
  • 1970-01-01
  • 2018-11-16
  • 1970-01-01
相关资源
最近更新 更多