用Python+MediaPipe零成本复刻AI武术教练:手把手教你搭建骨骼分析系统(附完整代码)

最近几年,身边不少朋友开始对传统武术感兴趣,但苦于找不到合适的老师,或者担心自己动作不标准。与此同时,AI视觉技术却越来越“亲民”,一个普通摄像头加上几行代码,就能让电脑看懂我们的动作。这让我萌生了一个想法:能不能自己动手,做一个能分析武术动作的AI小助手?不需要昂贵的动捕设备,就用我们手边的笔记本电脑和开源工具。经过一段时间的摸索和实践,我发现这件事的可行性远超预期。今天,我就把自己从零搭建一个“AI武术教练”原型系统的完整过程、踩过的坑以及优化心得,毫无保留地分享出来。无论你是想做个有趣项目的Python开发者,还是对技术赋能传统文化感兴趣的爱好者,这篇文章都将带你走通从环境搭建到核心算法调优的每一步。

1. 环境准备与工具选择:打造你的零成本AI实验室

搭建任何AI应用,第一步永远是准备好“战场”。我们的目标是零成本,这意味着所有工具都必须是开源、免费且易于获取的。这里没有复杂的云服务申请流程,也不需要购买任何专业硬件。

核心工具栈

  • 编程语言:Python。这是计算机视觉和机器学习领域事实上的标准语言,生态丰富,社区活跃。
  • 核心AI库MediaPipe。这是谷歌开源的一个跨平台框架,专门用于处理视频、音频等时序数据。它内置了预训练好的姿态估计模型,能精准识别出人体33个关键点(包括身体、面部、手部),而且推理速度极快,在普通CPU上也能实时运行。这是我们系统的“眼睛”。
  • 视觉处理库OpenCV。计算机视觉的“瑞士军刀”,负责视频流的读取、图像处理、关键点绘制和结果展示。
  • 开发环境:任选你熟悉的即可。我个人推荐 Jupyter NotebookVS Code,前者适合分步探索和演示,后者适合完整的项目开发。

1.1 一步到位的环境配置

为了避免大家陷入“依赖地狱”,我强烈推荐使用 Anaconda 来创建独立的Python环境。这能确保你的项目依赖不会干扰系统或其他项目。

打开你的终端(或Anaconda Prompt),依次执行以下命令:

# 1. 创建一个名为‘ai_wushu’的新环境,并指定Python版本
conda create -n ai_wushu python=3.9

# 2. 激活这个环境
conda activate ai_wushu

# 3. 安装核心依赖
pip install opencv-python mediapipe

注意:MediaPipe的安装包名称就是 mediapipe,它会自动处理其底层依赖(如NumPy)。OpenCV的安装包是 opencv-python。这两条命令足以满足我们基础版本的需求。

如果你想验证安装是否成功,可以打开Python解释器,尝试导入它们:

import cv2
import mediapipe as mp
print(“OpenCV版本:”, cv2.__version__)
print(“MediaPipe版本:”, mp.__version__)

如果没有报错,恭喜你,你的AI武术分析实验室已经搭建完毕!整个过程可能只需要几分钟。

1.2 硬件要求与替代方案

理想情况下,拥有一块独立GPU(如NVIDIA GTX系列)可以加速处理,但对于MediaPipe这样的轻量级模型,这绝非必需。我的大部分开发和测试都是在一台搭载英特尔集成显卡的笔记本电脑上完成的,处理实时摄像头流(约30帧/秒)依然流畅。

  • 最低配置:现代双核CPU,4GB内存,普通USB摄像头或笔记本电脑内置摄像头。
  • 推荐配置:四核及以上CPU,8GB内存。如果处理高分辨率或高帧率视频,更好的CPU会带来更流畅的体验。

如果你的电脑性能确实有限,或者想追求极致的实时性,可以考虑使用 Google Colab 的免费GPU资源。只需将代码稍作修改,上传到Colab笔记本中运行即可。不过,对于本教程的入门和原型验证阶段,本地环境完全够用。

2. 核心代码拆解:让AI“看见”你的骨骼

环境就绪后,我们进入最激动人心的环节:编写代码,让程序从摄像头中实时识别出人体的骨骼关键点。MediaPipe将复杂的神经网络模型封装成了极其简单的API,我们只需寥寥数行代码就能调用这个强大的能力。

2.1 初始化与视频流捕获

首先,我们导入必要的库,并初始化MediaPipe的姿态估计解决方案。

import cv2
import mediapipe as mp

# 初始化MediaPipe姿态估计模块
mp_drawing = mp.solutions.drawing_utils  # 绘图工具,用于画骨骼
mp_drawing_styles = mp.solutions.drawing_styles  # 绘图样式
mp_pose = mp.solutions.pose  # 姿态估计模型

# 创建一个Pose对象,这是核心检测器
# static_image_mode: False表示处理视频流(会利用帧间相关性优化)
# model_complexity: 模型复杂度,0(快)、1(平衡)、2(准)。武术动作建议从1开始。
# min_detection_confidence: 检测置信度阈值,高于此值才认为检测成功
# min_tracking_confidence: 跟踪置信度阈值,用于视频流中维持跟踪稳定性
pose = mp_pose.Pose(
    static_image_mode=False,
    model_complexity=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

# 打开默认摄像头(摄像头索引0)
cap = cv2.VideoCapture(0)

这段代码创建了我们的“检测引擎”并打开了眼睛(摄像头)。Pose 初始化时的几个参数值得关注,它们是我们后续调优的杠杆。

2.2 主循环:实时检测与绘制

接下来,我们进入一个无限循环,不断从摄像头读取帧,交给MediaPipe处理,并绘制出结果。

while cap.isOpened():
    success, image = cap.read()
    if not success:
        print(“无法从摄像头读取图像。”)
        break

    # 为了提高性能,可以将图像标记为不可写,传入MediaPipe处理会更快
    image.flags.writeable = False
    # MediaPipe模型需要RGB格式的图像,而OpenCV默认是BGR
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    # 进行关键点检测!
    results = pose.process(image)

    # 将图像改回可写状态,并转换回BGR格式用于OpenCV显示
    image.flags.writeable = True
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    # 如果检测到了姿态关键点
    if results.pose_landmarks:
        # 使用MediaPipe提供的绘图工具,将33个关键点和它们之间的连线画在图像上
        mp_drawing.draw_landmarks(
            image,
            results.pose_landmarks,
            mp_pose.POSE_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style()
        )

    # 水平翻转图像,获得更自然的镜像视图(就像照镜子)
    image = cv2.flip(image, 1)
    # 在窗口标题中显示FPS(帧率)信息(简单实现)
    cv2.putText(image, ‘AI武术教练 - 按ESC退出’, (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    # 显示处理后的图像
    cv2.imshow(‘AI武术教练 - 实时骨骼检测’, image)

    # 按下ESC键(ASCII 27)退出循环
    if cv2.waitKey(5) & 0xFF == 27:
        break

# 释放摄像头并关闭所有OpenCV窗口
cap.release()
cv2.destroyAllWindows()

将以上两段代码组合成一个完整的Python脚本并运行,你应该会看到一个实时窗口,里面是你的实时影像,并且身体上被画上了清晰的骨骼连线。这就是我们AI武术教练的“视觉基础”。第一次看到自己的动作被实时数字化,那种感觉非常奇妙。

3. 针对武术动作的深度调优策略

基础检测跑通了,但如果你马上开始打一套拳,可能会发现一些问题:低马步时腿部关键点丢失、快速出拳时手臂轨迹抖动、侧身时部分关键点被遮挡。这是因为默认参数是针对日常姿态优化的。武术动作幅度大、速度快、姿态低,需要我们进行针对性调优。

3.1 参数调优:找到武术动作的“甜点”

Pose 初始化时的几个参数是我们的主要调优对象。下面这个表格总结了它们对武术场景的影响及我的调优建议:

参数默认值含义武术场景调优建议
model_complexity1模型复杂度。值越高,精度越高,速度越慢。推荐设为2。武术动作细节多(如手掌方向),高精度模型能更好捕捉。如果实时性要求极高(>60FPS),可降回1。
min_detection_confidence0.5检测置信度阈值。低于此值,模型认为“没检测到人”。可适当降低至0.4-0.45。对于低马步、身体大幅扭转等容易被误判为“非人”或“不完整”的姿态,降低阈值能提高检出率,但可能引入更多噪声。
min_tracking_confidence0.5跟踪置信度阈值。低于此值,模型会重新进行全局检测,而非跟踪上一帧。可适当提高至0.6-0.7。武术动作连续性强,提高跟踪置信度能让关键点轨迹更平滑稳定,减少抖动和跳变。
static_image_modeFalse是否为静态图片模式。True则每帧独立检测;False则利用视频时序信息。保持False。视频流模式效率更高,跟踪更稳定。

基于以上分析,一个优化后的初始化配置可能是这样的:

pose = mp_pose.Pose(
    static_image_mode=False,
    model_complexity=2,          # 使用高精度模型
    min_detection_confidence=0.45, # 降低检测门槛,应对低姿态
    min_tracking_confidence=0.65   # 提高跟踪要求,使轨迹平滑
)

3.2 数据后处理:平滑与滤波

即使调优了参数,原始的关键点数据依然可能存在高频抖动。这对于需要计算角度、速度的武术分析来说是致命的。我们需要引入滤波算法。

一个简单有效的方法是使用 指数移动平均。它计算简单,能有效平滑数据,且对实时系统友好。

class LandmarkSmoother:
    """一个简单的关键点平滑器"""
    def __init__(self, alpha=0.5):
        self.alpha = alpha  # 平滑因子,0-1之间,越大越依赖新数据
        self.smoothed_landmarks = None

    def smooth(self, new_landmarks):
        if new_landmarks is None:
            return None
        if self.smoothed_landmarks is None:
            self.smoothed_landmarks = new_landmarks
        else:
            # 对每个关键点的x, y, z坐标分别进行平滑
            for i in range(len(new_landmarks.landmark)):
                nl = new_landmarks.landmark[i]
                sl = self.smoothed_landmarks.landmark[i]
                sl.x = self.alpha * nl.x + (1 - self.alpha) * sl.x
                sl.y = self.alpha * nl.y + (1 - self.alpha) * sl.y
                sl.z = self.alpha * nl.z + (1 - self.alpha) * sl.z
        return self.smoothed_landmarks

# 在主循环中使用
smoother = LandmarkSmoother(alpha=0.7) # alpha=0.7意味着新数据权重70%
# ... 在process之后 ...
if results.pose_landmarks:
    smoothed_landmarks = smoother.smooth(results.pose_landmarks)
    # 使用smoothed_landmarks进行绘制和后续计算

对于更复杂的场景,可以考虑卡尔曼滤波等高级算法,但EMA在大多数情况下已经能带来肉眼可见的改善。

3.3 应对遮挡与服装干扰

传统武术服装(如太极服)宽袍大袖,容易遮挡肘、腕等关键点。这是基于RGB摄像头的视觉系统的固有局限。除了更换紧身服装,我们还可以从算法层面做一些努力:

  1. 利用时序信息预测:当关键点被短暂遮挡(如袖子甩过手腕),可以利用其前几帧的运动轨迹和速度,预测当前帧的可能位置。
  2. 基于人体模型的约束:人体的骨骼长度是相对固定的,关节活动范围也有限制。当某个点丢失或位置明显异常时,可以根据相邻关节点(如肩和肘的位置推算手腕)和人体比例模型进行合理性校正。
  3. 多角度融合(进阶):如果条件允许,使用两个或多个摄像头从不同角度拍摄,当一个视角被遮挡时,另一个视角可能还能看到。这需要复杂的标定和三维重建技术,是更进阶的解决方案。

4. 从检测到分析:构建武术动作评估逻辑

检测出稳定的关键点只是第一步。真正的“教练”需要能评估动作质量。这需要我们设计一套从关键点数据中提取特征,并与标准动作进行比对的逻辑。

4.1 关键角度计算:量化动作标准

许多武术招式对关节角度有明确要求。例如,马步要求膝盖弯曲约90度,冲拳要求肘关节接近伸直。我们可以通过关键点坐标来计算这些角度。

下面是一个计算肘关节角度的函数示例:

import math

def calculate_angle(a, b, c):
    """
    计算由三点a, b, c构成的角abc的角度(以b为顶点)。
    参数a, b, c是包含x, y属性的对象(如landmark)。
    返回角度值(0-180度)。
    """
    # 将坐标转换为向量
    ba = [a.x - b.x, a.y - b.y]
    bc = [c.x - b.x, c.y - b.y]
    
    # 计算向量点积和模长
    dot_product = ba[0] * bc[0] + ba[1] * bc[1]
    norm_ba = math.sqrt(ba[0]**2 + ba[1]**2)
    norm_bc = math.sqrt(bc[0]**2 + bc[1]**2)
    
    # 计算夹角余弦值,防止除零和浮点误差导致acos参数超出[-1,1]
    cosine_angle = dot_product / (norm_ba * norm_bc + 1e-10)
    cosine_angle = max(-1.0, min(1.0, cosine_angle))
    
    # 返回角度
    angle = math.degrees(math.acos(cosine_angle))
    return angle

# 在主循环中,假设我们已经有了平滑后的关键点landmarks
# landmarks = results.pose_landmarks 或 smoother.smooth(...)
if landmarks:
    # 获取肩、肘、腕的关键点索引(MediaPipe Pose的33个点有固定索引)
    # 例如:左肩11,左肘13,左腕15
    shoulder = landmarks.landmark[mp_pose.PoseLandmark.LEFT_SHOULDER]
    elbow = landmarks.landmark[mp_pose.PoseLandmark.LEFT_ELBOW]
    wrist = landmarks.landmark[mp_pose.PoseLandmark.LEFT_WRIST]
    
    elbow_angle = calculate_angle(shoulder, elbow, wrist)
    # 可以将角度实时显示在图像上
    cv2.putText(image, f‘Elbow: {int(elbow_angle)}°’, 
                (int(elbow.x * image.shape[1]), int(elbow.y * image.shape[0])),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)

通过类似的方法,我们可以计算膝关节角度(髋-膝-踝)、躯干倾斜角(肩-髋连线与垂直线的夹角)等,从而量化动作的规范性。

4.2 动作序列比对:动态招式的评估

单个静态姿势的评估相对简单,但武术是一连串的动作。如何评估一段“弓步冲拳”或“转身摆莲”是否标准?这里需要引入时间序列的比对算法。

一个经典且实用的方法是 动态时间规整。它能很好地处理两个序列在速度上的差异(比如学员做得快,老师做得慢)。

# 首先需要安装 dtaidistance 库: pip install dtaidistance
import numpy as np
from dtaidistance import dtw

def preprocess_sequence(landmarks_list):
    """将一系列landmarks预处理为特征向量序列"""
    sequence = []
    for landmarks in landmarks_list:
        frame_vector = []
        for lm in landmarks.landmark:
            # 这里可以只取x,y,也可以加入角度等衍生特征
            frame_vector.extend([lm.x, lm.y]) 
        sequence.append(frame_vector)
    return np.array(sequence)

def compare_action_sequences(seq_student, seq_teacher):
    """
    比较学员序列和老师序列的相似度。
    返回一个0-1之间的相似度分数,越接近1越相似。
    """
    # 计算DTW距离
    distance = dtw.distance(seq_student, seq_teacher)
    # 将距离转换为相似度分数(可以根据数据分布调整转换函数)
    # 一个简单的方法是使用指数衰减: similarity = exp(-distance / scale)
    scale = np.mean(np.std(seq_teacher, axis=0)) * 10  # 一个自适应的缩放因子
    similarity = np.exp(-distance / scale)
    return similarity

# 使用示例
# 假设我们已经录制了老师的标准动作序列 teacher_landmarks_list
# 以及实时获取的学员动作序列 student_landmarks_list (最近N帧)
teacher_seq = preprocess_sequence(teacher_landmarks_list)
student_seq = preprocess_sequence(student_landmarks_list[-100:]) # 取最近100帧

if len(student_seq) > 10: # 确保有足够的数据
    score = compare_action_sequences(student_seq, teacher_seq)
    print(f“当前动作相似度评分:{score:.2f}”)

4.3 构建一个简单的反馈系统

有了角度和序列相似度,我们就可以给学员提供具体的反馈了。反馈系统应该直观、即时。

def generate_feedback(elbow_angle, knee_angle, similarity_score):
    """根据计算出的指标生成文本反馈"""
    feedback_lines = []
    
    # 肘部角度反馈
    if elbow_angle < 160:
        feedback_lines.append(“出拳时请尽量伸直手臂(当前肘角{:.0f}°)”.format(elbow_angle))
    
    # 膝部角度反馈(马步)
    if 70 < knee_angle < 110:
        feedback_lines.append(“马步扎实,膝盖角度良好({:.0f}°)”.format(knee_angle))
    elif knee_angle >= 110:
        feedback_lines.append(“马步可以蹲得更低一些(当前膝角{:.0f}°)”.format(knee_angle))
    else:
        feedback_lines.append(“注意膝盖不要超过脚尖,防止受伤(膝角{:.0f}°)”.format(knee_angle))
    
    # 整体动作相似度反馈
    if similarity_score > 0.8:
        feedback_lines.append(“整体动作非常标准!”)
    elif similarity_score > 0.6:
        feedback_lines.append(“动作基本正确,注意细节连贯性。”)
    else:
        feedback_lines.append(“请对照示范,注意动作的起始和运行轨迹。”)
    
    return feedback_lines

# 在主循环中,计算完各项指标后
feedback = generate_feedback(current_elbow_angle, current_knee_angle, current_similarity)
# 将反馈逐行显示在视频画面上
for i, line in enumerate(feedback):
    y_pos = 60 + i * 30  # 计算每行文字的位置
    cv2.putText(image, line, (10, y_pos), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 200, 255), 2)

这样一个具备基本检测、分析和反馈功能的AI武术教练原型就完成了。它虽然比不上商业系统,但零成本、可定制、学习过程透明,对于个人练习者或兴趣小组来说,已经是一个非常有价值的辅助工具。我在自己练习太极拳“云手”时用它来纠正手臂划圈的轨迹和平稳度,效果出乎意料的好。技术的乐趣就在于,用简单的工具解决实际的问题,并在过程中不断学习和改进。希望这个项目能成为你探索AI应用的一个有趣起点。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐