目录
[二、使用 PoseC3D 训练自定义数据集](#二、使用 PoseC3D 训练自定义数据集)
[2.1 单个视频生成单个pkl标注文件](#2.1 单个视频生成单个pkl标注文件)
[2.2 多个视频生成单个pkl标注文件](#2.2 多个视频生成单个pkl标注文件)
[2.2.1 标注文件](#2.2.1 标注文件)
一、前言
我不想去下载这个公用数据集NTU RGB+D,因为下载这个需要有学术机构的,但是不下载数据集想直接自建数据集训练还挺不好搞的。这导致我必须得想点别的方法,由于这个问题的复杂性,要分篇写了。
二、使用 PoseC3D 训练自定义数据集
2.1 单个视频生成单个pkl标注文件
mmaction2使用 PoseC3D 训练自定义数据集的官方教程。后面我们简称为训练教程。
https://github.com/open-mmlab/mmaction2/blob/main/configs/skeleton/posec3d/custom_dataset_training.md
首先你需要了解:基于 PoseC3D 的行为识别任务仅需骨骼信息 ,因此你需要准备自定义标注文件(分别用于训练和验证)。操作之初,你需要先安装 MMDetection 和 MMPose 这两个库。随后,你可以参考「标注准备」环节的示例,借助ntu_pose_extraction.py脚本,提取自定义数据集中每个视频的 2D 关键点。命令示例如下:
首先我们先在mmaction2/data创建一个test文件夹,并放一个A001.mp4的视频,然后运行下面的命令
python
(mmaction2_win_env) D:\zero_track\mmaction2>
python tools\data\skeleton\ntu_pose_extraction.py tools\data\skeleton\S001C001P001R001A001_rgb.avi tools\data\skeleton\S001C001P001R001A001_rgb.pkl
这个命令你可以猜到就是把输入的mp4进行目标检测和姿态估计并且转为pkl格式,因为只有转pkl格式才能给到mmaction2的基于骨骼点的动作识别算法进行训练。在动作识别专栏中的《动作识别3------mmpose和mmaction2》我们给出了一个代码将mmpose输出的json转pkl,而这里是用faster_rcnn做目标检测,然后用hrnet做姿态估计,最后输出pkl。两者最后都是为了输出pkl,只是做法不同而已。有时间可以研究一下这个文件里面做了什么:
https://github.com/open-mmlab/mmaction2/blob/main/tools/data/skeleton/ntu_pose_extraction.py
注意:如果随便找个视频去测试,它会报下面的错误:
label = int(vid.split('/')[-1].split('A')[1][:3])
IndexError: list index out of range
这是因为上面这个文件的ntu_det_postproc函数有这样一个代码:label = int(vid.split('/')[-1].split('A')[1][:3]),它是拿到文件名之后按'A'分割再取A之后的内容,然后再取后3个数字,这导致要求文件名必须满足:
文件名里必须出现大写字母 A;
A 后面要紧跟着三位数字(001~120);
其余部分任意,扩展名、路径、前后缀都无所谓。
我们大致看一下这个函数:
python
def ntu_det_postproc(vid, det_results):
"""
NTU 行为检测后处理主函数
参数
----
vid : str
视频文件路径,字符串里带着像 ".../S001C001P001R001A050.mp4" 这种格式,
其中 Axxx 就是动作标签号。
det_results : list[np.ndarray]
原始检测框,每帧一个 ndarray,形状 (N_i, 5) 或 (N_i, 4),
5 的话最后一列是置信度;4 的话就是纯框。
返回
----
np.ndarray
整理后的 bbox 序列,形状 (T, n_person, 4),
T 为总帧数,n_person 为 1 或 2,缺失帧用 NaN/0 补齐。
"""
# 1. 逐帧去重(NMS 或 IOU 去重)
det_results = [removedup(x) for x in det_results]
# 2. 从视频路径里抠出动作标签号 Axxx
label = int(vid.split('/')[-1].split('A')[1][:3])
# 3. 定义"多人动作"编号段:A050--A060 和 A106--A120
mpaction = list(range(50, 61)) + list(range(106, 121))
# 4. 如果是多人动作,则每帧要 2 个人;否则 1 个人
n_person = 2 if label in mpaction else 1
# 5. 先判断是不是"简单样例":每帧检测框数量都正好等于 n_person,
# 且相邻帧 IOU 足够高,不需要做任何跟踪/插值
is_easy, bboxes = is_easy_example(det_results, n_person)
if is_easy:
print('\nEasy Example')
return bboxes # 直接返回已对齐好的 (T, n_person, 4)
# 6. 复杂样例:先把每帧框连成 tracklet(基于 IOU+外观的小轨迹段)
tracklets = bbox2tracklet(det_results)
# 7. 去掉过短或置信度过低的 tracklet
tracklets = drop_tracklet(tracklets)
print(f'\nHard {n_person}-person Example, found {len(tracklets)} tracklet')
# 8. 单人场景
if n_person == 1:
if len(tracklets) == 1:
# 只有一条轨迹,直接把它扩展成每帧一个框
tracklet = list(tracklets.values())[0]
det_results = tracklet2bbox(tracklet, len(det_results))
return np.stack(det_results) # 形状 (T, 4)
else:
# 多条轨迹,需要合并/插值/补全成一条
bad, det_results = tracklets2bbox(tracklets, len(det_results))
return det_results # 形状 (T, 4)
# 9. 双人场景
if len(tracklets) <= 2:
# 轨迹条数 ≤2,直接每条轨迹扩展成完整帧序列,然后拼成 (T,2,4)
tracklets = list(tracklets.values())
bboxes = []
for tracklet in tracklets:
# tracklet2bbox 返回 (T,4),先升维成 (T,1,4) 再 concat
bboxes.append(tracklet2bbox(tracklet, len(det_results))[:, None])
bbox = np.concatenate(bboxes, axis=1) # 形状 (T, 2, 4)
return bbox
else:
# 轨迹条数 >2,说明检测很碎,用兜底策略:每帧选置信度最高的 2 个框
return bboxes2bbox(det_results, len(det_results)) # 形状 (T, 2, 4)
说明它这个转pkl的代码是专门为某个数据集设计的,你可以把你自己的视频改成A001.mp4,会发现也能跑通。
我们创建一个read_pkl.py,读一下这个pkl
python
import pickle
with open(r'tools\data\skeleton\S001C001P001R001A001_rgb.pkl', 'rb') as f:
data = pickle.load(f)
print(f"data:{data}")
这里有个教程,后面我们简称为骨架数据教程。它里面提到了pkl里面是什么格式
https://github.com/open-mmlab/mmaction2/blob/main/tools/data/skeleton/README_zh-CN.md
PoseC3D 的标注文件格式以
gym_train.pkl为例:gym_train.pkl存储一个长度为 20484 的 list,list 的每一项为单个视频的骨架标注 dict。每个 dict 的内容如下:
- keypoint:关键点坐标,大小为 N(#人数)x T(时序长度)x K(#关键点, 这里为17)x 2 (x,y 坐标)的 numpy array 数据类型
- keypoint_score:关键点的置信分数,大小为 N(#人数)x T(时序长度)x K(#关键点, 这里为17)的 numpy array 数据类型
- frame_dir: 对应视频名
- label: 动作类别
- img_shape: 每一帧图像的大小
- original_shape: 同
img_shape- total_frames: 视频时序长度
我们打开调试可看到确实是他说的那样
python
data:{
'frame_dir': 'S001C001P001R001A001_rgb',
'label': 0,
'img_shape': (1080, 1920),
'original_shape': (1080, 1920),
'total_frames': 103,
'keypoint': (1, 103, 17, 2),
'keypoint_score': (1, 103, 17),
}
接下来我们继续看一下训练教程
接下来,你需要将所有生成的 pickle 文件汇总到一个列表中(训练集和验证集需分别处理),并保存为单个文件(例如
custom_dataset_train.pkl或custom_dataset_val.pkl)。至此,自定义数据集的标注文件就准备完成了。然后,你可以参考「PoseC3D / 训练」环节的示例,使用以下脚本(根据自身需求做部分调整)启动训练:
bash运行
python tools/train.py configs/skeleton/posec3d/slowonly_r50_u48_240e_ntu120_xsub_keypoint.py \ --work-dir work_dirs/slowonly_r50_u48_240e_ntu120_xsub_keypoint \ --validate --test-best --gpus 2 --seed 0 --deterministic运行上述脚本前,你需要修改配置变量,使其指向你新制作的标注文件:
python运行
model = dict( ... cls_head=dict( ... num_classes=4, # 你的数据集类别数 ... ), ... ) # 训练集标注文件路径(替换为你的实际路径) ann_file_train = 'data/posec3d/custom_dataset_train.pkl' # 验证集标注文件路径(替换为你的实际路径) ann_file_val = 'data/posec3d/custom_dataset_val.pkl' # 可加载官方发布的预训练权重初始化模型,若从头训练则设为 None load_from = 'pretrained_weight.pth' # 你也可以调整超参数或训练调度策略完成以上配置后,你的机器就会开始训练了 ------ 此时你可以泡杯咖啡,静待训练过程推进。
关键术语说明(补充)
pickle 文件:Python 特有的数据序列化文件格式,常用于存储结构化的姿态 / 标注数据,无需额外翻译,是行业通用表述;ntu_det_postproc step:NTU 数据集专属的「检测后处理步骤」,因自定义数据集无需适配 NTU 格式,故可跳过;deterministic:确定性训练模式,开启后可保证相同种子下训练结果可复现;work-dir:训练过程中日志、权重文件的保存目录,是 MMPose/MMEngine 生态的标准参数。总结
- 训练自定义数据集的核心是:用指定脚本提取视频 2D 关键点(跳过 NTU 专属后处理),并将结果整理为统一的 pickle 标注文件;
- 需修改配置文件中的类别数、标注文件路径、预训练权重路径这三类核心参数;
- 保留了原教程中轻松的语气("泡杯咖啡等待训练"),同时确保技术术语准确、命令 / 代码格式完整。
2.2 多个视频生成单个pkl标注文件
2.2.1 标注文件
从这个训练教程我们可以看到,似乎训练要求的标注文件并不像我们想象的那样是每个视频对应一个pkl的标注文件,似乎是训练集一个pkl,验证集一个pkl。但是我们现在手里只有把一个视频转成一个pkl的代码,而且还强制要求视频名称要A001.mp4、A002.mp4这种,这咋办?
我们去看一下骨架数据教程
https://github.com/open-mmlab/mmaction2/blob/main/tools/data/skeleton/README_zh-CN.md
我们下载一个他给的pkl
- NTURGB+D [2D Skeleton]: https://download.openmmlab.com/mmaction/v1.0/skeleton/data/ntu60_2d.pkl
然后把上面的read_pkl.py改一下输入路径,然后在print那句打断点开调试
python
import pickle
with open(r'tools\data\skeleton\ntu60_2d.pkl', 'rb') as f:
data = pickle.load(f)
print(f"data:{data}")
里面是这样的
python
data:{
'split':{
'xsub_train': ['S001C001P001R001A001', 'xxx', ...], 40091个
'xsub_val': ['xxx', 'xxx', ...], 16487个
'xview_train': ['xxx', 'xxx', ...], 37646个
'xview_val': ['xxx', 'xxx', ...], 18932个
}
'annotations':{ 56578个
{
'frame_dir': 'S001C001P001R001A001',
'label': 0,
'img_shape': (1080, 1920),
'original_shape': (1080, 1920),
'total_frames': 103,
'keypoint': (1, 103, 17, 2),
'keypoint_score': (1, 103, 17),
},a
{
'frame_dir': 'S001C001P001R001A002',
'label': 1,
'img_shape': (1080, 1920),
'original_shape': (1080, 1920),
'total_frames': 158,
'keypoint': (1, 158, 17, 2),
'keypoint_score': (1, 158, 17),
},
......
}
}
这是一个骨骼动作识别数据集的结构说明,主要用于基于人体关键点的动作分类任务。下面详细解释各个部分:
1. 数据划分 (
split)包含两种评估协议的数据集划分方式:
xsub(跨主体) :按不同的人物ID划分训练和验证集
xsub_train: 40,091个训练样本
xsub_val: 16,487个验证样本
xview(跨视角) :按不同的摄像机视角划分训练和验证集
xview_train: 37,646个训练样本
xview_val: 18,932个验证样本注意:两种划分的样本都来自同一数据集,只是划分标准不同。
2. 标注信息 (
annotations)共56,578个样本,每个样本是一个字典,包含以下字段:
字段 含义 示例值解析 frame_dir样本唯一标识符,由五段编码组成: S{设置}C{相机}P{人物}R{重复}A{动作}'S001C001P001R001A001'表示:设置1,相机1,人物1,重复1,动作1label动作类别标签(整数) 0和1代表不同的动作类别img_shape图像分辨率(高, 宽) (1080, 1920)表示1080p视频original_shape原始视频分辨率 通常与 img_shape相同total_frames视频总帧数 103表示该动作序列有103帧keypoint关键点坐标数据,形状:(N, T, V, C) • N=人数(1=单人) • T=帧数 • V=关键点数量(17个人体关节) • C=坐标维度(2D坐标 x,y) (1, 103, 17, 2)表示1个人,103帧,17个关节的2D坐标keypoint_score每个关键点的检测置信度,形状:(N, T, V) (1, 103, 17)表示每帧每个关节的置信分数
整体理解
这是典型的 PoseC3D 或 ST-GCN 等骨骼动作识别模型使用的数据格式,特点:
基于2D/3D人体关键点(17个关节,符合COCO标准)
包含训练/验证划分,支持两种评估协议
适用于视频级动作识别任务
总样本数约56,578个,覆盖多种动作类别
典型应用:输入关键点和置信度,输出预测的动作类别。
再看看这个
- NTURGB+D [3D Skeleton]: https://download.openmmlab.com/mmaction/v1.0/skeleton/data/ntu60_3d.pkl
python
data:{
'split':{
'xsub_train': ['S001C001P001R001A001', 'xxx', ...], # 40091个
'xsub_val': ['xxx', 'xxx', ...], # 16487个
'xset_train': ['xxx', 'xxx', ...], # 54468个
'xset_val': ['xxx', 'xxx', ...], # 59477个
'xview_train': ['xxx', 'xxx', ...], # 37646个
'xview_val': ['xxx', 'xxx', ...] # 18932个
}
'annotations':{ 56578个
{
'frame_dir': 'S001C001P001R001A001',
'label': 0,
'keypoint': (1, 103, 25, 3),
'total_frames': 103,
},a
{
'frame_dir': 'S001C001P001R001A002',
'label': 1,
'keypoint': (1, 158, 25, 3),
'total_frames': 158,
},
......
}
}
1. 数据划分 (
split)包含三种评估协议的训练/验证集划分:
划分方式 说明 训练集大小 验证集大小 xsub跨主体:按不同的人物ID划分,测试模型对未见过的人的泛化能力 40,091 16,487 xview跨视角:按不同的摄像机视角划分,测试模型对未见过的角度的泛化能力 37,646 18,932 xset跨设置:按不同的环境/背景设置划分,测试模型对新场景的适应能力 54,468 59,477 注意:虽然三种划分的样本数量不同,但数据来源相同(总样本56,578个),只是划分标准不同。
2. 标注信息 (
annotations)共56,578个样本,每个样本是一个字典,关键字段解析:
字段 含义 示例值解析 frame_dir样本唯一标识符,格式: S{设置}C{相机}P{人物}R{重复}A{动作}'S001C001P001R001A001'表示设置1、相机1、人物1、重复1、动作1label动作类别标签(整数,通常0~119表示120类动作) 0和1代表不同的动作类别total_frames视频总帧数 103表示该动作序列有103帧keypoint关键点数据 ,形状: (N, T, V, C) • N=人数(1=单人) • T=帧数(与 total_frames一致) • V=关键点数量(25个关节 ) • C=坐标维度(3D坐标 x,y,z)(1, 103, 25, 3)表示1个人、103帧、25个关节的3D坐标
关键点说明
25个关节:NTU RGB+D数据集的标准骨架结构(比COCO的17个关节更详细,包含手指、脚趾等)
3D坐标:区别于2D姿态估计,这里包含深度信息(z轴),通常来自Kinect等深度相机
评估协议:这是学术论文中常用的标准评估方式,用于全面测试模型性能
整体应用场景
这是典型的 3D骨架动作识别 数据集,用于训练模型输入人体关节的3D坐标序列,输出预测的动作类别。