MediaPipe Pose模型转换:ONNX格式导出教程
1. 引言
1.1 AI 人体骨骼关键点检测的工程价值
在计算机视觉领域,人体姿态估计(Human Pose Estimation)是一项基础且关键的技术,广泛应用于动作识别、虚拟试衣、运动分析、人机交互和智能健身等场景。Google 开源的MediaPipe Pose模型凭借其高精度、低延迟和轻量级特性,成为目前最主流的姿态估计算法之一。
然而,MediaPipe 原生使用 TensorFlow Lite 格式进行推理,限制了其在非移动端或非 TFLite 支持环境中的部署灵活性。为了提升跨平台兼容性,尤其是便于集成到 ONNX Runtime、TensorRT 或 PyTorch 生态中,将 MediaPipe Pose 模型转换为ONNX(Open Neural Network Exchange)格式具有重要意义。
本文将详细介绍如何从 MediaPipe 的 Python 接口出发,提取其内置的 Pose 模型,并通过中间框架(如 tf2onnx)完成向 ONNX 格式的无损转换,最终实现一个可独立运行、支持 CPU 加速的 ONNX 版本人体骨骼关键点检测系统。
1.2 教程目标与适用读者
本教程面向具备一定深度学习部署经验的工程师或研究人员,目标是:
- 理解 MediaPipe Pose 模型的内部结构
- 掌握从 MediaPipe 提取计算图的方法
- 实现从 TFLite 到 ONNX 的完整转换流程
- 验证 ONNX 模型输出一致性并集成可视化功能
学完本教程后,你将获得一个可在任意支持 ONNX Runtime 的设备上运行的高精度姿态估计模型,彻底摆脱对 MediaPipe 运行时的依赖。
2. 技术背景与模型解析
2.1 MediaPipe Pose 模型架构概览
MediaPipe Pose 使用两阶段检测策略:
- BlazePose Detector:首先使用 BlazeNet 变体在整幅图像中定位人体区域(bounding box),该部分基于单阶段目标检测器设计。
- Pose Landmark Model:以裁剪后的人体 ROI 作为输入,通过回归方式预测 33 个 3D 关键点坐标(x, y, z, visibility)。
其中,我们关注的是第二阶段的pose_landmark_full_body.tflite模型,它是一个轻量级卷积神经网络,输入尺寸为256×256×3,输出包含: -landmarks:(1, 33, 4) → (x, y, z, visibility) -segmentation_mask(可选):用于身体分割
该模型已在大量真实与合成数据上训练,对遮挡、光照变化和复杂姿态具有良好的鲁棒性。
2.2 为何选择 ONNX 格式?
| 对比维度 | TFLite | ONNX |
|---|---|---|
| 跨平台支持 | 主要限于 Android/iOS | 支持 Windows/Linux/macOS/嵌入式 |
| 推理引擎 | TensorFlow Lite Interpreter | ONNX Runtime / TensorRT / OpenVINO |
| 社区生态 | 移动端强 | 工业级部署更成熟 |
| 模型融合能力 | 较弱 | 支持算子融合、量化、剪枝 |
| 可视化调试工具 | 少 | Netron 全面支持 |
将模型转为 ONNX 后,不仅可以利用 ONNX Runtime 在 CPU 上实现多线程加速,还能进一步转换为 TensorRT 引擎,在 GPU 上获得更高性能。
3. ONNX 模型导出实践步骤
3.1 环境准备
确保已安装以下依赖库:
pip install mediapipe onnx onnxruntime tf2onnx tensorflow numpy opencv-python flask⚠️ 注意:虽然 MediaPipe 不直接依赖 TensorFlow,但
tf2onnx工具需要 TensorFlow 作为中间解析器,因此必须安装。
3.2 提取 TFLite 模型文件
MediaPipe 的.tflite模型被编译进 Python 包中。我们需要手动提取pose_landmark_full_body.tflite文件。
查找路径通常位于:
import mediapipe as mp print(mp.__file__) # 输出类似:/path/to/site-packages/mediapipe/modules/pose_landmark/进入mediapipe/modules/pose_landmark/目录,找到pose_landmark_full_body.tflite文件。若不存在,请确认安装的是完整版 MediaPipe(非 lite 包)。
3.3 使用 tf2onnx 完成模型转换
由于 TFLite 模型不能直接由tf2onnx处理,需先将其加载为 TensorFlow GraphDef,再转换为 ONNX。
编写转换脚本convert_pose_to_onnx.py:
import tensorflow as tf import tf2onnx # Step 1: 加载 TFLite 模型并转换为 TF Lite Interpreter interpreter = tf.lite.Interpreter(model_path="pose_landmark_full_body.tflite") interpreter.allocate_tensors() # 获取输入输出张量信息 input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() print("Input shape:", input_details[0]['shape']) print("Output shapes:", [o['shape'] for o in output_details]) # Step 2: 使用 tf.lite.TFLiteConverter 加载并导出为 SavedModel(可选) # 更推荐方式:直接用 tf2onnx 转换 TFLite 模型 with open("pose_landmark_full_body.tflite", "rb") as f: tflite_model = f.read() # Step 3: 使用 tf2onnx.convert.from_tflite() 直接转换 onnx_model, _ = tf2onnx.convert.from_tflite( tflite_model, input_names=["input_image"], output_names=["landmarks", "presence", "fidelity"], opset=13 ) # Step 4: 保存 ONNX 模型 with open("mediapipe_pose.onnx", "wb") as f: f.write(onnx_model.SerializeToString()) print("✅ ONNX 模型已成功导出:mediapipe_pose.onnx")✅ 成功标志:输出
mediapipe_pose.onnx文件,大小约 3.8MB,可通过 Netron 打开查看结构。
3.4 验证 ONNX 模型输出一致性
编写验证脚本,对比原始 MediaPipe 与 ONNX 推理结果:
import cv2 import numpy as np import onnxruntime as ort import mediapipe as mp # 初始化 MediaPipe mp_pose = mp.solutions.pose pose = mp_pose.Pose(static_image_mode=True, model_complexity=1) # 读取测试图像 image = cv2.imread("test.jpg") rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w = rgb_image.shape[:2] # MediaPipe 原始推理 results = pose.process(rgb_image) if results.pose_landmarks: mp_landmarks = [[lm.x, lm.y, lm.z, lm.visibility] for lm in results.pose_landmarks.landmark] print("📌 MediaPipe 输出前5个关键点:") print(np.array(mp_landmarks)[:5]) # ONNX 推理 session = ort.InferenceSession("mediapipe_pose.onnx") input_tensor = cv2.resize(rgb_image, (256, 256)).astype(np.float32) / 255.0 input_tensor = np.expand_dims(input_tensor, axis=0) # (1, 256, 256, 3) onnx_outputs = session.run(None, {session.get_inputs()[0].name: input_tensor}) landmarks_onnx = onnx_outputs[0].reshape(33, 4) # (33, 4): x,y,z,vis print("\n📌 ONNX 输出前5个关键点:") print(landmarks_onnx[:5]) # 归一化坐标映射回原图尺寸 landmarks_onnx_scaled = [] for pt in landmarks_onnx: x = int(pt[0] * w) y = int(pt[1] * h) landmarks_onnx_scaled.append((x, y)) # 可视化 ONNX 结果 overlay = image.copy() for i, (x, y) in enumerate(landmarks_onnx_scaled): color = (0, 0, 255) if i < 33 else (255, 255, 255) cv2.circle(overlay, (x, y), 5, color, -1) cv2.addWeighted(overlay, 0.7, image, 0.3, 0, image) cv2.imwrite("onnx_result.jpg", image) print("🎨 可视化结果已保存:onnx_result.jpg")🔍 输出建议:两个模型的关键点坐标误差应小于 0.02(归一化坐标),表明转换成功。
4. WebUI 集成与本地服务部署
4.1 构建轻量级 Flask Web 服务
创建app.py实现上传→推理→返回骨架图的服务:
from flask import Flask, request, send_file import cv2 import numpy as np import onnxruntime as ort import tempfile app = Flask(__name__) session = ort.InferenceSession("mediapipe_pose.onnx") def draw_skeleton(image, landmarks): edges = [ (0,1),(1,2),(2,3),(3,7),(4,5),(5,6),(6,8),(7,9),(8,10), (9,11),(10,12),(11,13),(12,14),(13,15),(14,16),(15,17), (16,18),(17,19),(18,20),(19,21),(20,22),(21,23),(22,24), (11,23),(12,24),(23,25),(24,26),(25,27),(26,28),(27,29), (28,30),(29,31),(30,32),(31,32) ] h, w = image.shape[:2] for i, (x, y, _, v) in enumerate(landmarks): if v > 0.5: cx, cy = int(x * w), int(y * h) cv2.circle(image, (cx, cy), 6, (0, 0, 255), -1) for u, v in edges: if landmarks[u][3] > 0.5 and landmarks[v][3] > 0.5: xu, yu = int(landmarks[u][0] * w), int(landmarks[u][1] * h) xv, yv = int(landmarks[v][0] * w), int(landmarks[v][1] * h) cv2.line(image, (xu, yu), (xv, yv), (255, 255, 255), 2) return image @app.route('/upload', methods=['POST']) def upload(): file = request.files['image'] img_bytes = np.frombuffer(file.read(), np.uint8) bgr = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB) h, w = rgb.shape[:2] # 预处理 input_tensor = cv2.resize(rgb, (256, 256)).astype(np.float32) / 255.0 input_tensor = np.transpose(input_tensor, (2, 0, 1))[None, ...] # (1,3,256,256) # 推理 outputs = session.run(None, {'input_image': input_tensor}) landmarks = outputs[0].reshape(33, 4) # 映射回原图 landmarks[:, :2] = landmarks[:, :2] * [w, h] / 256 # 绘制骨架 result_img = draw_skeleton(bgr.copy(), landmarks) # 保存临时文件 temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') cv2.imwrite(temp_file.name, result_img) return send_file(temp_file.name, mimetype='image/jpeg') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)4.2 启动命令与访问方式
python app.py访问http://localhost:5000/upload并使用 POST 请求上传图片即可获得带骨架标注的结果图。
5. 总结
5.1 核心成果回顾
本文完成了以下关键技术闭环:
- 模型提取:成功从 MediaPipe Python 包中获取
pose_landmark_full_body.tflite模型; - 格式转换:利用
tf2onnx工具链将 TFLite 模型无损转换为 ONNX 格式; - 输出验证:通过数值对比与可视化双重手段验证了 ONNX 模型的准确性;
- 服务封装:构建基于 Flask 的 WebUI 接口,实现本地化、零依赖的姿态估计服务。
整个方案完全脱离云端 API 和 Token 认证机制,适用于隐私敏感、离线运行、边缘部署等工业级场景。
5.2 最佳实践建议
- 输入预处理一致性:务必保证 ONNX 推理前的图像缩放、归一化方式与 MediaPipe 完全一致;
- 后处理优化:可在 ONNX 模型外添加 NMS 或平滑滤波模块提升稳定性;
- 性能调优:启用 ONNX Runtime 的
CUDAExecutionProvider可显著提升 GPU 推理速度; - 模型裁剪:若仅需 2D 关键点,可移除 z 维度输出以减小计算量。
💡获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。