news 2026/10/16 10:11:06

Human36M三维人体姿态数据集:Python下载解析与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Human36M三维人体姿态数据集:Python下载解析与避坑指南

简介:面向机器学习与计算机视觉开发者的工具包,聚焦Human3.6M三维人体姿态数据集的自动化获取与预处理。资源提供一套完整的脚本链,覆盖数据下载、压缩包解压、元数据解析和关节坐标提取等关键环节,下载、解压、处理与元数据解析脚本分工明确,形成可复用的流水线;同时提供容器化配置、依赖清单与参数模板,便于快速复现处理环境。全包共十四个文件,以四个脚本程序为核心,辅以说明文档、校验文件、许可文件及容器配置等文件类型,压缩包仅三十四KB,轻量实用。目前已有三千六百三十二人学习使用,适合需要批量获取标准三维姿态数据用于模型训练、算法验证或科研复现的开发者,能有效缩短数据准备时间,降低入门门槛。无论是快速搭建实验环境,还是复现论文结果,都能直接套用。

1. Human36M 这套数据,决定你姿态模型能走多远

从三维人体姿态估计进入实操开始,大多数人的第一个坎根本不是模型结构,而是数据。Human36M 这类数据集覆盖了多组受试者、多种动作类别和多个相机视角,是单目三维姿态估计绕不开的基准,但它不提供统一的下载脚本,数据以原始 mat 文件、视频和 XML 标注混合存放,新手光是把数据从服务器拉到本地就可能耗费一整天。这个资源解决的就是这个环节:用 Python 把 Human36M 的下载、解压、校验、解析和可视化串成一条完整链路,适合准备入门三维姿态估计、需要快速拿到可用训练集的开发者。如果你已经在跑某个 2D 姿态模型,想往 3D 提升,这套脚本同样能帮你省掉整理数据的时间,直接跳到模型环节。

2. Human36M 的数据组织方式:先摸清目录和坐标系再动手

拿到数据之前,先搞清楚 Human36M 内部是怎么组织的。很多人下载完才发现解压出来的目录结构和模型代码里写死的路径对不上,回头改代码改到怀疑人生。这一章先把数据集的目录、文件类型、坐标系和相机参数讲透,再去写下载脚本。

2.1 目录结构和文件命名规则

Human36M 的原始包按受试者(Subject)分目录,每个受试者目录下再按动作序列(Action)分文件夹。典型的组织方式如下:

h36m/ S1/ Actions/ Directions/ Discussion/ Eating/ ... S5/ Actions/ ... S6/ S7/ S8/ S9/ S11/

每个动作文件夹里放的是对应相机视角的视频文件,以及该序列的标注文件。标注文件通常以 mat 格式存储,里面包含 3D 关节点坐标、相机内外参、图像尺寸等关键信息。视频文件名一般带相机编号,比如像S1_Directions_1.55011271.mp4这样的命名规则,其中1是序列编号,后面那串数字是相机序列号。

为什么要先讲这个?因为下载脚本里所有 URL 拼接和本地路径映射都依赖这套命名规则。如果只按序列号不按相机号建目录,后面解析 mat 文件时很难把 2D 框和 3D 骨架对应起来。

2.2 坐标系、相机参数和关节点定义

Human36M 的 3D 关节点是世界坐标系下的位置,单位是米。每个受试者的骨架包含 17 个关节点,但很多开源模型用的是 16 个或者 14 个,所以下载完数据后的第一件事往往不是直接训练,而是做关节点映射。常见做法是把 Human36M 的 17 关节点映射到你自己模型的关节点顺序,这一步错一个索引,训练出来的模型基本就是废的。

相机参数方面,每个序列都有对应的相机内参和外参。内参包含焦距和主点,外参包含旋转矩阵和平移向量。2D 投影公式是:

# 世界坐标 -> 相机坐标 -> 图像坐标 cam_coord = R @ world_coord + t uv = K @ cam_coord uv = uv[:2] / uv[2]

这里R是旋转矩阵,t是平移向量,K是内参矩阵。很多人在解析时把世界坐标直接当成相机坐标用,结果画出来的 2D 骨架位置完全错位。这个问题在第五章我会单独列一条避坑记录。

2.3 文件构成和下载前检查清单

解压后的数据量不小,但真正需要关心的文件是下面几类:

文件类型扩展名主要内容
标注文件.mat3D 关节点坐标、相机参数、动作标签
视频文件.mp4多视角下的动作序列
配置文件.xml相机标定参数和动作元信息
元数据.txt / .csv受试者、动作类别、序列划分说明

下载前建议先看一遍元数据里的序列划分,确认哪些用于训练、哪些用于测试。Human36M 的官方协议通常用一组受试者做训练,另一组做测试,如果混在一起训练,评估结果会虚高,论文里也没法跟别人的数字对比。

这一章的核心结论是:Human36M 本身是一套结构清晰但入口较隐晦的数据集,下载前必须先把目录规则和坐标系统搞清楚。接下来要做的,就是把这套规则翻译成 Python 脚本。

3. 写 Python 下载脚本:把多进程、断点续传和校验一次做对

Human36M 的原始发布站点提供的是 HTTP 直链下载,文件分散在多个目录下。直接浏览器另存为也能下载,但文件多、链接规律性强,写脚本才是正路。这一段给出一套从 URL 列表生成到本地落盘的完整方案。

3.1 依赖和最小下载实现

先准备依赖,建议新建虚拟环境,避免跟其他项目互相污染:

pip install requests tqdm numpy scipy

requests负责 HTTP 请求,tqdm用于显示下载进度,scipy后面解析 mat 文件时用得上。最小下载实现如下:

import requests from tqdm import tqdm def download_file(url, local_path): resp = requests.get(url, stream=True, timeout=30) resp.raise_for_status() total = int(resp.headers.get('content-length', 0)) with open(local_path, 'wb') as f: with tqdm(total=total, unit='B', unit_scale=True, desc=local_path.split('/')[-1]) as pbar: for chunk in resp.iter_content(chunk_size=1024 * 1024): f.write(chunk) pbar.update(len(chunk)) return local_path

这段代码的逻辑是:以流式方式发起 GET 请求,按 1MB 分块写入本地文件,同时用tqdm显示进度。content-length可能为空,所以total用0兜底。注意的是,resp.raise_for_status()会在 HTTP 状态码为 4xx/5xx 时直接抛异常,避免把错误页面写进文件。

3.2 多线程下载与断点续传

文件数量多的时候,单线程下载会非常慢。常见做法是用ThreadPoolExecutor做并发下载,同时用Range请求头实现断点续传。断点续传的写法是:先检查本地文件大小,如果服务端支持 Range,就从已下载的位置继续拉。

import os import requests from concurrent.futures import ThreadPoolExecutor, as_completed def download_with_resume(url, local_path, num_workers=4): if os.path.exists(local_path) and os.path.getsize(local_path) > 0: # 简单校验:如果文件已存在且大于 0,跳过 # 更严格的做法是比对服务端 content-length print(f'skip {local_path}') return local_path tmp_path = local_path + '.part' headers = {} existing_size = 0 if os.path.exists(tmp_path): existing_size = os.path.getsize(tmp_path) headers['Range'] = f'bytes={existing_size}-' resp = requests.get(url, stream=True, headers=headers, timeout=30) mode = 'ab' if existing_size > 0 else 'wb' with open(tmp_path, mode) as f: for chunk in resp.iter_content(chunk_size=1024 * 1024): f.write(chunk) os.rename(tmp_path, local_path) return local_path def parallel_download(url_list, local_path_list, num_workers=4): with ThreadPoolExecutor(max_workers=num_workers) as executor: futures = [ executor.submit(download_with_resume, url, path) for url, path in zip(url_list, local_path_list) ] for future in as_completed(futures): future.result()

这段代码的要点有两个。一是.part临时文件机制,下载中断时只会丢失.part文件,不会破坏已下载的正式文件。二是Range请求头只加了bytes=已下载字节数-,服务端如果返回206 Partial Content,就会从断点继续;如果服务端不支持 Range,返回200 OK,就从头重新下载。ThreadPoolExecutor的max_workers建议设置在 4 到 8 之间,开太多会被服务器限速,反而更慢。

3.3 完整性校验与失败重试

下载完成只是第一步,文件是否完整才是关键。Human36M 的服务器没有提供统一的 MD5 校验文件,常见做法是用文件大小做粗校验,再用scipy.io.loadmat或ffprobe验证关键文件能不能正常打开。

import time import random MAX_RETRIES = 3 def download_with_retry(url, local_path, retries=MAX_RETRIES): for attempt in range(retries): try: download_with_resume(url, local_path) # 校验文件大小 target_size = int(requests.head(url, timeout=10).headers.get('content-length', 0)) actual_size = os.path.getsize(local_path) if target_size > 0 and actual_size != target_size: raise ValueError(f'size mismatch: {actual_size} != {target_size}') return local_path except Exception as e: wait = 2 ** attempt + random.uniform(0, 1) print(f'attempt {attempt + 1} failed: {e}, retry in {wait:.1f}s') time.sleep(wait) raise RuntimeError(f'failed to download {url}')

逻辑说明:每次下载后通过 HEAD 请求拿到服务端文件大小,如果本地文件大小不一致就抛异常,进入指数退避重试。重试等待时间按2^attempt秒递增,并加上随机抖动,避免多个线程同时重试造成服务器压力过大。这种重试机制在实际下载大文件时非常管用,特别是网络不稳定的时候。

这一章给出的脚本已经覆盖了下载的核心链路。下一步,是怎么把下载下来的 mat 文件解析成可以喂给模型的数据。

4. 解析 H36M 的 mat 文件:从骨架到可视化

下载完数据后,真正的难点才开始。Human36M 的 mat 文件不是简单的数组,而是包含嵌套结构体的 MATLAB v7.3 格式文件。解析时容易遇到两个问题:一是文件格式版本太新,scipy.io.loadmat直接报错;二是嵌套结构体的字段名和预期不一致。这一章逐步拆解。

4.1 读取 mat 文件并查看结构

推荐用scipy.io.loadmat读取,注意设置squeeze_me=True和struct_as_record=False,能让嵌套结构体更接近 Python 字典的访问方式。

import scipy.io as sio import numpy as np mat_path = 'S1/MySequence/acting_directions.mat' data = sio.loadmat(mat_path, squeeze_me=True, struct_as_record=False) print(data.keys()) # 常见字段:jointPositions / camera / frame / action 等

如果loadmat报File is not a valid mat file或者提示MATLAB v7.3不支持,说明文件是用 HDF5 格式保存的,需要改用h5py读取。判断方法很简单:用h5py.is_hdf5(mat_path)检查一下。

import h5py def load_mat_any(path): if h5py.is_hdf5(path): with h5py.File(path, 'r') as f: # v7.3 格式,需要手动遍历 dataset return f else: return sio.loadmat(path, squeeze_me=True, struct_as_record=False)

4.2 提取关节点坐标并做坐标系转换

Human36M 的 3D 关节点数据通常放在jointPositions字段里,形状一般是(帧数, 关节点数, 3)。拿到后先做一次坐标系检查:把第一帧的骨架用 matplotlib 画出来,看是否合理。

import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D joints_3d = data['jointPositions'] # shape: (N, 17, 3) frame = joints_3d[0] # first frame fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(frame[:, 0], frame[:, 1], frame[:, 2]) ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') plt.show()

如果画出来的点云是倒着的,或者人形是横躺的,说明坐标系定义和可视化工具的约定不一致。常见做法是检查 Z 轴方向:Human36M 官方约定 Y 轴向上,但很多 3D 可视化库默认 Z 轴向上。转换方法是把坐标轴重排,比如[x, y, z] -> [x, z, y]。这一步做完,再画一次就能看到立起来的人形骨架。

4.3 生成训练样本的标准化流程

解析完成后,下一步是把骨架数据标准化成训练样本。需要处理的字段包括:3D 关键点坐标、相机参数、动作标签、帧号。常见做法是把每个序列的每一帧打包成字典,统一保存为 npz 文件:

def process_sequence(mat_data, subject_id, action_id): joints_3d = mat_data['jointPositions'] camera = mat_data['camera'] frames = [] for i in range(joints_3d.shape[0]): frames.append({ 'subject': subject_id, 'action': action_id, 'frame': i, 'joints_3d': joints_3d[i].astype(np.float32), 'camera_rot': camera['R'].astype(np.float32), 'camera_trans': camera['t'].astype(np.float32) }) return frames

这个函数把原始 mat 数据转换成更易读的 Python 字典列表,后续做数据加载时不需要再碰 mat 文件。字段命名可以按自己的模型来调整,但建议保留joints_3d和camera_rot、camera_trans这三个核心字段,因为它们同时涉及骨架数据和投影。

这一章的解析脚本可以直接接到第三章的下载脚本后面,形成一个从远程数据到本地 npz 文件的完整链路。接下来要说的,是这条链路上最容易翻车的几个点。

5. 避坑与排查:Human36M 下载解析的五个高频翻车点

这一章专门写我在实际复现中踩过的坑,每一条都是先看现象,再说原因,最后给解决方式。如果你在下载或解析过程中卡住,先对照这五条自查。

5.1 网络层:下载到一半卡死,重试也一直失败

现象:requests.get长时间不返回,或者下载到 70% 后速度骤降,重试多次仍然失败。

原因:Human36M 这类数据服务器对单 IP 的并发连接数有限制,同时下载任务开太多会被限速甚至断开。另外部分服务器不支持 Range 请求,断点续传实际上没有生效。

解决方式:一是把并发数降到 4 以内,减少单 IP 连接数;二是确认服务器是否支持 Range 请求,发一个 HEAD 请求看Accept-Ranges响应头有没有bytes;三是给请求加超时和流式读取,timeout=30避免线程无限等待。

5.2 文件层:下载完的文件打不开,文件大小对不上

现象:视频文件用播放器打不开,mat 文件用loadmat报错,检查后发现本地文件大小和服务器标称大小不一致。

原因:编写脚本时没有做完整性校验,或者误把.part临时文件当成了最终文件。我在早期版本里就犯过这个错误:下载中断后把.part文件改名成正式文件,结果加载时才发现数据不完整。

解决方式:下载完成后必须做大小校验,或者用h5py.is_hdf5验证 mat 文件头。不要跳过content-length比对这一步。更稳妥的做法是下载完成后把临时文件重命名为正式文件,而不是直接写正式文件。

5.3 数据层:mat 文件版本不对,loadmat 直接报错

现象:scipy.io.loadmat抛ValueError: Unknown mat file type, version 50或者提示需要使用 HDF5。

原因:Human36M 的部分标注文件经过后续重新导出,可能保存成了 MATLAB v7.3 格式,本质是 HDF5,loadmat读不了。这不是数据损坏,而是格式差异。

解决方式:先用h5py.is_hdf5判断,如果是 HDF5 格式就改用h5py读取。注意两种方式读取出来的嵌套结构返回类型不同,loadmat返回字典,h5py返回 HDF5 Dataset 引用,代码里要做兼容处理。我一般会封装一层load_mat_any(),上层代码无感知。

5.4 坐标系:2D 投影位置全乱,骨架画出来是散的

现象:把 3D 关节点投影到 2D 后,关键点位置跟视频画面完全对不上,甚至出现负数坐标。

原因:漏了外参转换。Human36M 的 3D 关节点是世界坐标系,必须先乘以旋转矩阵并加上平移向量,转成相机坐标系后,再乘内参矩阵投影到图像平面。很多人在这一步直接拿世界坐标乘内参矩阵,得到的 2D 点自然是乱的。

解决方式:严格按cam_coord = R @ world_coord + t的顺序做。另外注意矩阵维度,R是(3,3),world_coord是(3,),用@运算符而不是*做矩阵乘法。

5.5 标签层:受试者划分混合,评估结果虚高

现象:模型在训练集上表现很好,在测试集上评估时数字也不错,但换了新数据就崩。检查发现训练和测试用了同一个受试者的数据。

原因:Human36M 的协议要求跨受试者评估,即训练受试者和测试受试者不重叠。如果下载的时候把全部受试者混在一起,或者在自己划分数据集时用了随机划分,就会造成数据泄漏。

解决方式:严格按照官方协议划分训练集和测试集。受试者级别的划分,而不是序列级别。下载阶段就按受试者分目录保存,解析时保留受试者 ID 字段,评估时不混用。

这一章的五个坑基本覆盖了从下载到解析的最常见故障。如果都排查完还不行,建议先跑通单个序列的完整流程,再扩展成批量处理。

6. 进阶用法:把 Human36M 整理成统一训练入口

下载和解析都跑通之后,还有一步值得做:把散落的 npz 文件整合成统一的数据接口。因为 Human36M 本身是原始数据集,而模型代码通常只认一种标准格式。常见做法是做成一个简单的Dataset类,屏蔽底层文件差异。

先设计统一的数据加载接口:

class H36MDataset: def __init__(self, data_root, subjects, transform=None): self.data_root = data_root self.subjects = subjects self.transform = transform self.samples = self._build_index() def _build_index(self): samples = [] for subject in self.subjects: subject_dir = os.path.join(self.data_root, f'S{subject}') for seq_dir in os.listdir(subject_dir): for npz_file in glob.glob(os.path.join(subject_dir, seq_dir, '*.npz')): samples.append(npz_file) return samples def __len__(self): return len(self.samples) def __getitem__(self, idx): data = np.load(self.samples[idx], allow_pickle=True) joints_3d = data['joints_3d'] camera_rot = data['camera_rot'] camera_trans = data['camera_trans'] if self.transform: joints_3d = self.transform(joints_3d) return joints_3d, camera_rot, camera_trans

这个数据接口把文件路径和样本一一对应,加载时统一返回joints_3d和相机参数。如果你用的框架自带 Dataset 抽象,把这个类里面的__getitem__逻辑搬过去就行。

我现在的习惯是:每次拿到新数据集,先不急着跑模型,而是先花半天时间做数据链路验证。具体做法是随机抽 10 个序列,走一遍下载、解析、可视化、加载四个步骤,全部通过后再全量处理。这个方法帮我躲过了不少后期才发现的数据坑。

希望这份 Human36M 的三维人体姿态数据获取和解析笔记能帮到你。按项目标题找到对应资源,把脚本跑通,后续做模型训练时就能把精力放在网络结构和超参数上。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 10:10:26

Linux性能调优实战:从监控工具到内核参数与容量规划

最近在准备 RHCA II 的最后一门 EX442(性能调优与容量规划),相关笔记已经写到第 13 篇。说句实话,刚看这个标题时我也觉得“调优”离我很远,但真正动手做下来,发现它比想象中落地得多——不是在系统里随便改…

作者头像 李华
网站建设 2026/10/16 10:10:06

九个原生浏览器API,替代JS库实现前端性能优化与代码瘦身

先说一个我最近越来越强烈的感觉:很多前端团队还在手里揣着“老黄历”写代码。我说得直接一点,现在的 HTML 已经不是当年那个只能渲染静态标签的 HTML 了,它底层挂了一堆系统级接口。过去你得靠各种 JS 库才能实现的效果,现在浏览…

作者头像 李华
网站建设 2026/10/16 10:10:05

Gradle 7.2-all.zip 全量包详解:Android 构建兼容性与离线部署实战

简介:本资源为Gradle 7.2版本的完整离线发行包(gradle-7.2-all.zip),专为Android开发者、Java工程构建人员及Android Studio用户设计,旨在解决国内网络环境下Gradle在线下载缓慢、超时或失败导致的项目编译卡顿问题。压…

作者头像 李华
网站建设 2026/10/16 10:10:05

从0到1搭建智慧机械管理平台:设备数据采集、报警与健康度落地方案

我们做设备管理平台的时候,第一反应往往是想把“可视化大屏”和“数据中台”先立起来,但真正从0到1把一个智慧机械管理平台跑通之后,我的真实感受是:最核心的竞争力根本不在那些漂亮的页面里,而在你如何理解车间里那一…

作者头像 李华
网站建设 2026/10/16 10:09:40

端侧AI Agent重构实战:从1.2GB到280MB的移动端落地之旅

如果你最近在关注端侧 AI,大概率听说过那套被圈内俗称“大龙虾”的开源 Agent 框架——功能真叫一个全:意图识别、任务规划、工具调用、记忆管理、云端模型网关,几乎把 Agent 全家桶都塞进了一个进程,钳子够大、壳也够厚。前阵子我…

作者头像 李华