行为识别,时序检测,时空检测三种任务的联系
对于视频的理解
视频 = 空间 + 时间:图像为二维空间,视频是三维,视频相对于图像多出来的维度就是时间维度。
视频理解的重点
重点1:如何描述视频中的动作?
动作 = 外观 + 运动。外观是静态的,是图像帧。运动是动态的,也叫帧间运动,就是时序上的变化。
重点2:如何高效的处理视频数据?
视频的数据量远大于图像,一秒钟的视频就包含20~30张图像,对计算量,内存的占用都会带来巨大挑战
重点3:如何利用无标注的视频数据训练模型?
标注视频的工作量比起图像标注要打百倍千倍,传统的对每一张图片进行精细标注,不太现实。
光流的可视化
光流的两种类型
颜色表示方向,亮度表示大小
深度学习时代的视频理解
DeepVideo(2014)
用图像分类网络AlexNet在每一帧图片上提取特征并融合在一起,但是没有性能上的提升。因为它只关注每一帧图像的外观特征,没有捕捉运动特征。
Two Stream Networks(2014)
双流神经网络
Temporal Segment Networks(2016)
时序分段网络TSN
(1)与2D卷积网络的区别
(2)3D卷积网络——I3D的提出
I3D(2017)
关键点:3D网络由图像分类的2D网络“膨胀”得来,因此可以充分利用已有的图像分类模型。
从此,基于膨胀的三维卷积核的三维卷积网络I3D逐渐称为动作识别这个领域的主流方法。
相关视频理解方法的对比
(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)
IG-65M(2019)
OmniSource(2020)
背景:2020年,港中文提出的,使用多种来源的数据(长视频,短视频,图像)联合训练模型,对数据的利用更高效
主要内容
(1)数据爬取:获取原始网络的不同形态的图像视频
(2)数据过滤:使用与训练好的模型进行数据筛选,形成筛选后的数据集
(3)标准化处理:格式化视频与图像,形成标准化数据集
(4)混合数据训练模型
实验结果:分类精度进一步提升
(1)视频理解的3个基本任务:行为识别,时序动作检测,时空动作检测
(2)重点1:如何获得更好的动作特征?
(3)重点2:如何高效地处理视频数据,提高3D卷积模型地计算效率?
(4)重点3:如何控制标注成本?
action recognition实际上是个分类问题。主要由两类模型,一个是基于2D卷积神经网络的,另一种是基于3D卷积神经网络的。
2D,3D两类模型在构成上没有太大区别,区别在于处理输入方面。
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),
)
I3D是一个3D模型,主干网络使用ResNet-50层的结构
原始论文中I3D是基于Inception,这里基于ResNet-50
关于I3D的分类头:3D卷积网络要接受的是一个5维的输入,这里通常会使用average pooling把THW3个维度压缩成1个维度。然后只剩下batch维和通道维,通道维的维度是2048,经过average pooling后,针对每个数据可以得到2048个特征,然后再用一个全连接层产生一个400维的分类概率。模型最终输出400维的分类概率。
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),
)
数据集类型:
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
)
)
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格式
]
# 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
主要任务:
这里是我安装的版本,要注意相关版本的对应。具体的安装文档见最后的地址链接。
# 检查 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__)
# 创建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("""
""" % data_url)
demo视频为arm wrestling,地址:demo.mp4
# 查看推理Top-5结果
for result in results:
print(f'{result[0]}: ', result[1])
训练新模型的三个步骤:
下载并且解压数据集kinetics400_tiny,下载地址:kinetics400_tiny
# 查看标注文件格式(linux命令-cat,正斜杠/)
!type kinetics400_tiny\kinetics_tiny_train_video.txt
# 获得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:\n{cfg.pretty_text}')
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)
from mmaction.apis import single_gpu_test
from mmaction.datasets import build_dataloader
from mmcv.parallel import MMDataParallel
# 构建测试数据集
dataset = build_dataset(cfg.data.test, dict(test_mode=True))
data_loader = build_dataloader(
dataset,
videos_per_gpu=1, # batchsize设置为1
workers_per_gpu=cfg.data.workers_per_gpu,
dist=False,
shuffle=False)
model = MMDataParallel(model, device_ids=[0]) # 初始化模型
outputs = single_gpu_test(model, data_loader) # 得到所有模型的分类输出
# 在测试集上评价训练完成的识别模型
eval_config = cfg.evaluation
eval_config.pop('interval')
eval_res = dataset.evaluate(outputs, **eval_config) # 比较输出值与真实值,计算准确率
for name, val in eval_res.items():
print(f'{name}: {val:.04f}')
# 克隆mmdetection项目
%cd ..
!git clone https://github.com/open-mmlab/mmdetection.git
%cd mmdetection
# 以可编辑的模式安装mmdet
!pip install -e .
%cd ../mmaction2
# 上传视频至目录mmaction2下
!wget https://download.openmmlab.com/mmaction/dataset/sample/1j20qq1JyX4.mp4 -O demo/1j20qq1JyX4.mp4
# 完成时空检测
!python demo/demo_spatiotemporal_det.py --video demo/1j20qq1JyX4.mp4
(1)在时空检测的过程中,首先用下载的Fast-RCNN模型做一个人的检测(每帧都检测)
Fast-RCNN配置文件地址:Fast-RCNN
(2)然后下载行为识别模型slowfast(仅有slow),用OmniSource的方法预训练,完成时空检测。
SlowOnly(SlowFast)配置文件地址:SlowOnly
(3)最后检测完之后,下载ffmpeg把对应的图像编码成一个视频,做成可视化的demo。
# 查看视频
from IPython.display import HTML
from base64 import b64encode
mp4 = open('demo/stdet_demo.mp4','rb').read()
data_url = "data:video/mp4;base64," + b64encode(mp4).decode()
HTML("""
""" % data_url)
时空动作识别原视频地址:时空动作识别原视频
时空动作识别后:
时空动作识别——stdet_demo
1.相关课程 视频理解
2. MMAction安装步骤官方文档 MMAction2安装
3. 预训练模型TSN下载地址:TSN下载
4.kinetics400_tiny 下载地址:kinetics400_tiny数据集下载
5. MMAction2的demo.mp4地址 demo.mp4
6. Fast-RCNN配置文件地址:Fast-RCNN
7. SlowOnly(SlowFast)配置文件地址:SlowOnly
8. MMAction2的demo地址:demo