之前实现过一个基于 unet3d 模型来实现一个10帧视频流的识别。主要用一个模型,实现2个任务:
- 视频流的分类
- 视频流的分割
通过分割视频流中运动的物品以及对视频流进行行为分类,来实现业务上的一些需求。
然而,虽然 unet3d 模型在视频流的识别任务上表现出了很好的效果,计算量也还好。但是它基本不可能部署在边端的硬件上。
通过一些资料发现,世面上几乎所有的边端硬件都不支持 3d 算子,甚至就不支持5d的数据格式。
这时 RKNN 完全不支持任何 3d 算子和 5d 数据格式。高通的 SNPE 似乎支持个别的3d算子,但是没多大用,大概率还是会碰到坑。
这意味着我们的模型不能使用这些算子: BatchNorm3d, Conv3d, Dropout3d, MaxPool3d, ConvTranspose3d。
因此首先第一件事就是,我需要重新设计一个模型来实现视频流的识别。不能使用任何3d算子,不能使用5d数据格式。
设计模型
限制条件:
- 模型必须轻量化
- 推理速度必须要在 500 ms 以内
- 不支持任何 5D 数据
- 不支持任何 3D 算子
- 一个模型还要满足 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数据格式的情况下, 采用上面的方法设计的话,几乎无法收敛,训练难度非常的大。
我的尝试是把分类头的输入换成了分割头输出,由于这是一个前后顺序关系,分类头想要得好的效果,就必须保证分割头得结果是好的。
因此我这里的训练其实变成了两阶段训练:
- 第一阶段训练,训练主干网络以及分割头。
- 第二阶段训练,冻结主干网络和分割头,只训练分类头。
损失函数
分割头的损失函数采用的是改造过的 Dice 损失函数和BCE 损失函数。但是为了提高模型对运动区域的关注,还设计了帧间运动差异损失,通过帧间差异与目标mask做对比实现。 分类头的损失函数采用的是交叉熵损失函数。
由于实现的业务并不需要很高的分割精度,只要求分割的区域是运动区域,因此效果还可以接受。
整体效果
整体效果如下:

其他想法
其实如果使用对象检测的方式做可能会比分割的计算量要小一些的,毕竟最后我们需要的精度其实就是检测出运动区域的位置。只是对象检测在训练上要比分割复杂一些,还有一些后处理也要比分割要麻烦。
能不能用对象检测 + 追踪算法来实现呢?
我的理解是,以我们的业务需求以及购物车的复杂使用环境,不太可行。业务需要关注的是进出车的物品,但是摄像头是直接对着已存在的物品的。 对象检测 + 追踪意味着需要对已存在的物品进行识别和追踪,这在购物车这种复杂环境下是非常困难的。更可怕的是,用户还存在理货行为。