如何理解自動(dòng)駕駛,SLAM,BEV,訓(xùn)練數(shù)據(jù)源常見(jiàn)術(shù)語(yǔ)?(4)
圖29實(shí)際上是在照片坐標(biāo)系(uv)上拓展了一個(gè)深度Z構(gòu)成的新坐標(biāo)系。由于LSS默認(rèn)是5路攝像頭,把5個(gè)Frustum送到get_geometry函數(shù)里,會(huì)輸出5路Frustum構(gòu)成的一個(gè)組合籠子,其張量尺寸變?yōu)椋築 x N x D x H x W x 3,其中B是batch_size,默認(rèn)是4組訓(xùn)練數(shù)據(jù),N是相機(jī)數(shù)量5。get_geometry里一開(kāi)始要做一個(gè)
#undo post-transformation
這玩意是干啥的?這跟訓(xùn)練集有關(guān),在深度學(xué)習(xí)里里,有一種增強(qiáng)現(xiàn)有訓(xùn)練樣本的方法,一般叫做Augmentation(其實(shí)AR技術(shù)里這個(gè)A就是Augmentation,增強(qiáng)的意思),通過(guò)把現(xiàn)有的訓(xùn)練數(shù)據(jù)做一些隨機(jī)的:翻轉(zhuǎn)/平移/縮放/裁減,給樣本添加一些隨機(jī)噪音(Noise)。比如,在不做樣本增強(qiáng)前,相機(jī)的角度是不變的,訓(xùn)練后的模型只認(rèn)這個(gè)角度的照片,而隨機(jī)增強(qiáng)后再訓(xùn)練,模型可以學(xué)習(xí)出一定角度范圍變化內(nèi)的適應(yīng)性,也就是Robustness。
圖30Augmentation技術(shù)也是有相關(guān)理論和方法的,這里就貼個(gè)圖不贅述了。數(shù)據(jù)增強(qiáng)的代碼一般都是位于DataLoader內(nèi):
class NuscData(torch.utils.data.Dataset):
def sample_augmentation(self):
回到剛才的get_geometry,數(shù)據(jù)增強(qiáng)會(huì)給照片增加一些隨機(jī)變化,但相機(jī)本身是必須固定的,這樣才能讓DNN模型學(xué)習(xí)這些隨機(jī)變化的規(guī)律并去適應(yīng)它們。所以將5路Frustum的安置到車(chē)身坐標(biāo)系時(shí)候要先去掉(undo)這些隨機(jī)變化。然后通過(guò):
# cam_to_ego
points = torch.cat((points[:, :, :, :, :, :2] * points[:, :, :, :, :, 2:3],
points[:, :, :, :, :, 2:3]
), 5)
combine = rots.matmul(torch.inverse(intrins))
points = combine.view(B, N, 1, 1, 1, 3, 3).matmul(points).squeeze(-1)
points += trans.view(B, N, 1, 1, 1, 3)
將各路Frustum從相機(jī)坐標(biāo)系轉(zhuǎn)入車(chē)輛自身坐標(biāo)系,注意這里的intrins是相機(jī)內(nèi)參,rots和trans是相機(jī)外參,這些都是nuScenes訓(xùn)練集提供的,這里只有intrincs用了逆矩陣,而外參沒(méi)有,因?yàn)閚uScenes是先把每個(gè)相機(jī)放在車(chē)身原點(diǎn),然后按照各路相機(jī)的位姿先做偏移trans再做旋轉(zhuǎn)rots,這里就不用做逆運(yùn)算了。如果換個(gè)數(shù)據(jù)集或者自己架設(shè)相機(jī)采集數(shù)據(jù),要搞清楚這些變換矩陣的定義和計(jì)算順序。四視圖大概就是這個(gè)樣子:
圖31
LSS中推理深度和相片特征的模塊位于:
class CamEncode(nn.Module):
def __init__(self, D, C, downsample):
super(CamEncode, self).__init__()
self.D = D
self.C = C
self.trunk = EfficientNet.from_pretrained("efficientnet-b0")
self.up1 = Up(320+112, 512)
self.depthnet = nn.Conv2d(512, self.D + self.C, kernel_size=1, padding=0)
trunk用于同時(shí)推理原始的深度和圖片特征,depthnet用于將trunk輸出的原始數(shù)據(jù)解釋成LSS所需的信息,depthnet雖是卷積網(wǎng)但卷積核(Kernel)尺寸只有1個(gè)像素,功能接近一個(gè)全連接網(wǎng)FC(Full Connected),F(xiàn)C日常的工作是:分類(lèi)或者擬合,對(duì)圖片特征而言,它這里類(lèi)似分類(lèi),對(duì)深度特征而言,它這里類(lèi)似擬合一個(gè)深度概率分布。EfficientNet是一種優(yōu)化過(guò)的ResNet,就當(dāng)做一個(gè)高級(jí)的卷積網(wǎng)(CNN)看吧。對(duì)于這個(gè)卷積網(wǎng)而言,圖片特征和深度特征在邏輯上沒(méi)有區(qū)別,兩者都位于trunk上的同一個(gè)維度,只是區(qū)分了channel而已。這就引出了另外一個(gè)話題:從單張2D圖片上是如何推理/提取深度特征的。這類(lèi)問(wèn)題一般叫做:Monocular Depth Estimation,單目深度估計(jì)。一般這類(lèi)系統(tǒng)內(nèi)部分兩個(gè)階段:粗加工(Coarse Prediction)和精加工(Refine Prediction),粗加工對(duì)整個(gè)畫(huà)面做一個(gè)場(chǎng)景級(jí)別的簡(jiǎn)單深度推測(cè),精加工是在這個(gè)基礎(chǔ)上識(shí)別更細(xì)小的物體并推測(cè)出更精細(xì)的深度。這類(lèi)似畫(huà)家先用簡(jiǎn)筆畫(huà)出場(chǎng)景輪廓,然后再細(xì)致勾勒局部畫(huà)面。除了用卷積網(wǎng)來(lái)解決這類(lèi)深度估計(jì)問(wèn)題,還有用圖卷積網(wǎng)(GCN)和Transformer來(lái)做的,還有依賴(lài)測(cè)距設(shè)備(RangeFinder)輔助的DNN模型,這個(gè)話題先不展開(kāi)了,龐雜程度不亞于BEV本身。那么LSS這里僅僅采用了一個(gè)trunk就搞定深度特征是不是太兒戲了,事實(shí)上確實(shí)如此。LSS估計(jì)出的深度準(zhǔn)頭和分辨率極差,參看BEVDepth項(xiàng)目里對(duì)LSS深度問(wèn)題的各種測(cè)試報(bào)告:https://github.com/Megvii-BaseDetection/BEVDepthBEVDepth的測(cè)試?yán)锇l(fā)現(xiàn):如果把LSS深度估計(jì)部分的參數(shù)換成一個(gè)隨機(jī)數(shù),并且不參與學(xué)習(xí)過(guò)程(Back Propagation),其BEV的總體測(cè)試效果只有很小幅度的降低。但必須要說(shuō)明,Lift的機(jī)制本身是很強(qiáng)的,這個(gè)突破性的方法本身沒(méi)問(wèn)題,只是深度估計(jì)這個(gè)環(huán)節(jié)可以再加強(qiáng)。
LSS的訓(xùn)練過(guò)程還有另外一個(gè)問(wèn)題:相片上大約有1半的數(shù)據(jù)對(duì)訓(xùn)練的貢獻(xiàn)度為0,其實(shí)這個(gè)問(wèn)題是大部分BEV算法都存在的:
圖32右邊的標(biāo)注數(shù)據(jù)實(shí)際上只描述了照片紅線以下的區(qū)域,紅線上半部都浪費(fèi)了,你要問(wèn)LSS里的模型對(duì)上半部都計(jì)算了些什么,我也不知道,因?yàn)闆](méi)有標(biāo)注數(shù)據(jù)可以對(duì)應(yīng)上,而大部分的BEV都是這么訓(xùn)練的,所以這是一個(gè)普遍現(xiàn)象。訓(xùn)練時(shí),BEV都會(huì)選擇一個(gè)固定面積范圍的周遭標(biāo)注數(shù)據(jù),而照片一般會(huì)拍攝到更遠(yuǎn)的景物,這兩者在范圍上天生就是不匹配的,另一方面部分訓(xùn)練集只關(guān)注路面標(biāo)注,缺乏建筑,因?yàn)檠巯翨EV主要解決的是駕駛問(wèn)題,不關(guān)心建筑/植被。這也是為什么圖17哪里的深度圖和LSS內(nèi)部真實(shí)的深度圖是不一致的,真實(shí)深度圖只有接近路面這部分才有有效數(shù)據(jù):
圖33所以整個(gè)BEV的DNN模型勢(shì)必有部分算力被浪費(fèi)了。目前沒(méi)看到任何論文關(guān)于這方面的研究。
接著繼續(xù)深入LSS的Lift-Splat計(jì)算過(guò)程:
def get_depth_feat(self, x):
x = self.get_eff_depth(x)
# Depth
x = self.depthnet(x)
depth = self.get_depth_dist(x[:, :self.D])
new_x = depth.unsqueeze(1) * x[:, self.D:(self.D + self.C)].unsqueeze(2)
return depth, new_x
def get_voxels(self, x, rots, trans, intrins, post_rots, post_trans):
geom = self.get_geometry(rots, trans, intrins, post_rots, post_trans)
x = self.get_cam_feats(x)
x = self.voxel_pooling(geom, x)
return x
這里的new_x是把深度概率分布直接乘上了圖片紋理特征,為了便于直觀理解,我們假設(shè)圖片特征有3個(gè)channel:c1,c2,c3,深度只有3格:d1,d2,d3。我們從圖片上取某個(gè)像素,那么它們分別代表的意義是:c1:這個(gè)像素點(diǎn)有70%的可能性是車(chē)子,c2:有20%的可能性是路,c3:有10%的可能性是信號(hào)燈, d1:這個(gè)像素有80%的可能是在深度1,d2:有15%的可能性是在深度2,d3:有%5的可能性是在深度3上。如果把它們相乘的到:
那么這個(gè)像素最大的概率是:位于深度1的一輛車(chē)子。這也就是LSS里:
公式的意義,注意它這里把圖像特征叫做c(Context), a_d的意義是深度沿視線格子的概率分布,d是深度。new_x就是這個(gè)計(jì)算結(jié)果。前面說(shuō)過(guò),由于圖像特征和深度都是通過(guò)trunk訓(xùn)練出來(lái)的,它們位于同一維度,只是占用channel不同,深度占用了前self.D(41)個(gè)channel,Context占用了后面self.C(64)個(gè)channel。由于new_x是分別按照每路相機(jī)的Frustum單獨(dú)計(jì)算的,而5個(gè)Frustum有重疊區(qū)域,須要做作數(shù)據(jù)融合,所以在voxel_pooling里計(jì)算好格子的索引和對(duì)應(yīng)的空間位置,通過(guò)這個(gè)對(duì)應(yīng)關(guān)系,把new_x的內(nèi)容一一裝入指定索引的格子。LSS在voxel_pooling的計(jì)算力引入了cumsum這個(gè)機(jī)制,雖然有很多文章在解釋它,但這里不建議花太多功夫,它只是一個(gè)計(jì)算上的小技巧,對(duì)整個(gè)LSS是錦上添花的事,不是必要的。
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。