如何基于OpenPose骨骼关键点坐标用向量法计算人体关节角度
人体关节角度计算最优方案及向量法实现
原atan2方法误差原因
你之前用atan2计算得到的是平面有向角,返回范围是0360度,不符合人体关节角度的常规定义(我们需要的是两条骨骼段之间的最小无向夹角,范围0180度),同时你没有对低置信度的关键点做过滤,当OpenPose识别的关键点置信度不足时(比如坐标为(0,0)、置信度为0的无效点),计算结果必然偏离实际值。
向量法计算逻辑
关节角度的核心是计算以关节点为顶点、连接两端骨骼的两条线段的夹角,向量点积法是这类场景的最优方案,计算逻辑如下:
- 已知组成关节的三个点,编号为p1(远端骨骼端点)、p2(关节顶点)、p3(近端骨骼端点)
- 构造两个从关节顶点出发的向量:
v1 = (x1 - x2, y1 - y2),v2 = (x3 - x2, y3 - y2) - 通过点积公式计算夹角余弦值:
cosθ = (v1·v2) / (|v1| * |v2|) - 对cosθ做数值钳制,避免浮点计算误差导致超出arccos的定义域[-1, 1]
- 转换为角度值即可得到0~180度的关节夹角
完整代码实现
import os import json import math # 配置项 JSON_DIR = "你的JSON文件夹路径" CONFIDENCE_THRESHOLD = 0.5 # 关键点置信度阈值,低于该值的帧跳过计算 OPENPOSE_ANGLES = [(9, 10, 11, "right_knee"), (2, 3, 4, "right_arm")] def calculate_joint_angle(p1, p2, p3): """ 输入三个点的(x,y,c),返回关节角度,无效返回None """ x1, y1, c1 = p1 x2, y2, c2 = p2 x3, y3, c3 = p3 # 过滤低置信度点 if c1 < CONFIDENCE_THRESHOLD or c2 < CONFIDENCE_THRESHOLD or c3 < CONFIDENCE_THRESHOLD: return None # 过滤识别失败的零值点 if (x1 == 0 and y1 ==0) or (x2 ==0 and y2 ==0) or (x3 ==0 and y3 ==0): return None # 构造向量 v1 = (x1 - x2, y1 - y2) v2 = (x3 - x2, y3 - y2) # 计算点积 dot_product = v1[0] * v2[0] + v1[1] * v2[1] # 计算向量模长 norm_v1 = math.hypot(v1[0], v1[1]) norm_v2 = math.hypot(v2[0], v2[1]) # 避免除零错误 if norm_v1 == 0 or norm_v2 == 0: return None # 计算cosθ并钳制数值范围 cos_theta = dot_product / (norm_v1 * norm_v2) cos_theta = max(min(cos_theta, 1.0), -1.0) # 转换为角度 angle = math.degrees(math.acos(cos_theta)) return angle # 批量处理JSON文件 for file_name in os.listdir(JSON_DIR): if not file_name.endswith(".json"): continue file_path = os.path.join(JSON_DIR, file_name) with open(file_path, 'r') as f: data = json.load(f) if not data['people']: continue # 无识别结果跳过 # 提取单个人的关键点(你的场景只有一个骑行者,取索引0即可) pose_kps = data['people'][0]['pose_keypoints_2d'] # 转换为索引到(x,y,c)的映射 kp_map = {} for idx in range(0, len(pose_kps), 3): kp_idx = idx // 3 x = pose_kps[idx] y = pose_kps[idx+1] c = pose_kps[idx+2] kp_map[kp_idx] = (x, y, c) # 计算所有配置的关节角度 frame_angles = {} for p1_idx, p2_idx, p3_idx, angle_name in OPENPOSE_ANGLES: p1 = kp_map[p1_idx] p2 = kp_map[p2_idx] p3 = kp_map[p3_idx] angle = calculate_joint_angle(p1, p2, p3) frame_angles[angle_name] = angle # 这里可以加你自己的结果存储逻辑 print(f"文件名:{file_name},角度结果:{frame_angles}")
侧面骑行场景优化建议
- 你的场景是固定拍摄右侧轮廓,不存在左右翻转的问题,无需额外做坐标镜像处理
- 可以对连续5~10帧的角度结果做滑动平均滤波,消除OpenPose关键点识别的瞬时抖动,得到更平滑的角度序列
- 如果需要计算关节的屈伸方向,可以在向量法基础上结合角度正负判断,常规骑行场景下0~180度的无向夹角已经满足需求。
内容的提问来源于stack exchange,提问作者vesii
相关产品推荐
相关产品推荐

