继续上一篇骨骼关键点识别算法相关内容的学习,今天学习通用领域的工具使用以及网络的DIY
目前正在实习,具体的内容方向是基于骨骼关键点的动作识别(skeleton based action recognition)。 在经过了多多的调研之后,觉得目前还是mmaction2平台最香,因此希望后续工作(包括效果的验证、实验、对比等等)包括前期的端到端工作都在mmaction2工具箱上展开。
学习mmaction2的目的是能够借助该平台对数据集、算法、演示、流程、搭建等等环节都能够理解的更加清晰和透彻(毕竟目前是一个小白),为后续自己的真正实际问题解决做准备。
本篇文章主要是基于mmaction2的中文教程(链接见文末),此外还有一些参考的文章以及github库中的注释性教程。
mmaction2是商汤和港中文联合研发的一个基于pytorch框架的人体动作识别的深度学习开源工具库,可以提供包括行为识别(分类)、时序动作检测、时空动作检测、骨骼动作识别(分类)等等多种子类问题的算法框架,包括数据集等等,可以非常方便的使用。
mmaction2 和广为人知的检测工具库mmdetection 一样,都属于open-mmlab 工具箱下属的一个模块,目前仍在不断更新与拓展功能及算法,可以预见的是,其在人体行为识别方面将被更多人使用。
《先放参考链接》
MAction2 使用 python 文件作为配置文件。其配置文件系统的设计将模块化与继承整合进来,方便用户进行各种实验。 MMAction2 提供的所有配置文件都放置在 $MMAction2/configs
文件夹下,用户可以通过运行命令 python tools/analysis/print_config.py
/PATH/TO/CONFIG
来查看完整的配置信息,从而方便检查所对应的配置文件。
当用户使用脚本 “tools/train.py
” 或者 “tools/test.py
” 提交任务时,可以通过指定 --cfg-options
参数来直接修改所使用的配置文件内容。
用户可以按照原始配置中的字典键顺序来指定配置文件的设置。 例如,--cfg-options model.backbone.norm_eval=False
会改变 train 模式下模型主干网络 backbone 中所有的 BN 模块。
配置文件中,存在一些由字典组成的列表。例如,训练数据前处理流水线 data.train.pipeline
就是 python 列表。 如,[dict(type='SampleFrames'), ...]
。如果用户想更改其中的 ‘SampleFrames
’ 为 ‘DenseSampleFrames
’, 可以指定 --cfg-options data.train.pipeline.0.type=DenseSampleFrames
。
当配置文件中需要更新的是一个列表或者元组,例如,配置文件通常会设置 workflow=[('train', 1)]
,用户如果想更改, 需要指定 --cfg-options workflow="[(train,1),(val,1)]"
。注意这里的引号 ” 对于列表/元组数据类型的修改是必要的, 并且 不允许 引号内所指定的值的书写存在空格。
在 config/_base_
文件夹下存在 3 种基本组件类型: 模型(model), 训练策略(schedule), 运行时的默认设置(default_runtime)。 许多方法都可以方便地通过组合这些组件进行实现,如 TSN,I3D,SlowOnly 等。 其中,通过 _base_
下组件来构建的配置被称为 原始配置(primitive)。
对于在同一文件夹下的所有配置文件,MMAction2 推荐只存在 一个 对应的 原始配置 文件。 所有其他的配置文件都应该继承 原始配置 文件,这样就能保证配置文件的最大继承深度为 3。
为了方便理解,MMAction2 推荐用户继承现有方法的配置文件。 例如,如需修改 TSN 的配置文件,用户应先通过 _base_ = '../tsn/tsn_r50_1x1x3_100e_kinetics400_rgb.py
’ 继承 TSN 配置文件的基本结构, 并修改其中必要的内容以完成继承。
如果用户想实现一个独立于任何一个现有的方法结构的新方法,则需要像 configs/recognition
, configs/detection
等一样,在 configs/TASK
中建立新的文件夹。
更多详细内容,请参考 mmcv。
MMAction2 按照以下风格进行配置文件命名,代码库的贡献者需要遵循相同的命名规则。
{model}_[model setting]_{backbone}_[misc]_{data setting}_[gpu x batch_per_gpu]_{schedule}_{dataset}_{modality}
其中,{xxx}
表示必要的命名域,[yyy]
表示可选的命名域。
{model}
:模型类型,如 tsn,i3d 等。[model setting]
:一些模型上的特殊设置。{backbone}
:主干网络类型,如 r50(ResNet-50)等。[misc]
:模型的额外设置或插件,如 dense,320p,video等。{data setting}
:采帧数据格式,形如 {clip_len}x{frame_interval}x{num_clips}。[gpu x batch_per_gpu]
:GPU 数量以及每个 GPU 上的采样。{schedule}
:训练策略设置,如 20e 表示 20 个周期(epoch)。{dataset}
:数据集名,如 kinetics400,mmit等。{modality}
:帧的模态,如 rgb, flow等。为了方便更好的理解配置文件的设置命令及作用,mmaction2工具库在文档中已经对:时序动作检测、动作识别、时空动作检测 三种识别任务给出了代码的一个具体示例以及注释。目前针对骨骼关键点动作识别问题还没有进行具体注释实例的更新,因此本篇对: 动作识别 ;时空动作检测 官方所给实例进行记录与解读,并且附上Pose-C3D的以及 ST-GCN的配置文件做对比阅读(因为两者不是一个结构,差别还是挺大的)。
# 模型设置
model = dict( # 模型的配置
type='Recognizer2D', # 动作识别器的类型
backbone=dict( # Backbone 字典设置
type='ResNet', # Backbone 名
pretrained='torchvision://resnet50', # 预训练模型的 url 或文件位置
depth=50, # ResNet 模型深度
norm_eval=False), # 训练时是否设置 BN 层为验证模式
cls_head=dict( # 分类器字典设置
type='TSNHead', # 分类器名
num_classes=400, # 分类类别数量
in_channels=2048, # 分类器里输入通道数
spatial_type='avg', # 空间维度的池化种类
consensus=dict(type='AvgConsensus', dim=1), # consensus 模块设置
dropout_ratio=0.4, # dropout 层概率
init_std=0.01), # 线性层初始化 std 值
# 模型训练和测试的设置
train_cfg=None, # 训练 TSN 的超参配置
test_cfg=dict(average_clips=None)) # 测试 TSN 的超参配置
# 数据集设置
dataset_type = 'RawframeDataset' # 训练,验证,测试的数据集类型
data_root = 'data/kinetics400/rawframes_train/' # 训练集的根目录
data_root_val = 'data/kinetics400/rawframes_val/' # 验证集,测试集的根目录
ann_file_train = 'data/kinetics400/kinetics400_train_list_rawframes.txt' # 训练集的标注文件
ann_file_val = 'data/kinetics400/kinetics400_val_list_rawframes.txt' # 验证集的标注文件
ann_file_test = 'data/kinetics400/kinetics400_val_list_rawframes.txt' # 测试集的标注文件
img_norm_cfg = dict( # 图像正则化参数设置
mean=[123.675, 116.28, 103.53], # 图像正则化平均值
std=[58.395, 57.12, 57.375], # 图像正则化方差
to_bgr=False) # 是否将通道数从 RGB 转为 BGR
train_pipeline = [ # 训练数据前处理流水线步骤组成的列表
dict( # SampleFrames 类的配置
type='SampleFrames', # 选定采样哪些视频帧
clip_len=1, # 每个输出视频片段的帧
frame_interval=1, # 所采相邻帧的时序间隔
num_clips=3), # 所采帧片段的数量
dict( # RawFrameDecode 类的配置
type='RawFrameDecode'), # 给定帧序列,加载对应帧,解码对应帧
dict( # Resize 类的配置
type='Resize', # 调整图片尺寸
scale=(-1, 256)), # 调整比例
dict( # MultiScaleCrop 类的配置
type='MultiScaleCrop', # 多尺寸裁剪,随机从一系列给定尺寸中选择一个比例尺寸进行裁剪
input_size=224, # 网络输入
scales=(1, 0.875, 0.75, 0.66), # 长宽比例选择范围
random_crop=False, # 是否进行随机裁剪
max_wh_scale_gap=1), # 长宽最大比例间隔
dict( # Resize 类的配置
type='Resize', # 调整图片尺寸
scale=(224, 224), # 调整比例
keep_ratio=False), # 是否保持长宽比
dict( # Flip 类的配置
type='Flip', # 图片翻转
flip_ratio=0.5), # 执行翻转几率
dict( # Normalize 类的配置
type='Normalize', # 图片正则化
**img_norm_cfg), # 图片正则化参数
dict( # FormatShape 类的配置
type='FormatShape', # 将图片格式转变为给定的输入格式
input_format='NCHW'), # 最终的图片组成格式
dict( # Collect 类的配置
type='Collect', # Collect 类决定哪些键会被传递到行为识别器中
keys=['imgs', 'label'], # 输入的键
meta_keys=[]), # 输入的元键
dict( # ToTensor 类的配置
type='ToTensor', # ToTensor 类将其他类型转化为 Tensor 类型
keys=['imgs', 'label']) # 将被从其他类型转化为 Tensor 类型的特征
]
val_pipeline = [ # 验证数据前处理流水线步骤组成的列表
dict( # SampleFrames 类的配置
type='SampleFrames', # 选定采样哪些视频帧
clip_len=1, # 每个输出视频片段的帧
frame_interval=1, # 所采相邻帧的时序间隔
num_clips=3, # 所采帧片段的数量
test_mode=True), # 是否设置为测试模式采帧
dict( # RawFrameDecode 类的配置
type='RawFrameDecode'), # 给定帧序列,加载对应帧,解码对应帧
dict( # Resize 类的配置
type='Resize', # 调整图片尺寸
scale=(-1, 256)), # 调整比例
dict( # CenterCrop 类的配置
type='CenterCrop', # 中心裁剪
crop_size=224), # 裁剪部分的尺寸
dict( # Flip 类的配置
type='Flip', # 图片翻转
flip_ratio=0), # 翻转几率
dict( # Normalize 类的配置
type='Normalize', # 图片正则化
**img_norm_cfg), # 图片正则化参数
dict( # FormatShape 类的配置
type='FormatShape', # 将图片格式转变为给定的输入格式
input_format='NCHW'), # 最终的图片组成格式
dict( # Collect 类的配置
type='Collect', # Collect 类决定哪些键会被传递到行为识别器中
keys=['imgs', 'label'], # 输入的键
meta_keys=[]), # 输入的元键
dict( # ToTensor 类的配置
type='ToTensor', # ToTensor 类将其他类型转化为 Tensor 类型
keys=['imgs']) # 将被从其他类型转化为 Tensor 类型的特征
]
test_pipeline = [ # 测试数据前处理流水线步骤组成的列表
dict( # SampleFrames 类的配置
type='SampleFrames', # 选定采样哪些视频帧
clip_len=1, # 每个输出视频片段的帧
frame_interval=1, # 所采相邻帧的时序间隔
num_clips=25, # 所采帧片段的数量
test_mode=True), # 是否设置为测试模式采帧
dict( # RawFrameDecode 类的配置
type='RawFrameDecode'), # 给定帧序列,加载对应帧,解码对应帧
dict( # Resize 类的配置
type='Resize', # 调整图片尺寸
scale=(-1, 256)), # 调整比例
dict( # TenCrop 类的配置
type='TenCrop', # 裁剪 10 个区域
crop_size=224), # 裁剪部分的尺寸
dict( # Flip 类的配置
type='Flip', # 图片翻转
flip_ratio=0), # 执行翻转几率
dict( # Normalize 类的配置
type='Normalize', # 图片正则化
**img_norm_cfg), # 图片正则化参数
dict( # FormatShape 类的配置
type='FormatShape', # 将图片格式转变为给定的输入格式
input_format='NCHW'), # 最终的图片组成格式
dict( # Collect 类的配置
type='Collect', # Collect 类决定哪些键会被传递到行为识别器中
keys=['imgs', 'label'], # 输入的键
meta_keys=[]), # 输入的元键
dict( # ToTensor 类的配置
type='ToTensor', # ToTensor 类将其他类型转化为 Tensor 类型
keys=['imgs']) # 将被从其他类型转化为 Tensor 类型的特征
]
data = dict( # 数据的配置
videos_per_gpu=32, # 单个 GPU 的批大小
workers_per_gpu=2, # 单个 GPU 的 dataloader 的进程
train_dataloader=dict( # 训练过程 dataloader 的额外设置
drop_last=True), # 在训练过程中是否丢弃最后一个批次
val_dataloader=dict( # 验证过程 dataloader 的额外设置
videos_per_gpu=1), # 单个 GPU 的批大小
test_dataloader=dict( # 测试过程 dataloader 的额外设置
videos_per_gpu=2), # 单个 GPU 的批大小
train=dict( # 训练数据集的设置
type=dataset_type,
ann_file=ann_file_train,
data_prefix=data_root,
pipeline=train_pipeline),
val=dict( # 验证数据集的设置
type=dataset_type,
ann_file=ann_file_val,
data_prefix=data_root_val,
pipeline=val_pipeline),
test=dict( # 测试数据集的设置
type=dataset_type,
ann_file=ann_file_test,
data_prefix=data_root_val,
pipeline=test_pipeline))
# 优化器设置
optimizer = dict(
# 构建优化器的设置,支持:
# (1) 所有 PyTorch 原生的优化器,这些优化器的参数和 PyTorch 对应的一致;
# (2) 自定义的优化器,这些优化器在 `constructor` 的基础上构建。
# 更多细节可参考 "tutorials/5_new_modules.md" 部分
type='SGD', # 优化器类型, 参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13
lr=0.01, # 学习率, 参数的细节使用可参考 PyTorch 的对应文档
momentum=0.9, # 动量大小
weight_decay=0.0001) # SGD 优化器权重衰减
optimizer_config = dict( # 用于构建优化器钩子的设置
grad_clip=dict(max_norm=40, norm_type=2)) # 使用梯度裁剪
# 学习策略设置
lr_config = dict( # 用于注册学习率调整钩子的设置
policy='step', # 调整器策略, 支持 CosineAnnealing,Cyclic等方法。更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
step=[40, 80]) # 学习率衰减步长
total_epochs = 100 # 训练模型的总周期数
checkpoint_config = dict( # 模型权重钩子设置,更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py
interval=5) # 模型权重文件保存间隔
evaluation = dict( # 训练期间做验证的设置
interval=5, # 执行验证的间隔
metrics=['top_k_accuracy', 'mean_class_accuracy'], # 验证方法
save_best='top_k_accuracy') # 设置 `top_k_accuracy` 作为指示器,用于存储最好的模型权重文件
log_config = dict( # 注册日志钩子的设置
interval=20, # 打印日志间隔
hooks=[ # 训练期间执行的钩子
dict(type='TextLoggerHook'), # 记录训练过程信息的日志
# dict(type='TensorboardLoggerHook'), # 同时支持 Tensorboard 日志
])
# 运行设置
dist_params = dict(backend='nccl') # 建立分布式训练的设置,其中端口号也可以设置
log_level = 'INFO' # 日志等级
work_dir = './work_dirs/tsn_r50_1x1x3_100e_kinetics400_rgb/' # 记录当前实验日志和模型权重文件的文件夹
load_from = None # 从给定路径加载模型作为预训练模型. 这个选项不会用于断点恢复训练
resume_from = None # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行
workflow = [('train', 1)] # runner 的执行流. [('train', 1)] 代表只有一个执行流,并且这个名为 train 的执行流只执行一次
为了帮助用户理解 MMAction2 的完整配置文件结构,以及时空检测系统中的一些模块,这里以 FastRCNN 为例,给出其配置文件的注释。 对于每个模块的详细用法以及对应参数的选择,请参照 API 文档
# 模型设置
model = dict( # 模型的配置
type='FastRCNN', # 时空检测器类型
backbone=dict( # Backbone 字典设置
type='ResNet3dSlowOnly', # Backbone 名
depth=50, # ResNet 模型深度
pretrained=None, # 预训练模型的 url 或文件位置
pretrained2d=False, # 预训练模型是否为 2D 模型
lateral=False, # backbone 是否有侧连接
num_stages=4, # ResNet 模型阶数
conv1_kernel=(1, 7, 7), # Conv1 卷积核尺寸
conv1_stride_t=1, # Conv1 时序步长
pool1_stride_t=1, # Pool1 时序步长
spatial_strides=(1, 2, 2, 1)), # 每个 ResNet 阶的空间步长
roi_head=dict( # roi_head 字典设置
type='AVARoIHead', # roi_head 名
bbox_roi_extractor=dict( # bbox_roi_extractor 字典设置
type='SingleRoIExtractor3D', # bbox_roi_extractor 名
roi_layer_type='RoIAlign', # RoI op 类型
output_size=8, # RoI op 输出特征尺寸
with_temporal_pool=True), # 时序维度是否要经过池化
bbox_head=dict( # bbox_head 字典设置
type='BBoxHeadAVA', # bbox_head 名
in_channels=2048, # 输入特征通道数
num_classes=81, # 动作类别数 + 1(背景)
multilabel=True, # 数据集是否多标签
dropout_ratio=0.5)), # dropout 比率
# 模型训练和测试的设置
train_cfg=dict( # 训练 FastRCNN 的超参配置
rcnn=dict( # rcnn 训练字典设置
assigner=dict( # assigner 字典设置
type='MaxIoUAssignerAVA', # assigner 名
pos_iou_thr=0.9, # 正样本 IoU 阈值, > pos_iou_thr -> positive
neg_iou_thr=0.9, # 负样本 IoU 阈值, < neg_iou_thr -> negative
min_pos_iou=0.9), # 正样本最小可接受 IoU
sampler=dict( # sample 字典设置
type='RandomSampler', # sampler 名
num=32, # sampler 批大小
pos_fraction=1, # sampler 正样本边界框比率
neg_pos_ub=-1, # 负样本数转正样本数的比率上界
add_gt_as_proposals=True), # 是否添加 ground truth 为候选
pos_weight=1.0, # 正样本 loss 权重
debug=False)), # 是否为 debug 模式
test_cfg=dict( # 测试 FastRCNN 的超参设置
rcnn=dict( # rcnn 测试字典设置
action_thr=0.002))) # 某行为的阈值
# 数据集设置
dataset_type = 'AVADataset' # 训练,验证,测试的数据集类型
data_root = 'data/ava/rawframes' # 训练集的根目录
anno_root = 'data/ava/annotations' # 标注文件目录
ann_file_train = f'{anno_root}/ava_train_v2.1.csv' # 训练集的标注文件
ann_file_val = f'{anno_root}/ava_val_v2.1.csv' # 验证集的标注文件
exclude_file_train = f'{anno_root}/ava_train_excluded_timestamps_v2.1.csv' # 训练除外数据集文件路径
exclude_file_val = f'{anno_root}/ava_val_excluded_timestamps_v2.1.csv' # 验证除外数据集文件路径
label_file = f'{anno_root}/ava_action_list_v2.1_for_activitynet_2018.pbtxt' # 标签文件路径
proposal_file_train = f'{anno_root}/ava_dense_proposals_train.FAIR.recall_93.9.pkl' # 训练样本检测候选框的文件路径
proposal_file_val = f'{anno_root}/ava_dense_proposals_val.FAIR.recall_93.9.pkl' # 验证样本检测候选框的文件路径
img_norm_cfg = dict( # 图像正则化参数设置
mean=[123.675, 116.28, 103.53], # 图像正则化平均值
std=[58.395, 57.12, 57.375], # 图像正则化方差
to_bgr=False) # 是否将通道数从 RGB 转为 BGR
train_pipeline = [ # 训练数据前处理流水线步骤组成的列表
dict( # SampleFrames 类的配置
type='AVASampleFrames', # 选定采样哪些视频帧
clip_len=4, # 每个输出视频片段的帧
frame_interval=16), # 所采相邻帧的时序间隔
dict( # RawFrameDecode 类的配置
type='RawFrameDecode'), # 给定帧序列,加载对应帧,解码对应帧
dict( # RandomRescale 类的配置
type='RandomRescale', # 给定一个范围,进行随机短边缩放
scale_range=(256, 320)), # RandomRescale 的短边缩放范围
dict( # RandomCrop 类的配置
type='RandomCrop', # 给定一个尺寸进行随机裁剪
size=256), # 裁剪尺寸
dict( # Flip 类的配置
type='Flip', # 图片翻转
flip_ratio=0.5), # 执行翻转几率
dict( # Normalize 类的配置
type='Normalize', # 图片正则化
**img_norm_cfg), # 图片正则化参数
dict( # FormatShape 类的配置
type='FormatShape', # 将图片格式转变为给定的输入格式
input_format='NCTHW', # 最终的图片组成格式
collapse=True), # 去掉 N 梯度当 N == 1
dict( # Rename 类的配置
type='Rename', # 重命名 key 名
mapping=dict(imgs='img')), # 改名映射字典
dict( # ToTensor 类的配置
type='ToTensor', # ToTensor 类将其他类型转化为 Tensor 类型
keys=['img', 'proposals', 'gt_bboxes', 'gt_labels']), # 将被从其他类型转化为 Tensor 类型的特征
dict( # ToDataContainer 类的配置
type='ToDataContainer', # 将一些信息转入到 ToDataContainer 中
fields=[ # 转化为 Datacontainer 的域
dict( # 域字典
key=['proposals', 'gt_bboxes', 'gt_labels'], # 将转化为 DataContainer 的键
stack=False)]), # 是否要堆列这些 tensor
dict( # Collect 类的配置
type='Collect', # Collect 类决定哪些键会被传递到时空检测器中
keys=['img', 'proposals', 'gt_bboxes', 'gt_labels'], # 输入的键
meta_keys=['scores', 'entity_ids']), # 输入的元键
]
val_pipeline = [ # 验证数据前处理流水线步骤组成的列表
dict( # SampleFrames 类的配置
type='AVASampleFrames', # 选定采样哪些视频帧
clip_len=4, # 每个输出视频片段的帧
frame_interval=16), # 所采相邻帧的时序间隔
dict( # RawFrameDecode 类的配置
type='RawFrameDecode'), # 给定帧序列,加载对应帧,解码对应帧
dict( # Resize 类的配置
type='Resize', # 调整图片尺寸
scale=(-1, 256)), # 调整比例
dict( # Normalize 类的配置
type='Normalize', # 图片正则化
**img_norm_cfg), # 图片正则化参数
dict( # FormatShape 类的配置
type='FormatShape', # 将图片格式转变为给定的输入格式
input_format='NCTHW', # 最终的图片组成格式
collapse=True), # 去掉 N 梯度当 N == 1
dict( # Rename 类的配置
type='Rename', # 重命名 key 名
mapping=dict(imgs='img')), # 改名映射字典
dict( # ToTensor 类的配置
type='ToTensor', # ToTensor 类将其他类型转化为 Tensor 类型
keys=['img', 'proposals']), # 将被从其他类型转化为 Tensor 类型的特征
dict( # ToDataContainer 类的配置
type='ToDataContainer', # 将一些信息转入到 ToDataContainer 中
fields=[ # 转化为 Datacontainer 的域
dict( # 域字典
key=['proposals'], # 将转化为 DataContainer 的键
stack=False)]), # 是否要堆列这些 tensor
dict( # Collect 类的配置
type='Collect', # Collect 类决定哪些键会被传递到时空检测器中
keys=['img', 'proposals'], # 输入的键
meta_keys=['scores', 'entity_ids'], # 输入的元键
nested=True) # 是否将数据包装为嵌套列表
]
data = dict( # 数据的配置
videos_per_gpu=16, # 单个 GPU 的批大小
workers_per_gpu=2, # 单个 GPU 的 dataloader 的进程
val_dataloader=dict( # 验证过程 dataloader 的额外设置
videos_per_gpu=1), # 单个 GPU 的批大小
train=dict( # 训练数据集的设置
type=dataset_type,
ann_file=ann_file_train,
exclude_file=exclude_file_train,
pipeline=train_pipeline,
label_file=label_file,
proposal_file=proposal_file_train,
person_det_score_thr=0.9,
data_prefix=data_root),
val=dict( # 验证数据集的设置
type=dataset_type,
ann_file=ann_file_val,
exclude_file=exclude_file_val,
pipeline=val_pipeline,
label_file=label_file,
proposal_file=proposal_file_val,
person_det_score_thr=0.9,
data_prefix=data_root))
data['test'] = data['val'] # 将验证数据集设置复制到测试数据集设置
# 优化器设置
optimizer = dict(
# 构建优化器的设置,支持:
# (1) 所有 PyTorch 原生的优化器,这些优化器的参数和 PyTorch 对应的一致;
# (2) 自定义的优化器,这些优化器在 `constructor` 的基础上构建。
# 更多细节可参考 "tutorials/5_new_modules.md" 部分
type='SGD', # 优化器类型, 参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13
lr=0.2, # 学习率, 参数的细节使用可参考 PyTorch 的对应文档
momentum=0.9, # 动量大小
weight_decay=0.00001) # SGD 优化器权重衰减
optimizer_config = dict( # 用于构建优化器钩子的设置
grad_clip=dict(max_norm=40, norm_type=2)) # 使用梯度裁剪
lr_config = dict( # 用于注册学习率调整钩子的设置
policy='step', # 调整器策略, 支持 CosineAnnealing,Cyclic等方法。更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
step=[40, 80], # 学习率衰减步长
warmup='linear', # Warmup 策略
warmup_by_epoch=True, # Warmup 单位为 epoch 还是 iteration
warmup_iters=5, # warmup 数
warmup_ratio=0.1) # 初始学习率为 warmup_ratio * lr
total_epochs = 20 # 训练模型的总周期数
checkpoint_config = dict( # 模型权重文件钩子设置,更多细节可参考 https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py
interval=1) # 模型权重文件保存间隔
workflow = [('train', 1)] # runner 的执行流. [('train', 1)] 代表只有一个执行流,并且这个名为 train 的执行流只执行一次
evaluation = dict( # 训练期间做验证的设置
interval=1, save_best='[email protected]') # 执行验证的间隔,以及设置 `[email protected]` 作为指示器,用于存储最好的模型权重文件
log_config = dict( # 注册日志钩子的设置
interval=20, # 打印日志间隔
hooks=[ # 训练期间执行的钩子
dict(type='TextLoggerHook'), # 记录训练过程信息的日志
])
# 运行设置
dist_params = dict(backend='nccl') # 建立分布式训练的设置,其中端口号也可以设置
log_level = 'INFO' # 日志等级
work_dir = ('./work_dirs/ava/' # 记录当前实验日志和模型权重文件的文件夹
'slowonly_kinetics_pretrained_r50_4x16x1_20e_ava_rgb')
load_from = ('https://download.openmmlab.com/mmaction/recognition/slowonly/' # 从给定路径加载模型作为预训练模型. 这个选项不会用于断点恢复训练
'slowonly_r50_4x16x1_256e_kinetics400_rgb/'
'slowonly_r50_4x16x1_256e_kinetics400_rgb_20200704-a69556c6.pth')
resume_from = None # 加载给定路径的模型权重文件作为断点续连的模型, 训练将从该时间点保存的周期点继续进行
model = dict(
type='Recognizer3D',
backbone=dict(
type='ResNet3dSlowOnly',
depth=50,
pretrained=None,
in_channels=17,
base_channels=32,
num_stages=3,
out_indices=(2, ),
stage_blocks=(4, 6, 3),
conv1_stride_s=1,
pool1_stride_s=1,
inflate=(0, 1, 1),
spatial_strides=(2, 2, 2),
temporal_strides=(1, 1, 2),
dilations=(1, 1, 1)),
cls_head=dict(
type='I3DHead',
in_channels=512,
num_classes=60,
spatial_type='avg',
dropout_ratio=0.5),
train_cfg=dict(),
test_cfg=dict(average_clips='prob'))
dataset_type = 'PoseDataset'
ann_file_train = 'data/posec3d/ntu60_xsub_train.pkl'
ann_file_val = 'data/posec3d/ntu60_xsub_val.pkl'
left_kp = [1, 3, 5, 7, 9, 11, 13, 15]
right_kp = [2, 4, 6, 8, 10, 12, 14, 16]
train_pipeline = [
dict(type='UniformSampleFrames', clip_len=48),
dict(type='PoseDecode'),
dict(type='PoseCompact', hw_ratio=1., allow_imgpad=True),
dict(type='Resize', scale=(-1, 64)),
dict(type='RandomResizedCrop', area_range=(0.56, 1.0)),
dict(type='Resize', scale=(56, 56), keep_ratio=False),
dict(type='Flip', flip_ratio=0.5, left_kp=left_kp, right_kp=right_kp),
dict(
type='GeneratePoseTarget',
sigma=0.6,
use_score=True,
with_kp=True,
with_limb=False),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs', 'label'])
]
val_pipeline = [
dict(type='UniformSampleFrames', clip_len=48, num_clips=1, test_mode=True),
dict(type='PoseDecode'),
dict(type='PoseCompact', hw_ratio=1., allow_imgpad=True),
dict(type='Resize', scale=(-1, 64)),
dict(type='CenterCrop', crop_size=64),
dict(
type='GeneratePoseTarget',
sigma=0.6,
use_score=True,
with_kp=True,
with_limb=False),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
test_pipeline = [
dict(
type='UniformSampleFrames', clip_len=48, num_clips=10, test_mode=True),
dict(type='PoseDecode'),
dict(type='PoseCompact', hw_ratio=1., allow_imgpad=True),
dict(type='Resize', scale=(-1, 64)),
dict(type='CenterCrop', crop_size=64),
dict(
type='GeneratePoseTarget',
sigma=0.6,
use_score=True,
with_kp=True,
with_limb=False,
double=True,
left_kp=left_kp,
right_kp=right_kp),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
data = dict(
videos_per_gpu=16,
workers_per_gpu=2,
test_dataloader=dict(videos_per_gpu=1),
train=dict(
type=dataset_type,
ann_file=ann_file_train,
data_prefix='',
pipeline=train_pipeline),
val=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix='',
pipeline=val_pipeline),
test=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix='',
pipeline=test_pipeline))
# optimizer
optimizer = dict(
type='SGD', lr=0.2, momentum=0.9,
weight_decay=0.0003) # this lr is used for 8 gpus
optimizer_config = dict(grad_clip=dict(max_norm=40, norm_type=2))
# learning policy
lr_config = dict(policy='CosineAnnealing', by_epoch=False, min_lr=0)
total_epochs = 240
checkpoint_config = dict(interval=10)
workflow = [('train', 10)]
evaluation = dict(
interval=10,
metrics=['top_k_accuracy', 'mean_class_accuracy'],
topk=(1, 5))
log_config = dict(
interval=20, hooks=[
dict(type='TextLoggerHook'),
])
dist_params = dict(backend='nccl')
log_level = 'INFO'
work_dir = './work_dirs/posec3d/slowonly_r50_u48_240e_ntu60_xsub_keypoint'
load_from = None
resume_from = None
find_unused_parameters = False
model = dict(
type='SkeletonGCN',
backbone=dict(
type='STGCN',
in_channels=3,
edge_importance_weighting=True,
graph_cfg=dict(layout='coco', strategy='spatial')),
cls_head=dict(
type='STGCNHead',
num_classes=60,
in_channels=256,
loss_cls=dict(type='CrossEntropyLoss')),
train_cfg=None,
test_cfg=None)
dataset_type = 'PoseDataset'
ann_file_train = 'data/posec3d/ntu60_xsub_train.pkl'
ann_file_val = 'data/posec3d/ntu60_xsub_val.pkl'
train_pipeline = [
dict(type='PaddingWithLoop', clip_len=300),
dict(type='PoseDecode'),
dict(type='FormatGCNInput', input_format='NCTVM'),
dict(type='PoseNormalize'),
dict(type='Collect', keys=['keypoint', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['keypoint'])
]
val_pipeline = [
dict(type='PaddingWithLoop', clip_len=300),
dict(type='PoseDecode'),
dict(type='FormatGCNInput', input_format='NCTVM'),
dict(type='PoseNormalize'),
dict(type='Collect', keys=['keypoint', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['keypoint'])
]
test_pipeline = [
dict(type='PaddingWithLoop', clip_len=300),
dict(type='PoseDecode'),
dict(type='FormatGCNInput', input_format='NCTVM'),
dict(type='PoseNormalize'),
dict(type='Collect', keys=['keypoint', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['keypoint'])
]
data = dict(
videos_per_gpu=16,
workers_per_gpu=2,
test_dataloader=dict(videos_per_gpu=1),
train=dict(
type=dataset_type,
ann_file=ann_file_train,
data_prefix='',
pipeline=train_pipeline),
val=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix='',
pipeline=val_pipeline),
test=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix='',
pipeline=test_pipeline))
# optimizer
optimizer = dict(
type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001, nesterov=True)
optimizer_config = dict(grad_clip=None)
# learning policy
lr_config = dict(policy='step', step=[10, 50])
total_epochs = 80
checkpoint_config = dict(interval=5)
evaluation = dict(interval=5, metrics=['top_k_accuracy'])
log_config = dict(interval=100, hooks=[dict(type='TextLoggerHook')])
# runtime settings
dist_params = dict(backend='nccl')
log_level = 'INFO'
work_dir = './work_dirs/stgcn_80e_ntu60_xsub_keypoint/'
load_from = None
resume_from = None
workflow = [('train', 1)]
(这一个部分没有太看懂)
配置文件中会用到一些中间变量,如 train_pipeline
/val_pipeline
/test_pipeline
, ann_file_train/ann_file_val/ann_file_test, img_norm_cfg
等。
例如,首先定义中间变量 train_pipeline
/val_pipeline
/test_pipeline
,再将上述变量传递到 data
。因此, train_pipeline
/val_pipeline
/test_pipeline
为中间变量
这里也定义了 ann_file_train
/ann_file_val
/ann_file_test
和 data_root
/data_root_val
为数据处理流程提供一些基本信息。
此外,使用 img_norm_cfg
作为中间变量,构建一些数组增强组件。
...
dataset_type = 'RawframeDataset'
data_root = 'data/kinetics400/rawframes_train'
data_root_val = 'data/kinetics400/rawframes_val'
ann_file_train = 'data/kinetics400/kinetics400_train_list_rawframes.txt'
ann_file_val = 'data/kinetics400/kinetics400_val_list_rawframes.txt'
ann_file_test = 'data/kinetics400/kinetics400_val_list_rawframes.txt'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_bgr=False)
train_pipeline = [
dict(type='SampleFrames', clip_len=32, frame_interval=2, num_clips=1),
dict(type='RawFrameDecode'),
dict(type='Resize', scale=(-1, 256)),
dict(
type='MultiScaleCrop',
input_size=224,
scales=(1, 0.8),
random_crop=False,
max_wh_scale_gap=0),
dict(type='Resize', scale=(224, 224), keep_ratio=False),
dict(type='Flip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs', 'label'])
]
val_pipeline = [
dict(
type='SampleFrames',
clip_len=32,
frame_interval=2,
num_clips=1,
test_mode=True),
dict(type='RawFrameDecode'),
dict(type='Resize', scale=(-1, 256)),
dict(type='CenterCrop', crop_size=224),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
test_pipeline = [
dict(
type='SampleFrames',
clip_len=32,
frame_interval=2,
num_clips=10,
test_mode=True),
dict(type='RawFrameDecode'),
dict(type='Resize', scale=(-1, 256)),
dict(type='ThreeCrop', crop_size=256),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
data = dict(
videos_per_gpu=8,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file=ann_file_train,
data_prefix=data_root,
pipeline=train_pipeline),
val=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix=data_root_val,
pipeline=val_pipeline),
test=dict(
type=dataset_type,
ann_file=ann_file_val,
data_prefix=data_root_val,
pipeline=test_pipeline))
本教程介绍如何使用预训练模型在其他数据集上进行微调。
对新数据集上的模型进行微调需要进行两个步骤:
增加对新数据集的支持。在下一节中会具体展开讲。
修改配置文件。这部分将在本教程中做具体讨论。
例如,如果用户想要微调 Kinetics-400 数据集的预训练模型到另一个数据集上,如 UCF101,则需要注意 配置文件 中 Head、数据集、训练策略、预训练模型四个部分,下面分别介绍。
cls_head
中的 num_classes
参数需改为新数据集中的类别数。 预训练模型中,除了最后一层外的权重都会被重新利用,因此这个改动是安全的。 例如,UCF101 拥有 101 类行为,因此需要把 400 (Kinetics-400 的类别数) 改为 101。model = dict(
type='Recognizer2D',
backbone=dict(
type='ResNet',
pretrained='torchvision://resnet50',
depth=50,
norm_eval=False),
cls_head=dict(
type='TSNHead',
num_classes=101, # 从 400 改为 101
in_channels=2048,
spatial_type='avg',
consensus=dict(type='AvgConsensus', dim=1),
dropout_ratio=0.4,
init_std=0.01),
train_cfg=None,
test_cfg=dict(average_clips=None))
其中, pretrained='torchvision://resnet50'
表示通过 ImageNet 预训练权重初始化 backbone。 然而,模型微调时的预训练权重一般通过 load_from
(而不是 pretrained
)指定。
RawframeDataset
和 VideoDataset
等通用的数据集读取类,数据集格式相对简单。 以 UCF101
和 RawframeDataset
为例:# 数据集设置
dataset_type = 'RawframeDataset'
data_root = 'data/ucf101/rawframes_train/'
data_root_val = 'data/ucf101/rawframes_val/'
ann_file_train = 'data/ucf101/ucf101_train_list.txt'
ann_file_val = 'data/ucf101/ucf101_val_list.txt'
ann_file_test = 'data/ucf101/ucf101_val_list.txt'
# 优化器
optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0001) # 从 0.01 改为 0.005
optimizer_config = dict(grad_clip=dict(max_norm=40, norm_type=2))
# 学习策略
lr_config = dict(policy='step', step=[20, 40]) # step 与 total_epoch 相适应
total_epochs = 50 # 从 100 改为 50
checkpoint_config = dict(interval=5)
pretrained
,仅会在主干网络模型上加载预训练参数),可通过 load_from
指定模型文件路径或模型链接,实现预训练权重导入。 MMAction2 在 configs/_base_/default_runtime.py
文件中将 load_from=None
设为默认。由于配置文件的可继承性,用户可直接在下游配置文件中设置 load_from
的值来进行更改。# 将预训练模型用于整个 TSN 网络
load_from = 'https://open-mmlab.s3.ap-northeast-2.amazonaws.com/mmaction/mmaction-v1/recognition/tsn_r50_1x1x3_100e_kinetics400_rgb/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth' # 模型路径可以在 model zoo 中找到
在本教程中,我们将介绍一些有关如何按已支持的数据格式进行数据组织,和组合已有数据集来自定义数据集的方法。
rawframe directory of relative path
), 总帧数(total frames
)以及 标签(label
),通过空格进行划分:some/directory-1 163 1
some/directory-2 122 1
some/directory-3 258 2
some/directory-4 234 2
some/directory-5 295 3
some/directory-6 121 3
some/path/000.mp4 1
some/path/001.mp4 1
some/path/002.mp4 2
some/path/003.mp4 2
some/path/004.mp4 3
some/path/005.mp4 3
{
"video1": {
"duration_second": 211.53,
"duration_frame": 6337,
"annotations": [
{
"segment": [
30.025882995319815,
205.2318595943838
],
"label": "Rock climbing"
}
],
"feature_frame": 6336,
"fps": 30.0,
"rfps": 29.9579255898
},
"video2": {
"duration_second": 26.75,
"duration_frame": 647,
"annotations": [
{
"segment": [
2.578755070202808,
24.914101404056165
],
"label": "Drinking beer"
}
],
"feature_frame": 624,
"fps": 24.0,
"rfps": 24.1869158879
}
}
有两种使用自定义数据集的方法:
在线转换
用户可以通过继承 BaseDataset 基类编写一个新的数据集类,并重写三个抽象类方法: load_annotations(self)
,evaluate(self, results, metrics, logger)
和 dump_results(self, results, out)
, 如 RawframeDataset,VideoDataset 或 ActivityNetDataset。
本地转换
用户可以转换标注文件格式为上述期望的格式,并将其存储为 pickle 或 json 文件,然后便可以应用于 RawframeDataset
,VideoDataset
或 ActivityNetDataset
中。
数据预处理后,用户需要进一步修改配置文件以使用数据集。 这里展示了以帧形式使用自定义数据集的例子:
在 configs/task/method/my_custom_config.py
下:
...
# 数据集设定
dataset_type = 'RawframeDataset'
data_root = 'path/to/your/root'
data_root_val = 'path/to/your/root_val'
ann_file_train = 'data/custom/custom_train_list.txt'
ann_file_val = 'data/custom/custom_val_list.txt'
ann_file_test = 'data/custom/custom_val_list.txt'
...
data = dict(
videos_per_gpu=32,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file=ann_file_train,
...),
val=dict(
type=dataset_type,
ann_file=ann_file_val,
...),
test=dict(
type=dataset_type,
ann_file=ann_file_test,
...))
...
annotation.txt
中。#文件夹,总帧数,类别
D32_1gwq35E,299,66
-G-5CJ0JkKY,249,254
T4h1bvOd9DA,299,33
4uZ27ivBl00,299,341
0LfESFkfBSw,249,186
-YIsNpBEx6c,299,169
在 mmaction/datasets/my_dataset.py 中创建新数据集加载数据
import copy
import os.path as osp
import mmcv
from .base import BaseDataset
from .builder import DATASETS
@DATASETS.register_module()
class MyDataset(BaseDataset):
def __init__(self,
ann_file,
pipeline,
data_prefix=None,
test_mode=False,
filename_tmpl='img_{:05}.jpg'):
super(MyDataset, self).__init__(ann_file, pipeline, test_mode)
self.filename_tmpl = filename_tmpl
def load_annotations(self):
video_infos = []
with open(self.ann_file, 'r') as fin:
for line in fin:
if line.startswith("directory"):
continue
frame_dir, total_frames, label = line.split(',')
if self.data_prefix is not None:
frame_dir = osp.join(self.data_prefix, frame_dir)
video_infos.append(
dict(
frame_dir=frame_dir,
total_frames=int(total_frames),
label=int(label)))
return video_infos
def prepare_train_frames(self, idx):
results = copy.deepcopy(self.video_infos[idx])
results['filename_tmpl'] = self.filename_tmpl
return self.pipeline(results)
def prepare_test_frames(self, idx):
results = copy.deepcopy(self.video_infos[idx])
results['filename_tmpl'] = self.filename_tmpl
return self.pipeline(results)
def evaluate(self,
results,
metrics='top_k_accuracy',
topk=(1, 5),
logger=None):
pass
然后在配置文件中,用户可通过如下修改来使用 MyDataset
:
dataset_A_train = dict(
type='MyDataset',
ann_file=ann_file_train,
pipeline=train_pipeline
)
MMAction2 还支持组合已有数据集以进行训练。 目前,它支持重复数据集(repeat dataset)。
dataset_A_train = dict(
type='RepeatDataset',
times=N,
dataset=dict( # 这是 Dataset_A 的原始配置
type='Dataset_A',
...
pipeline=train_pipeline
)
)
《先放参考链接》
在本教程中,我们将介绍一些有关数据前处理流水线设计的方法,以及如何为项目自定义和扩展自己的数据流水线。
按照惯例,MMAction2 使用 Dataset
和 DataLoader
实现多进程数据加载。 Dataset
返回一个字典,作为模型的输入。 由于动作识别和时序动作检测的数据大小不一定相同(图片大小,边界框大小等),MMAction2 使用 MMCV 中的 DataContainer
收集和分配不同大小的数据, 详情可见 这里。
“数据前处理流水线” 和 “数据集构建” 是相互解耦的。通常,“数据集构建” 定义如何处理标注文件,“数据前处理流水线” 定义数据加载、预处理、格式化等功能(后文将详细介绍)。 数据前处理流水线由一系列相互解耦的操作组成。每个操作都输入一个字典(dict),新增/更新/删除相关字段,最终输出该字典,作为下一个操作的输入。
下图中展示了一个典型的流水线。 蓝色块是流水线操作。 随着流水线的深入,每个操作都可以向结果字典添加新键(标记为绿色)或更新现有键(标记为橙色)。
这些操作分为数据加载,数据预处理和数据格式化。
这里以 TSN 的数据前处理流水线为例:
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_bgr=False)
train_pipeline = [
dict(type='SampleFrames', clip_len=1, frame_interval=1, num_clips=3),
dict(type='RawFrameDecode', io_backend='disk'),
dict(type='Resize', scale=(-1, 256)),
dict(
type='MultiScaleCrop',
input_size=224,
scales=(1, 0.875, 0.75, 0.66),
random_crop=False,
max_wh_scale_gap=1),
dict(type='Resize', scale=(224, 224), keep_ratio=False),
dict(type='Flip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs', 'label'])
]
val_pipeline = [
dict(
type='SampleFrames',
clip_len=1,
frame_interval=1,
num_clips=3,
test_mode=True),
dict(type='RawFrameDecode', io_backend='disk'),
dict(type='Resize', scale=(-1, 256)),
dict(type='CenterCrop', crop_size=224),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
test_pipeline = [
dict(
type='SampleFrames',
clip_len=1,
frame_interval=1,
num_clips=25,
test_mode=True),
dict(type='RawFrameDecode', io_backend='disk'),
dict(type='Resize', scale=(-1, 256)),
dict(type='TenCrop', crop_size=224),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs'])
]
MMAction2 也支持一些 lazy 操作符。 Lazy 操作记录如何处理数据,但是它会推迟对原始数据的处理,直到进入 Fuse 阶段。 具体而言,lazy 操作符避免了对原始数据的频繁读取和修改操作,只在最后的 Fuse 阶段中对原始数据进行了一次处理,从而加快了数据预处理速度,因此,推荐用户使用本功能。
这是使用 lazy 运算符的数据前处理流水线的例子:
train_pipeline = [
dict(type='SampleFrames', clip_len=32, frame_interval=2, num_clips=1),
dict(type='RawFrameDecode', decoding_backend='turbojpeg'),
# 以下三个 lazy 操作符仅处理帧的 bbox 而不修改原始数据。
dict(type='Resize', scale=(-1, 256), lazy=True),
dict(
type='MultiScaleCrop',
input_size=224,
scales=(1, 0.8),
random_crop=False,
max_wh_scale_gap=0,
lazy=True),
dict(type='Resize', scale=(224, 224), keep_ratio=False, lazy=True),
# lazy 操作符 “Flip” 仅记录是否应该翻转框架和翻转方向。
dict(type='Flip', flip_ratio=0.5, lazy=True),
# 在 Fuse 阶段处理一次原始数据
dict(type='Fuse'),
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs', 'label'])
]
本节将所有操作分为数据加载、数据预处理、数据格式化三类,列出每个操作 新增/更新/删除 的相关字典字段,其中 * 代表所对应的键值不一定会被影响。
SampleFrames
新增: frame_inds, clip_len, frame_interval, num_clips, *total_frames
DenseSampleFrames
新增: frame_inds, clip_len, frame_interval, num_clips, *total_frames
PyAVDecode
新增: imgs, original_shape
更新: *frame_inds
DecordDecode
新增: imgs, original_shape
更新: *frame_inds
OpenCVDecode
新增: imgs, original_shape
更新: *frame_inds
RawFrameDecode
新增: imgs, original_shape
更新: *frame_inds
RandomCrop
新增: crop_bbox, img_shape
更新: imgs
RandomResizedCrop
新增: crop_bbox, img_shape
更新: imgs
MultiScaleCrop
新增: crop_bbox, img_shape, scales
更新: imgs
Resize
新增: img_shape, keep_ratio, scale_factor
更新: imgs
Flip
新增: flip, flip_direction
更新: imgs, label
Normalize
新增: img_norm_cfg
更新: imgs
CenterCrop
新增: crop_bbox, img_shape
更新: imgs
ThreeCrop
新增: crop_bbox, img_shape
更新: imgs
TenCrop
新增: crop_bbox, img_shape
更新: imgs
ToTensor
更新: specified by keys
.
ImageToTensor
更新: specified by keys
.
Transpose
更新: specified by keys
.
Collect
新增: img_metas (所有需要的图像元数据,会被在此阶段整合进 meta_keys
键值中)
删除: 所有没有被整合进 keys
的键值
值得注意的是,第一个键,通常是 imgs
,会作为主键用来计算批大小。
FormatShape
新增: input_shape
更新: imgs
my_pipeline.py
。它以一个字典作为输入并返回一个字典from mmaction.datasets import PIPELINES
@PIPELINES.register_module()
class MyTransform:
def __call__(self, results):
results['key'] = value
return results
from .my_pipeline import MyTransform
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
dict(type='DenseSampleFrames', clip_len=8, frame_interval=8, num_clips=1),
dict(type='RawFrameDecode', io_backend='disk'),
dict(type='MyTransform'), # 使用自定义流水线操作
dict(type='Normalize', **img_norm_cfg),
dict(type='FormatShape', input_format='NCTHW'),
dict(type='Collect', keys=['imgs', 'label'], meta_keys=[]),
dict(type='ToTensor', keys=['imgs', 'label'])
]
mmaction2 github 官网:https://github.com/open-mmlab/mmaction2
官方中文文档教程:https://mmaction2.readthedocs.io/zh_CN/latest/index.html
官方知乎介绍文章 :https://zhuanlan.zhihu.com/p/347705276
mmaction2: 使用自定义数据集训练 PoseC3D 的教程 :https://github.com/open-mmlab/mmaction2/blob/master/configs/skeleton/posec3d/custom_dataset_training.md