MMAction2-视频理解行为识别(学习笔记-附代码实操)

Posted 想养一群英短啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MMAction2-视频理解行为识别(学习笔记-附代码实操)相关的知识,希望对你有一定的参考价值。

一、MMAction2——视频理解与行为识别

  • 行为识别,时序检测,时空检测三种任务的联系

  • 对于视频的理解

    视频 = 空间 + 时间:图像为二维空间,视频是三维,视频相对于图像多出来的维度就是时间维度。

  • 视频理解的重点

    • 重点1:如何描述视频中的动作?

      动作 = 外观 + 运动。外观是静态的,是图像帧。运动是动态的,也叫帧间运动,就是时序上的变化。

      • 思路1:独立提取图像特征,再进行时序建模

        把静态的外观特征与动态的时序上的变化分成两个层次建模。

        第一层次:提取每一个图像的外观特征。

        第二层次:在外观特征所形成的序列基础之上进行一次时序建模。

        提取动态信息,根据动态信息获取动作特征。

      • 思路2:外观特征与运动特征并行计算,最后融合

        首先根据单帧图像提取外观特征

        通过相邻帧的变化,提取瞬时变化的信号,进而提取运动特征

        以上两步并行计算,最后融合

      • 思路3:利用更加强大的模型,从多帧图像直接计算运动特征

    • 重点2:如何高效的处理视频数据?

      视频的数据量远大于图像,一秒钟的视频就包含20~30张图像,对计算量,内存的占用都会带来巨大挑战

    • 重点3:如何利用无标注的视频数据训练模型?

      标注视频的工作量比起图像标注要打百倍千倍,传统的对每一张图片进行精细标注,不太现实。

1. 光流和2D卷积(解决重点1,2)

  • 光流——捕捉视频中的运动。光流是图像平面上的向量场,光流通常基于相邻图像帧进行估计得到。光流就是把图像中每一个点的位移表示出来。光流整体能表达出全图各个点的位移的方向和幅度。

  • 光流的估计


    这里有些Latex符号在这里会报错,所以直接贴图了。(Typora中打的)

  • 光流的可视化

  • 光流的两种类型

    颜色表示方向,亮度表示大小

    • 稀疏光流:跟踪少量感兴趣的点
    • 稠密光流:估算所有像素的光流
  • 深度学习时代的视频理解

    • DeepVideo(2014)

      用图像分类网络AlexNet在每一帧图片上提取特征并融合在一起,但是没有性能上的提升。因为它只关注每一帧图像的外观特征,没有捕捉运动特征。

    • Two Stream Networks(2014)

      双流神经网络

      • Spatial stream是空间流,以单张图像为输入,主要提取每一帧图像的外观特征
      • Temporal stream是时间流,以多帧光流作为输入,用卷积网络作用在光流场(即二维信号)上面取提取运动特征。
      • 最终,两个分支提取出外观特征和运动特征,结合在一起
      • 解决重点2
        • 在训练时:随机选择视频的某一时刻,计算图像的光流
        • 在测试时:在全部时刻进行预测,再平均所有时刻的分类概率
      • 双流神经网络存在的问题:双流网络聚焦在短时建模,动作由单一时刻的图像和光流所确定,会存在一些信息上的误解。因此需要长时建模,动作应该由整个时间段内的图像和运动信息所确定

    • Temporal Segment Networks(2016)

      时序分段网络TSN

      • 当一段视频进来的时候,不会逐帧取采样,会按照一个固定的间隔去采。例如,把整段视频分割为3个段落,每个段落取1个瞬间,再送进双流网络进行外观与运动的特征提取 。3个段落中分别进行前,中,后段的动作预测。最后,融合得到全视频的动作预测。
      • TSN关键点:用新的分段采样的方式,而不是按照一定固定的频率去进行密集采样。让视野得到有效的扩大,信息更加综合多元。

2. 3D卷积网络(解决重点1,2)

  • (1)与2D卷积网络的区别

    • 在双流网络和TSN上,会基于光流去提取运动特征。即通过图像帧去提取运动特征

    • 3D卷积:2D卷积用于图像特征提取。而3D卷积用于视频特征提取,就是多增加了一个时间维度。

  • (2)3D卷积网络——I3D的提出

    • I3D(2017)

      关键点:3D网络由图像分类的2D网络“膨胀”得来,因此可以充分利用已有的图像分类模型。

      从此,基于膨胀的三维卷积核的三维卷积网络I3D逐渐称为动作识别这个领域的主流方法。

      • 关于膨胀2D卷积

        3D卷积相比于2D卷积多了一个在时间维度的堆叠

      • 对图像分类的2D网络Inception进行膨胀

        卷积膨胀:从二维的一个卷积核复制几份叠到一起,形成一个三维卷积核

    • 相关视频理解方法的对比

      (a)DeepVideo模型(2D卷积+LSTM):2D卷积只能处理单帧数据,而对于视频数据来说,则需要将2D卷积处理的多张单帧数据做融合,LSTM就是融合的方法。(即直接作用在图像上,通过二维卷积方法提取每一帧图像的外观特征,然后将特征送入LSTM来捕捉时间特征。)

      (b)C3D模型(3D卷积):将2维卷积核变为3维卷积核。(单纯基于空间流,但没有使用膨胀卷积核,参数多,训练难度加大)

      (c) 双流神经网络(2D卷积):还是基于图像帧去提取外观特征与运动特征。

      (d)I3D模型——基于3D卷积的双流模型(3D卷积):3D卷积模型没有像2D卷积一样有成熟的预训练参数,所以借鉴了成熟的图像分类网络(2D卷积网络)Inception,将网络中的2D卷积核变为3D卷积核。H,W对应的参数直接从Inception获取,D参数自己训练。(I3D的训练方式是先通过Kinetics数据集进行预训练,再训练HMD51和UCF101并验证效果)

      总而言之,I3D就是将C3D与双流网络进行融合。

  • (3)更高效的3D卷积网络解决重点2

    • 解决办法1分解3D卷积核

      原因:全部使用3D卷积参数量最大

      目的:降低参数量

      思路:先过一遍空间卷积,进行空间信息的融合,再过一遍时间卷积,进行时间维度的融合。
      t d 2 > d 2 + t td^2>d^2+t td2>d2+t

      相关网络:S3D(2018) & R2 + 1D(2018)

    • 解决办法2:减少通道关联

      输入通道是c,输出通道是c的时候,则需要c²个卷积核。可以选择分组卷积&逐层卷积的方法。

    • SlowFast(2019)

      • 关键点:外观和运动速度的变化不同,外观变化慢,运动变化快。

      • 思路:用低帧率对外观进行采样,用高帧率对运动进行采样。并用相对轻量级的网络结构来平衡计算量。

3. 弱监督学习方法(解决重点2,3)

  • 弱监督学习:使用标注不完整的数据进行学习,但也要有一定的标注去引导。
  • 基本思路
    1. 大规模无标注或精确标注的数据进来,进行过滤筛选,得到值得标注的数据,进行标注
    2. 针对部分标注的数据进行监督学习,获得预训练模型

  • IG-65M(2019)

    • 背景:Facebook2019年提出。使用Kinetics数据集。Kinetics是一个标准的学术数据集,里面提供大量视频。

    • 主要内容:利用Kinetics数据里的关键标签,再从Instagram(大型的图像和视频分享网站)收集6500万个视频,对Kinetics上经过良好标注的视频进行补充。从而形成一个整体的弱监督的数据集,去预训练一个大模型。预训练完成后,再在Kinetics数据集上进行微调训练。

    • 实验结果

      (1)经过预训练的模型的性能优于直接在目标数据集上训练的模型

      (2)预训练使用的数据越多,性能越好,不准确的标注由数据量弥补

  • OmniSource(2020)

    • 背景:2020年,港中文提出的,使用多种来源的数据(长视频,短视频,图像)联合训练模型,对数据的利用更高效

    • 主要内容

      (1)数据爬取:获取原始网络的不同形态的图像视频

      (2)数据过滤:使用与训练好的模型进行数据筛选,形成筛选后的数据集

      (3)标准化处理:格式化视频与图像,形成标准化数据集

      (4)混合数据训练模型

    • 实验结果:分类精度进一步提升

总结(基于重点1,2,3)

(1)视频理解的3个基本任务:行为识别,时序动作检测,时空动作检测

(2)重点1:如何获得更好的动作特征?

  • 深度学习时代以前:DT
  • 深度学习时代早期:双流网络,TSN提出分段采样的方式
  • 如今:基于3D卷积网络,尤其是I3D,通过对卷积核进行膨胀,获得有效的训练3D卷积网络的方法

(3)重点2:如何高效地处理视频数据,提高3D卷积模型地计算效率?

  • 时空分解:S3D,R(2+1)D
  • 优化参数分配:SlowFast

(4)重点3:如何控制标注成本?

  • 弱监督学习:IG-65M,OmniSource

二、视频理解工具包——MMAction2

1. 行为识别模型的结构

  • action recognition实际上是个分类问题。主要由两类模型,一个是基于2D卷积神经网络的,另一种是基于3D卷积神经网络的。

  • 2D,3D两类模型在构成上没有太大区别,区别在于处理输入方面。

    • 2D:接受一些独立的图像帧,送进主干网络进行分类
    • 3D:接受连续的图像帧,送进主干网络进行分类

2. TSN模型配置

  • TSN是一个2D模型,主干网络使用ResNet-50层的结构
  • TSN需要输入一个视频在不同时刻若干个图像帧(clip),处理不同的clip的神经网络是共享参数的,所以只初始话1个backbone。即用一个backbone处理所有的clip的图像,再把结果平均放到TSNHead里去产生最终的分类结果
model = dict(
	type = 'Recognizer2D',
    
    # 2D ResNet-50作为主干网络
    backbone = dict(
    	type = 'ResNet',
        pretrained = 'torchvision://resnet50',   # 从torchvision中拿取ResNet-50的预训练参数
        depth = 50,
        norm_eval = False),
    
    # TSN的分类头
    cls_head = dict(
    	type = 'TSNHead',      # TSN的头会接收ResNet产生的特征
    	num_classes = 400,
        in_channels = 2048,
        spatial_type = 'avg',
        consensus = dict(type = 'AvgConsensus', dim = 1),    # 通过平均共识函数,把多个特征平均到一起,再产生400类的分类(Kinetics400)
        dropout_ratio = 0.4,
        init_std = 0.01),


)

3. I3D模型配置

  • I3D是一个3D模型,主干网络使用ResNet-50层的结构

  • 原始论文中I3D是基于Inception,这里基于ResNet-50

  • 关于I3D的分类头:3D卷积网络要接受的是一个5维的输入,这里通常会使用average pooling把THW3个维度压缩成1个维度。然后只剩下batch维和通道维,通道维的维度是2048,经过average pooling后,针对每个数据可以得到2048个特征,然后再用一个全连接层产生一个400维的分类概率。模型最终输出400维的分类概率。

    • 5个维度:batch维,通道维,THW维(时间,宽度,高度)
model = dict(
	type = 'Recognizer3D',
    
    # 膨胀的3D ResNet-50作为主干网络
    backbone = dict(
    	type = 'ResNet3d',
        pretrained2d = True,                     # 使用 2D ResNet-50的预训练参数
        pretrained = 'torchvision://resnet50',   # 从torchvision中拿取ResNet-50的预训练参数
        depth = 50,
        conv_cfg = dict(type = 'Conv3d'),
        norm_eval = False,
        '''
        infalte = 1,表示在对应的层使用膨胀策略,将2D卷积变为3D卷积,指定为0就不使用膨胀
        
        ResNet-50有4组残差模块每组残差模块中分别有3,4,6,3个残差模块,1和0就表示指定的残差模块是否膨胀。	
        '''
        inflate = ((1, 1, 1), (1, 0, 1, 0), (1, 0, 1, 0, 1, 0), (0, 1, 0)),
        zero_init_residual = False),     # 分类时设置为True
    
    # I3D的分类头
    cls_head = dict(
    	type = 'I3DHead',
        num_classes = 400,
        in_channels = 2048,     # 通道维
        spatial_type = 'avg',
        dropout_ratio = 0.5,
        init_std = 0.01),

)

4. 数据集配置

  • 数据集类型

    • RawframeDataset(读图像帧)——将MP4或者其他编码格式的视频先在线下解码成一帧一帧的图像,然后将所有的图像帧存到对应的目录里,这个目录就代表一个视频。目的是为了减少在训练时的解码时间。所以预先把所有视频解码好,放到一个文件夹里训练的时候按照帧的序号去读对应的图像文件。如下图所示。

      依次为:图像帧目录,图像帧数量,动作分类的序号。其中,每一个目录代表一个视频。

    • VideoDataset(读视频)

data = dict(
	# batchsize(每个视频加载的进程数)
    videos_per_gpu = 8,
    # 视频读取进程数
    workers_per_gpu = 4,
    # 指定数据子集
    train/val/test = dict(
    	# 数据集类型
        type = 'RawframeDataset'/'VideoDataset'/...,
        # 类别标注文件(RawframeDataset读图像帧的文件夹)
        ann_file = 'annotation.txt',
        # 数据集根目录
        data_prefix = 'data/kinetics400/rawframes_train',
        # 数据是图像还是光流
        modality = 'RGB'/'Flow',
        # 指定数据处理的工作流,通常做数据读取或者数据增强之类的任务
        pipeline = train_pipeline
    )
)
  • 数据处理的pipeline
    • SampleFrames:从视频抽取一些帧,用clip_len定义帧长度,frame_interval为抽取的步长,num_clips为抽取几个片段
    • RawFrameDecode:把对应的图像帧读取进来,并且进行解码
    • Resize:裁剪
    • Flip:翻转
    • Normalize:像素值归一化
    • FormatShape:对维度进行排序。例如NCTHW就是batch维,通道维,时间维,空间维
    • ToTensor:从处理好的数据中把对应的图像,类别标签转化成对应格式的tensor,最终传给分类模型进行前传计算。
    • 总体步骤:读取连续帧图像→解码对应帧图像→数据增强→将数据转化为torch.Tensor→分类模型
train_pipeline = [
    dict(type = 'SampleFrames', clip_len = 32, frame_interval = 2, num_clips = 1),  # 32帧,每隔两帧抽取一帧,覆盖64帧,抽取1个片段
    dict(type = 'RawFrameDecode'),             # 解码,成为32个h×W数组
    dict(type = 'Resize', scale = (-1, 256)),  # 裁剪
    dict(type = 'RandomResizedCrop'),
    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'])                # 转化为totensor格式
]

5. 常用的训练策略(MMAction2中)

  • sgd_50e.py配置文件
    • SGD优化器
    • lr_config:步长下降策略,在20轮和40轮的时候将学习率降为原来的1/10,训练50轮结束
    • optimizer_config:梯度策略,每一次算出梯度之后,把所有的梯度通过norm2(求平方和再开平方)算出梯度整体的norm。当梯度的总体norm超过max_norm使进行归一化,把norm进行整体缩小。以增加训练稳定性,防止梯度过大。
# optimizer
optimizer = dict(
	type = 'SGD',
    lr = 0.01,    # 8 gpus
    momentum = 0.9,
    weight_dacay = 0.0001)
optimizer_config = dict(grad_clip = dict(max_norm = 40, norm_type = 2))

# learning policy
lr_config = dict(policy = 'step', step = [20, 40])
total_epochs = 50

三、代码实操

主要任务

  1. 用MMAction2的识别模型做一次推理
  2. 用新数据集训练一个新的识别模型
  3. 用MMAction2的时空检测模型做一次推理

1. 安装依赖库

这里是我安装的版本,要注意相关版本的对应。具体的安装文档见最后的地址链接。

  • cuda 11.1
  • GCC 6.3.0
  • torch 1.8.0
  • torchvision 0.9.0
  • MMAction2 0.24.0
# 检查 nvcc,gcc 版本
!nvcc -V
!gcc --version
# 检查torch的版本>1.5,GPU是否可用
import torch, torchvision
print(torch.__version__, torch.cuda.is_available(), torchvision.__version__)
# 检查mmcv版本
from mmcv.ops import get_compiling_cuda_version, get_compiler_version
print(get_compiler_version())
print(get_compiling_cuda_version())
# 检查MMAction2的版本
import mmaction
print(mmaction.__version__)

2. MMAction2识别模型推理(用提供的预训练模型)

  • 预训练模型TSN下载地址:TSN下载
  • 配置文件命名:
    • r50:主干网络ResNet50
    • kinetics400:数据集
    • rgb:只基于RGB去训练,没有使用光流
    • 1×1×3:用了3个clip,把视频分割为前中后3段,每一段去采集对应的图像帧
    • 100e:总共训练了100个epoch
  • 下载完预训练模型放到checkpoints文件夹中
# 创建checkpoints文件夹,并下载TSN模型
!mkdir checkpoints
from mmaction.apis import inference_recognizer, init_recognizer

# 选择tsn对应的配置文件
config = 'configs/recognition/tsn/tsn_r50_video_inference_1x1x3_100e_kinetics400_rgb.py'
# 加载上面下载的checkpoint文件
checkpoint = 'checkpoints/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'
# 在GPU上初始化该模型
model = init_recognizer(config, checkpoint, device='cuda:0')
# 选择视频进行推理
video = 'demo/demo.mp4'
label = 'tools/data/kinetics/label_map_k400.txt'    # 400行的文件,每行就是数据集的一个类别
results = inference_recognizer(model, video)

labels = open(label).readlines()
labels = [x.strip() for x in labels]
results = [(labels[k[0]], k[1]) for k in results]
# 查看视频,传入已定义的video
from IPython.display import html
from base64 import b64encode
mp4 = open(video,'rb').read()        # rb以二进制格式打开一个文件用于只读。
data_url = "data:video/mp4;base64," + b64encode(mp4).decode()
HTML("""
<video width=400 controls>
      <source src="%s" type="video/mp4">
</video>
""" % data_url)

demo视频为arm wrestling,地址:demo.mp4

# 查看推理Top-5结果
for result in results:
    print(f'result[0]: ', result[1])

最终用这个预训练的TSN模型得到以下分类结果

3. 在自定义数据集上训练模型(kinetics400_tiny)

训练新模型的三个步骤:

  1. 整理数据:通常要把数据整理成固定的格式,并且对应生成一些标注文件
  2. 修改配置文件:把里面原有的数据替换成我们自定义的数据
  3. 训练模型

3.1 整理数据

  • 将数据转换为已有数据集格式的示例。
  • 用到的是一个从Kinetics-400中获取的tiny数据集。包含30个训练视频,10个测试视频。
  • 有两个标注文件分别对应训练集和验证集(二分类)

下载并且解压数据集kinetics400_tiny,下载地址:kinetics400_tiny

# 查看标注文件格式(linux命令-cat,正斜杠/)
!type kinetics400_tiny\\kinetics_tiny_train_video.txt

3.2 修改配置文件

  • 我们需要修改配置文件,同时会用到之前下载的checkpoint作为pre-trained模型。
  • 在之前用于kinetics400-full数据集训练的tsn模型配置上进行修改,让模型可以在Kinetics400-tiny数据集上进行训练。在小数据集上进行训练。
  • 30个epoch的训练进程pth文件,会保存到生成的tutorial_exps文件夹中。
# 获得tsn对应的配置文件cfg(分了8个clip)
from mmcv import Config
cfg = Config.fromfile('./configs/recognition/tsn/tsn_r50_video_1x1x8_100e_kinetics400_rgb.py')
from mmcv.runner import set_random_seed

# 修改数据集类型和各个文件路径
cfg.dataset_type = 'VideoDataset'
cfg.data_root = 'kinetics400_tiny/train/'
cfg.data_root_val = 'kinetics400_tiny/val/'
cfg.ann_file_train = 'kinetics400_tiny/kinetics_tiny_train_video.txt'
cfg.ann_file_val = 'kinetics400_tiny/kinetics_tiny_val_video.txt'
cfg.ann_file_test = 'kinetics400_tiny/kinetics_tiny_val_video.txt'

cfg.data.test.type = 'VideoDataset'
cfg.data.test.ann_file = 'kinetics400_tiny/kinetics_tiny_val_video.txt'
cfg.data.test.data_prefix = 'kinetics400_tiny/val/'

cfg.data.train.type = 'VideoDataset'
cfg.data.train.ann_file = 'kinetics400_tiny/kinetics_tiny_train_video.txt'
cfg.data.train.data_prefix = 'kinetics400_tiny/train/'

cfg.data.val.type = 'VideoDataset'
cfg.data.val.ann_file = 'kinetics400_tiny/kinetics_tiny_val_video.txt'
cfg.data.val.data_prefix = 'kinetics400_tiny/val/'

# 这里用于确认是否使用到omnisource训练
cfg.setdefault('omnisource', False)
# 修改cls_head中类别数为2
cfg.model.cls_head.num_classes = 2
# 使用预训练好的tsn模型
cfg.load_from = './checkpoints/tsn_r50_1x1x3_100e_kinetics400_rgb_20200614-e508be42.pth'

# 设置工作目录
cfg.work_dir = './tutorial_exps'

# 由于是单卡训练,修改对应的lr
cfg.data.videos_per_gpu = cfg.data.videos_per_gpu // 16   # 为了加速运行,把batchsize改为原来的1/16
cfg.optimizer.lr = cfg.optimizer.lr / 8 / 16              # 原始配置文件中使用8卡训练,根据线性扩展策略,把lr降到原来的1/128
cfg.total_epochs = 30

# 设置存档点间隔减少存储空间的消耗
cfg.checkpoint_config.interval = 10
# 设置日志打印间隔减少打印时间
cfg.log_config.interval = 5

# 固定随机种子使得结果可复现
cfg.seed = 0
set_random_seed(0, deterministic=False)
cfg.gpu_ids = range(1)

# 打印所有的配置参数
print(f'Config:\\ncfg.pretty_text')

3.3 在(自定义的)kinetics400_tiny数据集上训练模型

import os.path as osp

from mmaction.datasets import build_dataset   # 调用build_dataset构建数据集
from mmaction.models import build_model       # 调用build_model构建模型
from mmaction.apis import train_model         # 调用train_model训练模型,传入配置文件,数据,模型

import mmcv

# 构建数据集
datasets = [build_dataset(cfg.data.train)]

# 构建动作识别模型(基于预训练模型,把分类数改为2)
model = build_model(cfg.model, train_cfg=cfg.get('train_cfg'), test_cfg=cfg.get('test_cfg'))

# 创建工作目录并训练模型
mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))
train_model(model, datasets, cfg, distributed=False, validate=True)

30个epoch后,最终精度如下图。

4. 评价模型

  • 只有两类,所以top5_acc: 1.0000。在测试集上分错了1个,最终分类精度为0.9
from mmaction.apis import single_gpu_test
from mmaction.datasets import build_dataloader
from mmcv.parallel import MMDataParallel

# 构建测试数据集
dataset = build_dataset(cfg.<

mmaction2 slowfast 行为分析(商用级别)总目录

以上是关于MMAction2-视频理解行为识别(学习笔记-附代码实操)的主要内容,如果未能解决你的问题,请参考以下文章

08mmaction2 行为识别商用级别自定义ava数据集 之 将视频裁剪为帧

12mmaction2 行为识别商用级别X3D复现 demo实现 检测自己的视频 Expanding Architecturesfor Efficient Video Recognition

12mmaction2 行为识别商用级别X3D复现 demo实现 检测自己的视频 Expanding Architecturesfor Efficient Video Recognition(代码片段

00mmaction2 行为识别商用级别快速搭建mmaction2 pytorch 1.6.0与 pytorch 1.8.0 版本,faster rcnn 与 slowfast

04mmaction2 行为识别商用级别slowfast检测算法使用yolov3来检测人

04mmaction2 行为识别商用级别slowfast检测算法使用yolov3来检测人