动捕关节数据采集系统设计与实现

动捕关节数据采集系统设计与实现

设计过程

1. 需求分析

  • 核心目标:实时读取动捕系统中特定关节的位置(X,Y,Z)和旋转角度(Euler XYZ)
  • 目标关节:左右膝、髋、踝、足等下肢关键关节
  • 数据要求:每帧实时输出,可读性强的格式化数据
  • 性能要求:低延迟,高可靠性

2. 系统架构设计

+---------------------+      +---------------------+      +---------------------+
|   Motion Capture    |      |   Mocap API         |      |   Data Processing   |
|   Hardware          |----->|   (MocapApi.dll)    |----->|   & Visualization   |
+---------------------+      +---------------------+      +---------------------+
        ↑                                   ↑                         ↑
        |                                   |                         |
        | 实时动作数据                      | API调用                 | 关节数据输出
        |                                   |                         |
+---------------------+              +---------------------+          |
|   Calibration       |              |   Application       |          |
|   & Tracking        |              |   Layer             |<---------+
+---------------------+              +---------------------+

3. 关键模块设计

(1) 初始化模块
创建应用程序实例
创建设置对象
设置UDP端口
应用设置
打开连接
连接成功?
准备数据采集
报错退出
(2) 数据采集模块
AvatarUpdated
其他事件
轮询事件
事件类型?
获取虚拟形象
获取根关节
递归遍历关节树
是目标关节?
记录位置和旋转
继续遍历
忽略或处理
存储关节数据
(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. 流程图

AvatarUpdated
其他事件
开始
初始化动捕应用
设置UDP端口
打开连接
连接成功?
报错退出
进入主循环
轮询事件
有事件?
事件类型?
获取虚拟形象
获取根关节
递归遍历关节树
是目标关节?
记录位置和旋转
继续遍历
存储关节数据
输出当前帧数据
达到统计间隔?
输出统计信息
忽略或处理
收到中断信号?
关闭连接
退出程序

完整代码实现

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()

系统特点

  1. 高效数据采集

    • 使用递归算法遍历关节树
    • 只处理目标关节,减少不必要的计算
    • 低延迟轮询机制
  2. 数据处理能力

    • 实时显示每帧关节数据
    • 定期生成统计报告
    • 计算平均位置和旋转值
    • 帧率监控
  3. 用户友好输出

    • 清晰的中文关节名称
    • 格式化数值显示(保留4位小数)
    • 分层数据展示
    • 视觉分隔符增强可读性
  4. 健壮性设计

    • 完善的错误处理
    • 安全退出机制
    • 异常捕获
    • 资源清理
  5. 扩展性

    • 可轻松添加新目标关节
    • 支持多种数据输出格式
    • 可集成到更大型的运动分析系统

使用说明

  1. 确保动捕硬件已正确连接并校准
  2. 设置正确的UDP端口(默认为7001)
  3. 运行程序,系统将自动连接动捕服务器
  4. 观察实时输出的关节位置和旋转数据
  5. 每10帧会显示一次统计信息
  6. 按Ctrl+C安全退出程序

应用场景

  1. 运动生物力学分析
  2. 运动员训练监控
  3. 医疗康复评估
  4. 动画制作与游戏开发
  5. 虚拟现实/增强现实交互

本系统通过高效的数据采集和处理流程,提供了精确、实时的关节运动数据,适用于多种需要动作捕捉和分析的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值