以下链接是个人关于mmaction2(SlowFast-动作识别) 所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:17575010159 相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。 文末附带 \color{blue}{文末附带} 文末附带 公众号 − \color{blue}{公众号 -} 公众号− 海量资源。 \color{blue}{ 海量资源}。 海量资源。
动作识别0-00:mmaction2(SlowFast)-目录-史上最新无死角讲解
极度推荐的商业级项目: \color{red}{极度推荐的商业级项目:} 极度推荐的商业级项目:这是本人落地的行为分析项目,主要包含(1.行人检测,2.行人追踪,3.行为识别三大模块):行为分析(商用级别)00-目录-史上最新无死角讲解
前言数据的加载有两种方式分别为RawframeDataset,VideoDataset。如本人训练的my_slowfast_r50_4x16x1_256e_ucf101_rgb.py中的如下代码:
dataset_type = 'RawframeDataset'
其就指定了加载数据的方式。 1.RawframeDataset:把视频先切割成每帧图片,然后加载训练。 2.VideoDataset:不需要进行视频切割,直接加载进行训练(本人喜欢这种方式)。
在tools/train.py文件中,我们可以看到如下代码:
# 创建数据迭代器
datasets = [build_dataset(cfg.data.train)]
其就会根据 cfg 中的 dataset_type 参数构建相应的数据迭代器,本人简单注释build_dataset函数如下:
def build_dataset(cfg, default_args=None):
"""Build a dataset from config dict.
Args:
cfg (dict): Config dict. It should at least contain the key "type".
default_args (dict, optional): Default initialization arguments.
Default: None.
Returns:
Dataset: The constructed dataset.
"""
# 如果type为重复采样(本人调试为RawframeDataset)
if cfg['type'] == 'RepeatDataset':
# 构建重复采样器
dataset = RepeatDataset(
build_dataset(cfg['dataset'], default_args), cfg['times'])
else:
# DATASETS = Registry('dataset'),可以理解为一个用于装载dataset相关类的容器
# 根据cdif的type=RawframeDataset,从DATASETS中获得RawframeDataset类,并创建对象
dataset = build_from_cfg(cfg, DATASETS, default_args)
return dataset
其最终会调用到 mmaction/datasets/rawframe_dataset.py中的 class RawframeDataset( B a s e D a t a s e t \color{red}{BaseDataset} BaseDataset): 或者 mmaction/datasets/video_dataset.py中的 class VideoDataset( B a s e D a t a s e t \color{red}{BaseDataset} BaseDataset):
从这里我们可以看到其两个类都会继承于BaseDataset,其实现于项目根目录下的mmaction\datasets\base.py中。注释代码如下:
BaseDatasetimport copy
import os.path as osp
from abc import ABCMeta, abstractmethod
import mmcv
from torch.utils.data import Dataset
from .pipelines import Compose
class BaseDataset(Dataset, metaclass=ABCMeta):
"""Base class for datasets.
All datasets to process video should subclass it.
All subclasses should overwrite:
- Methods:`load_annotations`, supporting to load information from an
annotation file.
- Methods:`prepare_train_frames`, providing train data.
- Methods:`prepare_test_frames`, providing test data.
Args:
ann_file (str): Path to the annotation file.
pipeline (list[dict | callable]): A sequence of data transforms.
data_prefix (str): Path to a directory where videos are held.
Default: None.
test_mode (bool): Store True when building test or validation dataset.
Default: False.
multi_class (bool): Determines whether the dataset is a multi-class
dataset. Default: False.
num_classes (int): Number of classes of the dataset, used in
multi-class datasets. Default: None.
modality (str): Modality of data. Support 'RGB', 'Flow'.
Default: 'RGB'.
"""
def __init__(self,
ann_file, # 注释文件的路径
pipeline, # 数据转换序列(后续重点讲解)
data_prefix=None, # 存放视频的目录
test_mode=False, # 在构建测试或验证数据集时需要设置为True
multi_class=False, # 是否进行多标签的训练或者测试
num_classes=None, # 数据集的类别数目
modality='RGB'): # 数据的格式,默认为RGB
super().__init__()
# 注释文件的路径
self.ann_file = ann_file
# 存放视频的目录
self.data_prefix = osp.realpath(data_prefix) if osp.isdir(
data_prefix) else data_prefix
# 在构建测试或验证数据集时需要设置为True
self.test_mode = test_mode
# 是否进行多标签的训练或者测试
self.multi_class = multi_class
# 数据集的类别数目
self.num_classes = num_classes
# 数据的格式,默认为RGB
self.modality = modality
# 数据转换序列
self.pipeline = Compose(pipeline)
# 加载视频信息,该self.load_annotations()函数具体需要子类实现
self.video_infos = self.load_annotations()
@abstractmethod
def load_annotations(self):
"""Load the annotation according to ann_file into video_infos."""
pass
@abstractmethod #模型评估,需要子类实现
def evaluate(self, results, metrics, logger):
"""Evaluation for the dataset.
Args:
results (list): Output results.
metrics (str | sequence[str]): Metrics to be performed.
logger (logging.Logger | None): Logger for recording.
Returns:
dict: Evaluation results dict.
"""
pass
# 导出结果,导出为json/yaml/pickle等
def dump_results(self, results, out):
"""Dump data to json/yaml/pickle strings or files."""
return mmcv.dump(results, out)
def prepare_train_frames(self, idx):
"""Prepare the frames for training given the index.
根据输入的idx号,进行数据转换。
"""
results = copy.deepcopy(self.video_infos[idx])
results['modality'] = self.modality
return self.pipeline(results)
def prepare_test_frames(self, idx):
"""Prepare the frames for testing given the index.
根据输入的idx号,进行数据转换。
"""
results = copy.deepcopy(self.video_infos[idx])
results['modality'] = self.modality
return self.pipeline(results)
def __len__(self):
"""Get the size of the dataset获得数据的长度信息"""
return len(self.video_infos)
def __getitem__(self, idx):
"""Get the sample for either training or testing given index.
根据训练或者测试模式,进行不同的数据转换
"""
if self.test_mode:
return self.prepare_test_frames(idx)
else:
return self.prepare_train_frames(idx)
BaseDataset仅仅一个基类,RawframeDataset,VideoDataset(下篇博客对他们进行分析)都继承于他。其上的 def prepare_train_frames(self, idx) 与 def prepare_train_frames(self, idx) 会被继承的类重写。但是他们都调用了一个至关重要的函数 self.pipeline(results)。
pipeline我们现在来看看 pipeline 到底是何方神圣。本人启动 debug 模式显示如下:
# 训练模式(train)
: [{'type': 'SampleFrames', 'clip_len': 16, 'frame_interval': 2, 'num_clips': 1}, {'type': 'FrameSelector'}, {'type': 'Resize', 'scale': (-1, 256)}, {'type': 'RandomResizedCrop'}, {'type': 'Resize', 'scale': (224, 224), 'keep_ratio': False}, {'type': 'Flip', 'flip_ratio': 0.5}, {'type': 'Normalize', 'mean': [123.675, 116.28, 103.53], 'std': [58.395, 57.12, 57.375], 'to_bgr': False}, {'type': 'FormatShape', 'input_format': 'NCTHW'}, {'type': 'Collect', 'keys': ['imgs', 'label'], 'meta_keys': []}, {'type': 'ToTensor', 'keys': ['imgs', 'label']}]
# clip_len表示采样的图片帧数, frame_interval表示每次采集间隔几帧, num_clips表示采集几个clip
00 = {ConfigDict} {'type': 'SampleFrames', 'clip_len': 16, 'frame_interval': 2, 'num_clips': 1}
# 视频帧选择器
01 = {ConfigDict} {'type': 'FrameSelector'}
# 对图片进行缩放
02 = {ConfigDict} {'type': 'Resize', 'scale': (-1, 256)}
# 进行随机剪切
03 = {ConfigDict} {'type': 'RandomResizedCrop'}
# 图片缩放到指定尺寸,keep_ratio控制图片长宽比例是否改变
04 = {ConfigDict} {'type': 'Resize', 'scale': (224, 224), 'keep_ratio': False}
# 以一定概率水平反转
05 = {ConfigDict} {'type': 'Flip', 'flip_ratio': 0.5}
# 正则化处理
06 = {ConfigDict} {'type': 'Normalize', 'mean': [123.675, 116.28, 103.53], 'std': [58.395, 57.12, 57.375], 'to_bgr': False}
# 把训练数据的变成'NCTHW'形状,方便进行训练
07 = {ConfigDict} {'type': 'FormatShape', 'input_format': 'NCTHW'}
# 把训练数据以及对应的标签整合起来
08 = {ConfigDict} {'type': 'Collect', 'keys': ['imgs', 'label'], 'meta_keys': []}
# 转化为pytorch的ToTensor格式
09 = {ConfigDict} {'type': 'ToTensor', 'keys': ['imgs', 'label']}
# 测试模式(test)
pipeline = {list} : [{'type': 'SampleFrames', 'clip_len': 32, 'frame_interval': 2, 'num_clips': 10, 'test_mode': True}, {'type': 'FrameSelector'}, {'type': 'Resize', 'scale': (-1, 256)}, {'type': 'ThreeCrop', 'crop_size': 256}, {'type': 'Flip', 'flip_ratio': 0
# clip_len表示采样的图片帧数, frame_interval表示每次采集间隔几帧, num_clips表示采集几个clip
00 = {ConfigDict} {'type': 'SampleFrames', 'clip_len': 32, 'frame_interval': 2, 'num_clips': 10, 'test_mode': True}
# 视频帧选择器
01 = {ConfigDict} {'type': 'FrameSelector'}
# 对图片进行缩放
02 = {ConfigDict} {'type': 'Resize', 'scale': (-1, 256)}
# 对采集的每帧图片剪裁3吃
03 = {ConfigDict} {'type': 'ThreeCrop', 'crop_size': 256}
# 设置为0不进行左右反转
04 = {ConfigDict} {'type': 'Flip', 'flip_ratio': 0}
# 进行正则化处理
05 = {ConfigDict} {'type': 'Normalize', 'mean': [123.675, 116.28, 103.53], 'std': [58.395, 57.12, 57.375], 'to_bgr': False}
# 把训练数据的变成'NCTHW'形状,方便进行训练
06 = {ConfigDict} {'type': 'FormatShape', 'input_format': 'NCTHW'}
# 把训练数据以及对应的标签整合起来
07 = {ConfigDict} {'type': 'Collect', 'keys': ['imgs', 'label'], 'meta_keys': []}
# 转化为pytorch的ToTensor格式
08 = {ConfigDict} {'type': 'ToTensor', 'keys': ['imgs']}
从上面可以看到,几乎数据的预处理过程都包含在pipeline之中,所以是非常重要的一部分,这些过程都是在my_slowfast_r50_4x16x1_256e_ucf101_rgb.py配置文件中可以进行配置的。其函数的实现几乎都在项目根目录的mmaction/datasets/pipelines目录中。
结语博客也写了这么多了,但是我们只讲解了BaseDataset这个父类,下小节,我会对RawframeDataset以及VideoDataset进行介绍。