动捕关节数据采集系统设计与实现
动捕关节数据采集系统设计与实现
设计过程
1. 需求分析
- 核心目标:实时读取动捕系统中特定关节的位置(X,Y,Z)和旋转角度(Euler XYZ)
- 目标关节:左右膝、髋、踝、足等下肢关键关节
- 数据要求:每帧实时输出,可读性强的格式化数据
- 性能要求:低延迟,高可靠性
2. 系统架构设计
+---------------------+ +---------------------+ +---------------------+
| Motion Capture | | Mocap API | | Data Processing |
| Hardware |----->| (MocapApi.dll) |----->| & Visualization |
+---------------------+ +---------------------+ +---------------------+↑ ↑ ↑| | || 实时动作数据 | API调用 | 关节数据输出| | |
+---------------------+ +---------------------+ |
| Calibration | | Application | |
| & Tracking | | Layer |<---------+
+---------------------+ +---------------------+
3. 关键模块设计
(1) 初始化模块
(2) 数据采集模块
(3) 数据处理模块
4. 数据结构设计
关节数据结构:
{"joint_name": str, # 关节名称"position": (x, y, z), # 位置坐标 (float, float, float)"rotation": (x, y, z) # 旋转欧拉角 (float, float, float)
}
帧数据结构:
{"frame_id": int, # 帧序号"timestamp": float, # 时间戳"joints_data": [ # 关节数据列表{joint_data}, {joint_data},...]
}
5. 算法设计
递归遍历关节树算法:
function traverse_joint(joint):if joint.tag in target_joints:position = joint.get_local_position()rotation = joint.get_local_rotation_by_euler()store_data(joint.name, position, rotation)for child in joint.get_children():traverse_joint(child)
6. 流程图
完整代码实现
import time
from ctypes import *
from collections import namedtuple
from platform import system
import os# 加载MocapApi动态链接库
MocapApi = cdll.LoadLibrary(os.path.join(os.path.dirname(__file__), {'Darwin': '','Linux': '','Windows': 'windows/MocapApi.dll'
}[system()]))# 错误代码定义
MCPError = namedtuple('EMCPError', ['NoError', 'MoreEvent', 'InsufficientBuffer', 'InvalidObject', 'InvalidHandle','InvalidParameter', 'NotSupported', 'IgnoreUDPSettings', 'IgnoreTCPSettings','IgnoreBvhSettings', 'JointNotFound', 'WithoutTransformation', 'NoneMessage','NoneParent', 'NoneChild', 'AddressInUse'
])._make(range(16))# 关节标签定义
MCPJointTag = namedtuple('EMCPJointTag', ['Hips', 'RightUpLeg', 'RightLeg', 'RightFoot', 'LeftUpLeg', 'LeftLeg', 'LeftFoot','Spine', 'Spine1', 'Spine2', 'Neck', 'Neck1', 'Head', 'RightShoulder', 'RightArm','RightForeArm', 'RightHand', 'RightHandThumb1', 'RightHandThumb2', 'RightHandThumb3','RightInHandIndex', 'RightHandIndex1', 'RightHandIndex2', 'RightHandIndex3','RightInHandMiddle', 'RightHandMiddle1', 'RightHandMiddle2', 'RightHandMiddle3','RightInHandRing', 'RightHandRing1', 'RightHandRing2', 'RightHandRing3','RightInHandPinky', 'RightHandPinky1', 'RightHandPinky2', 'RightHandPinky3','LeftShoulder', 'LeftArm', 'LeftForeArm', 'LeftHand', 'LeftHandThumb1','LeftHandThumb2', 'LeftHandThumb3', 'LeftInHandIndex', 'LeftHandIndex1','LeftHandIndex2', 'LeftHandIndex3', 'LeftInHandMiddle', 'LeftHandMiddle1','LeftHandMiddle2', 'LeftHandMiddle3', 'LeftInHandRing', 'LeftHandRing1','LeftHandRing2', 'LeftHandRing3', 'LeftInHandPinky', 'LeftHandPinky1','LeftHandPinky2', 'LeftHandPinky3', 'Spine3', 'JointsCount'
])._make(range(61))# 刚体句柄类型
MCPRigidBodyHandle = c_uint64# 刚体对象类
class MCPRigidBody:# 类实现(与原始代码相同)pass# 传感器模块句柄类型
MCPSensorModuleHandle = c_uint64# 传感器模块类
class MCPSensorModule:# 类实现(与原始代码相同)pass# 身体部位句柄类型
MCPBodyPartHandle = c_uint64# 身体部位类
class MCPBodyPart:# 类实现(与原始代码相同)pass# 关节句柄类型
MCPJointHandle = c_uint64# 关节类
class MCPJoint:# 类实现(与原始代码相同)pass# 虚拟形象句柄类型
MCPAvatarHandle = c_uint64# 虚拟形象类
class MCPAvatar:# 类实现(与原始代码相同)pass# 事件结构体
class MCPEventDataReserved(Structure):_fields_ = [('reserved0', c_uint64),('reserved1', c_uint64),('reserved2', c_uint64),('reserved3', c_uint64),('reserved4', c_uint64),('reserved5', c_uint64),]class MCPEventData(Union):_fields_ = [('reserved', MCPEventDataReserved),('avatar_handle', MCPAvatarHandle),('error', c_int32)]class MCPEvent(Structure):_fields_ = [("size", c_uint32),("event_type", c_int32),('timestamp', c_double),("event_data", MCPEventData)]# 事件类型枚举
MCPEventType = namedtuple('EMCPEventType', ['InvalidEvent', 'AvatarUpdated', 'RigidBodyUpdated', 'Error'
])(0, 256, 512, 768)# 设置句柄类型
MCPSettingsHandle = c_uint64# 设置类
class MCPSettings:# 类实现(与原始代码相同)pass# 应用程序句柄类型
MCPApplicationHandle = c_uint64# 应用程序类
class MCPApplication:# 类实现(与原始代码相同)pass# 主程序
def main():# 目标关节列表(左右膝、髋、踝、足)target_joints = {MCPJointTag.LeftUpLeg: "左髋",MCPJointTag.LeftLeg: "左膝",MCPJointTag.LeftFoot: "左踝",MCPJointTag.LeftFoot: "左足",MCPJointTag.RightUpLeg: "右髋",MCPJointTag.RightLeg: "右膝",MCPJointTag.RightFoot: "右踝",MCPJointTag.RightFoot: "右足"}# 关节数据统计joint_statistics = {name: {"pos_sum": [0, 0, 0], "rot_sum": [0, 0, 0], "count": 0} for name in target_joints.values()}# 初始化动捕应用print("初始化动捕系统...")app = MCPApplication()settings = MCPSettings()settings.set_udp(7001) # 设置UDP端口app.set_settings(settings)# 打开连接print("连接动捕服务器...")success, msg = app.open()if not success:print(f"连接失败: {msg}")returnprint("连接成功,开始接收数据...")# 递归获取关节数据的函数def get_joint_data(joint, frame_data):try:tag = joint.get_tag()if tag in target_joints:# 获取关节名称joint_name = target_joints[tag]# 获取位置和旋转数据position = joint.get_local_position()rotation = joint.get_local_rotation_by_euler()# 添加到帧数据frame_data[joint_name] = {"position": position,"rotation": rotation}# 更新统计数据if joint_name in joint_statistics:stat = joint_statistics[joint_name]for i in range(3):stat["pos_sum"][i] += position[i]stat["rot_sum"][i] += rotation[i]stat["count"] += 1# 递归处理子关节children = joint.get_children()for child in children:get_joint_data(child, frame_data)except Exception as e:print(f"处理关节数据时出错: {str(e)}")# 主循环frame_count = 0start_time = time.time()try:while True:# 轮询事件evts = app.poll_next_event()# 处理每个事件for evt in evts:# 处理虚拟形象更新事件if evt.event_type == MCPEventType.AvatarUpdated:frame_count += 1avatar = MCPAvatar(evt.event_data.avatar_handle)# 当前帧数据frame_data = {}# 从根关节开始获取数据root_joint = avatar.get_root_joint()get_joint_data(root_joint, frame_data)# 打印帧头print(f"\n=== 帧号: {frame_count} ===")print(f"时间戳: {time.time() - start_time:.3f}s")# 打印关节数据for joint_name, data in frame_data.items():pos = data["position"]rot = data["rotation"]print(f"【{joint_name}】")print(f" 位置: X={pos[0]:>8.4f}, Y={pos[1]:>8.4f}, Z={pos[2]:>8.4f}")print(f" 旋转: X={rot[0]:>8.4f}, Y={rot[1]:>8.4f}, Z={rot[2]:>8.4f}")# 每10帧打印统计信息if frame_count % 10 == 0:print("\n--- 统计信息 ---")print(f"总帧数: {frame_count}")print("关节平均数据:")for joint_name, stat in joint_statistics.items():if stat["count"] > 0:avg_pos = [s / stat["count"] for s in stat["pos_sum"]]avg_rot = [s / stat["count"] for s in stat["rot_sum"]]print(f"【{joint_name}】")print(f" 平均位置: X={avg_pos[0]:>8.4f}, Y={avg_pos[1]:>8.4f}, Z={avg_pos[2]:>8.4f}")print(f" 平均旋转: X={avg_rot[0]:>8.4f}, Y={avg_rot[1]:>8.4f}, Z={avg_rot[2]:>8.4f}")print("----------------")# 控制循环频率time.sleep(0.001)except KeyboardInterrupt:print("\n用户中断,关闭连接...")finally:# 计算总运行时间total_time = time.time() - start_timefps = frame_count / total_time if total_time > 0 else 0# 打印最终统计print("\n=== 最终统计 ===")print(f"总运行时间: {total_time:.2f}秒")print(f"总帧数: {frame_count}")print(f"平均帧率: {fps:.2f} FPS")# 关闭连接app.close()print("动捕连接已关闭")print("程序退出")if __name__ == '__main__':main()
系统特点
-
高效数据采集:
- 使用递归算法遍历关节树
- 只处理目标关节,减少不必要的计算
- 低延迟轮询机制
-
数据处理能力:
- 实时显示每帧关节数据
- 定期生成统计报告
- 计算平均位置和旋转值
- 帧率监控
-
用户友好输出:
- 清晰的中文关节名称
- 格式化数值显示(保留4位小数)
- 分层数据展示
- 视觉分隔符增强可读性
-
健壮性设计:
- 完善的错误处理
- 安全退出机制
- 异常捕获
- 资源清理
-
扩展性:
- 可轻松添加新目标关节
- 支持多种数据输出格式
- 可集成到更大型的运动分析系统
使用说明
- 确保动捕硬件已正确连接并校准
- 设置正确的UDP端口(默认为7001)
- 运行程序,系统将自动连接动捕服务器
- 观察实时输出的关节位置和旋转数据
- 每10帧会显示一次统计信息
- 按Ctrl+C安全退出程序
应用场景
- 运动生物力学分析
- 运动员训练监控
- 医疗康复评估
- 动画制作与游戏开发
- 虚拟现实/增强现实交互
本系统通过高效的数据采集和处理流程,提供了精确、实时的关节运动数据,适用于多种需要动作捕捉和分析的场景。