Skip to content

未来数据适配方法论

目标

建立一套可复用的数据科学流程,使得验证管线能够:

  1. 无缝适配新的运动类型(高尔夫、棒球、网球等)
  2. 支持不同的传感器配置
  3. 快速迁移到新的数据集

核心设计原则

1. 模态无关 (Modality-Agnostic)

我们的管线不依赖于具体的传感器类型,而是依赖于数据的物理含义:

抽象层物理含义当前实现未来可能的替代
Vision身体姿态光学动捕 (33 markers)MediaPipe (33 landmarks)
IMU运动速度6-axis IMU (240Hz)手机传感器 (100Hz)
EMG肌肉激活表面 EMG (2048Hz)可穿戴 EMG (1000Hz)

关键洞察: 只要能获取这三种物理信息,具体传感器类型不重要。

2. 特征提取的层次化 (Hierarchical Feature Extraction)

特征提取分为三个层次:

python
# Level 1: 传感器特定特征 (Sensor-Specific)
def extract_marker_features(marker_data):
    # 直接从原始传感器数据提取
    return {
        'joint_angles': compute_angles(marker_data),
        'com_trajectory': compute_com(marker_data)
    }

# Level 2: 运动学特征 (Kinematic)
def extract_kinematic_features(sensor_features):
    # 从多个传感器组合得出
    return {
        'peak_velocity': max(imu_features['angular_vel']),
        'tempo_ratio': compute_tempo(marker_features, imu_features)
    }

# Level 3: 生物力学特征 (Biomechanical)
def extract_biomechanical_features(kinematic_features, emg_features):
    # 最高抽象层,与运动原理相关
    return {
        'kinetic_chain_correct': analyze_chain(emg_features),
        'power_transfer_efficiency': compute_efficiency(...)
    }

优势: 更换传感器时,只需修改 Level 1,Level 2-3 保持不变。

3. 规则引擎的参数化 (Parameterized Rules)

规则逻辑保持不变,但阈值可配置:

python
@dataclass
class RuleConfig:
    """运动类型特定的规则配置"""
    motion_type: str  # 'jump', 'golf', 'baseball'

    # 倒序运动链阈值
    inverted_chain_threshold: float  # ms

    # 节奏比例范围
    tempo_ratio_min: float
    tempo_ratio_max: float

    # 核心激活强度
    core_activation_min: float  # %

# 跳跃配置
JUMP_CONFIG = RuleConfig(
    motion_type='jump',
    inverted_chain_threshold=-20,
    tempo_ratio_min=0.8,
    tempo_ratio_max=2.5,
    core_activation_min=60
)

# 高尔夫配置 (待调整)
GOLF_CONFIG = RuleConfig(
    motion_type='golf',
    inverted_chain_threshold=-50,  # 高尔夫允许更大负值
    tempo_ratio_min=2.0,           # 高尔夫准备时间更长
    tempo_ratio_max=5.0,
    core_activation_min=70         # 高尔夫需要更强核心激活
)

适配新数据的标准流程

Step 1: 数据映射 (Data Mapping)

创建新的数据加载器,映射到统一接口:

python
class DataLoader(ABC):
    """抽象数据加载器"""

    @abstractmethod
    def load(self, file_path: str) -> MotionData:
        """加载数据,返回标准化格式"""
        pass

    @abstractmethod
    def get_sampling_rates(self) -> Dict[str, int]:
        """返回各模态采样率"""
        pass

# 当前实现: 跳跃数据 (.mat 文件)
class JumpDataLoader(DataLoader):
    def load(self, file_path: str) -> MotionData:
        mat_data = loadmat(file_path)
        return MotionData(
            marker_data=mat_data['data']['marker_data'][0, 0],
            imu_data=mat_data['data']['imu_data'][0, 0],
            emg_data=mat_data['data']['emg_data'][0, 0]
        )

# 未来实现: 高尔夫数据 (可能是 .csv + .npy)
class GolfDataLoader(DataLoader):
    def load(self, file_path: str) -> MotionData:
        # 从不同格式加载,但返回相同的 MotionData
        marker_data = np.load(f"{file_path}/markers.npy")
        imu_data = pd.read_csv(f"{file_path}/imu.csv").values
        emg_data = pd.read_csv(f"{file_path}/emg.csv").values

        return MotionData(
            marker_data=marker_data,
            imu_data=imu_data,
            emg_data=emg_data
        )

Step 2: 特征校准 (Feature Calibration)

使用小样本标注数据调整阈值:

python
def calibrate_thresholds(
    validation_trials: List[MotionData],
    ground_truth_labels: List[bool],  # 人工标注的"正确"/"错误"
    initial_config: RuleConfig
) -> RuleConfig:
    """
    自动调整规则阈值以最大化准确率

    Args:
        validation_trials: 验证数据集
        ground_truth_labels: 人工标注(True=正确, False=有问题)
        initial_config: 初始配置

    Returns:
        优化后的配置
    """
    from sklearn.metrics import accuracy_score
    from scipy.optimize import minimize

    def objective(params):
        config = RuleConfig(
            motion_type=initial_config.motion_type,
            inverted_chain_threshold=params[0],
            tempo_ratio_min=params[1],
            tempo_ratio_max=params[2],
            core_activation_min=params[3]
        )

        predictions = [
            evaluate_trial(trial, config)
            for trial in validation_trials
        ]

        return -accuracy_score(ground_truth_labels, predictions)

    # 优化
    result = minimize(
        objective,
        x0=[
            initial_config.inverted_chain_threshold,
            initial_config.tempo_ratio_min,
            initial_config.tempo_ratio_max,
            initial_config.core_activation_min
        ],
        method='Nelder-Mead'
    )

    return RuleConfig(
        motion_type=initial_config.motion_type,
        inverted_chain_threshold=result.x[0],
        tempo_ratio_min=result.x[1],
        tempo_ratio_max=result.x[2],
        core_activation_min=result.x[3]
    )

Step 3: 交叉验证 (Cross-Validation)

验证适配效果:

python
def validate_adaptation(
    data: List[MotionData],
    labels: List[bool],
    config: RuleConfig,
    n_folds: int = 5
) -> Dict[str, float]:
    """
    K-fold 交叉验证

    Returns:
        性能指标: accuracy, precision, recall, f1
    """
    from sklearn.model_selection import KFold
    from sklearn.metrics import classification_report

    kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)

    all_predictions = []
    all_labels = []

    for train_idx, test_idx in kf.split(data):
        # 在训练集上校准
        train_data = [data[i] for i in train_idx]
        train_labels = [labels[i] for i in train_idx]

        calibrated_config = calibrate_thresholds(
            train_data, train_labels, config
        )

        # 在测试集上评估
        test_data = [data[i] for i in test_idx]
        test_labels = [labels[i] for i in test_idx]

        predictions = [
            evaluate_trial(trial, calibrated_config)
            for trial in test_data
        ]

        all_predictions.extend(predictions)
        all_labels.extend(test_labels)

    # 计算指标
    report = classification_report(
        all_labels, all_predictions, output_dict=True
    )

    return {
        'accuracy': report['accuracy'],
        'precision': report['True']['precision'],
        'recall': report['True']['recall'],
        'f1': report['True']['f1-score']
    }

具体应用场景

场景 1: 跳跃 → 高尔夫挥杆

需要调整的部分:

  1. 数据加载器: 实现 GolfDataLoader
  2. 规则配置: 调整阈值
    • 倒序运动链阈值: -20ms-50ms (高尔夫旋转更复杂)
    • 节奏比例: 0.8-2.52.0-5.0 (高尔夫准备更长)
  3. 特征定义: 添加高尔夫特定特征
    • X-Factor (上下身扭转差)
    • 手腕释放时机

保持不变的部分:

  • EMG 信号处理算法
  • 运动链检测逻辑
  • 时间同步验证方法
  • 可视化管线

预计工作量: 2-3天 (如果有10-20个标注样本)

场景 2: 光学动捕 → MediaPipe

需要调整的部分:

  1. 数据加载器:

    python
    class MediaPipeLoader(DataLoader):
        def load(self, video_path: str) -> MotionData:
            # 使用 MediaPipe 提取关键点
            landmarks = extract_mediapipe_landmarks(video_path)
    
            # 转换为标准格式
            marker_data = convert_landmarks_to_markers(landmarks)
    
            return MotionData(
                marker_data=marker_data,
                imu_data=None,  # MediaPipe 没有 IMU
                emg_data=emg_data
            )
  2. 特征提取: 修改 Level 1 特征提取

    python
    def extract_marker_features(marker_data):
        # 适配 MediaPipe 的 33 个 landmarks
        # (与光学动捕的 33 markers 正好一致!)
        return compute_joint_angles(marker_data)

保持不变的部分:

  • Level 2-3 特征提取
  • 所有 EMG 处理
  • 规则引擎

预计工作量: 1-2天

场景 3: 添加新的运动类型(网球发球)

步骤:

  1. 收集标注数据: 20-30 个网球发球试验,人工标注"好"/"差"
  2. 初始化配置: 复制 GOLF_CONFIG,作为起点
  3. 校准阈值: 运行 calibrate_thresholds()
  4. 交叉验证: 验证准确率 >85%
  5. 部署: 添加到生产环境

预计工作量: 3-5天


数据需求分析

最小数据需求

要适配新的运动类型,至少需要:

数据类型数量用途
原始试验50+理解数据分布
人工标注20-30校准阈值
测试集10-15验证性能

标注指南

标注时需要回答:

  1. 运动链是否正确? (Yes/No)
  2. 准备阶段是否合理? (Too short / Good / Too long)
  3. 核心激活是否充分? (Weak / Good / Excessive)

数据质量检查清单

  • [ ] 所有模态的采样率已知
  • [ ] 时间同步精度 <50ms
  • [ ] EMG 信号信噪比 >10dB
  • [ ] 至少包含 3 个完整的运动周期
  • [ ] 有清晰的"准备"和"执行"阶段划分

自动化工具

1. 数据适配助手

python
class AdaptationAssistant:
    """帮助快速适配新数据的工具"""

    def __init__(self, reference_config: RuleConfig):
        self.reference_config = reference_config

    def suggest_initial_config(
        self,
        new_data_sample: MotionData,
        motion_type: str
    ) -> RuleConfig:
        """
        基于新数据样本,推荐初始配置
        """
        # 分析数据特征
        features = extract_all_features(new_data_sample)

        # 计算与参考数据的差异
        tempo_ratio_median = np.median(features['tempo_ratios'])
        core_activation_median = np.median(features['core_activations'])

        # 调整阈值
        return RuleConfig(
            motion_type=motion_type,
            inverted_chain_threshold=self.reference_config.inverted_chain_threshold,
            tempo_ratio_min=tempo_ratio_median * 0.5,
            tempo_ratio_max=tempo_ratio_median * 1.5,
            core_activation_min=core_activation_median * 0.8
        )

    def generate_calibration_script(self, motion_type: str) -> str:
        """
        生成校准脚本模板
        """
        return f"""
# Auto-generated calibration script for {motion_type}

from validation.calibration import calibrate_thresholds
from validation.data_loader import {motion_type.capitalize()}DataLoader

# 1. Load data
loader = {motion_type.capitalize()}DataLoader()
trials = loader.load_all('./data/{motion_type}/')

# 2. Load labels
labels = load_labels('./data/{motion_type}/labels.json')

# 3. Calibrate
initial_config = SUGGESTED_CONFIG  # From assistant
calibrated_config = calibrate_thresholds(trials, labels, initial_config)

# 4. Validate
metrics = validate_adaptation(trials, labels, calibrated_config)
print(f"Accuracy: {{metrics['accuracy']:.2%}}")

# 5. Save
save_config(calibrated_config, './configs/{motion_type}_config.json')
"""

2. 数据质量检查工具

python
def check_data_quality(data: MotionData) -> Dict[str, bool]:
    """
    自动检查数据质量

    Returns:
        检查结果字典
    """
    checks = {}

    # 1. 采样率检查
    checks['sampling_rates_valid'] = all([
        data.sampling_rates['marker'] >= 30,
        data.sampling_rates['imu'] >= 100,
        data.sampling_rates['emg'] >= 1000
    ])

    # 2. 时间同步检查
    duration_marker = len(data.marker_data[0, 0, :]) / data.sampling_rates['marker']
    duration_imu = len(data.imu_data) / data.sampling_rates['imu']
    duration_emg = len(data.emg_data) / data.sampling_rates['emg']

    max_diff = max(abs(duration_marker - duration_imu),
                   abs(duration_marker - duration_emg),
                   abs(duration_imu - duration_emg))

    checks['time_sync_ok'] = max_diff < 0.05  # <50ms

    # 3. 信号质量检查
    emg_snr = compute_snr(data.emg_data)
    checks['emg_quality_ok'] = emg_snr > 10  # dB

    # 4. 数据完整性
    checks['no_missing_data'] = not (
        np.isnan(data.marker_data).any() or
        np.isnan(data.imu_data).any() or
        np.isnan(data.emg_data).any()
    )

    return checks

总结: 可移植性设计

我们的验证管线具有高度可移植性,因为:

1. 三层抽象

  • 数据层: 统一的 MotionData 格式
  • 特征层: 层次化特征提取
  • 规则层: 参数化规则引擎

2. 配置驱动

  • 所有阈值都在配置文件中
  • 不需要修改核心代码

3. 自动化工具

  • 数据质量检查
  • 阈值校准
  • 交叉验证

4. 最小标注需求

  • 只需 20-30 个标注样本
  • 可以快速迭代

关键洞察: 生物力学原理是通用的,只有具体参数需要调整。


下一步建议

  1. 收集高尔夫数据后:

    • 运行 AdaptationAssistant 生成初始配置
    • 人工标注 20-30 个试验
    • 运行校准脚本
    • 交叉验证性能
  2. 如果性能不佳 (<80% 准确率):

    • 检查数据质量(采样率、同步精度)
    • 增加标注样本(30 → 50)
    • 考虑添加运动特定特征
  3. 如果性能良好 (>85% 准确率):

    • 部署到生产环境
    • 开始收集用户反馈
    • 持续优化阈值