Birds-Eyes-View(BEV):鳥瞰圖,這個(gè)詞本身沒什么特別意義,但在自動(dòng)駕駛(Autonomous Driving,簡稱AD)領(lǐng)域逐漸普及后變成了這個(gè)行業(yè)內(nèi)的一種術(shù)語。
Simultaneous Localization and Mapping(SLAM):并發(fā)定位與地圖測繪,相對(duì)于BEV的另外一種感知技術(shù)。
Perception:感知,SLAM和BEV在AD領(lǐng)域里都是協(xié)助控制系統(tǒng)了解車輛周圍狀況的感知技術(shù):知道自己在哪,有哪些障礙物,障礙物在自己的什么方位,距離多遠(yuǎn),哪些障礙物是靜態(tài)的那些是移動(dòng)的,等等相關(guān)信息,便于隨后做出駕駛決策。
SLAM VS BEV:SLAM主要通過各種傳感器掃描周圍空間的物體結(jié)構(gòu),以3維數(shù)據(jù)來描述這些信息。BEV同樣通過傳感器掃描獲知周邊狀況,主要以2維數(shù)據(jù)來描述這些信息。從應(yīng)用范圍來講,目前SLAM更為廣闊,在AD火起來之前主要應(yīng)用在VR/AR等領(lǐng)域,BEV主要集中在AD行業(yè)里。從技術(shù)實(shí)現(xiàn)來看,SLAM偏向于傳統(tǒng)數(shù)學(xué)工具,包括各種幾何/概率論/圖論/群論相關(guān)的軟件包,而BEV基本上清一色的基于深度神經(jīng)網(wǎng)絡(luò)DNN。兩者最好不要對(duì)立著看,很多情況下可以互補(bǔ)。
以下將側(cè)重于BEV的基礎(chǔ)介紹。
SLAM和BEV最基礎(chǔ)和核心的傳感器就是相機(jī)(Camera),所以兩者在計(jì)算過程中有大量的算力都被消耗在了圖像中信息提取/識(shí)別和變換計(jì)算。SLAM傾向于識(shí)別圖像中的特征(Feature)點(diǎn),屬于特征信息里的低級(jí)信息,通過計(jì)算這些特征點(diǎn)在不同圖像幀上的位置來獲取場景結(jié)構(gòu)以及相機(jī)自身的位姿(Position and Pose)。而BEV傾向于識(shí)別車輛/道路/行人/障礙物等等高級(jí)特征信息,這些是卷積網(wǎng)CNN和Transformer擅長的。
相機(jī)有兩個(gè)最基礎(chǔ)的數(shù)據(jù):內(nèi)參(Instrinsics)和外參(Extrinsics),內(nèi)參主要描述的是相機(jī)的CCD/CMOS感光片尺寸/分辨率以及光學(xué)鏡頭的系數(shù),外參主要描述的是相機(jī)在世界坐標(biāo)系下的擺放位置和朝向角度。
其中內(nèi)參的常見矩陣是:
其中fx和fy分別表示光學(xué)鏡頭的橫向/縱向焦距長度(Focus),正常情況下焦距是不分橫縱向的,但因?yàn)镃CD/CMOS感光片上的像素單元不夠正,如果這個(gè)像素是絕對(duì)的正方形,那么fx = fy,實(shí)際上很難做到,有微小的差異,導(dǎo)致光線經(jīng)過鏡頭投射到感光片上后,橫縱坐標(biāo)在單位距離上出現(xiàn)不等距的問題,所以相機(jī)模塊的廠家會(huì)測量這個(gè)差異并給出fx和fy來,當(dāng)然開發(fā)者也可以利用標(biāo)定(calibration)過程來測量這兩個(gè)值。
圖1
圖2
另外,在傳統(tǒng)的光學(xué)領(lǐng)域里,fx和fy的默認(rèn)單位是毫米:mm,但在這個(gè)領(lǐng)域默認(rèn)單位是像素:Pixel,導(dǎo)致很多有攝影經(jīng)驗(yàn)的人看到fx和fy的值都挺納悶,特別大,動(dòng)不動(dòng)就是大幾千,這數(shù)值都遠(yuǎn)超業(yè)余天文望遠(yuǎn)鏡了。為什么這里用像素?我們試著通過內(nèi)參計(jì)算一下相機(jī)的FOV(Field of View,視場大小,通常以角度為單位)就明白了:
圖3
這里fy是縱向焦距,h是照片高度。因?yàn)閔的單位是像素,所以fy也必須是像素,這樣才好便于計(jì)算機(jī)處理,所以fx和fy的單位就統(tǒng)一成了像素。其實(shí)都不用到計(jì)算機(jī)這步,CCD/CMOS感光片一般是要集成另外一塊芯片ISP(Image Signal Processor)的,這塊芯片內(nèi)部就要把感光數(shù)據(jù)轉(zhuǎn)成數(shù)字化的圖片,這里就可以用像素單位了。
內(nèi)參除了這個(gè)矩陣外還有一套畸變(Distortion)系數(shù)K,這個(gè)東西不詳細(xì)說了,正常的鏡頭成像后都是居中位置的變形小,四周變形大,一般通過標(biāo)定(Calibration)獲得這個(gè)參數(shù)后,對(duì)照片做反畸變處理,恢復(fù)出一個(gè)相對(duì)“正常”的照片。SLAM算法里很強(qiáng)調(diào)這個(gè)反畸變的重要性,因?yàn)樘卣鼽c(diǎn)在照片上的絕對(duì)位置直接關(guān)系到了定位和建圖的準(zhǔn)確性,而大部分的BEV代碼里看不到這個(gè)反畸變處理,一方面是BEV注重物體級(jí)別的高級(jí)特征,像素級(jí)別的輕微偏移影響不大,另一方面是很多BEV項(xiàng)目都是為了寫論文,采用了類似nuScenes/Argoverse這類訓(xùn)練數(shù)據(jù),這些數(shù)據(jù)的畸變比較小而已,一旦你在自己的項(xiàng)目里用了奇怪的鏡頭還是老老實(shí)實(shí)得做反畸變預(yù)處理。
圖4
外參就簡單多了,一個(gè)偏移(Transform)系數(shù)加一個(gè)旋轉(zhuǎn)(Rotation)系數(shù)。
3維空間里表述旋轉(zhuǎn)的計(jì)算方式常見的有2種:矩陣(Matrix)和四元數(shù)(Quaternion),為了防止矩陣方式存在萬向節(jié)死鎖(Gimbal Lock)問題,通常采用四元數(shù)來計(jì)算旋轉(zhuǎn)。但在AD領(lǐng)域里很少這么干,因?yàn)橄鄼C(jī)是固定在車子上,只有垂直于地面的軸(一般是Z軸)才會(huì)發(fā)生360度的旋轉(zhuǎn),根本無法引發(fā)萬向節(jié)問題,總不至于用戶堅(jiān)持在翻車的階段仍舊保持自動(dòng)駕駛這個(gè)詭異的需求。所以BEV的代碼里通常就是矩陣形式,SLAM因?yàn)檫€會(huì)用在AR和其它領(lǐng)域,相機(jī)不是相對(duì)固定的,所以會(huì)采用四元數(shù)。另外,AD領(lǐng)域里不考慮透視現(xiàn)象,所以外參都是仿射矩陣(Affine Matrix),這點(diǎn)和CG領(lǐng)域的3維渲染是不同的。
另外,一般文章里介紹內(nèi)參時(shí)還會(huì)考慮旋轉(zhuǎn)偏差,這是由于CCD/CMOS感光片在工廠里被機(jī)器給裝歪了,但AD領(lǐng)域一般不會(huì)考慮它,誤差太小,而相機(jī)安裝在車輛上時(shí)本身外參就有很大的相對(duì)旋轉(zhuǎn),不如一并算了,最后交由DNN學(xué)習(xí)過濾掉,而AR領(lǐng)域里的SLAM更是要主動(dòng)計(jì)算外參,這點(diǎn)毛毛雨就不考慮了。
內(nèi)外參了解之后,下一個(gè)基礎(chǔ)的重點(diǎn)就是坐標(biāo)系。AD的坐標(biāo)系有好幾個(gè),不事先理清楚就直接看代碼有點(diǎn)暈。
世界坐標(biāo)系(World Coordination),這個(gè)是真實(shí)世界空間里,車輛的位置和方位角,通常粗略的位置是由GNSS(Global Navigation Satellite System)衛(wèi)星定位系統(tǒng)獲取,GNSS包括了美國GPS/中國BDS/歐洲Galileo/毛子GLONASS/日本QZSS/印度IRNSS,各有千秋,定位精度一言難盡,一般標(biāo)稱的精度都是指:車輛在空曠地區(qū),上面有好幾顆定位衛(wèi)星罩著你,車輛靜止,定位設(shè)備天線粗壯,無其它信號(hào)源干擾的情況下的測試結(jié)果。如果你處在城市內(nèi),四周高樓林立,各種無線電干擾源,衛(wèi)星相對(duì)你時(shí)隱時(shí)現(xiàn),車速還不慢,這種情況下給你偏個(gè)幾十米都是對(duì)的起你了。為此有兩種常見解決方案:差分基站糾偏和地圖通行大數(shù)據(jù)糾偏。這能給你造成一種錯(cuò)覺:衛(wèi)星定位還是蠻準(zhǔn)的。不管怎么弄,最后得到的坐標(biāo)位置是經(jīng)緯度,但跟常規(guī)GIS(Geographic Information System)相比,AD的經(jīng)緯度不是球面坐標(biāo)系,而是展開成2維地圖的坐標(biāo)系,所以最終在系統(tǒng)內(nèi)的坐標(biāo)系也是有區(qū)別的,比如google會(huì)把WGS84的經(jīng)緯度換算成它自家地圖的矩形切片編碼,Uber提出過一種六邊形切片的H3坐標(biāo)編碼,百度則是在火星坐標(biāo)的基礎(chǔ)上疊加了一個(gè)BD09的矩形切片坐標(biāo),等等諸如此類。這些都是絕對(duì)坐標(biāo)位置,而通過類似SLAM技術(shù)掃描的高精度地圖還會(huì)在這個(gè)基礎(chǔ)上引入一些相對(duì)坐標(biāo)。不管怎么樣,最后在代碼里看到的只剩下XY了。但這些系統(tǒng)都不能獲取車輛朝向(地理正北為0度,地理正東為90度,依此類推,這仍舊是在2維地圖上表示方式),所以AD里的車輛角度都是指“軌跡朝向”,用當(dāng)前位置坐標(biāo)減去上一時(shí)刻的坐標(biāo)獲得一個(gè)指向性的矢量。當(dāng)然在高精度地圖的加持下,是可以通過SLAM技術(shù)算出車輛的瞬時(shí)方位角。在缺失GNSS定位的時(shí)候,比如過隧道,需要用車輛的IMU(Inertial Measurement Unit)這類芯片做慣性導(dǎo)航補(bǔ)充,它們提供的數(shù)值是一個(gè)相對(duì)的坐標(biāo)偏移,但隨著時(shí)間的推移累積誤差大,所以長時(shí)間沒有GNSS信號(hào)的時(shí)候,IMU表示也沒辦法。
BEV訓(xùn)練數(shù)據(jù)集的世界坐標(biāo)系(nuScenes World Coordination,其它訓(xùn)練集就不特別說明了),這個(gè)跟GNSS的絕對(duì)坐標(biāo)系就不同了:
圖5
這是一個(gè)nuScenes地圖,它的世界坐標(biāo)系是圖片坐標(biāo)系,原點(diǎn)在圖片左下角,單位是米,因此在使用訓(xùn)練數(shù)據(jù)集時(shí),是不用考慮經(jīng)緯度的。數(shù)據(jù)集中會(huì)根據(jù)時(shí)間序列給出車輛的瞬時(shí)位置,也就是在這個(gè)圖片上的XY。
Ego坐標(biāo)系(Ego Coordination),在BEV里,這個(gè)Ego是特指車輛本身,它是用來描述攝像機(jī)/激光雷達(dá)(Lidar,light detection and ranging)/毫米波雷達(dá)(一般代碼里就簡稱為Radar)/IMU在車身上的安裝位置(單位默認(rèn)都是米)和朝向角度,坐標(biāo)原點(diǎn)一般是車身中間,朝向如圖:
圖6
所以車頭正放的相機(jī)默認(rèn)都是Yaw(Z軸)為0度,外參(Extrinsics Matrix)主要就是描述這個(gè)坐標(biāo)系的。
相機(jī)坐標(biāo)系(Camera Coordination),切記,這個(gè)不是照片坐標(biāo)系,坐標(biāo)原點(diǎn)在CCD/CMOS感光片的中央,單位是像素,內(nèi)參(Intrinsics Matrix)主要就是描述這個(gè)坐標(biāo)系的。
照片坐標(biāo)系(Image Coordination),坐標(biāo)原點(diǎn)在圖片的左上角,單位是像素,橫縱坐標(biāo)軸一般不寫成XY,而是uv。
圖7
左中右三套坐標(biāo)系分別為:Ego Coordination, Camera Coordination, Image Coordination。
所以,當(dāng)在BEV中做LSS(Lift,Splat,Shoot)時(shí),需要把照片中的像素位置轉(zhuǎn)換到世界坐標(biāo)系時(shí),要經(jīng)歷:
Image_to_Camera, Camera_to_Ego, Ego_to_World,用矩陣表示:
Position_in_World = Inv_World_to_Ego * Inv_Ego_to_Camera * Inv_Camera_to_Image * (Position_in_Image)
其中Inv_表示矩陣的逆。實(shí)際代碼里,Camera_to_Image通常就是Intrinsics參數(shù)矩陣,Ego_to_Camera就是Extrinsics參數(shù)矩陣。
這里要注意的一點(diǎn)是:fx,fy,它們實(shí)際上是這樣計(jì)算得到的:
Fx和Fy分別是橫向/縱向的鏡頭焦距,但單位是米,Dx和Dy分別是一個(gè)像素有幾米寬幾米高,得出fx和fy的單位就是像素。當(dāng)使用(Ego_to_Camera * Camera_to_Image)矩陣乘上Ego空間的坐標(biāo),會(huì)以像素為單位投影到照片空間,當(dāng)使用(Inv_Ego_to_Camera * Inv_Camera_to_Image)矩陣乘上照片空間的坐標(biāo),會(huì)以米為單位投影到Ego空間,不會(huì)有單位上的問題。
大部分的BEV是多攝像頭的,意味著要一次性把多組攝像頭拍攝的照片像素?fù)Q算到Ego或者世界坐標(biāo)系:
在統(tǒng)一的坐標(biāo)系下,多角度的照片才能正確得“環(huán)繞”出周邊的景象。另外還有一些單目(Monocular)攝像頭的BEV方案,它們有的不考慮Ego坐標(biāo)系,因?yàn)橹挥幸粋€(gè)朝向正前方(Yaw,Pitch,Roll全部為0)的攝像頭,而且原點(diǎn)就是這個(gè)攝像頭本身,所以直接從相機(jī)坐標(biāo)系跳到世界坐標(biāo)系。
Frustum,這個(gè)東西在3維渲染領(lǐng)域通常叫做“視錐體”,用來表示相機(jī)的可視范圍:
圖9
紅面和綠面以及線框包圍起來的空間就是視錐體,綠面通常叫做近平面(Near Plane),紅面叫做遠(yuǎn)平面(Far Plane),線框構(gòu)成的角度叫做FOV,如果CCD/CMOS成像的高寬相同,那么近平面和遠(yuǎn)平面就都是正方形,一個(gè)FOV就足以表示,反之,就要區(qū)分為FOVx和FOVy了,超出這個(gè)視錐體范圍的物體都不考慮進(jìn)計(jì)算。圖7中由6個(gè)三角面構(gòu)成了組合的可視范圍,實(shí)際上應(yīng)該是6個(gè)俯視的視錐體構(gòu)成,能看出視錐體之間是有交疊區(qū)域的,這些區(qū)域有利于DNN在訓(xùn)練/推理中對(duì)6組數(shù)據(jù)做相互矯正,提高模型準(zhǔn)確性,在不增加相機(jī)數(shù)量的前提下,如果想擴(kuò)大這個(gè)交疊區(qū)域,就必須選擇FOV更大的相機(jī),但FOV越大的相機(jī)一般鏡頭畸變就會(huì)越嚴(yán)重(反畸變再怎么做也只能一定程度上的矯正圖片),物體在圖片上的成像面積也越小,干擾DNN對(duì)圖片上特征的識(shí)別和提取。
BEV是個(gè)龐大的算法族,傾向于不同方向的算法選擇,粗略得看,有Tesla主導(dǎo)的以視覺感知流派,核心算法建立在多路攝像頭上,另外一大類是激光雷達(dá)+毫米波雷達(dá)+多路攝像頭的融合(Fusion)派,國內(nèi)很多AD公司都是融合派的,Google的Waymo也是。
嚴(yán)格得講,Tesla正在從BEV(Hydranet)過渡到一種新的技術(shù):Occupancy Network,從2維提升到3維:
圖10
無論是2維的還是3維的,都在試圖描述周遭空間的Occupany(占用)情況,只是一個(gè)用2維棋盤格來表述這種占用情況,一個(gè)是用3維的積木方式表述占用。DNN在度量這種占用時(shí)采用的是概率,比如我們直觀看到某個(gè)格子上是一輛車,而DNN給出的原始結(jié)果是:這個(gè)格子上,是車的可能性有80%,是路面的可能性為5%,是行人的可能性為3%。。。。。所以,在BEV代碼里,一般將各種可能出現(xiàn)的物體分了類,通常是兩大類:
不常變化的:車輛可通信區(qū)域(Driveable),路面(Road),車道(Lane),建筑(Building),植被(Foliage/Vegetation),停車區(qū)域(Parking),信號(hào)燈(Traffic Light)以及一些未分類靜態(tài)物體(Static),它們之間的關(guān)系是可以相互包容的,比如Driveable可以包含Road/Lane等等。
可變的,也就是會(huì)發(fā)生移動(dòng)的物體:行人(Pedestrian),小汽車(Car),卡車(Truck),錐形交通標(biāo)/安全桶(Traffic Cone)等等
這樣分類的目的是便于AD做后續(xù)的駕駛規(guī)劃(Planning,有的翻譯成決策)和控制(Control)。而BEV在感知(Perception)階段就是按照這些物體在格子上出現(xiàn)的概率打分,最后通過Softmax函數(shù)將概率歸一取出最大的那個(gè)可能性作為占用這個(gè)格子的物體類型。
但這有個(gè)小問題:BEV的DNN模型(Model)在訓(xùn)練階段,是要指明照片中各個(gè)物體是啥?也就是要在標(biāo)注數(shù)據(jù)(Labeled Data)上給各種物體打上類型標(biāo)簽的:
右邊的我們權(quán)當(dāng)做是標(biāo)注數(shù)據(jù)吧,左邊是對(duì)應(yīng)的相片,按照這個(gè)物體分類訓(xùn)練出來的DNN模型,真得跑上路面,如果遭遇了訓(xùn)練集里未出現(xiàn)的物體類型怎么辦?如果模型效果不好,比如某個(gè)姿勢奇葩的人體未被識(shí)別成行人和其它已知類型,又當(dāng)如何?Occupancy Network為此改變的感知策略,不再強(qiáng)調(diào)分類了(不是不分類,只是重點(diǎn)變了),核心關(guān)注路面上是否有障礙物(Obstacle),先保證別撞上去就行了,別管它是什么類型。3維的積木方式表述這種障礙物更為貼切,有的地方借用了3維渲染(Rendering/Shading)領(lǐng)域的常見概念把這種3維表述叫做體素(Voxel),想象一下我的世界(MineCraft)就很簡單了。
以上是視覺流派的簡述,混合派在干嘛?它們除了相機(jī)外,還側(cè)重于激光雷達(dá)的數(shù)據(jù),毫米波雷達(dá)由于數(shù)據(jù)品相太差逐漸退出,留守的去充當(dāng)停車?yán)走_(dá)了,也不能說它一無是處,Tesla雖然強(qiáng)調(diào)視覺處理,但也保留了一路朝向正前方的毫米波雷達(dá),而且AD這個(gè)領(lǐng)域技術(shù)變化非??欤洳欢∧奶煊行滤惴俺鲇帜馨押撩撞ɡ走_(dá)的價(jià)值發(fā)揚(yáng)光大一把。
激光雷達(dá)的好處是什么:可以直接測出物體的遠(yuǎn)近,精度比視覺推測出的場景深度要高很多,一般會(huì)轉(zhuǎn)化為深度(Depth)數(shù)據(jù)或者點(diǎn)云(Point Cloud),這兩者配套的算法有很長的歷史了,所以AD可以直接借用,減少開發(fā)量。另外,激光雷達(dá)可以在夜間或糟糕的天氣環(huán)境下工作,相機(jī)就抓瞎了。
但這幾天出現(xiàn)了一種新的感知技術(shù)HADAR(Heat-Assisted Detection and Ranging),可以和相機(jī)/激光雷達(dá)/毫米波雷達(dá)并列的傳感器級(jí)別感知技術(shù)。它的特點(diǎn)是利用特殊的算法把常規(guī)熱成像在夜間拍攝的圖片轉(zhuǎn)化為周圍環(huán)境/物體的紋理和深度,這個(gè)東西和相機(jī)配合能解決夜間視覺感知的問題。
以前的BEV為什么不提熱成像/紅外相機(jī),因?yàn)閭鹘y(tǒng)算法有些明顯的缺陷:只能提供場景的熱量分布,形成一張灰度(Gray)圖,缺乏紋理(Texture),原始數(shù)據(jù)缺乏深度信息,推算出的深度精度差,如果僅僅通過從灰度圖上提取的輪廓(Contour)和亮度過渡(Gradient),很難精確還原場景/物體的體積信息,并且目前的2維物體識(shí)別是很依賴紋理和色彩的。這個(gè)HADAR的出現(xiàn),恰好可以解決這個(gè)問題:在較暗的環(huán)境下提取場景的深度以及紋理:
圖13
左列,自上而下:
基礎(chǔ)的熱成像,簡稱T
用常規(guī)熱成像算法從T提取的深度
用HADAR算法從T提取的紋理圖
用HADAR算法從T提取的深度
真實(shí)場景的深度
右列,自上而下:
這個(gè)場景在白天用可見光相機(jī)拍攝的照片
通過照片推理的深度
真實(shí)場景的深度
HADAR的這個(gè)深度信息老牛逼了,對(duì)比一下激光雷達(dá)的效果就知道了:
激光雷達(dá)的掃描范圍是有限的,一般半徑100米,從上圖可以看出,沒有紋理信息,遠(yuǎn)處的場景也沒有深度了,掃描線導(dǎo)致其數(shù)據(jù)是個(gè)稀疏(Sparse)結(jié)構(gòu),想要覆蓋半徑更大更稠密(Dense)就必須買更昂貴的型號(hào),最好是停下來多掃一段時(shí)間。激光雷達(dá)模塊廠家在展示產(chǎn)品時(shí),當(dāng)然得給出更好看的圖了,只有AD研發(fā)人員才知道這里面有多苦。
以上都是基礎(chǔ)的概念,作為BEV算法的入門,必須先提到LSS(Lift,Splat,Shoot):
https://link.zhihu.com/?target=https%3A//github.com/nv-tlabs/lift-splat-shoot
老黃家的,很多文章都把它列為BEV的開山(Groundbreaking)之作。它構(gòu)建了一個(gè)簡單有效的處理過程:
把相機(jī)的照片從2維數(shù)據(jù)投影成3維數(shù)據(jù),然后像打蒼蠅一樣把它拍扁,再從上帝視角來看這個(gè)被拍扁的場景,特別符合人看地圖的直覺模式。一般看到這里會(huì)有疑惑的:都已經(jīng)建立了3維的場景數(shù)據(jù),3維不香么?干嘛還要拍扁?不是不想要3維,是沒辦法,它不是一個(gè)完善的3維數(shù)據(jù):
圖15
看過這玩意吧,它就是LSS的本質(zhì),從正面看,能形成一張2維照片,這個(gè)照片被LSS拉伸到3維空間后就是上圖,你從BEV的視角也就是正上方向下看會(huì)是啥?什么都看不出來,所以后續(xù)要拍扁(Splat),具體過程是這樣:
先提取圖像特征和深度(Feature and Depth,LSS里是同時(shí)提取的,后面會(huì)具體解釋),深度圖類似
只能說類似,并不準(zhǔn)確,后面也會(huì)具體說明的,這個(gè)深度信息可以構(gòu)建一個(gè)偽3D模型(Point Cloud點(diǎn)云模式),類似圖15:
圖18
看著還行,但把這個(gè)3D模型轉(zhuǎn)到BEV俯視角下,估計(jì)親娘都認(rèn)不出來了:
圖19
拍扁后結(jié)合特征Feature再做一次語義識(shí)別,形成:
圖20
這個(gè)就是喜聞樂見的BEV圖了。以上是對(duì)LSS的直觀認(rèn)知,算法層面是如何實(shí)現(xiàn)的?
先給單個(gè)相機(jī)可拍攝的范圍構(gòu)建一個(gè)立方體模樣的鐵絲籠子(高8寬22深41),祭出大殺器Blender:
圖21
這里是示意圖,不要糾結(jié)于格子的數(shù)量和尺寸。這個(gè)3D網(wǎng)格代表的是一路相機(jī)的視錐體(Frustum),前面貼過視錐體的形狀(圖9),這里變形成立方體,在相機(jī)空間里看這個(gè)照片和這個(gè)立體網(wǎng)格的關(guān)系就是:
圖22
右邊是個(gè)正對(duì)著網(wǎng)格立方體的相機(jī)示意圖,相片提取深度后(深度圖的實(shí)際像素尺寸是高8寬22):
圖23
把這個(gè)深度圖按照每個(gè)像素的深度沿著紅線方向展開(Lift)后:
圖24
可以看到,部分深度像素已經(jīng)超出了視錐體的范圍,因?yàn)長SS一開始就假設(shè)了這么個(gè)有限范圍的籠子,超出部分直接過濾掉。這里必須提醒一下:LSS并不是直接算出每個(gè)像素的深度,而是推理出每個(gè)像素可能處于籠子里每個(gè)格子的概率,圖24是已經(jīng)通過Softmax提取出每個(gè)像素最有可能位于哪個(gè)格子,然后把它裝進(jìn)對(duì)應(yīng)格子的示意結(jié)果,便于理解,更準(zhǔn)確的描述如下:
圖25
在圖25中選取深度圖的某個(gè)像素(紅色格子,事實(shí)上LSS的深度圖分辨率是很小的,默認(rèn)只有8*22像素,所以這里可以用一個(gè)格子當(dāng)做一個(gè)像素),它隸屬于籠子下方邊沿的一條深度格子(這條格子其實(shí)就代表相機(jī)沿著深度看向遠(yuǎn)方的一條視線):
圖26
圖25中的那個(gè)紅色的深度像素,沿著圖26這條視線格子的概率分布就是:
圖27
黃線的起伏表示2D深度圖像素在Lift后沿著視線3D深度的概率分布(Depth Distribution,我這是示意性得畫法,不是嚴(yán)格按照實(shí)際數(shù)據(jù)做的)。等價(jià)于LSS論文里的這張圖:
圖28
LSS中構(gòu)建立方籠子的代碼位于:
?
?
class?LiftSplatShoot(nn.Module): ????def?__init__(self,?grid_conf,?data_aug_conf,?outC): ????????self.frustum?=?self.create_frustum() ????def?create_frustum(self): ????????#?D?x?H?x?W?x?3 ????????frustum?=?torch.stack((xs,?ys,?ds),?-1) ????????return?nn.Parameter(frustum,?requires_grad=False) ????def?get_geometry(self,?rots,?trans,?intrins,?post_rots,?post_trans): ????????"""Determine?the?(x,y,z)?locations?(in?the?ego?frame) ????????of?the?points?in?the?point?cloud. ????????Returns?B?x?N?x?D?x?H/downsample?x?W/downsample?x?3 ????????""" ????????B,?N,?_?=?trans.shape ????????#?undo?post-transformation ????????#?B?x?N?x?D?x?H?x?W?x?3 ????????points?=?self.frustum?-?post_trans.view(B,?N,?1,?1,?1,?3) ????????points?=?torch.inverse(post_rots).view(B,?N,?1,?1,?1,?3,?3).matmul(points.unsqueeze(-1)) ????????#?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) ????????return?points
?
?
為了便于分析,我裁減了代碼。單個(gè)相機(jī)的frustum尺寸為:D x H x W x 3(深度D:41,高度H:8,寬度W:22),也就是創(chuàng)建了一個(gè)D x H x W的容器,容器的每個(gè)格子里存儲(chǔ)了這個(gè)格子的坐標(biāo)值(X,Y,Z)。
圖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里一開始要做一個(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)的意思),通過把現(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。
圖30
Augmentation技術(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的安置到車身坐標(biāo)系時(shí)候要先去掉(undo)這些隨機(jī)變化。
然后通過:
?
?
#?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)入車輛自身坐標(biāo)系,注意這里的intrins是相機(jī)內(nèi)參,rots和trans是相機(jī)外參,這些都是nuScenes訓(xùn)練集提供的,這里只有intrincs用了逆矩陣,而外參沒有,因?yàn)閚uScenes是先把每個(gè)相機(jī)放在車身原點(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日常的工作是:分類或者擬合,對(duì)圖片特征而言,它這里類似分類,對(duì)深度特征而言,它這里類似擬合一個(gè)深度概率分布。EfficientNet是一種優(yōu)化過的ResNet,就當(dāng)做一個(gè)高級(jí)的卷積網(wǎng)(CNN)看吧。對(duì)于這個(gè)卷積網(wǎng)而言,圖片特征和深度特征在邏輯上沒有區(qū)別,兩者都位于trunk上的同一個(gè)維度,只是區(qū)分了channel而已。
這就引出了另外一個(gè)話題:從單張2D圖片上是如何推理/提取深度特征的。這類問題一般叫做:Monocular Depth Estimation,單目深度估計(jì)。一般這類系統(tǒng)內(nèi)部分兩個(gè)階段:粗加工(Coarse Prediction)和精加工(Refine Prediction),粗加工對(duì)整個(gè)畫面做一個(gè)場景級(jí)別的簡單深度推測,精加工是在這個(gè)基礎(chǔ)上識(shí)別更細(xì)小的物體并推測出更精細(xì)的深度。這類似畫家先用簡筆畫出場景輪廓,然后再細(xì)致勾勒局部畫面。
除了用卷積網(wǎng)來解決這類深度估計(jì)問題,還有用圖卷積網(wǎng)(GCN)和Transformer來做的,還有依賴測距設(shè)備(RangeFinder)輔助的DNN模型,這個(gè)話題先不展開了,龐雜程度不亞于BEV本身。
那么LSS這里僅僅采用了一個(gè)trunk就搞定深度特征是不是太兒戲了,事實(shí)上確實(shí)如此。LSS估計(jì)出的深度準(zhǔn)頭和分辨率極差,參看BEVDepth項(xiàng)目里對(duì)LSS深度問題的各種測試報(bào)告:
https://link.zhihu.com/?target=https%3A//github.com/Megvii-BaseDetection/BEVDepth
BEVDepth的測試?yán)锇l(fā)現(xiàn):如果把LSS深度估計(jì)部分的參數(shù)換成一個(gè)隨機(jī)數(shù),并且不參與學(xué)習(xí)過程(Back Propagation),其BEV的總體測試效果只有很小幅度的降低。但必須要說明,Lift的機(jī)制本身是很強(qiáng)的,這個(gè)突破性的方法本身沒問題,只是深度估計(jì)這個(gè)環(huán)節(jié)可以再加強(qiáng)。
LSS的訓(xùn)練過程還有另外一個(gè)問題:相片上大約有1半的數(shù)據(jù)對(duì)訓(xùn)練的貢獻(xiàn)度為0,其實(shí)這個(gè)問題是大部分BEV算法都存在的:
右邊的標(biāo)注數(shù)據(jù)實(shí)際上只描述了照片紅線以下的區(qū)域,紅線上半部都浪費(fèi)了,你要問LSS里的模型對(duì)上半部都計(jì)算了些什么,我也不知道,因?yàn)闆]有標(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主要解決的是駕駛問題,不關(guān)心建筑/植被。
這也是為什么圖17哪里的深度圖和LSS內(nèi)部真實(shí)的深度圖是不一致的,真實(shí)深度圖只有接近路面這部分才有有效數(shù)據(jù):
圖33
所以整個(gè)BEV的DNN模型勢必有部分算力被浪費(fèi)了。目前沒看到任何論文關(guān)于這方面的研究。
接著繼續(xù)深入LSS的Lift-Splat計(jì)算過程:
?
?
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%的可能性是車子,c2:有20%的可能性是路,c3:有10%的可能性是信號(hào)燈, d1:這個(gè)像素有80%的可能是在深度1,d2:有15%的可能性是在深度2,d3:有%5的可能性是在深度3上。如果把它們相乘的到:
那么這個(gè)像素最大的概率是:位于深度1的一輛車子。這也就是LSS里:
公式的意義,注意它這里把圖像特征叫做c(Context), a_d的意義是深度沿視線格子的概率分布,d是深度。new_x就是這個(gè)計(jì)算結(jié)果。前面說過,由于圖像特征和深度都是通過trunk訓(xùn)練出來的,它們位于同一維度,只是占用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)的空間位置,通過這個(gè)對(duì)應(yīng)關(guān)系,把new_x的內(nèi)容一一裝入指定索引的格子。
LSS在voxel_pooling的計(jì)算力引入了cumsum這個(gè)機(jī)制,雖然有很多文章在解釋它,但這里不建議花太多功夫,它只是一個(gè)計(jì)算上的小技巧,對(duì)整個(gè)LSS是錦上添花的事,不是必要的。
編輯:黃飛
?
評(píng)論
查看更多