AI骨骼关键点可视化实战:WebUI骨架连线颜色自定义教程
1. 引言:AI人体骨骼关键点检测的工程价值
随着计算机视觉技术的发展,人体姿态估计(Human Pose Estimation)已成为智能健身、动作捕捉、虚拟试衣、人机交互等场景的核心支撑技术。Google推出的MediaPipe Pose模型凭借其高精度、低延迟和轻量化特性,成为边缘设备与本地化部署的首选方案。
本项目基于 MediaPipe 构建了一套完整的本地化人体骨骼关键点检测系统,支持在无网络环境下通过 WebUI 实时上传图像并生成火柴人骨架图。默认情况下,系统使用红点表示关节点、白线表示骨骼连接。但在实际应用中,我们往往需要根据品牌风格、视觉对比度或用户偏好对连线颜色进行个性化定制。
本文将带你从零开始,深入实践如何修改 WebUI 中骨架连线的颜色配置,实现高度可定制化的可视化效果,并提供完整代码示例与避坑指南。
2. 技术架构与核心组件解析
2.1 系统整体架构
本系统采用前后端分离设计,整体流程如下:
[用户上传图片] ↓ [Flask WebUI 接收请求] ↓ [调用 MediaPipe Pose 模型推理] ↓ [获取33个3D关键点坐标] ↓ [使用自定义绘图逻辑绘制骨架] ↓ [返回带骨骼连线的可视化图像]所有模块均运行于本地环境,不依赖任何外部 API 或云服务,确保数据隐私与系统稳定性。
2.2 MediaPipe Pose 关键能力说明
MediaPipe Pose 支持识别以下三类共33 个关键点:
- 面部特征点:鼻尖、左/右眼、耳等
- 上肢关节:肩、肘、腕、手部关键点
- 下肢关节:髋、膝、踝、脚尖等
输出为每个点的(x, y, z, visibility)四维坐标,其中z表示深度信息(相对比例),visibility表示置信度。
这些关键点之间通过预定义的“连接关系”形成骨架结构,例如: -LEFT_SHOULDER → LEFT_ELBOW → LEFT_WRIST-RIGHT_HIP → RIGHT_KNEE → RIGHT_ANKLE
默认情况下,MediaPipe 使用mp_drawing_styles.get_default_pose_connections_style()定义线条样式。
3. 骨架连线颜色自定义实战
3.1 修改目标与设计思路
原始 WebUI 输出的骨架为白色连线,在浅色背景或低对比度图像中难以辨识。我们的目标是:
✅ 将不同部位的骨骼线设置为不同颜色
✅ 提升视觉区分度(如上肢蓝色、下肢绿色)
✅ 保持代码简洁且不影响性能
为此,我们将绕过默认样式函数,手动构建连接样式映射表,并传入绘图接口。
3.2 核心代码实现
以下是实现颜色自定义的关键代码片段(Python + Flask 后端):
import cv2 import mediapipe as mp from flask import Flask, request, send_file import numpy as np from io import BytesIO app = Flask(__name__) mp_pose = mp.solutions.pose mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles # 自定义颜色映射:BGR格式 COLOR_UPPER_BODY = (255, 0, 0) # 蓝色 - 上半身 COLOR_LOWER_BODY = (0, 255, 0) # 绿色 - 下半身 COLOR_HEAD = (0, 0, 255) # 红色 - 头部连接 # 自定义连接样式 CUSTOM_CONNECTIONS_STYLE = { # 上肢:蓝色 (mp_pose.PoseLandmark.LEFT_SHOULDER, mp_pose.PoseLandmark.LEFT_ELBOW): mp_drawing.DrawingSpec(color=COLOR_UPPER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.LEFT_ELBOW, mp_pose.PoseLandmark.LEFT_WRIST): mp_drawing.DrawingSpec(color=COLOR_UPPER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.RIGHT_SHOULDER, mp_pose.PoseLandmark.RIGHT_ELBOW): mp_drawing.DrawingSpec(color=COLOR_UPPER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.RIGHT_ELBOW, mp_pose.PoseLandmark.RIGHT_WRIST): mp_drawing.DrawingSpec(color=COLOR_UPPER_BODY, thickness=4, circle_radius=2), # 躯干:紫色 (mp_pose.PoseLandmark.LEFT_SHOULDER, mp_pose.PoseLandmark.RIGHT_SHOULDER): mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=5, circle_radius=3), (mp_pose.PoseLandmark.LEFT_HIP, mp_pose.PoseLandmark.RIGHT_HIP): mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=5, circle_radius=3), (mp_pose.PoseLandmark.LEFT_SHOULDER, mp_pose.PoseLandmark.LEFT_HIP): mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=4, circle_radius=2), (mp_pose.PoseLandmark.RIGHT_SHOULDER, mp_pose.PoseLandmark.RIGHT_HIP): mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=4, circle_radius=2), # 下肢:绿色 (mp_pose.PoseLandmark.LEFT_HIP, mp_pose.PoseLandmark.LEFT_KNEE): mp_drawing.DrawingSpec(color=COLOR_LOWER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.LEFT_KNEE, mp_pose.PoseLandmark.LEFT_ANKLE): mp_drawing.DrawingSpec(color=COLOR_LOWER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.RIGHT_HIP, mp_pose.PoseLandmark.RIGHT_KNEE): mp_drawing.DrawingSpec(color=COLOR_LOWER_BODY, thickness=4, circle_radius=2), (mp_pose.PoseLandmark.RIGHT_KNEE, mp_pose.PoseLandmark.RIGHT_ANKLE): mp_drawing.DrawingSpec(color=COLOR_LOWER_BODY, thickness=4, circle_radius=2), # 头部:红色 (mp_pose.PoseLandmark.NOSE, mp_pose.PoseLandmark.LEFT_EYE): mp_drawing.DrawingSpec(color=COLOR_HEAD, thickness=2, circle_radius=1), (mp_pose.PoseLandmark.NOSE, mp_pose.PoseLandmark.RIGHT_EYE): mp_drawing.DrawingSpec(color=COLOR_HEAD, thickness=2, circle_radius=1), } def draw_custom_landmarks(image, results): """使用自定义样式绘制骨架""" if results.pose_landmarks: h, w, _ = image.shape # 手动遍历每一条连接线 for connection in mp_pose.POSE_CONNECTIONS: start_idx = connection[0] end_idx = connection[1] # 获取起点和终点坐标 start_point = results.pose_landmarks.landmark[start_idx] end_point = results.pose_landmarks.landmark[end_idx] # 只有当两个点都可见时才绘制 if start_point.visibility > 0.5 and end_point.visibility > 0.5: x1, y1 = int(start_point.x * w), int(start_point.y * h) x2, y2 = int(end_point.x * w), int(end_point.y * h) # 查找该连接对应的样式 style_key = (start_idx, end_idx) spec = CUSTOM_CONNECTIONS_STYLE.get(style_key) if spec is None: spec = CUSTOM_CONNECTIONS_STYLE.get((end_idx, start_idx)) # 反向查找 if spec is None: continue # 忽略未定义的连接 # 绘制线条 cv2.line(image, (x1, y1), (x2, y2), spec.color[::-1], thickness=spec.thickness) # 单独绘制关键点(保持红色圆点) for landmark in results.pose_landmarks.landmark: if landmark.visibility > 0.5: cx, cy = int(landmark.x * w), int(landmark.y * h) cv2.circle(image, (cx, cy), 5, (0, 0, 255), -1) # 红色实心圆 return image @app.route('/upload', methods=['POST']) def upload_image(): file = request.files['image'] img_bytes = np.frombuffer(file.read(), np.uint8) img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR) with mp_pose.Pose(static_image_mode=True, min_detection_confidence=0.5) as pose: rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) result = pose.process(rgb_img) annotated_img = draw_custom_landmarks(img.copy(), result) _, buffer = cv2.imencode('.jpg', annotated_img) io_buf = BytesIO(buffer) io_buf.seek(0) return send_file(io_buf, mimetype='image/jpeg') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)3.3 代码解析与关键点说明
| 代码段 | 功能说明 |
|---|---|
CUSTOM_CONNECTIONS_STYLE | 字典结构,以元组(起始点, 结束点)为键,DrawingSpec对象为值,定义颜色、粗细等属性 |
color=COLOR_UPPER_BODY | 使用 BGR 格式(OpenCV 默认),注意与 RGB 区分 |
thickness=4 | 设置线宽,增强可视性 |
circle_radius=2 | 控制关键点半径(仅作样式占位) |
手动遍历POSE_CONNECTIONS | 替代mp_drawing.draw_landmarks(),实现精细化控制 |
visibility > 0.5 | 过滤低置信度点,避免误连 |
⚠️重要提示:MediaPipe 的
draw_landmarks()函数不支持直接传入自定义连接样式字典,必须手动实现绘图逻辑。
4. 实践优化建议与常见问题
4.1 性能优化技巧
- 关闭不必要的绘图操作:若仅需特定区域(如上肢),可在
CUSTOM_CONNECTIONS_STYLE中只保留相关连接。 - 降低图像分辨率:输入图像过大时可先缩放至 640×480 左右,显著提升处理速度。
- 复用 DrawingSpec 对象:避免重复创建相同样式的对象,减少内存开销。
4.2 常见问题与解决方案
| 问题现象 | 原因分析 | 解决方法 |
|---|---|---|
| 连线颜色未生效 | 使用了默认draw_landmarks()方法 | 改用手动绘图逻辑 |
| 图像变黑或崩溃 | 坐标越界导致cv2.line报错 | 添加边界检查if 0 <= x < w and 0 <= y < h |
| 部分连接缺失 | 自定义字典未覆盖所有POSE_CONNECTIONS条目 | 显式添加所需连接,或设置默认样式兜底 |
| 颜色显示异常 | 使用了 RGB 色彩空间而非 BGR | 将(R,G,B)写成(B,G,R),或使用[::-1]反转 |
4.3 扩展应用场景
- 运动康复监测:用不同颜色标记受伤侧肢体,辅助医生评估恢复情况
- 舞蹈教学系统:实时比对学员与标准动作的骨架差异,颜色编码偏差程度
- AR 虚拟换装:基于骨骼姿态驱动 3D 人体模型,提升贴合度
5. 总结
本文围绕AI骨骼关键点可视化中的颜色自定义需求,系统讲解了如何基于 Google MediaPipe 和 Flask WebUI 实现高度个性化的骨架连线渲染方案。
我们完成了以下核心内容:
- 深入理解 MediaPipe 的绘图机制,明确其默认样式的局限性;
- 构建自定义连接样式字典,实现按身体部位分配颜色(上肢蓝、下肢绿、头部红);
- 编写完整可运行的后端代码,支持图像上传、姿态检测与彩色骨架绘制;
- 提供性能优化与避坑指南,确保方案稳定落地。
通过本次实践,你不仅掌握了 MediaPipe 的高级用法,更具备了将 AI 模型输出转化为专业级可视化产品的工程能力。
未来可进一步探索动态颜色映射(如根据动作类型切换配色)、多人体支持、视频流实时处理等进阶功能。
💡获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。