譯者 | 張哲剛
審校 | 重樓
自動駕駛汽車是不能犯錯誤的,忽視一個紅綠燈或一個行人都可能意味著災難。但城市環境是動態的,在這樣的環境中目標檢測是一個大難題。
我使用空洞空間卷積池化金字塔(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