SCNN

Table of Contents

1. SCNN

https://arxiv.org/pdf/1712.06080.pdf

SCNN 是一个针对车道线识别的语义分割模型.

1.1. Tusimple Dataset

https://github.com/TuSimple/tusimple-benchmark

tusimple dataset 格式为:

{
    "lanes": [
        [-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 587, 611, 600, 587, 574, 562, 549,
         536, 523, 510, 497, 484, 472, 459, 446, 433, 420, 407, 395, 382, 369, 356,
         343, 330, 317, 305, 292, 279, 266, 253, 240, 227, 215, 202, 189, 176, 163,
         150, 137, 125, 112, 99, 86, 73, 60, 47],

        [-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 634, 665, 679, 693, 708, 717, 727,
         737, 747, 756, 766, 776, 785, 795, 805, 815, 824, 834, 844, 853, 863, 873,
         883, 892, 902, 912, 921, 931, 941, 950, 960, 970, 980, 989, 999, 1009, 1018,
         1028, 1038, 1048, 1057, 1067, 1077, 1086, 1096, 1106],

        [-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 685, 730, 766, 797, 828, 858, 889,
         919, 950, 981, 1011, 1042, 1073, 1103, 1134, 1164, 1195, 1226, 1256, -2, -2,
         -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
         -2, -2, -2, -2, -2, -2]
    ],

    "h_samples": [160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 290,
                  300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420, 430,
                  440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570,
                  580, 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710],

    "raw_file": "hello_tusimple.jpg"
}

上面的 json 显示三条车道线线上的 56 个采样点的坐标, 例如:

第一条车道线的点为: (587,260), (611, 270), (600, 280)… 第二条车道线的点为: (634,260), (665, 270), (679, 280)… 第二条车道线的点为: (685,260), (730, 270), (766, 280)…

每条 "lane" 中的负数坐标例如 -2 表示该车道线在该位置没有点. 这个样本中每条国道的前 10 个点都是负数, 意味着图片的上面部分没有车道线, 因为这部分通常是天空背景.

tusimple dataset 标注的是车道线上的点的坐标, 但更多的数据集例如 culane (https://xingangpan.github.io/projects/CULane.html) 标注的直接是代表图片分割信息的 png 图片

1.2. Label

模型的 label 称为 prob_map, 它的格式为: (H, W, class), 用来表示图片中的每个点所属的 class (背景, 车道线 0, 车道线 1, … ), 语义分割模型实际是一个逐像素 (per-pixel) 的分类模型.

有的数据集例如 culane 提供的标签直接就是 (H, W, class) 的格式. 由于 tusimple 提供的是坐标的形式, 需要预处理一下, 这里用了一个有些 trick 的方法:

  1. 用 cv2 生成一个全 0 的图片
  2. 针对每一条车道线, 用 cv2.line 把点依次连接起来, 而且用车道线的类别的做为 line color
  3. 把图片保存为 png, 即可得到和 culane 同样的标签数据.
  4. 训练时用 cv2 读取 png, 但只需要读取 rgb 中的某一个值 (例如 r) 做为该像素的 class label 即可

1.3. Network

scnn.png

scnn 网络结构分为三部分:

  1. backbone
  2. message_passing
  3. upsample

1.3.1. backbone

backbone 使用 vgg16 的特征提取部分, 但删除 33, 43 层 maxpooling, 因为 pooling 导致空间信息丢失; 同时替换了最后几层 conv2d 为 dilated conv2d, 因为删除 pooling 会导致 receptive field 变小, 通过 dilated conv2d 可以增加改善这种情况.

关于 pooling 与 semantic segmentation 可以参考 FCN

关于 dilated conv2d 与 semantic segmentation 可以参考: dilated_net 2016/4, deeplab 2017/5

# 输出 img resize 为 [1, 3, 288, 800]
x = self.backbone(img)
# x: [1, 512, 36, 100]

1.3.2. message_passing

# layer1, layer2 基本都是普通的 conv2d
x = self.layer1(x)
# x: [1, 128, 36, 100]
x = self.message_passing_forward(x)
# x: [1, 128, 36, 100]
x = self.layer2(x)
# x: [1, 5, 36, 100]

message_passing_forward 是 SCNN 最核心的部分, 一共需要做 4 遍 message_passing: up, down, left, right

以 down 为例, 计算步骤:

  1. 按 H 方向切成 H 个 (C,1,W) 的 slice
  2. out[0]=slice[0]
  3. out[1]=slice[1]+conv(out[0])
  4. out[2]=slice[2]+conv(out[1])

计算完以后 out shape 和 input shape 是一样的, 因为每一层 slice 时每层使用的 conv 都是使用的同一个 conv, 且:

  1. stride = 1
  2. padding=k//2
  3. k 为奇数

所以 O=(I+2p-k)//s+1=I

另外整个过程和 rnn 很像: slice 是 input 序列, out 是 hidden state 序列

按论文的说法, 这样操作更容易捕获长条形状的物体 (比如车道线) 或大的物体的空间信息

Intuition:

为了让 cnn 能捕获大物体的信息, 需要有较大的 receptive field:

  1. 通过 pooling
  2. 通过更大的 kernel

前者会导致信息丢失, 后者计算量太大

SCNN 的作法是通过类似 rnn 的作法让空间中不同位置的信息能传递(合并)在一起, 让网络能看到整个空间的信息

1.3.3. upsample

# interpolate 在这里是在做 up sampling, 36*8=288, 100*8=800
#
# 在 semantic segmentation 网络中最后都需要 up sampling 操作, 才能生成针
# 对每个像素的 prob_map
#
# 如果暂时忽略 message_passing_forward 不关注, scnn 实际就是不断的做
# conv2d, 把 channel 降成 5 之后, 再做一个 up sampling 恢复原始的尺寸
#
seg_pred = F.interpolate(x, scale_factor=8, mode="bilinear", align_corners=True)
# seg_pred: [1, 5, 288, 800]
x = self.layer3(x)
# x: [1, 5, 18, 50]
x = x.view(-1, self.fc_input_feature)
# x: [1, 4500]
exist_pred = self.fc(x)
# exist_pred: [1, 4]

1.4. Inference

scnn 的输出有两个: seg_pred 和 exist_pred

seg_pred: [5, 288, 800], 表示每一个像素所属 class 的概率.

0 表示属于 backgroud 的概率, 即这个像素不属于任何一条车道线. 1 表示属于车道线 0 的概率 … 4 表示属于车道线 3 的概率, 一共有 4 条车道线

exist_pred: [1, 4], 表示检测到有相应车道的概率

seg_pred 这种针对所有像素的分类概率叫做 prob_map. lane detection 本质上是一个语义分割的问题

有了 prob_map 后, 那些 argmax(prob_map,axis=0) == 1 的点就是预测出来的车道线 0 上的点

1.5. Loss

scnn 的 loss 基本上就是针对每个像素计算 cross entropy loss 即可

Backlinks

Semantic Segmentation (Semantic Segmentation > SCNN): SCNN

Author: [email protected]
Date: 2022-01-18 Tue 00:00
Last updated: 2022-01-28 Fri 13:54

知识共享许可协议