來源丨https://zhuanlan.zhihu.com/p/458589977雙目匹配在雙目匹配的範疇里,本次內容主要局限在以下兩個小的部分:* SGM(經典) 原理解析* comparation with monodepthSGM半全局匹配算法(SGM)是實時立體視覺里最流行的一個算法,已經大規模的在很多產品里得到了應用。其最早由H. Hirschmuller 在2005年發表於CVPR的文章中被提出(Accurate and efficient stereo processing by semi-global matching and mutual information)。立體匹配算法在深度學習算法強勢來襲之前,可以分為3大流派,包括局部派(SAD, SSD, NCC, Census-Transform, Mutual Information ...),全局派 (Graph Cut, Belief Propagation, Dynamic Programming ...), 以及半全局派 (SGM). SGM是半全局領域的代表之作,相對於局部派的簡單粗暴,SGM更加優雅複雜,同時也沒有全局派那麼time-consuming (https://blog.csdn.net/rs_lys/?type=blog)opencv 接口cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(minDisparity, numDisparity, SADWindowSize, p1, p2, diso12MaxDiff,preFilterCap, uniqueRatio, speckleWindowSize, speckleRange, fullDp);cv::Mat disparity_sgbm;sgbm->compute(frame->left, frame->right, disparity_sgbm);disparity_sgbm.convertTo(frame->disparity, CV_32F, 1.0 / 16.0f);參數含義解釋 (https://docs.opencv.org/):1. minDisparity: 最小視差2. numDisparity: 視差個數(64 / 96 / 128 / 256 ...)3. SADWindowSize: 灰度相關時的窗口大小 (3 / 5 / 7 ...)4. p1, p2, 平滑性懲罰係數, 下文會介紹詳細含義5. diso12MaxDiff 左右視差檢查中允許的最大差異6. preFilterCap 預濾波圖像像素的截斷值 (下文中未用到),主要是圖像預處理的操作,用來排除噪聲干擾,提高邊界的可區分性7. uniqueRatio 唯一性比值 (ratio test)8. speckleWindowSize 平滑視差區域的最大尺寸 (過濾一些斑點噪聲)9. speckleRange 連接組件(斑點)內的最大視差變化樣例雙目輸入與輸出


可以看到有比較多的空洞和視差不連續的地方,正常的流程中還包括一步視差圖濾波後處理(weighted least square filtering)wls_filter = createDisparityWLSFilter(left_matcher);wls_filter->filter(left_disp,left,filtered_disp,right_disp);
Reference: "Fast Global Image Smoothing Based on Weighted Least Squares", 大意是使用加權最小二乘算法進行優化,使得圖像全局平滑的同時能夠進行邊緣的保持, 與雙邊濾波的整體功能相近, 可以看到在經過後處理後,視差圖更加平滑,輪廓更加清晰。利用開源的monodepth,不加任何參數修改的進行訓練,然後推斷上面圖片對應的深度結果(https://github.com/OniroAI/MonoDepth-PyTorch):
從上圖中可以看出,在整體表現上,深度學習方法要優於傳統方法(The overall performance outperforms by a larger marjin then traditional SGM method)。SGM整個算法流程1. Census-Transform 將原始圖像轉換為census圖像,為了便於匹配代價體計算2. Compute-Cost 通過兩幅census圖像進行初始的匹配成本計算3. Cost-Aggregation 代價體聚合, 'key step'4. Compute-Disparity 基於聚合後的代價體進行每個像素的視差值計算5. LR-Check 左右視差一致性檢查 (optional)6. Remove-Sparkles 零散的斑點移除(optional)7. Fill-Holes 空洞填充(optional)8. Middle-Filter 中值濾波去噪平滑H 老爺子最早的匹配代價選擇的是MI(互信息),但相對於census-transform其計算效率比較低,所以主流方式變成了census變換. 所謂census image就是通過census-transform將原始圖像逐像素變換得到的,每個像素的census值是明暗相對關係比較的一個比特串。ok, 以一個例子簡單說明,加入選擇的census的窗口是3*3, 有這樣的一個小的image patch:
那中間像素5的census值為(110100101), 這個二進制比特串所代表的十進制數字421就是對應的census image的像素值. 所有經過census變換後可以分別得到左圖的census image和右圖的census image ,reference:https://www.cnblogs.com/riddick/p/7295581.html。這一步的過程主要是為了構建初始代價立方體,注意是三維的立方體 print(cost_init_.shape) [D, H, W]其中D為disparity range, H為圖像高度,W為圖像寬度,ok, 現在的輸入是census_left, 一個二維矩陣, census_right, 一個二維矩陣,想要的輸出是cost_init, 一個三維矩陣, 如何構造,不失一般性的公式如下:
計算當前像素在每個視差下的漢明距離作為度量,這裡漢明距離的計算有個面試點分享給有需要的同學:int SGMUtils::hamming_dist(const unsigned int census_x, const unsigned int census_y){ int dist = 0; int val = census_x ^ census_y; while(val) { dist++; val &= (val - 1); } return dist; }
橫方向代表圖像列,縱方向代表圖像行,朝里的方向代表深度範圍, 當代價體構造好之後,如果不進行關鍵的代價聚合, 也可以進行視差計算, 這裡先跳過代價聚合,直接基於代價體進行視差計算。視差計算的方法很直觀,對於代價體中的每個像素,在視差方向進行遍歷,當前像素的視差滿足對應的代價最小這一原則,三重循環過後,可以生成相應的視差圖,也就是所謂的WTA(winner take all)準則。經過上面簡單的幾步, 可以獲得如下的結果,來自明德學院的經典的左右樣圖:

現在討論下最關鍵的代價聚合步驟,這一步是sgm的靈魂,先看下效果:
目前常用的代價聚合有4path聚合和8path聚合, 4路聚合包括從上到下, 從下到上, 從左往右, 從右往左, 8路聚合增加了45度方向和135度方向的聚合路徑,路徑聚合的目的就是不僅考慮局部的代價信息, 還要加入全局的平滑信息,只是用多個方向一維的聚合來對二維進行近似,精度相似,效率大幅度提升。以四路聚合為例,會分別得到四個聚合的代價立方體,將其相加得到最終的代價立方體:for(sint32 i =0;i<size;i++) { cost_aggr_[i] = cost_aggr_1_[i] + cost_aggr_2_[i] + cost_aggr_3_[i] + cost_aggr_4_[i]; if (option_.num_paths == 8) { cost_aggr_[i] += cost_aggr_5_[i] + cost_aggr_6_[i] + cost_aggr_7_[i] + cost_aggr_8_[i]; } }這裡的size = width * height * disparity_range,以1路聚合,方向從左到右為例,看看如何得到其對應的代價立方體,代價立方體某一元素的計算公式:
其中p代表像素位置, d代表視差值, r是移動的路徑,在當前例子中為從左到右,所以當前的Lr 為代碼中的cost_aggr_1_, 代表聚合後的代價立方體,C為初始代價立方體, 公式的含義翻譯成中文就是:聚合後的代價立方體中像素p,視差d位置處的值 = 這個位置的初始代價值 + min(L1, L2, L3, L4) - L4其中:1. L1 代表當前立方體像素p-r處,視差d位置處的值,在從左到右的例子中則代表當前像素左側的像素2. L2 代表同樣的左側像素, 但視差為d-1位置處的代價值, 再加上p1懲罰項 (小小的對視差變化1進行懲罰)3. L3 代表同樣的左側像素, 視差為d+1位置處的代價值,再加上p1懲罰項 (小小的對視差變化1進行懲罰)4. L4 代表同樣的當前像素的左側像素,所有視差位置(disparity channel)中的代價最小值, 再加上p2懲罰項 (視差變化較大, 對應大一點的懲罰)
灰度變化越大,相應的懲罰會越小, 為什麼呢? 在物體的邊界處,深度會發生很大變化,對應的視差也會發生很大變化,這時候的懲罰應該要小一點,所以要除以灰度的變化值來抑制懲罰項。這樣設計的聚合代價不僅包括了原始匹配代價,也包括了視差變化(平滑項的代價) 。
經過上面的一通操作,可以得到最終想要的聚合代價立方體,然後基於代價立方體再進行後面的最優視差計算等。說完了代價聚合的整個過程,再看看理論上為何要做這個?與全局算法相似,sgm希望能做到全局最優,意味着希望當每個像素的視差值確定之後,整體上的能量函數達到最優
其中第一項是匹配代價能量,第二項是表面連續性的平滑約束,所以核心問題就是如何求解這個二維最優問題,SGM沒有直接求解,採用了單方向聚合一維近似的方法求解,也可以理解為一種一維的動態規劃. 只經過從左到右的一路聚合,形成的視差圖如下:

相比於不進行代價聚合,可以看到聚合這一靈魂一步的巨大作用. 一般來說,追求效率的話四路就足夠了,若希望能夠更好的效果,則可以選擇8路聚合。可以看到經過聚合後,已經有了基本的樣子,後續的所有操作都是對聚合後的視差圖進行優化處理,優化處理中的很多步驟都可以根據實際情況進行合理的取捨. OK, 現在可以進入繁雜細碎的視差優化處理階段。視差優化的目的:提高視差精度, 剔除錯誤視差, 使得視差值精確, 可靠。同時要保持左右一致性和唯一性。加入視差圖的中值濾波可以去掉視差噪聲, 採用雙邊濾波也可以, 可以同時保持邊緣的精度, 但效率稍低。·子像素擬合-- 3個點 (x1, y1), (x2, y2), (x3, y3)拋物線擬合,求最小值或最大值·左右一致性檢查-- LR check, 左右影像同名像素的視差應該一致, 若二者的差大於一定的閾值,則將這一像素的視差置為無效值。左右一致性檢查的圖形含義為:
下圖為monodepth中的左右一致性損失,不同於SGM中同名點像素距離損失,monodepth等深度學習方法採用的是根據右目圖像和左目視差生成虛擬左目,和真實的左目構造灰度域(appearence)上的loss,類似於slam中的直接法中的光度誤差。
1.通過左影像聚合代價立方體生成右影像聚合代價立方體
2. 根據右影像聚合代價立方體生成右影像視差圖, 如步驟4ratio = best_score / second_score,如果唯一性沒有那麼的顯著,則將當前視差值置為無效值,這也是特徵點匹配中常用的一種魯棒化策略, 其對應的圖形含義為:
存在一些小的連通域,與物體的整體視差很不協調,這樣的連通域,一般的濾波又很難弄掉它,所以使用一種區域跟蹤的方法,尋找灰度變化在一定範圍內的區域,這樣的區域面積如果小於一定的閾值,則將區域內的所有像素的視差置為無效值。method: 深度優先遍歷,尋找連通區域 (DFS)SGM和MonoDepth等深度學習方法的比較
loss項總共有3項, 第一項appearance matching loss:
光度誤差 + 結構相似度誤差,本質上和sgm中漢明距離的匹配代價是等效的。但表達能力上應該更強一些。第二項Disparity Smooth Loss:
從公式中可以定性的看出 平滑性loss與視差的變化成正比,與圖像的梯度成指數反比,公式所闡述的現實物理含義是不希望在平坦區域視差會有大的變化,但又不能壓制在物體邊界區域視差應該有較大的突變這樣一個事實,sgm中的靈魂一步「代價聚合」不就做的是這樣一件事情麼?第三項Left-Right Disparity Consistency Loss:
sgm中的左右一致性檢查在monodepth裡面得到的應用。看了上面的幾個技術點的比較,是否覺得所謂的monodepth也只是sgm技術集中的子集,在深度學習的背景下,採用各種方式對sgm中的東西進行包裝,使其能夠運行在學習的範式之下,從而能夠以巨量參數的形式對深度進行估計。藉助於深度學習強大的表達能力,視差估計的精度顯著提升。但不可否認,深度學習方法可能會存在一些泛化性問題。展望同時monodepth有很多sgm中的東西沒有包裝,是否有進一步發展的可能?同時我們也可以看到,推出monodepth2的組織nianticlabs(https://github.com/nianticlabs)去年也推出了新的改進版本,depth hints,(https://arxiv.org/pdf/1909.09051.pdf),通過在訓練過程中融合sgm的監督信息,更好的深度估計結果:
