动捕关节数据采集系统设计与实现
设计过程
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}")
return
print("连接成功,开始接收数据...")
# 递归获取关节数据的函数
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 = 0
start_time = time.time()
try:
while True:
# 轮询事件
evts = app.poll_next_event()
# 处理每个事件
for evt in evts:
# 处理虚拟形象更新事件
if evt.event_type == MCPEventType.AvatarUpdated:
frame_count += 1
avatar = 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_time
fps = 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安全退出程序
应用场景
- 运动生物力学分析
- 运动员训练监控
- 医疗康复评估
- 动画制作与游戏开发
- 虚拟现实/增强现实交互
本系统通过高效的数据采集和处理流程,提供了精确、实时的关节运动数据,适用于多种需要动作捕捉和分析的场景。