目录
2、Activity Classification Subnet
写在前面
由于这是一篇比较经典的论文,网上相关讲解的文章比较多,我这里就不重复了,主要记录一些自己遇到的问题。
论文详解
参考链接:
以下引用快内容均参考上面两个链接,有对其中的内容进行调整:
R-C3D(Region 3-Dimensional Convolution)网络是基于 Faster R-CNN 和 C3D 网络思想。对于任意的输入视频 L,先进行 Proposal,然后对 proposal segments 进行 NMS 排除掉一些重复(IoU 大)的 proposal segments。再对经过 selected 的 proposal segments 使用 3D-ROI-pooling 变换为固定尺寸,最后进行分类和回归(start time 和 end time 的 refinement)操作。文章主要贡献点有以下3个:
- 可以针对任意长度视频、任意长度行为进行端到端的检测
- 速度很快(是目前网络的5倍),通过共享 Progposal generation 和 Classification 网络的 C3D feature maps
- 作者测试了3个不同的数据集,效果都很好,显示了通用性。
网络结构
R-C3D 网络可以分为4个部分:
- 特征提取网络(C3D):对于输入任意长度的视频进行特征提取
- Temporal Proposal Subnet: 用来提取可能存在行为的时序片段(Proposal Segments)
- Activity Classification Subnet: 行为分类子网络
- Loss Function
下图是整个网络结构图。
1、特征提取网络
骨干网络作者选择了 C3D 网络,经过C3D网络的 5 层卷积(conv1~conv5b)后,可以得到 512 x L/8 x H/16 x W/16 大小的特征图。这里不同于 C3D 网络的是,R-C3D允许任意长度的视频 L 作为输入。
2、Temporal Proposal Subnet
这一部分是时序候选框提取网络,类似于Faster R-CNN中的 RPN(Region Proposal Network),用来提取一系列可能存在目标的候选框。这里是提取一系列可能存在行为的候选时序。
Step1:候选时序生成
输入视频经过上述C3D网络后得到了 512 x L/8 x H/16 x W/16 大小的特征图。然后作者假设 anchor 均匀分布在 L/8 的时间域上,也就是有 L/8 个 anchors,每个 anchors 生成 K 个不同 scale 的候选时序,共 L/8 * K 个 proposal segments。
Step2: 3D Pooling
得到的 512xL/8xH/16xW/16 的特征图后,为了获得每个时序点(anchor)上每段候选时序的中心位置偏移(offset)和时序的长度(temporal length),作者将空间上 H/16 x W/16 的特征图经过一个 3x3x3 的卷积核和一个 3D pooling 层下采样到 1x1。最后输出 512xL/8x1x1。
Step3: Training
类似于Faster R-CNN,这里也需要判定得到的候选时序是正样本还是负样本。文章中的判定如下:
正样本:IoU > 0.7 候选时序帧和 ground truth 的重叠数
负样本: IOU < 0.3 为了平衡正负样本,正/负样本比例为1:1.
2、Activity Classification Subnet
行为分类子网络有如下几个功能:
- 从TPS(Temporal Proposal subnet)中选择出 Proposal segment(selected proposal segments)
- 对于上述的 proposal,用 3D RoI Pooling 提取固定大小特征
- 以上述特征为基础,将选择的 Proposal 做类别判断和时序边框回归
Step1: NMS
针对上述 Temporal Proposal Subnet 提取出的 segment,采用 NMS(Non-maximum Suppression)非极大值抑制生成优质的 proposal。NMS 阈值为 0.7.
Step2:3D RoI Pooling
RoI (Region of interest,兴趣区域)。这里,个人感觉作者的图有点问题,提取兴趣区域的特征图的输入应该是 C3D 的输出,也就是 512xL/8xH/16xW/16,可能作者遗忘了一个输入的箭头。 假设C3D输出的是 512xL/8x7x7大小的特征图,假设其中有一个 proposal 的长度(时序长度)为 lp,那么这个 proposal 的大小为 512xlpx7x7,这里借鉴 SPPnet 中的池化层,利用一个动态大小的池化核,ls x hs x ws。最终得到 512x1x4x4 大小的特征图
Step3: 全连接层
经过 3D-RoI-Pooling 后,再分别喂入两个独立的全连接层。一个用来边框回归(start-end time),另一个用来做类别分类(Activity Scores)。
Step4: Traning
在训练的时候同样需要定义行为的类别,如何给一个 proposal 定 label?同样采用 IoU。
- IoU > 0.5,那么定义这个 proposal 与 ground truth 相同。如果某个 proposal 与多个 ground truth 的 IoU 均大于0.5,则取第 i 个 ground truth 的 label(其中,第 i 个 ground truth 是 ground truths 中与该 proposal 有着最大的 IoU)
- IoU 与所有的 ground truth 都小于 0.5,那么定义为 background
这里,训练的时候正/负样本比例为1:3。
4、Loss Function
文章将分类和回归联合,而且联合两个子网络。分类采用 softmax,回归采用 smooth L1。
- 其中的 N 都代表batch size
- lamda 为1
- 作者说这个 Loss 既用于 proposal subnet,又用于 classification subnet。其中在 proposal subnet 中,
用来预测某个 proposal 中是否包含动作(不对动作类别分类),
用来优化某个 proposal 的时序边框;在 proposal subnet 中,
用来预测某个 selected proposal 属于哪个动作类别,
用来优化某个 activity 的时序边框。
NMS(非极大值抑制)
参考链接:
- https://blog.csdn.net/m0_37605642/article/details/98358864?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161404590516780265467869%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161404590516780265467869&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-98358864.first_rank_v2_pc_rank_v29&utm_term=NMS
- https://mp.weixin.qq.com/s?__biz=MzAwNTMzODc4OA==&mid=2456142455&idx=1&sn=56222bb45955f219329681f618d0ee4a&chksm=8c8f41bcbbf8c8aa176f29ea6687e3da031405ce1c05161ab3ab1129419b7ce4afbf328e6080&token=1440280921&lang=zh_CN#rd
算法流程
举例
NMS 即 non maximum suppression 即非极大抑制,顾名思义就是抑制不是极大值的元素,搜索局部的极大值。在最近几年常见的物体检测算法(包括 rcnn、sppnet、fast-rcnn、faster-rcnn 等)中,最终都会从一张图片中找出很多个可能是物体的矩形框,然后为每个矩形框为做类别分类概率。
就像上面的图片一样,定位一个车辆,最后算法就找出了一堆的方框,我们需要判别哪些矩形框是没用的。
所谓非极大值抑制:先假设有6个矩形框,根据分类器类别分类概率做排序,从小到大分别属于车辆的概率分别为A<B<C<D<E<F。(1) 从最大概率矩形框F开始,分别判断A、B、C、D、E与F的重叠度IOU是否大于某个设定的阈值;
(2) 假设B、D与F的重叠度超过阈值,那么就扔掉B、D;并标记第一个矩形框F,是我们保留下来的。
(3) 从剩下的矩形框A、C、E中,选择概率最大的E,然后判断A、C与E的重叠度,重叠度大于一定的阈值,那么就扔掉;并标记E是我们保留下来的第二个矩形框。
(4) 重复这个过程,找到所有被保留下来的矩形框。
ROI Pooling
参考链接:
- https://blog.csdn.net/AUTO1993/article/details/78514071?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161404839916780269844977%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161404839916780269844977&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-78514071.first_rank_v2_pc_rank_v29&utm_term=ROI+Pooling
- https://blog.deepsense.ai/region-of-interest-pooling-explained/
具体操作步骤
(1)根据输入image,将 ROI 映射到 feature map 对应位置;
(2)将映射后的区域划分为相同大小的 sections(sections数量与输出的维度相同);
(3)对每个 sections 进行max pooling操作;
这样我们就可以从不同大小的方框得到固定大小的相应的 feature maps。值得一提的是,输出的feature maps的大小不取决于 ROI 和卷积 feature maps大小。ROI pooling 最大的好处就在于极大地提高了处理速度。
举例
考虑一个8*8大小的feature map,一个ROI,以及输出大小为2*2.
(1)输入的固定大小的feature map
![]()
(2)region proposal 投影之后位置(左上角,右下角坐标):(0,3),(7,8)。
(3)将其划分为(2*2)个sections(因为输出大小为2*2),我们可以得到:
(4)对每个 section 做 max pooling,可以得到:
回归实例
参考链接:
本质上还是全连接层。再深入一下可以参考目标检测回归 bbox 的方法。
class Net(torch.nn.Module): # 建立网络
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 标准写法
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隐藏层 输入 输出个数
self.predict = torch.nn.Linear(n_hidden, n_output) # 输出层 输入 输出个数
def forward(self, x):
x = F.relu(self.hidden(x)) # 隐藏层的激活函数
x = self.predict(x) # 线性输出
return x