译者 | 张哲刚
审校 | 重楼
自动驾驶汽车是不能犯错误的,忽视一个红绿灯或一个行人都可能意味着灾难。但城市环境是动态的,在这样的环境中目标检测是一个大难题。
我使用空洞空间卷积池化金字塔(ASPP)和迁移学习来优化自动驾驶汽车的目标检测,结果如何呢?结果是这个模型能够在多个尺度下很好地检测到目标,即使在光线不太好的情形下,实时运行的效果也非常好。
下面叙述一下我的实施过程。
面临问题:户外目标检测
自动驾驶汽车依靠卷积神经网络(CNNs)来检测目标物体,但现实世界中有很多干扰因素,例如:
- 交通灯大小比例是变化的——距离远时较小,距离近时较大。
- 车道标记会随着角度而变形。
- 会有遮挡的情形——可能会看不到停放的汽车后面的行人。
- 照明条件的差异——可能会有阴影、眩光或夜间驾驶情形。
传统的卷积神经网络(CNNs)难以进行多尺度目标检测,如果从零开始训练则需要很长时间。这时候空洞空间卷积池化金字塔(ASPP)和迁移学习就有了用武之地。
ASPP:以不同的比例来检测捕获目标
CNNs适用于大小固定的目标,但现实世界中目标物体的大小和距离大都是各不相同的。 空洞空间卷积池化金字塔(ASPP)通过使用膨胀卷积,来检测和捕获目标多个尺度的特征,从而解决了这个问题。
ASPP 的工作原理
ASPP使用多个具有不同膨胀率的卷积滤波器来提取不同分辨率的特征,涵盖了小型目标、大型目标以及介于两者之间的所有目标物体。
下面讲讲我是如何在PyTorch中实现ASPP的,将组归一化和注意力机制相结合,在复杂的应用环境中也能够表现出强大的性能:
复制import torch import torch.nn as nn import torch.nn.functional as F class ASPP(nn.Module): """ A more advanced ASPP with optional attention and group normalization. """ def__init__(self,in_channels,out_channels,dilation_rates=(6,12,18), groups=8): super(ASPP,self).__init__() self.aspp_branches = nn.ModuleList() #1x1 Conv branch self.aspp_branches.append( nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False), nn.GroupNorm(groups, out_channels), nn.ReLU(inplace=True) ) ) ) For rate in dilation_rates: self.aspp_branches.append( nn.Sequential( nn.Conv2d(in_channels,out_channels,kernel_size=3,stride=1, padding=rate,dilatinotallow=rate,bias=False), nn.GroupNorm(groups,out_channels), nn.ReLU(inplace=True) ) ) #Global average pooling branch self.global_pool = nn.AdaptiveAvgPool2d((1, 1)) self.global_conv = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, bias=False), nn.GroupNorm(groups, out_channels), nn.ReLU(inplace=True) ) #Attention mechanism to refine the concatenated features self.attention = nn.Sequential( nn.Conv2d(out_channels*(len(dilation_rates)+2), out_channels, kernel_size =1, bias=False), nn.Sigmoid() ) self.project=nn.Sequential( nn.Conv2d(out_channels*(len(dilation_rates)+2), out_channels, kernel_size=1, bias=False), nn.GroupNorm(groups, out_channels), nn.ReLU(inplace=True) ) def forward(self, x): cat_feats = [ ] for branch in self.aspp_branches: cat_feats.append(branch(x)) g_feat = self.global_pool(x) g_feat = self.global_conv(g_feat) g_feat = F.interpolate(g_feat,size=x.shape[2:], mode='bilinear', align_corners=False) cat_feats.append(g_feat) #Concatenate along channels x_cat = torch.cat(cat_feats, dim=1) #channel-wise attention att_map = self.attention(x_cat) x_cat = x_cat * att_map out = self.project(x_cat) return out
实现原理:
- 不同的感受野可以使模型一次性检测到小型目标(例如远处的红绿灯)和大型目标(例如公共汽车)。
- 全局平均池化分支衍生的全局上下文有助于消除对目标的误判断。
- 轻量级注意力着重于信息量最大的通道,从而提高复杂纷乱场景下的检测准确性。
成果:
- 不同规格尺度的目标均可以检测得到(不再漏掉较小的红绿灯)。
- 平均精确度(mAP)提高14%。
- 更好地处理了遮挡问题,部分隐藏的目标也能够检测到。
迁移学习:站在巨人的肩膀之上
当预先训练的模型已经存在时,从零开始训练一个目标检测模型并不是一个理想选择。这时候,我们可以利用迁移学习来微调一个已经理解目标的模型。
我使用了 DETR(Detection Transformer),这是Facebook AI基于Transformer的对象检测模型。它能够学习上下文,比如,它不仅可以识别到一个停车标志,还能理解这是道路场景组成的一部分。
下面是我在自动驾驶数据集上微调DETR的操作:
复制import torch import torch.nn as nn from transformers import DetrConfig, DetrForObjectDetection class CustomBackbone(nn.Module): def __init__(self,in_channels=3,hidden_dim=256): super(CustomBackbone, self).__init__() # Example: basic conv layers + ASPP self.initial_conv= nn.Sequential( nn.Conv2d(in_channels, 64, kernel_sizestride=2, padding=3,bias=False), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3,stride=2, padding=1) ) self.aspp=ASPP(in_channels=64,out_channels=hidden_dim) def forward(self, x): x = self.initial_conv(x) x = self.aspp(x) return x Class DETRWithASPP(nn.Module): def __init__(self, num_classes=91): super(DETRWithASPP, self).__init__() self.backbone = CustomBackbone() config=DetrConfig.from_pretrained("facebook/detr-resnet-50") config.num_labels = num_classes self.detr=DetrForObjectDetection.from_pretrained("facebook/detr-resnet-50",config=config) self.detr.model.backbone.body = nn.Identity() def forward(self, images, pixel_masks=None): features = self.backbone(images) feature_dict = { "0": features } outputs=self.detr.model(inputs_embeds=None, pixel_values=None,pixel_mask=pixel_masks, features=feature_dict,output_attentions=False) return outputs model = DETRWithASPP(num_classes=10) images = torch.randn(2, 3, 512, 512) outputs = model(images)
成果:
- 训练时间缩短了80%。
- 改善了夜间和大雾天气时的实际性能。
- 训练时需要相对较少的标记数据。
使用合成图像来增强数据
自动驾驶汽车需要海量的数据集,但现实世界中的标记数据却很有限。那怎么办呢?解决方法是使用生成对抗网络(GAN)生成合成数据。
我使用GAN创建了虽是虚拟但非常逼真的车道标记和交通场景,以扩展数据集。
下面是一个简单的GAN,用于生成车道标记:
复制import torch import torch.nn as nn Import torch.nn.functional as F class LaneMarkingGenerator(nn.Module): """ A DCGAN-style generator designed for producing synthetic lane or road-like images. Input is a latent vector (noise), and the output is a (1 x 64 x 64) grayscale image. You can adjust channels, resolution, and layers to match your target data. """ def __init__(self, z_dim=100, feature_maps=64): super(LaneMarkingGenerator, self).__init__() self.net = nn.Sequential( #Z latent vector of shape (z_dim, 1, 1) nn.utils.spectral_norm(nn.ConvTranspose2d(z_dim, feature_maps * 8, 4, 1, 0, bias=False)), nn.BatchNorm2d(feature_maps * 8), nn.ReLU(True), #(feature_maps * 8) x 4 x 4 nn.utils.spectral_norm(nn.ConvTranspose2d(feature_maps * 8, feature_maps * 4, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps * 4), nn.ReLU(True), #(feature_maps * 4) x 8 x 8 nn.utils.spectral_norm(nn.ConvTranspose2d(feature_maps * 4, feature_maps * 2, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps * 2), nn.ReLU(True), #(feature_maps * 2) x 16 x 16 nn.utils.spectral_norm(nn.ConvTranspose2d(feature_maps * 2, feature_maps, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps), nn.ReLU(True), #(feature_maps) x 32 x 32 nn.utils.spectral_norm(nn.ConvTranspose2d(feature_maps, 1, 4, 2, 1, bias=False)), nn.Tanh() ) def forward(self, z): return self.net(z) class LaneMarkingDiscriminator(nn.Module): """ A DCGAN-style discriminator. It takes a (1 x 64 x 64) image and attempts to classify whether it's real or generated (fake). """ def __init__(self, feature_maps=64): super(LaneMarkingDiscriminator, self).__init__() self.net = nn.Sequential( #1x 64 x 64 nn.utils.spectral_norm(nn.Conv2d(1, feature_maps, 4, 2, 1, bias=False)), nn.LeakyReLU(0.2, inplace=True), #(feature_maps) x 32 x 32 nn.utils.spectral_norm(nn.Conv2d(feature_maps, feature_maps * 2, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps * 2), nn.LeakyReLU(0.2, inplace=True), #(feature_maps * 2) x 16 x 16 nn.utils.spectral_norm(nn.Conv2d(feature_maps * 2, feature_maps * 4, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps * 4), nn.LeakyReLU(0.2, inplace=True), #(feature_maps * 4) x 8 x 8 nn.utils.spectral_norm(nn.Conv2d(feature_maps * 4, feature_maps * 8, 4, 2, 1, bias=False)), nn.BatchNorm2d(feature_maps * 8), nn.LeakyReLU(0.2, inplace=True), #(feature_maps * 8) x 4 x 4 nn.utils.spectral_norm(nn.Conv2d(feature_maps * 8, 1, 4, 1, 0, bias=False)), ) def forward(self, x): return self.net(x).view(-1)
成果:
- 不需要手动标记,数据集增加了5倍 。
- 经过训练的模型对于边缘场景的处理更加稳健。
- 数据集偏差得以减少(训练样本更加多样化)。
最终成果:目标检测得以更加智能、更加快速
通过结合 ASPP、迁移学习和合成数据,我为自动驾驶汽车构建了一个更精确而又可扩展的目标检测系统。最终主要成果如下:
- 目标检测速度:110 毫秒/帧
- 较小目标检测(红绿灯):+14%mAP
- 遮挡处理:更强大的遮挡物检测功能
- 训练时间:缩短至6小时
- 所需训练数据:50%可以由GANs合成
下一步:如何让它变得更出色
- 添加实时跟踪功能,随时跟踪检测到的目标。
- 使用更先进的Transformers(如OWL-ViT)进行零样本目标检测。
- 进一步优化推理速度,以便更好地在嵌入式硬件上部署。
结论
ASPP、Transformers和数据合并这三项组合算得上是自主目标检测的三面手,它们能够把以往那些反应迟钝、容易出现盲点的模型进化为快速而敏锐的系统,从而可以在一个街区之外就能观测到红绿灯。通过采用膨胀卷积来实现多尺度目标检测,利用迁移学习来进行快速微调,还能够使用GAN生成的数据来填补每一个空白。这样,我们能够将推理时间缩短接近一半,并节省大量的训练时间。这是一个巨大的飞跃,使得自动驾驶汽车可以像我们人类一样观察这个世界,并且更快、更精确。哪怕是在最混乱无序的街道上,有朝一日也定能够信心十足地飞驰。
译者介绍
张哲刚,51CTO社区编辑,系统运维工程师,国内较早一批硬件评测及互联网从业者,曾入职阿里巴巴。
原文标题:How I Made Object Detection Smarter for Self-Driving Cars With Transfer Learning & ASPP,作者:Vineeth Reddy Vatti