【參考演算法】地平線雙目深度估計參考演算法 StereoNetPlus-v1.2.1
1.引言
本文將介紹地平線基於公版的雙目深度估計演算法 StereoNet 做的最佳化設計。首先介紹了雙目深度估計的原理以及雙目點雲和 Lidar 點雲的對比,然後由公版 StereoNet 的介紹切入到地平線參考演算法的針對性最佳化,最後對視覺化結果進行了解讀。
2.雙目深度估計原理
2.1 基本假設
假設雙目系統是標準形式,即:
- 兩相機內引數相同,即焦距、解析度等引數一致;
- 兩相機光軸平行;
- 成像平面處於同一水平線;
假設以左相機座標系為主座標系,也就是說兩相機只存在 X 軸方向上的平移變換。
2.2 幾何法
- P 是待測物體上的某一點,
- O_R 與 O_T 分別是兩個相機的光心,
- p、p':點 P 在兩個相機感光器上的成像點,相機的成像平面經過旋轉後放在了鏡頭前方
- f 為相機焦距,B 為兩相機中心距,Z 為我們想求得的深度資訊。
公式中,焦距 f 和攝像頭中心距 B 可透過標定得到,因此,只要獲得了視差 d=X_R−X_T,就可以計算出深度 Z。
視差是同一個場景在兩個相機下成像的畫素的位置偏差。
2.3 相機引數推導法
由基本假設可以可知,左右相機內參相等,且左右相機只存在 X 軸方向的平移運動。那麼有:
相機模型和各種座標系介紹:https://blog.csdn.net/qq_40918859/article/details/122271381
2.4 雙目點雲和鐳射雷達點雲的比較
參考連結:https://www.zhihu.com/question/264726552
- 感知距離
雙目模型在近處比較有優勢(幾十米),在遠處的時候類似於單目,而 Lidar 感知距離可以達到 200m+(210-250)。
- 點雲****密度
雙目的點雲比 Lidar 要稠密。雙目模型估計出的深度是畫素級別的,camera 解析度越大,點雲就越稠密。而 Lidar 的取樣點覆蓋相對於場景的尺度來講,具有很強的稀疏性。
- 精度
Lidar 是主動方法,雙目是被動方法,而且雙目是根據模型估計視差計算出的深度,存在一定的標定、安裝誤差以及深度失真問題,所以其輸出的深度資訊的精度是不及 Lidar 的,但是需要注意的是 Lidar 受天氣影響更大。
雙目測距在某些場景下,深度圖邊界容易失真,錯誤主要體現在以下三方面。
- 缺失(Missing):邊界缺失是指高質量 RGB 影像中存在真實物件邊界,但在深度圖中這些邊界丟失了;
- 虛假(Fake):虛假邊界是指在深度圖中存在物件邊界,但在 RGB 影像中不存在真實邊界的情況;
- 錯位(Misaligned):RGB 影像和深度圖中均有真實邊界,但彼此沒有很好的對齊;
- 黑夜不 work
3. StereoNet
3.1 基本情況
- dataset: SceneFlow
- Input shape: 540x960
- 精度:
3.2 網路結構
3.2.1.特徵提取
採用共享權重的孿生神經網路提取雙目影像的特徵,使用 K 個下采樣 block 進行高層特徵提取;
3.2.2.Cost volume 構建
- Cost volume 是在雙目匹配中用於衡量左右檢視的相似性的張量。
- 特徵下采樣之後,在低解析度下計算 cost volume,輸出的 shape 是
- Cost volume 的計算方式為 concat,即將左右檢視的特徵圖在通道維進行 concat:
def build_concat_volume(
self,
refimg_fea: Tensor,
targetimg_fea: Tensor,
maxdisp: int,
) -> Tensor:
"""
Build the concat cost volume.
Args:
refimg_fea: Left image feature.
targetimg_fea: Right image feature.
maxdisp: Maximum disparity value.
Returns:
volume: Concatenated cost volume.
"""
B, C, H, W = refimg_fea.shape
C = 2 * C
tmp_volume = []
for i in range(maxdisp):
if i > 0:
tmp = self.c_cat[i].cat(
(refimg_fea[:, :, :, i:], targetimg_fea[:, :, :, :-i]),
dim=1,
)
tmp_volume.append(self.c_pad[i](tmp).view(-1, 1, H, W))
else:
tmp_volume.append(
self.c_cat[i]
.cat((refimg_fea, targetimg_fea), dim=1)
.view(-1, 1, H, W)
)
volume = self.c_cat_final.cat(tmp_volume, dim=1).view(
B, C, maxdisp, H, W
)
return volume
- 透過 concat 獲得的 cost volumes 不包含有關特徵相似性的資訊,因此在後續模組中需要更多引數來學習相似度函式。
- maxdisp 是預先設定的最大視差,也就是模型能預測到的最大視差。
3.2.3.Cost volume 最佳化和計算視差
獲得 4D cost volume,使用 conv3d 進行最佳化。
然後基於最佳化後的 cost volume 獲得低解析度下的視差圖。cost volume 最佳化後得到 Nx1xDxHxW 大小的特徵圖,然後使用 softmax 得到在每個視差值下的機率。
相關程式碼:
# Optimize cost volume to obtain low disparity
for f in self.filter:
cost0 = f(cost0)
#最佳化視差
cost0 = self.conv3d_alone(cost0)
cost0 = cost0.squeeze(1)
#轉化為機率
pred0 = self.softmax(cost0)
#乘以對應的視差值
pred0 = self.dis_mul(pred0)
pred0 = self.dis_sum(pred0)
3.2.4.refine 到原圖
利用層次化微調模組來逐漸對視差圖進行修復,補充高頻資訊以實現邊緣保留。具體做法是對粗糙的低解析度視差圖進行上取樣,並根據粗糙視差圖和原始左檢視 rgb 輸入預測出一個殘差圖,加到該視差圖中獲得一個細化視差圖。
具體操作:
首先將深度圖和 RGB 影像拼接(Concatenate),得到的拼合張量再經過一個 3x3 的卷積操作得到 32 通道的表示張量,之後再透過 6 個 殘差塊(Residual Block)的操作,每個殘差塊由於卷積、批正則化(Batch Normalization)、矯正線性單元(Leakey ReLU)等操作;為了擴大網路,在每個殘差塊中使用了擴張(Dilate)卷積的操作,最後經過一個 3x3 的卷積,得到最後的單通道深度圖。
SteroNet 由左右圖的特徵經過連線得到一個 4D cost volume, 之後利用 3D 卷積進行代價聚合得到最終的視差圖,這種方式導致其計算成本十分高昂。
4.StereoNetPlus
4.1 總體結構
4.2 模型最佳化點
4.2.1 特徵提取
使用 MixVarGENet+FPN 來提取雙目影像的多尺度特徵;
4.2.2 Cost Volume 構建和最佳化
採用 AANet 的思想,基於相關性構建多尺度 cost volume,並進行尺度內和跨尺度融合,最終輸出 1/8 原圖尺度下的 cost volume。
AANet 網路
作者透過設計兩個有效且高效的成本聚合模組:自適應同尺度聚合模組(Adaptive Intra-Scale Aggregation)
和自適應跨尺度聚合模組(Adaptive Cross-ScaleAggregation)來實現成本聚合。並且使用特徵相關性而不是 concat 的方式構造多尺度 Cost Volume。
cost volume 構建
使用 1/8,1/16, 1/32 原圖尺度下的特徵圖構造多尺度 cost volume;
def build_aanet_volume(self, refimg_fea, maxdisp, offset, idx):
B, C, H, W = refimg_fea.shape
num_sample = B // 2
tmp_volume = []
#maxdisp:最大預測視差
for i in range(maxdisp):
if i > 0:
#計算左右檢視特徵的相關性
cost = self.gc_mul[i + offset].mul(
refimg_fea[:num_sample, :, :, i:],
refimg_fea[num_sample:, :, :, :-i],
)
#取均值
tmp = self.gc_mean[i + offset].mean(cost, dim=1)
#padding
tmp_volume.append(self.gc_pad[i + offset](tmp))
else:
#計算左右檢視特徵的相關性
cost = self.gc_mul[i + offset].mul(
refimg_fea[:num_sample, :, :, :],
refimg_fea[num_sample:, :, :, :],
)
#取均值
tmp = self.gc_mean[i + offset].mean(cost, dim=1)
tmp_volume.append(tmp)
volume = (
self.gc_cat_final[idx]
.cat(tmp_volume, dim=1)
.view(num_sample, maxdisp, H, W)
)
return volume
ISA
在視差非連續時,邊緣位置總會有一圈連續的錯誤匹配值,為了緩解這種 edge-fattening 問題,使用 3 個殘差模組 BasicResBlock 對每個尺度的 cost volume 進行聚合。
CSA
雙目影像進行下采樣後,在相同的 patch 尺寸下,紋理資訊將更具鑑別性,所以跨尺度成本聚合演算法中引入了多尺度互動。最終的 cost volume 是透過對不同尺度的成本聚合結果進行自適應組合得到的,公式如下:
下面將對公式中的 3 種情況進行說明:
最佳化效果
由於沒有使用 conv3d,所以相對於基礎版模型,效能有了一定的提升。
基礎版模型:720P 輸入下,單核 fps 18.22, latency:54.9ms, 960*540 輸入下精度 1.12(浮點)
最佳化版模型:54.9ms ->16.59ms epe: 1.12->0.948
4.2.3 計算粗略視差
使用融合後的 1/8 尺度下的 cost volume 計算小圖視差。
# Fusion costvolume as AANet
aanet_volumes = aanet_volumes[::-1]
for i in range(len(self.fusions)):
fusion = self.fusions[i]
aanet_volumes = fusion(aanet_volumes)
#1/8尺度:1X24x68x120(24=192/8)
cost0 = self.final_conv(aanet_volumes[0])
# Obtain low disparity and unfold it.
#轉化為視差機率
pred0 = self.softmax(cost0)
#乘以對應的視差值範圍[0,23]
#24=192/8
pred0 = self.dis_mul(pred0)
#sum求和,獲得1/8尺度下的粗略視差
pred0 = self.dis_sum(pred0)
#unfold操作:使用2x2conv替代
#pred0_unfold shape:1x4x68x120
pred0_unfold = self.unfold(pred0)
對粗略視差進行上取樣時,會用到鄰域點的特徵,所以用了一個 unfold 的一個操作,先把這個視差從那個小圖上面取出來,取出來過後,然後上取樣,然後再跟 featuremap 預測出來的權重去相乘,最後會得到一個最終的時差值。這種做法會減少 refine 的過程中的計算量。
這裡為了進一步節省耗時,使用 conv2x2 替換 unfold 操作:
class UnfoldConv(nn.Module):
"""
A unfold module using conv.
Args:
in_channels: The channels of inputs.
kernel_size: The kernel_size of unfold.
"""
...
def forward(self, x: Tensor) -> Tensor:
"""Perform the forward pass of the model."""
#將尺寸pad到(1,1,69,121)
x = self.pad(x)
#conv代替unfold
x = self.conv(x)
return x
4.2.4 refine 原圖
使用左圖特徵預測出權重,然後和上取樣到原圖尺寸的粗略視差相乘,這樣就得到了最終視差。
CoEx
- 特徵提取:使用 MobileNetV2 作為主幹特徵提取器,因為它具有輕量級的特性,並構建了一個 U-Net 方式的上取樣模組,在每個尺度層次上都有長跳躍連線;
- 構建 cost volume:在左右影像的 1/4 尺度上提取的特徵圖構建相關層,以輸出 D/4×H/4×W/4 cost volume;
- cost volume 聚合:使用了 3D 卷積的沙漏結構,但減少了通道數量和網路深度以降低計算代價,在每一個模組之後加入引導代價體激勵;
- Guided Cost volume Excitation (GCE):利用從影像中提取的特徵圖作為成本聚合的指導,以提高精度
GCE
CoEx 使用 DeconvResModule 融合 P1/2、P1/4、P1/8 的左圖特徵,從而獲得原始影像解析度下的視差權重。
將預測到的權重和上取樣到原圖大小的粗略視差相乘:
- 將雙線性插值最佳化為最近鄰插值;
- 此部分在模型的後處理部分進行。
4.3 計算深度並視覺化
4.3.1 計算深度
根據獲得的視差計算畫素深度:
#基於幾何法,根據模型預測的視差計算深度
def process_outputs(model_outs, viz_func, vis_inputs):
preds = model_outs.squeeze(0).cpu().numpy()
f = float(vis_inputs["f"])
baseline = float(vis_inputs["baseline"])
img = vis_inputs["img"]
#f:焦距;baseline:相機平行光軸之間的距離
#preds:預測視差
depth = baseline * f / preds
preds = viz_func(img, preds, depth)
return None
4.3.2 視覺化
- 視覺化結果從左至右為:left 左圖、right 右圖、disparity 視差圖、根據視差計算的 depth 深度圖;
- 視差圖中的顏色代表:顏色越紅,視差值越小;顏色越藍,視差值越大;
- 深度圖中的顏色代表:顏色越紅,對應的畫素深度值則越小;顏色越藍,對應的畫素深度值則越大;
- 視差圖到深度圖的計算原理可參考【雙目深度估計】—原理理解中的幾何法,相應程式碼可以參考 config 檔案中的 process_outputs 函式。
參考連結
- https://blog.csdn.net/sinat_29819401/article/details/129382207
- https://blog.csdn.net/wjinjie/article/details/122303148
- https://github.com/meteorshowers/X-StereoLab
- https://zhuanlan.zhihu.com/p/302888864
- https://blog.csdn.net/gy1153441419/article/details/126709760
- https://zhuanlan.zhihu.com/p/58165275