Two-Stream Convolutional Networks for Action Recognition in Videos双流网络论文精读

Two-Stream Convolutional Networks for Action Recognition in Videos双流网络论文精读

论文:Two-Stream Convolutional Networks for Action Recognition in Videos
链接:https://arxiv.org/abs/1406.2199

本文是深度学习应用在视频分类领域的开山之作,双流网络的意思就是使用了两个卷积神经网络,一个是Spatial stream ConvNet,一个是Temporal stream ConvNet。此前的研究者在将卷积神经网络直接应用在视频分类中时,效果并不好。作者认为可能是因为卷积神经网络只能提取局部特征,于是作者使用了视频的光流信息,先提取了一遍视频光流特征,再将其送入卷积神经网络中,这样就取得了不错的效果。

双流卷积神经网络的示意图如下,其中上半部分的空间流卷积神经网络输入是静态的单帧图像,该网络输出动作的分类概率;下半部分的事件流卷积神经网络输入的是optical flow,即视频中的光流信息,最后也是通过softmax输出分类概率,最后这两个分类概率取加权平均值,就能得到最终的预测。

OpenCv提取光流的代码在这里:OpenCV: Optical Flow

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dWcTVVnW-1657977598402)(C:Users86133AppDataRoamingTyporatypora-user-imagesimage-20220716182601911.png)]

那么什么是视频中的光流信息呢,作者给出了如下解释。如下图(a)(b)所示,图中人物在抽出弓箭,图©就是相邻两帧的光流信息,图中的箭头就是人物手臂的运动方向,也就是视频的运动特征。因为光流有x,y两个方向,其中图(d)就表示这个视频x方向的光流特征,图(e)表示视频y方向的光流特征。假设图片长度为320,宽度为240,那么输入的相邻两帧视频维度即为(320,240,3),图©中光流图的维度即为(320,240,2)2代表xy两个方向,最后图(d)(e)的维度就是(320,240,1)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tgZ4HLvK-1657977598404)(C:Users86133AppDataRoamingTyporatypora-user-imagesimage-20220716184303131.png)]

为了得到整个视频的光流特征,作者认为不能直接把视频的光流图像输入2D卷积神经网络,因为这样还是不能得到视频之间的连续信息。作者想出了以下两种方法得到输入的光流信息,分别是optical flow stacking和trajectory stacking,如下图所示。

optical flow stacking的计算公式如下,

I

τ

(

u

,

v

,

2

k

1

)

=

d

τ

+

k

1

x

(

u

,

v

)

I

τ

(

u

,

v

,

2

k

)

=

d

τ

+

k

1

y

(

u

,

v

)

,

u

=

[

1

;

w

]

,

v

=

[

1

;

h

]

,

k

=

[

1

;

L

]

.

begin{aligned} &I_{tau}(u, v, 2 k-1)=d_{tau+k-1}^{x}(u, v) \ &I_{tau}(u, v, 2 k)=d_{tau+k-1}^{y}(u, v), quad u=[1 ; w], v=[1 ; h], k=[1 ; L] . end{aligned}

Iτ(u,v,2k1)=dτ+k1x(u,v)Iτ(u,v,2k)=dτ+k1y(u,v),u=[1;w],v=[1;h],k=[1;L].
trajectory stacking的计算公式如下:

I

τ

(

u

,

v

,

2

k

1

)

=

d

τ

+

k

1

x

(

p

k

)

I

τ

(

u

,

v

,

2

k

)

=

d

τ

+

k

1

y

(

p

k

)

,

u

=

[

1

;

w

]

,

v

=

[

1

;

h

]

,

k

=

[

1

;

L

]

.

begin{aligned} &I_{tau}(u, v, 2 k-1)=d_{tau+k-1}^{x}left(mathbf{p}_{k}right) \ &I_{tau}(u, v, 2 k)=d_{tau+k-1}^{y}left(mathbf{p}_{k}right), quad u=[1 ; w], v=[1 ; h], k=[1 ; L] . end{aligned}

Iτ(u,v,2k1)=dτ+k1x(pk)Iτ(u,v,2k)=dτ+k1y(pk),u=[1;w],v=[1;h],k=[1;L].
其中

p

k

mathbf{p}_{k}

pk 是轨迹上的第

k

k

k个点, 从第

τ

tau

τ帧的位置

(

u

,

v

)

(u, v)

(u,v)开始,且由以下递归关系定义:

p

1

=

(

u

,

v

)

;

p

k

=

p

k

1

+

d

τ

+

k

2

(

p

k

1

)

,

k

>

1.

mathbf{p}_{1}=(u, v) ; quad mathbf{p}_{k}=mathbf{p}_{k-1}+mathbf{d}_{tau+k-2}left(mathbf{p}_{k-1}right), k>1 .

p1=(u,v);pk=pk1+dτ+k2(pk1),k>1.
其中,这里的每一帧已经被resize为(224,224)的大小。左边表示视频的每一帧都在相同位置取出光流信息,即每次都在P1的位置上取光流的值,右边这种方式表示沿着光流运动的轨迹来取光流的值,即在第T+1帧时第T帧在P1的点运动到了P2,那么就继续从P2的位置开始取光流信息。最终作者发现左边的方式效果更好一些。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kohksSLm-1657977598405)(C:Users86133AppDataRoamingTyporatypora-user-imagesimage-20220716190640020.png)]

那么光流怎么作为网络输入呢?假设给定L+1帧的一个视频,可以得到L个光流图,这L个光流图可以作为(w,h,2L)这么大的一个张量输入网络。因为每个光流图都有L个x方向的光流和L个y方向的光流,所以2L这个channel的叠加方式就是(x1,x2…,xL,y1,y2,…yL)。再看回图1,作者设计的训练网络中Spatial stream ConNet部分有RGB三个channel,然后作者每个视频从中抽样出11帧图片,可以获得10个光流图,因此Temporal stream ConvNet部分有20个channel。

那么最后的结果是怎么通过双流网络获得的呢?对于空间流卷积网络,作者从每个视频中等间隔抽取25帧图片,然后抽取这个图片的四个边角和中心框,再将图像翻转,重复上述操作。这样一张照片就被增强为了10张照片。由于每个视频从中抽取了25帧,增强过后一个视频就变成了250帧,这250张照片分别输入空间流卷积网络,得到动作是每个类别的概率。最后这250个概率值取平均,就可得到这个视频属于每个类别的总概率。对于时间流卷积网络,作者在上述25帧之后都取了连续的11帧,然后将张量送入卷积网络,再取平均,得到每个类别的平均概率。最后将这两个流的结果做一个late fusion,即结果相加再除以2,得到最终的双流网络预测结果。

我从github上看到一份pytorch复现代码,但是目前还没有尝试运行,链接如下:blacknwhite5/pytorch-two-stream-CNN: Two-Stream Convolutional Networks for Action Recognition in Videos (github.com)

pytorch网络实现细节:

class SpatialNet(nn.Module):
    def __init__(self):
        super(SpatialNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=7, stride=2),
            # nn.batchnorm2d(96),
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
            nn.LocalResponseNorm(2),
            nn.Conv2d(96, 256, kernel_size=5, stride=2),
            # nn.batchnorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
            nn.LocalResponseNorm(2),
            nn.Conv2d(256, 512, kernel_size=3),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3),
            # nn.batchnorm2d(512),            
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(2048, 4096),
            nn.Dropout(),
            nn.Linear(4096, 2048),
            nn.Dropout(),
            nn.Linear(2048, 5),
            
        )
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
    
class TemporalNet(nn.Module):
    def __init__(self):
        super(TemporalNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=7, stride=2),
            # nn.batchnorm2d(96),
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
            nn.LocalResponseNorm(2),
            nn.Conv2d(96, 256, kernel_size=5, stride=2),
            # nn.batchnorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
            nn.LocalResponseNorm(2),
            nn.Conv2d(256, 512, kernel_size=3),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3),
            # nn.batchnorm2d(512),            
            nn.ReLU(),
            nn.MaxPool2d(3, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(2048, 4096),
            nn.Dropout(),
            nn.Linear(4096, 2048),
            nn.Dropout(),
            nn.Linear(2048, 5),
            
        )

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>