Mar 26, 2024
2 min read
Pytorch,

如何不使用任何3d算子解决视频流的识别

如何在不使用任何3d算子和5d数据格式的情况下,重新设计模型来实现视频流的识别,包括模型设计思路、两阶段训练方法和损失函数设计

之前实现过一个基于 unet3d 模型来实现一个10帧视频流的识别。主要用一个模型,实现2个任务:

  1. 视频流的分类
  2. 视频流的分割

通过分割视频流中运动的物品以及对视频流进行行为分类,来实现业务上的一些需求。

然而,虽然 unet3d 模型在视频流的识别任务上表现出了很好的效果,计算量也还好。但是它基本不可能部署在边端的硬件上。

通过一些资料发现,世面上几乎所有的边端硬件都不支持 3d 算子,甚至就不支持5d的数据格式。

这时 RKNN 完全不支持任何 3d 算子和 5d 数据格式。高通的 SNPE 似乎支持个别的3d算子,但是没多大用,大概率还是会碰到坑。

这意味着我们的模型不能使用这些算子: BatchNorm3d, Conv3d, Dropout3d, MaxPool3d, ConvTranspose3d。

因此首先第一件事就是,我需要重新设计一个模型来实现视频流的识别。不能使用任何3d算子,不能使用5d数据格式。

设计模型

限制条件:

  1. 模型必须轻量化
  2. 推理速度必须要在 500 ms 以内
  3. 不支持任何 5D 数据
  4. 不支持任何 3D 算子
  5. 一个模型还要满足 2 个任务:分类和分割

之前的 unet3d 模型,采用的是[1, 3, 10, 224, 224]的输入格式。由于不支持5d数据格式,我这里的想法是把通道维度与时间维度合并起来,变成[1, 30, 224, 224]的输入格式。也就是:

[batch, channel, time, height, width] => [batch, channel * time, height, width]

事实上,通道合并后,在模型内容的数据转换是个比较麻烦的事,因为模型内部也不能出现任何的5d数据格式。那我是不是可以把channel 改为1?使用灰度图来训练虽然信息会减少,但是计算量也会减少。

也就是[1, 30, 224, 224] => [1,10, 224, 224]

输出为2个输出,一个分割头: [1,10, 224, 224],一个分类头:[1,3]

当然,考虑到之前的硬件产出的10帧视频帧数据质量,我这里其实把帧数扩大了一倍,变成了20帧,也就是[1,20,224,224]。

模型主干网络的设计依然是 UNet 结构的思想,但是也添加了一些空间注意力机制,来增强模型对运动部位的特征提取能力。

分类头

有意思的是分类头。在 unet3d 里,分类头其实是的输入来自于与分割共享的特征层。在3d算子对视频流强大的特征提取能力下,分类头可以做到与分割头同时训练。

但是在不能使用3d算子,不能使用5d数据格式的情况下, 采用上面的方法设计的话,几乎无法收敛,训练难度非常的大。

我的尝试是把分类头的输入换成了分割头输出,由于这是一个前后顺序关系,分类头想要得好的效果,就必须保证分割头得结果是好的。

因此我这里的训练其实变成了两阶段训练:

  1. 第一阶段训练,训练主干网络以及分割头。
  2. 第二阶段训练,冻结主干网络和分割头,只训练分类头。

损失函数

分割头的损失函数采用的是改造过的 Dice 损失函数和BCE 损失函数。但是为了提高模型对运动区域的关注,还设计了帧间运动差异损失,通过帧间差异与目标mask做对比实现。 分类头的损失函数采用的是交叉熵损失函数。

由于实现的业务并不需要很高的分割精度,只要求分割的区域是运动区域,因此效果还可以接受。

整体效果

整体效果如下:

result

其他想法

其实如果使用对象检测的方式做可能会比分割的计算量要小一些的,毕竟最后我们需要的精度其实就是检测出运动区域的位置。只是对象检测在训练上要比分割复杂一些,还有一些后处理也要比分割要麻烦。

能不能用对象检测 + 追踪算法来实现呢?

我的理解是,以我们的业务需求以及购物车的复杂使用环境,不太可行。业务需要关注的是进出车的物品,但是摄像头是直接对着已存在的物品的。 对象检测 + 追踪意味着需要对已存在的物品进行识别和追踪,这在购物车这种复杂环境下是非常困难的。更可怕的是,用户还存在理货行为。