本文复现CVPR2021 重新思考空洞卷积,超越Deeplab,BiseNetv2让分割实现实时+高精度
详细实现细节以及相关数据集请移步aistudio
RegSeg:重新思考空洞卷积| 超越DeepLab、BiSeNetv2 - 飞桨AI Studio - 人工智能学习与实训社区
哔哩哔哩论文讲解视频地址:
小熊带你读论文:RegSeg重新思考空洞卷积CVPR2021论文讲解_哔哩哔哩_bilibili
一、第十七届全国大学生智能汽车竞赛:完全模型组线上资格赛!
比赛地址:https://aistudio.baidu.com/aistudio/competition/detail/131/0/task-definition
1.比赛简介
车辆的普及和人们对出行体验的重视,对地图导航提出了更高的要求。基于车载影像的AR导航系统能够精准快速的识别车道线、行驶区域,不仅能够辅助驾驶系统进行高效的决策,同时结合终端渲染与语音技术,也能够为用户带来更为智能精准的导航体验。
作为『新一代人工智能地图』的百度地图,秉承着『用科技让出行更简单』的使命,借助于图像识别、语音识别、大数据处理等人工智能技术,大幅提升地图数据采集和处理的自动化程度,实现道路覆盖超过1000万公里,已成为业内AI化水平最高、搭载的AI技术最强最丰富的地图数据厂商。本次比赛数据由百度地图提供,要求在统一的计算资源下,能够对车载影像中实车道线、虚车道线和斑马线等区域进行快速精准的识别。
本次竞赛的题目和数据由百度地图数据引擎部提供,模型和基线相关技术支持由深度学习技术平台部提供,一站式AI开发平台AI Studio由百度AI技术生态部提供。期待参赛者们能够以此为契机,共同推进智能交通领域的发展。 2.比赛任务
要求参赛者利用提供的训练数据,在统一的计算资源下,实现一个能够识别虚车道线、实车道线和斑马线具体位置和类别的深度学习模型,不限制深度学习任务。 样例示范:
3.比赛数据
数据集描述 本届赛题数据集包括16000张可以直接用于训练的车载影像数据,官方采用分割连通域标注方法,对这些图片数据标注了虚车道线、实车道线和斑马线的区域和类别,其中标注数据以灰度图的方式存储。 标注数据是与原图尺寸相同的单通道灰度图,其中背景像素的灰度值为0,不同类别的目标像素分别为不同的灰度值。实车道线、虚车道线和斑马线类别编号分别为1、2和3。选手报名之后,可在「数据集介绍」tab中下载数据集。
图像与标注数据依文件名编号对应,组织结构如下:
| -- train
| | -- image
| | |-- 00001.jpg
| | |-- …
| | |-- 08000.jpg
| | -- label
| | |-- 00001.png
| | |-- …
| | | -- 08000.png
需注意的是,灰度值较低仅影响肉眼可见性,并不影响深度学习训练。如需要观测标注图像,可以将其拉伸至0~255的RGB图像。 4.提交要求
本次比赛要求选手使用飞桨PaddlePaddle2.1及以上版本生成端到端深度学习模型。
选手需上传训练模型、预测代码及环境库(可选)的zip形式压缩包到AI Studio平台进行自动评测。其中,预测代码需命名为predict.py,model目录不超过200M(不压缩),整体压缩包不超过1G,目录结构约定如下:
| -- model
| | -- xxx.pb
| | …
| -- env
| | -- xxx.lib
| |…
| -- predict.py
| -- …
二.环境准备
In [ ]
# 首先从gitee上下载PaddleDetection和PaddleSeg程序
# 此步骤已经完成,在此基线环境内,不需要再次进行
# 当前为大家提供了PaddleSeg2.1版本做为基线,用户也可以选择PaddleSeg2.2、2.3或者develop版本。
%cd work/
!git clone https://gitee.com/paddlepaddle/PaddleSeg.git
# 查看一下是否下载成功
!ls /home/aistudio/work
# 在AIStudio环境中安装相关依赖
!pip install paddleseg -i https://mirror.baidu.com/pypi/simple
三.数据准备
1.解压数据
In [ ]
!unzip data/data125507/CM_data_2022.zip
2.划分数据集
In [ ]
data_base_dir='/home/aistudio/work/data_2022_baseline/'
data_folader='JPEGImages'
label_folader='Annotations'
train_list='/home/aistudio/train_list.txt'
eval_list='/home/aistudio/eval_list.txt'
test_list='/home/aistudio/test_list.txt'
def split_eval_train_test(root:str,train_rate:float,eval_rate:float,test_rate:float,train_txt:str,eval_txt:str,test_txt:str):
image_folader_path=os.path.join(root,data_folader)
label_folader_path=os.path.join(root,label_folader)
data_list=[x for x in os.listdir(image_folader_path) ]
random.shuffle(data_list)
train_data=random.sample(data_list,k=int(len(data_list)*train_rate))
test_val_data=[]
for data in data_list:
if data not in train_data:
test_val_data.append(data)
random.shuffle(test_val_data)
test_data=random.sample(test_val_data,k=int(len(test_val_data)*(test_rate)/(test_rate+eval_rate)))
#清空txt
files=open(train_txt,'w')
files=open(test_txt,'w')
files=open(eval_txt,'w')
for data in data_list:
if data in train_data:
files=open(train_txt,'a')
files.write(os.path.join(image_folader_path,data)+'\t'+os.path.join(label_folader_path,data[:-4]+'.png')+'\n')
elif data in test_data:
files=open(test_txt,'a')
files.write(os.path.join(image_folader_path,data)+'\t'+os.path.join(label_folader_path,data[:-4]+'.png')+'\n')
else:
files=open(eval_list,'a')
files.write(os.path.join(image_folader_path,data)+'\t'+os.path.join(label_folader_path,data[:-4]+'.png')+'\n')
print('数据集完成分割')
split_eval_train_test(data_base_dir,0.6,0.2,0.2,train_list,eval_list,test_list)
四.模型训练
1.模型配置
由上图可知RegSeg的性能远比BiseNetv2_L的精度要搞且计算量明显小于BiseNetv2。 在之前语义分割通常采用的是ImageNet的Backbone去做特征提取,通常采用较大的池化尺寸(如PPM)或较大的膨胀率(如ASPP)去提高感受野,但是这样的运算代价十分的大。并且语义分割任务来说输入图片的分辨率与得到的精度有一定的关系,而ImageNet的分辩率明显有悖于这个原则。 ImageNet Backbone通常在最后几个卷积层中有大量的通道, 但是Mobilenetv3发现把最后一个卷积层的通道数减半并不会影响语义分割性能。 ImageNet模型获取分辨率约为224x224的输入图像,但语义分割的图像要大得多, 如 Cityscapes的图像分辨率为1024x2048, CamVid的图像分辨率为720x960
经过本人验证:相同训练条件下在第一轮验证就能比BiseNetv2的miou提高0.0001
总结:这篇文章其实提出了一个非常有意思的观点,在之前的结构设计中大部分都十分小心点使用着空洞卷积这个模块,因为当我们膨胀卷积率大到一定程度的时候,就会失去提高感受野的作用,甚至起到负面影响。就是因为膨胀率过大中间忽略的信息过多,这一点在ocrnet中提出结构能够解决,而本文设计的就是在膨胀率一定的情况下不断扩大卷积核,以达到在相同膨胀率下不断填充"空隙"避免大的膨胀率反而不起作用的现象出现。细节可以去看看我的阅读论文视频(献丑)
1.键入层与深度可分离卷积层
import paddle
import paddle.nn as nn
import paddle.io as io
import numpy as np
import cv2
import os
import sys
#放入paddleseg时请加如下行
#from paddleseg.cvlibs import manager
#from paddleseg.models import layers
#from paddleseg.utils import utils
class Stem(nn.Layer):
def __init__(self,in_dim=3,out_dim=32):
super(Stem,self).__init__()
self.conv_bn_relu=nn.Sequential(
nn.Conv2D(in_channels=in_dim,out_channels=out_dim,kernel_size=3,stride=2,padding=1),
nn.BatchNorm2D(out_dim),
nn.ReLU()
)
def forward(self,inputs):
outputs=self.conv_bn_relu(inputs)
return outputs
class Conv_Bn_Relu(nn.Layer):
def __init__(self,in_dim,out_dim,kernel,stride,pad):
super(Conv_Bn_Relu,self).__init__()
self.conv_bn_relu=nn.Sequential(
nn.Conv2D(in_channels=in_dim,out_channels=out_dim,kernel_size=kernel,stride=stride,padding=pad),
nn.BatchNorm2D(out_dim),
nn.ReLU()
)
def forward(self,inputs):
outputs=self.conv_bn_relu(inputs)
return outputs
class DepthWise_Conv(nn.Layer):
def __init__(self,in_channels,kernel_size=3,stride=1,padding=1,groups=16,dilate=1):
super(DepthWise_Conv,self).__init__()
self.conv=nn.Sequential(
nn.Conv2D(in_channels=in_channels,out_channels=in_channels,kernel_size=kernel_size,stride=stride,padding=padding,dilation=dilate,groups=groups),
nn.BatchNorm2D(in_channels),
nn.ReLU()
)
def forward(self, inputs):
x=self.conv(inputs)
return x
2.通道注意力机制:SE
class SEBlock(nn.Layer):
'''
注意力模块
'''
def __init__(self,in_channel,reduce=4):
super(SEBlock,self).__init__()
self.avg_pool=nn.AdaptiveAvgPool2D(output_size=1)#平均池化
self.flatten=nn.Flatten()
self.fc1=nn.Linear(in_features=in_channel,out_features=in_channel//reduce)#全局池化后的全连接层
#将缩小的fc层再缩放回原来的维数
self.fc2=nn.Linear(in_features=in_channel//reduce,out_features=in_channel)
self.relu=nn.ReLU()
self.hsigmoid=nn.Hardsigmoid()
def forward(self,inputs):
x=self.avg_pool(inputs)#B,C,1,1
x=self.flatten(x)#B C*1*1
x=self.fc1(x)
x=self.relu(x)
x=self.fc2(x)
x=self.hsigmoid(x)#得到注意力值
x=x.reshape((inputs.shape[0],inputs.shape[1],1,1))
#print('x_shape:',x.shape,' inputs_shape',inputs.shape)
output=x*inputs
return output
3.残差连接块DBlock
class DBlock(nn.Layer):
def __init__(self,in_dim,out_dim,stride,d2):
super(DBlock,self).__init__()
#all block g=16
self.g=16
self.stride=stride
self.conv_1x1_in=Conv_Bn_Relu(in_dim=in_dim,out_dim=out_dim,kernel=1,stride=1,pad=0)
dkernel=d2*2+1
same_pad=(dkernel-stride+1)//2
if self.stride==1:
self.split_DW1=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=1,dilate=1,groups=self.g)
self.split_DW2=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=same_pad,dilate=d2,groups=self.g)
elif self.stride==2:
self.split_DW1=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=1,dilate=1,groups=self.g)
self.split_DW2=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=same_pad,dilate=d2,groups=self.g)
self.se=SEBlock(in_channel=out_dim*2,reduce=4)
if stride==1:
self.conv_1x1_out=Conv_Bn_Relu(in_dim=out_dim*2,out_dim=in_dim,kernel=1,stride=1,pad=0)
else:
self.conv_1x1_out=Conv_Bn_Relu(in_dim=out_dim*2,out_dim=out_dim,kernel=1,stride=1,pad=0)
self.avgpool=nn.AvgPool2D(kernel_size=2,stride=2)
self.point_Conv=Conv_Bn_Relu(in_dim=in_dim,out_dim=out_dim,kernel=1,stride=1,pad=0)
def forward(self,inputs):
h=inputs
x=self.conv_1x1_in(inputs)
split=x
x1=self.split_DW1(split)
x2=self.split_DW2(x)
concat_=paddle.concat([x1,x2],axis=1)
se=self.se(concat_)
out1=self.conv_1x1_out(se)
if(self.stride==2):
h=self.avgpool(h)
h=self.point_Conv(h)
outputs=h+out1
return outputs
class DBlock_last(nn.Layer):
def __init__(self,in_dim,out_dim,stride=1,d2=14):
super(DBlock_last,self).__init__()
#all block g=16
self.g=16
self.stride=stride
self.conv_1x1_in=Conv_Bn_Relu(in_dim=in_dim,out_dim=out_dim,kernel=1,stride=1,pad=0)
dkernel=d2*2+1
same_pad=(dkernel-stride+1)//2
self.split_DW1=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=1,dilate=1,groups=self.g)
self.split_DW2=DepthWise_Conv(in_channels=out_dim,kernel_size=3,stride=stride,padding=same_pad,dilate=d2,groups=self.g)
self.se=SEBlock(in_channel=out_dim*2,reduce=4)
self.conv_1x1_out=Conv_Bn_Relu(in_dim=out_dim*2,out_dim=out_dim,kernel=1,stride=1,pad=0)
self.conv=Conv_Bn_Relu(in_dim=in_dim,out_dim=out_dim,kernel=1,stride=1,pad=0)
def forward(self,inputs):
h=inputs
x=self.conv_1x1_in(inputs)
split=x
x1=self.split_DW1(split)
x2=self.split_DW2(x)
concat_=paddle.concat([x1,x2],axis=1)
se=self.se(concat_)
out1=self.conv_1x1_out(se)
h=self.conv(h)
outputs=h+out1
return outputs
#放入paddleseg时请取消下行注释
#@manager.MODELS.add_component
class RegSeg(nn.Layer):
def __init__(self,num_classes=4,pretrained=None):
super(RegSeg,self).__init__()
self.out_dims=[32,48,128,256,256,256,256,320]
self.strides=[2,2,2,2,1,1,1,1]
self.repeat= [3,2,1,4,6]
self.dilate_rate=[1,1,1,2,4,14,14]
self.stem=Stem(in_dim=3,out_dim=self.out_dims[0])
self.dblock1=DBlock(in_dim=self.out_dims[0],out_dim=self.out_dims[1],stride=self.strides[1],d2=1)
self.dblock2=DBlock(in_dim=self.out_dims[1],out_dim=self.out_dims[2],stride=self.strides[2],d2=1)
self.dblock2_re=DBlock(in_dim=self.out_dims[2],out_dim=self.out_dims[2],stride=1,d2=1)
#1/4
self.dblock3=DBlock(in_dim=self.out_dims[2],out_dim=self.out_dims[3],stride=self.strides[3],d2=1)
self.dblock3_re=DBlock(in_dim=self.out_dims[3],out_dim=self.out_dims[3],stride=1,d2=1)
#1/8
self.dblock4=DBlock(in_dim=self.out_dims[3],out_dim=self.out_dims[4],stride=self.strides[4],d2=2)
self.dblock5=DBlock(in_dim=self.out_dims[4],out_dim=self.out_dims[5],stride=self.strides[5],d2=4)
self.dblock5_re=DBlock(in_dim=self.out_dims[5],out_dim=self.out_dims[5],stride=1,d2=4)
self.dblock6=DBlock(in_dim=self.out_dims[5],out_dim=self.out_dims[6],stride=self.strides[6],d2=14)
self.dblock6_re=DBlock(in_dim=self.out_dims[6],out_dim=self.out_dims[6],stride=1,d2=14)
self.dblock_last=DBlock_last(in_dim=self.out_dims[6],out_dim=self.out_dims[7],stride=1,d2=14)
self._16_dimconv=Conv_Bn_Relu(in_dim=320,out_dim=128,kernel=1,stride=1,pad=0)
self._8_dimconv=Conv_Bn_Relu(in_dim=256,out_dim=128,kernel=1,stride=1,pad=0)
self.conv2=Conv_Bn_Relu(in_dim=128,out_dim=64,kernel=3,stride=1,pad=1)
self._4_dimconv=Conv_Bn_Relu(in_dim=128,out_dim=8,kernel=1,stride=1,pad=0)
self.conv_last1=Conv_Bn_Relu(in_dim=72,out_dim=64,kernel=3,stride=1,pad=0)
self.conv_last2=nn.Conv2D(in_channels=64,out_channels=num_classes,kernel_size=1,stride=1,padding=0)
def forward(self,inps):
x=self.stem(inps)
x=self.dblock1(x)
x=self.dblock2(x)
for i in range(self.repeat[0]):
x=self.dblock2_re(x)
out1_4=x
x=self.dblock3(x)
for i in range(self.repeat[1]):
x=self.dblock3_re(x)
out2_8=x
x=self.dblock4(x)
x=self.dblock5(x)
for i in range(self.repeat[3]):
x=self.dblock5_re(x)
x=self.dblock6(x)
for i in range(self.repeat[4]):
x=self.dblock6_re(x)
x=self.dblock_last(x)
out_16=x
#1/16与1/8变dim
out_up_16=self._16_dimconv(out_16)
out_up_8=paddle.nn.functional.interpolate(out_up_16,size=[out2_8.shape[2],out2_8.shape[3]])
out_sum_8=self._8_dimconv(out2_8)
out_sum_8=out_sum_8+out_up_8
out_sum_8=self.conv2(out_sum_8)
out_sum_8=paddle.nn.functional.interpolate(out_sum_8,size=[out1_4.shape[2],out1_4.shape[3]])
out_4_dimconv=self._4_dimconv(out1_4)
concat_last=paddle.concat([out_sum_8,out_4_dimconv],axis=1)
outputs=self.conv_last1(concat_last)
outputs=self.conv_last2(outputs)
outputs=paddle.nn.functional.interpolate(outputs,size=[inps.shape[2],inps.shape[3]])
return [outputs]
def init_weight(self):
if self.pretrained is not None:
utils.load_entire_model(self, self.pretrained)
测试网络结构是否正确
data=paddle.randn([1,3,480,480])
model=RegSeg()
print(model(data)[0].shape)
[1, 4, 480, 480]
2.使用paddleseg配件进行训练
使用步骤: 1.在paddleseg/models子目录下 创建RegSeg.py文件 2.在__init__.py文件下添加如下内容:
3.在config子目录下添加yml文件
_base_: '../_base_/car2022.yml'
model:
type: RegSeg
num_classes: 4
optimizer:
type: sgd
weight_decay: 0.0001
loss:
types:
- type: CrossEntropyLoss
coef: [1]
batch_size: 2
iters: 160000
lr_scheduler:
type: PolynomialDecay
learning_rate: 0.01
end_lr: 0.0
power: 0.9
下面就可以直接进行训练啦!
!!!!!切记在添加入paddleseg时取消注释代码块中的部分!!!
--config 后面跟自己的yml路径,其余可以不更改
模型训练
In [ ]
%cd PaddleSeg
!python train.py \
--config configs/regseg/train.yml \
--do_eval \
--use_vdl \
--save_interval 500 \
--save_dir output
模型评估
In [ ]
!python val.py --config configs/regseg/train.yml --model_path output/best_model/model.pdparams
预测
In [ ]
!python predict.py --config configs/regseg/train.yml --model_path output/best_model/model.pdparams --image_path ./../../data/data_2022_baseline/JPEGImages --save_dir output/result
关于作者
- 姓名:Diana!NeuralNetwork
- 武汉理工大学智能制造工程专业2019级本科在读
- 感兴趣方向:计算机视觉、推理部署、迁移学习、强化学习
- AIstudio主页:Diana!NN
- 欢迎大家有问题一起交流讨论,共同进步~