从YouTube视频到16个关键点:手把手教你用Python解析MPII人体姿态数据集(附完整代码)

张开发
2026/4/21 15:45:50 15 分钟阅读

分享文章

从YouTube视频到16个关键点:手把手教你用Python解析MPII人体姿态数据集(附完整代码)
从YouTube视频到16个关键点Python实战MPII人体姿态数据集解析与可视化当我们需要训练一个能够理解人体姿态的AI模型时高质量的数据集是成功的关键。MPII Human Pose Database作为计算机视觉领域的标杆数据集包含了从YouTube视频中提取的25,000多张图像标注了超过40,000个人体目标的16个关键关节信息。本文将带你从零开始完整掌握MPII数据集的获取、解析到可视化呈现的全流程。1. MPII数据集全景认知MPII数据集诞生于马克斯·普朗克信息学研究所已成为人体姿态估计领域的黄金标准。这个数据集的独特之处在于真实场景多样性所有图像均来自YouTube视频覆盖日常活动、运动场景、社交互动等多种真实环境丰富标注维度除了标准的16个关节点坐标还包含身体部位遮挡信息、3D躯干方向和头部朝向标注多人姿态挑战约70%的图像包含多个人物为开发鲁棒的多人姿态估计算法提供了理想测试平台数据集的标准划分如下表所示数据子集图像数量标注人体数量主要用途训练集14,67922,246模型训练验证集2,7292,958超参调优测试集6,61911,731最终评估在实际应用中我们通常会遇到三种不同格式的标注文件原始的.mat文件MATLAB格式转换后的.h5文件HDF5格式便于处理的.json文件2. 数据获取与预处理实战2.1 数据集下载与结构解析MPII官方数据集可通过学术申请获取下载后的典型目录结构如下MPII/ ├── images/ # 存放所有原始图像 │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── annotations/ # 存放标注文件 │ ├── mpii_human_pose_v1_u12_1.mat │ ├── train.h5 │ ├── valid.h5 │ └── test.h5 └── README.txt # 数据集说明文档对于无法直接获取官方数据的研究者可以通过以下Python代码从公开源构建简化版数据集import requests import os from tqdm import tqdm def download_mpii_subset(urls, save_dir): os.makedirs(save_dir, exist_okTrue) for url in urls: filename url.split(/)[-1] filepath os.path.join(save_dir, filename) print(fDownloading {filename}...) response requests.get(url, streamTrue) total_size int(response.headers.get(content-length, 0)) with open(filepath, wb) as f, tqdm( descfilename, totaltotal_size, unitiB, unit_scaleTrue, unit_divisor1024, ) as bar: for data in response.iter_content(chunk_size1024): size f.write(data) bar.update(size) # 示例下载链接实际使用时需替换为有效链接 image_urls [ http://example.com/mpii/images/000001.jpg, http://example.com/mpii/images/000002.jpg ] download_mpii_subset(image_urls, mpii_images)2.2 关键标注字段深度解读MPII标注文件包含多个重要字段每个都有特定的几何意义center(中心点)[x, y]格式表示人体边界框的中心坐标scale(缩放因子)基于200像素标准高度的比例系数计算公式为缩放因子 实际人体高度 / 200part(关节点)16×2数组存储各关节点的(x,y)坐标顺序如下JOINT_NAMES [ right_ankle, right_knee, right_hip, left_hip, left_knee, left_ankle, pelvis, thorax, upper_neck, head_top, right_wrist, right_elbow, right_shoulder, left_shoulder, left_elbow, left_wrist ]visible(可见性)16维数组1.0表示关节点可见0.0表示被遮挡以下代码展示了如何从h5文件中提取这些关键信息import h5py def parse_h5_annotations(h5_path): with h5py.File(h5_path, r) as f: # 获取所有标注索引 indices list(f[index]) annotations [] for idx in indices: ann { imgname: f[imgname][idx].decode(utf-8), center: f[center][idx], scale: f[scale][idx][0], parts: f[part][idx], visible: f[visible][idx], torso_angle: f[torsoangle][idx][0] if torsoangle in f else None } annotations.append(ann) return annotations3. 数据可视化核心技术3.1 骨架绘制算法实现将抽象的关节点数据转化为直观的可视化效果需要解决三个关键问题关节点连接逻辑定义哪些关节点应该相连形成骨骼遮挡处理策略对被遮挡关节点采用特殊视觉标记多尺度适配确保在不同缩放因子下都能正确显示以下是使用OpenCV和Matplotlib实现骨架可视化的完整代码import cv2 import numpy as np import matplotlib.pyplot as plt from matplotlib.collections import LineCollection # 定义关节点连接关系哪些点之间应该画线 SKELETON [ [0, 1], [1, 2], [2, 6], # 右腿到骨盆 [3, 4], [4, 5], [3, 6], # 左腿到骨盆 [6, 7], [7, 8], [8, 9], # 躯干到头部 [7, 12], [12, 11], [11, 10], # 右臂 [7, 13], [13, 14], [14, 15] # 左臂 ] def draw_skeleton(image, keypoints, visibleNone, thickness2): 在图像上绘制人体骨架 # 转换关键点为numpy数组 kpts np.array(keypoints).reshape(-1, 2) # 创建画布副本 canvas image.copy() # 绘制关节点 for i, (x, y) in enumerate(kpts): if visible is None or visible[i] 0.5: # 只绘制可见关节点 color (0, 255, 0) if i 6 else (0, 0, 255) # 下肢绿色上肢红色 cv2.circle(canvas, (int(x), int(y)), 5, color, -1) # 绘制骨骼连接线 for i, j in SKELETON: if visible is None or (visible[i] 0.5 and visible[j] 0.5): start tuple(kpts[i].astype(int)) end tuple(kpts[j].astype(int)) cv2.line(canvas, start, end, (255, 0, 0), thickness) return canvas def visualize_annotation(image_path, annotation): 完整可视化流程 # 读取图像 img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 提取关键信息 center annotation[center] scale annotation[scale] keypoints annotation[parts] visible annotation[visible] # 绘制骨架 skeleton_img draw_skeleton(img, keypoints, visible) # 显示结果 plt.figure(figsize(12, 8)) plt.imshow(skeleton_img) plt.title(fScale: {scale:.2f}, Center: ({center[0]:.1f}, {center[1]:.1f})) plt.axis(off) plt.show()3.2 高级可视化技巧为了更深入地分析数据集特征我们可以实现以下几种增强可视化多人物同框可视化def visualize_multiple_persons(image_path, annotations): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for ann in annotations: if ann[imgname] os.path.basename(image_path): img draw_skeleton(img, ann[parts], ann[visible]) plt.imshow(img) plt.axis(off) plt.show()热力图可视化def create_heatmap(image, keypoints, sigma10): h, w image.shape[:2] heatmap np.zeros((h, w)) for x, y in keypoints: if x 0 and y 0: # 忽略无效点 xx, yy np.meshgrid(np.arange(w), np.arange(h)) dist (xx - x)**2 (yy - y)**2 heatmap np.exp(-dist / (2 * sigma**2)) plt.imshow(heatmap, cmapjet, alpha0.5) plt.colorbar() plt.imshow(image, alpha0.7) plt.show()4. 数据增强实战策略高质量的数据增强可以显著提升模型的泛化能力。针对MPII数据集我们推荐以下几种增强方法及其实现4.1 几何变换增强随机旋转增强def random_rotate(image, keypoints, max_angle30): h, w image.shape[:2] center (w // 2, h // 2) angle np.random.uniform(-max_angle, max_angle) # 获取旋转矩阵 rot_mat cv2.getRotationMatrix2D(center, angle, 1.0) # 旋转图像 rotated_img cv2.warpAffine(image, rot_mat, (w, h)) # 旋转关键点 rotated_kpts [] for x, y in keypoints: if x 0 and y 0: # 有效点 new_pt np.dot(rot_mat, [x, y, 1]) rotated_kpts.append(new_pt) else: rotated_kpts.append([0, 0]) return rotated_img, np.array(rotated_kpts)仿射变换增强def random_affine(image, keypoints, scale_range(0.8, 1.2)): h, w image.shape[:2] # 随机生成变换参数 scale np.random.uniform(*scale_range) tx np.random.uniform(-0.1, 0.1) * w ty np.random.uniform(-0.1, 0.1) * h # 构建变换矩阵 M np.float32([[scale, 0, tx], [0, scale, ty]]) # 应用变换 transformed_img cv2.warpAffine(image, M, (w, h)) # 变换关键点 transformed_kpts [] for x, y in keypoints: if x 0 and y 0: new_x scale * x tx new_y scale * y ty transformed_kpts.append([new_x, new_y]) else: transformed_kpts.append([0, 0]) return transformed_img, np.array(transformed_kpts)4.2 颜色空间增强HSV颜色扰动def hsv_augmentation(image, h_gain0.5, s_gain0.5, v_gain0.5): 在HSV空间进行随机颜色扰动 hsv cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32) # 随机增益 h hsv[:, :, 0] * (1 np.random.uniform(-h_gain, h_gain)) s hsv[:, :, 1] * (1 np.random.uniform(-s_gain, s_gain)) v hsv[:, :, 2] * (1 np.random.uniform(-v_gain, v_gain)) # 限制数值范围 h np.clip(h, 0, 179) # OpenCV中H的范围是0-179 s np.clip(s, 0, 255) v np.clip(v, 0, 255) hsv np.stack([h, s, v], axis2).astype(np.uint8) return cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)光照模拟增强def simulate_lighting(image, alpha_range(0.7, 1.3), beta_range(-30, 30)): 模拟不同光照条件 alpha np.random.uniform(*alpha_range) # 对比度 beta np.random.uniform(*beta_range) # 亮度 enhanced cv2.convertScaleAbs(image, alphaalpha, betabeta) return enhanced5. 评估指标与模型训练准备5.1 PCKh指标深度解析MPII数据集采用PCKhHead-normalized Percentage of Correct Keypoints作为评估标准其计算过程可分为三步归一化距离计算归一化距离 预测点与真实点的欧氏距离 / 头部段长度头部段长度定义为头顶到颈部关键点的距离阈值判断 当归一化距离小于阈值通常为0.5时认为预测正确准确率计算PCKh 正确预测的关键点数 / 总关键点数 × 100%以下是PCKh的Python实现def compute_pckh(preds, targets, head_length, threshold0.5): 计算PCKh指标 :param preds: 预测关键点 [N, 16, 2] :param targets: 真实关键点 [N, 16, 2] :param head_length: 头部长度 [N] :param threshold: 判定阈值 :return: 各关节PCKh及平均PCKh # 计算欧氏距离 distances np.linalg.norm(preds - targets, axis2) # [N, 16] # 归一化 norm_distances distances / head_length[:, None] # [N, 16] # 计算各关节PCKh joint_pckh np.mean(norm_distances threshold, axis0) * 100 # 计算平均PCKh mean_pckh np.mean(joint_pckh) return joint_pckh, mean_pckh5.2 数据加载器实现为高效训练深度学习模型我们需要实现专门的数据加载器import torch from torch.utils.data import Dataset, DataLoader class MPIIDataset(Dataset): def __init__(self, image_dir, annotations, transformNone): self.image_dir image_dir self.annotations annotations self.transform transform def __len__(self): return len(self.annotations) def __getitem__(self, idx): ann self.annotations[idx] # 加载图像 img_path os.path.join(self.image_dir, ann[imgname]) image cv2.imread(img_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 准备关键点数据 keypoints ann[parts].copy() visible ann[visible].copy() # 应用变换 if self.transform: transformed self.transform( imageimage, keypointskeypoints, visibilityvisible ) image transformed[image] keypoints transformed[keypoints] visible transformed[visibility] # 转换为张量 image torch.from_numpy(image).float().permute(2, 0, 1) / 255.0 keypoints torch.from_numpy(np.array(keypoints)).float() visible torch.from_numpy(np.array(visible)).float() return { image: image, keypoints: keypoints, visible: visible, scale: torch.tensor(ann[scale]).float(), center: torch.tensor(ann[center]).float() } # 使用示例 train_dataset MPIIDataset( image_dirmpii/images, annotationstrain_annotations, transformcreate_train_transform() ) train_loader DataLoader( train_dataset, batch_size32, shuffleTrue, num_workers4 )6. 常见问题与解决方案在实际使用MPII数据集的过程中开发者常会遇到以下几个典型问题问题1标注与图像不对齐解决方案检查图像文件名是否匹配验证图像是否经过裁剪或缩放使用以下代码验证标注位置def validate_annotation(image_path, annotation): img cv2.imread(image_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 绘制中心点 center tuple(map(int, annotation[center])) cv2.circle(img, center, 10, (255, 0, 0), -1) # 计算边界框 scale annotation[scale] box_size int(200 * scale) x1 center[0] - box_size // 2 y1 center[1] - box_size // 2 x2 x1 box_size y2 y1 box_size # 绘制边界框 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) plt.imshow(img) plt.show()问题2关节点可见性处理不一致解决方案明确区分标注不可见和关节点不存在两种情况在损失函数中给予不同权重def weighted_mse_loss(pred, target, visible): 带可见性权重的MSE损失 :param pred: 预测关键点 [B, 16, 2] :param target: 目标关键点 [B, 16, 2] :param visible: 可见性 [B, 16] :return: 加权损失 squared_diff (pred - target)**2 # [B, 16, 2] per_joint_loss torch.mean(squared_diff, dim2) # [B, 16] weighted_loss per_joint_loss * visible # [B, 16] return torch.mean(weighted_loss)问题3多人场景下的标注匹配解决方案使用非极大值抑制(NMS)处理多人检测结果基于OKS(Object Keypoint Similarity)进行匹配def compute_oks(det_keypoints, gt_keypoints, gt_visible, scale): 计算目标关键点相似度(OKS) :param det_keypoints: 检测到的关键点 [16, 2] :param gt_keypoints: 真实关键点 [16, 2] :param gt_visible: 真实可见性 [16] :param scale: 人物尺度 :return: OKS分数 sigmas np.array([ 0.026, 0.025, 0.025, # 脚踝、膝盖、髋部 0.035, 0.035, 0.035, # 髋部、膝盖、脚踝 0.079, 0.079, # 骨盆、胸部 0.072, 0.062, # 颈部、头顶 0.062, 0.107, 0.087, # 手腕、肘部、肩部 0.087, 0.107, 0.062 # 肩部、肘部、手腕 ]) variances (sigmas * 2)**2 distances np.sum((det_keypoints - gt_keypoints)**2, axis1) # 只考虑可见关节点 vis_mask gt_visible 0.5 if np.sum(vis_mask) 0: return 0.0 # 计算每个关节的贡献 kpt_oks np.exp(-distances / (2 * scale**2 * variances 1e-7)) kpt_oks kpt_oks * vis_mask return np.sum(kpt_oks) / np.sum(vis_mask)7. 性能优化技巧处理大规模姿态数据集时以下优化技巧可以显著提升效率技巧1使用LMDB加速数据读取import lmdb import pickle class MPIIDatasetLMDB(Dataset): def __init__(self, lmdb_path): self.env lmdb.open(lmdb_path, readonlyTrue) with self.env.begin() as txn: self.length pickle.loads(txn.get(b__len__)) def __len__(self): return self.length def __getitem__(self, idx): with self.env.begin() as txn: key f{idx:08d}.encode() data pickle.loads(txn.get(key)) return data # 创建LMDB数据集 def create_lmdb_dataset(image_dir, annotations, lmdb_path): env lmdb.open(lmdb_path, map_size1099511627776) # 1TB with env.begin(writeTrue) as txn: txn.put(b__len__, pickle.dumps(len(annotations))) for idx, ann in enumerate(annotations): img_path os.path.join(image_dir, ann[imgname]) image cv2.imread(img_path) data { image: image, keypoints: ann[parts], visible: ann[visible], scale: ann[scale], center: ann[center] } key f{idx:08d}.encode() txn.put(key, pickle.dumps(data))技巧2使用多进程预处理from multiprocessing import Pool def parallel_preprocess(annotations, num_workers8): def process_item(ann): img_path os.path.join(mpii/images, ann[imgname]) image cv2.imread(img_path) # 执行各种预处理... return processed_data with Pool(num_workers) as pool: results pool.map(process_item, annotations) return results技巧3使用混合精度训练from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for batch in train_loader: images batch[image].cuda() keypoints batch[keypoints].cuda() visible batch[visible].cuda() optimizer.zero_grad() # 混合精度上下文 with autocast(): preds model(images) loss criterion(preds, keypoints, visible) # 缩放损失并反向传播 scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()8. 扩展应用与前沿方向掌握了MPII数据集的基础处理流程后我们可以进一步探索以下前沿方向多人姿态估计扩展使用Top-Down方法先检测人再估计姿态尝试Bottom-Up方法先检测所有关节点再分组实现基于Transformer的端到端多人姿态估计时序姿态分析class PoseSequenceDataset(Dataset): 处理视频序列中的姿态数据 def __init__(self, video_dir, seq_length16): self.video_dir video_dir self.seq_length seq_length self.video_frames self._load_video_frames() def _load_video_frames(self): # 实现视频帧加载逻辑 pass def __getitem__(self, idx): frames self.video_frames[idx:idxself.seq_length] # 实现时序处理逻辑 return sequence_data3D姿态估计延伸从2D关键点估计3D姿态结合MPII的2D标注与3D数据集实现基于视频的3D姿态追踪轻量化部署方案def convert_to_onnx(model, output_path): dummy_input torch.randn(1, 3, 256, 256) torch.onnx.export( model, dummy_input, output_path, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, output: {0: batch_size} } )

更多文章