OpenCV學習筆記(18)雙目測距與三維重建的OpenCV實現問題集錦(三)立體匹配與視差計算
四、雙目匹配與視差計算
立體匹配主要是通過找出每對影像間的對應關係,根據三角測量原理,得到視差圖;在獲得了視差資訊後,根據投影模型很容易地可以得到原始影像的深度資訊和三維資訊。立體匹配技術被普遍認為是立體視覺中最困難也是最關鍵的問題,主要是以下因素的影響:
(1) 光學失真和噪聲(亮度、色調、飽和度等失衡)
(2) 平滑表面的鏡面反射
(3) 投影縮減(Foreshortening)
(4) 透視失真(Perspective distortions)
(5) 低紋理(Low texture)
(6) 重複紋理(Repetitive/ambiguous patterns)
(7) 透明物體
(8) 重疊和非連續
目前立體匹配演算法是計算機視覺中的一個難點和熱點,演算法很多,但是一般的步驟是:
A、匹配代價計算
匹配代價計算是整個立體匹配演算法的基礎,實際是對不同視差下進行灰度相似性測量。常見的方法有灰度差的平方SD(squared intensity differences),灰度差的絕對值AD(absolute intensity differences)等。另外,在求原始匹配代價時可以設定一個上限值,來減弱疊加過程中的誤匹配的影響。以AD法求匹配代價為例,可用下式進行計算,其中T為設定的閾值。
圖18
B、 匹配代價疊加
一般來說,全域性演算法基於原始匹配代價進行後續演算法計算。而區域演算法則需要通過視窗疊加來增強匹配代價的可靠性,根據原始匹配代價不同,可分為:
圖19
C、 視差獲取
對於區域演算法來說,在完成匹配代價的疊加以後,視差的獲取就很容易了,只需在一定範圍內選取疊加匹配代價最優的點(SAD和SSD取最小值,NCC取最大值)作為對應匹配點,如勝者為王演算法WTA(Winner-take-all)。而全域性演算法則直接對原始匹配代價進行處理,一般會先給出一個能量評價函式,然後通過不同的優化演算法來求得能量的最小值,同時每個點的視差值也就計算出來了。
D、視差細化(亞畫素級)
大多數立體匹配演算法計算出來的視差都是一些離散的特定整數值,可滿足一般應用的精度要求。但在一些精度要求比較高的場合,如精確的三維重構中,就需要在初始視差獲取後採用一些措施對視差進行細化,如匹配代價的曲線擬合、影像濾波、影像分割等。
有關立體匹配的介紹和常見匹配演算法的比較,推薦大家看看Stefano Mattoccia 的講義 Stereo Vision: algorithms and applications,190頁的ppt,講解得非常形象詳盡。
1. opencv2.1和opencv2.0在做stereo vision方面有什麼區別了?
2.1版增強了Stereo Vision方面的功能:
(1) 新增了 SGBM 立體匹配演算法(源自Heiko Hirschmuller的《Stereo Processing by Semi-global Matching and Mutual Information》),可以獲得比 BM 演算法物體輪廓更清晰的視差圖(但低紋理區域容易出現橫/斜紋路,在 GCstate->fullDP 選項使能時可消減這種異常紋路,但對應區域視差變為0,且執行速度會有所下降),速度比 BM 稍慢, 352*288的幀處理速度大約是 5 幀/秒;
(2) 視差效果:BM < SGBM < GC;處理速度:BM > SGBM > GC ;
(3) BM 演算法比2.0版效能有所提升,其狀態引數新增了對左右檢視感興趣區域 ROI 的支援(roi1 和 roi2,由stereoRectify函式產生);
(4) BM 演算法和 GC 演算法的核心程式碼改動不大,主要是面向多執行緒運算方面的(由 OpenMP 轉向 Intel TBB);
(5) cvFindStereoCorrespondenceBM 函式的disparity引數的資料格式新增了 CV_32F 的支援,這種格式的資料給出實際視差,而 2.0 版只支援 CV_16S,需要除以 16.0 才能得到實際的視差數值。
2. 用於立體匹配的影像可以是彩色的嗎?
在OpenCV2.1中,BM和GC演算法只能對8位灰度影像計算視差,SGBM演算法則可以處理24位(8bits*3)彩色影像。所以在讀入影像時,應該根據採用的演算法來處理影像:
//////////////////////////////////////////////////////////////////////////
// 載入影像
cvGrabFrame( lfCam );
cvGrabFrame( riCam );
frame1 = cvRetrieveFrame( lfCam );
frame2 = cvRetrieveFrame( riCam );
if(frame1.empty()) break;
resize(frame1, img1, img_size, 0, 0);
resize(frame2, img2, img_size, 0, 0);
// 選擇彩色或灰度格式作為雙目匹配的處理影像
if (!color_mode && cn>1)
{
cvtColor(img1, img1gray, CV_BGR2GRAY);
cvtColor(img2, img2gray, CV_BGR2GRAY);
img1p = img1gray;
img2p = img2gray;
}
else
{
img1p = img1;
img2p = img2;
}
3. 怎樣獲取與原影像有效畫素區域相同的視差圖?
在OpenCV2.0及以前的版本中,所獲取的視差圖總是在左側和右側有明顯的黑色區域,這些區域沒有有效的視差資料。視差圖有效畫素區域與視差視窗(ndisp,一般取正值且能被16整除)和最小視差值(mindisp,一般取0或負值)相關,視差視窗越大,視差圖左側的黑色區域越大,最小視差值越小,視差圖右側的黑色區域越大。其原因是為了保證參考影像(一般是左檢視)的畫素點能在目標影像(右檢視)中按照設定的視差匹配視窗匹配對應點,OpenCV 只從參考影像的第 (ndisp - 1 + mindisp) 列開始向右計算視差,第 0 列到第 (ndisp - 1 + mindisp) 列的區域視差統一設定為 (mindisp - 1) *16;視差計算到第 width + mindisp 列時停止,餘下的右側區域視差值也統一設定為 (mindisp - 1) *16。
00177 static const int DISPARITY_SHIFT = 4;
…
00411 int ndisp = state->numberOfDisparities;
00412 int mindisp = state->minDisparity;
00413 int lofs = MAX(ndisp - 1 + mindisp, 0);
00414 int rofs = -MIN(ndisp - 1 + mindisp, 0);
00415 int width = left->cols, height = left->rows;
00416 int width1 = width - rofs - ndisp + 1;
…
00420 short FILTERED = (short)((mindisp - 1) << DISPARITY_SHIFT);
…
00466 // initialize the left and right borders of the disparity map
00467 for( y = 0; y < height; y++ )
00468 {
00469 for( x = 0; x < lofs; x++ )
00470 dptr[y*dstep + x] = FILTERED;
00471 for( x = lofs + width1; x < width; x++ )
00472 dptr[y*dstep + x] = FILTERED;
00473 }
00474 dptr += lofs;
00475
00476 for( x = 0; x < width1; x++, dptr++ )
…
這樣的設定很明顯是不符合實際應用的需求的,它相當於把攝像頭的視場範圍縮窄了。因此,OpenCV2.1 做了明顯的改進,不再要求左右檢視和視差圖的大小(size)一致,允許對視差圖進行左右邊界延拓,這樣,雖然計算視差時還是按上面的程式碼思路來處理左右邊界,但是視差圖的邊界得到延拓後,有效視差的範圍就能夠與對應檢視完全對應。具體的實現程式碼範例如下:
//////////////////////////////////////////////////////////////////////////
// 對左右檢視的左邊進行邊界延拓,以獲取與原始檢視相同大小的有效視差區域
copyMakeBorder(img1r, img1b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
copyMakeBorder(img2r, img2b, 0, 0, m_nMaxDisp, 0, IPL_BORDER_REPLICATE);
//////////////////////////////////////////////////////////////////////////
// 計算視差
if( alg == STEREO_BM )
{
bm(img1b, img2b, dispb);
// 擷取與原始畫面對應的視差區域(捨去加寬的部分)
displf = dispb.colRange(m_nMaxDisp, img1b.cols);
}
else if(alg == STEREO_SGBM)
{
sgbm(img1b, img2b, dispb);
displf = dispb.colRange(m_nMaxDisp, img1b.cols);
}
4. cvFindStereoCorrespondenceBM的輸出結果好像不是以畫素點為單位的視差?
“@scyscyao:在OpenCV2.0中,BM函式得出的結果是以16位符號數的形式的儲存的,出於精度需要,所有的視差在輸出時都擴大了16倍(2^4)。其具體程式碼表示如下:
dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*128/d : 0) + 15) >> 4);
可以看到,原始視差在左移8位(256)並且加上一個修正值之後又右移了4位,最終的結果就是左移4位。
因此,在實際求距離時,cvReprojectTo3D出來的X/W,Y/W,Z/W都要乘以16 (也就是W除以16),才能得到正確的三維座標資訊。”
在OpenCV2.1中,BM演算法可以用 CV_16S 或者 CV_32F 的方式輸出視差資料,使用32位float格式可以得到真實的視差值,而CV_16S 格式得到的視差矩陣則需要 除以16 才能得到正確的視差。另外,OpenCV2.1另外兩種立體匹配演算法 SGBM 和 GC 只支援 CV_16S 格式的 disparity 矩陣。
5. 如何設定BM、SGBM和GC演算法的狀態引數?
(1)StereoBMState
// 預處理濾波引數
- preFilterType:預處理濾波器的型別,主要是用於降低亮度失真(photometric distortions)、消除噪聲和增強紋理等, 有兩種可選型別:CV_STEREO_BM_NORMALIZED_RESPONSE(歸一化響應) 或者 CV_STEREO_BM_XSOBEL(水平方向Sobel運算元,預設型別), 該引數為 int 型;
- preFilterSize:預處理濾波器視窗大小,容許範圍是[5,255],一般應該在 5x5..21x21 之間,引數必須為奇數值, int 型
- preFilterCap:預處理濾波器的截斷值,預處理的輸出值僅保留[-preFilterCap, preFilterCap]範圍內的值,引數範圍:1 - 31(文件中是31,但程式碼中是 63), int
// SAD 引數
- SADWindowSize:SAD視窗大小,容許範圍是[5,255],一般應該在 5x5 至 21x21 之間,引數必須是奇數,int 型
- minDisparity:最小視差,預設值為 0, 可以是負值,int 型
- numberOfDisparities:視差視窗,即最大視差值與最小視差值之差, 視窗大小必須是 16 的整數倍,int 型
// 後處理引數
- textureThreshold:低紋理區域的判斷閾值。如果當前SAD視窗內所有鄰居畫素點的x導數絕對值之和小於指定閾值,則該視窗對應的畫素點的視差值為 0(That is, if the sum of absolute values of x-derivatives computed over SADWindowSize by SADWindowSize pixel neighborhood is smaller than the parameter, no disparity is computed at the pixel),該引數不能為負值,int 型
- uniquenessRatio:視差唯一性百分比, 視差視窗範圍內最低代價是次低代價的(1 + uniquenessRatio/100)倍時,最低代價對應的視差值才是該畫素點的視差,否則該畫素點的視差為 0 (the minimum margin in percents between the best (minimum) cost function value and the second best value to accept the computed disparity, that is, accept the computed disparity d^ only if SAD(d) >= SAD(d^) x (1 + uniquenessRatio/100.) for any d != d*+/-1 within the search range ),該引數不能為負值,一般5-15左右的值比較合適,int 型
- speckleWindowSize:檢查視差連通區域變化度的視窗大小, 值為 0 時取消 speckle 檢查,int 型
- speckleRange:視差變化閾值,當視窗內視差變化大於閾值時,該視窗內的視差清零,int 型
// OpenCV2.1 新增的狀態引數
- roi1, roi2:左右檢視的有效畫素區域,一般由雙目校正階段的 cvStereoRectify 函式傳遞,也可以自行設定。一旦在狀態引數中設定了 roi1 和 roi2,OpenCV 會通過cvGetValidDisparityROI 函式計算出視差圖的有效區域,在有效區域外的視差值將被清零。
- disp12MaxDiff:左視差圖(直接計算得出)和右視差圖(通過cvValidateDisparity計算得出)之間的最大容許差異。超過該閾值的視差值將被清零。該引數預設為 -1,即不執行左右視差檢查。int 型。注意在程式除錯階段最好保持該值為 -1,以便檢視不同視差視窗生成的視差效果。具體請參見《使用OpenGL動態顯示雙目視覺三維重構效果示例》一文中的討論。
在上述引數中,對視差生成效果影響較大的主要引數是 SADWindowSize、numberOfDisparities 和 uniquenessRatio 三個,一般只需對這三個引數進行調整,其餘引數按預設設定即可。
在OpenCV2.1中,BM演算法有C和C++ 兩種實現模組。
(2)StereoSGBMState
SGBM演算法的狀態引數大部分與BM演算法的一致,下面只解釋不同的部分:
- SADWindowSize:SAD視窗大小,容許範圍是[1,11],一般應該在 3x3 至 11x11 之間,引數必須是奇數,int 型
- P1, P2:控制視差變化平滑性的引數。P1、P2的值越大,視差越平滑。P1是相鄰畫素點視差增/減 1 時的懲罰係數;P2是相鄰畫素點視差變化值大於1時的懲罰係數。P2必須大於P1。OpenCV2.1提供的例程 stereo_match.cpp 給出了 P1 和 P2 比較合適的數值。
- fullDP:布林值,當設定為 TRUE 時,執行雙通道動態程式設計演算法(full-scale 2-pass dynamic programming algorithm),會佔用O(W*H*numDisparities)個位元組,對於高解析度影像將佔用較大的記憶體空間。一般設定為 FALSE。
注意OpenCV2.1的SGBM演算法是用C++ 語言編寫的,沒有C實現模組。與H. Hirschmuller提出的原演算法相比,主要有如下變化:
- 演算法預設執行單通道DP演算法,只用了5個方向,而fullDP使能時則使用8個方向(可能需要佔用大量記憶體)。
- 演算法在計算匹配代價函式時,採用塊匹配方法而非畫素匹配(不過SADWindowSize=1時就等於畫素匹配了)。
- 匹配代價的計算採用BT演算法("Depth Discontinuities by Pixel-to-Pixel Stereo" by S. Birchfield and C. Tomasi),並沒有實現基於互熵資訊的匹配代價計算。
- 增加了一些BM演算法中的預處理和後處理程式。
(3)StereoGCState
GC演算法的狀態引數只有兩個:numberOfDisparities 和 maxIters ,並且只能通過 cvCreateStereoGCState 在建立演算法狀態結構體時一次性確定,不能在迴圈中更新狀態資訊。GC演算法並不是一種實時演算法,但可以得到物體輪廓清晰準確的視差圖,適用於靜態環境物體的深度重構。
注意GC演算法只能在C語言模式下執行,並且不能對視差圖進行預先的邊界延拓,左右檢視和左右視差矩陣的大小必須一致。
6. 如何實現視差圖的偽彩色顯示?
首先要將16位符號整形的視差矩陣轉換為8位無符號整形矩陣,然後按照一定的變換關係進行偽彩色處理。我的實現程式碼如下:
// 轉換為 CV_8U 格式,彩色顯示
dispLfcv = displf, dispRicv = dispri, disp8cv = disp8;
if (alg == STEREO_GC)
{
cvNormalize( &dispLfcv, &disp8cv, 0, 256, CV_MINMAX );
}
else
{
displf.convertTo(disp8, CV_8U, 255/(m_nMaxDisp*16.));
}
F_Gray2Color(&disp8cv, vdispRGB);
灰度圖轉偽彩色圖的程式碼,主要功能是使灰度圖中 亮度越高的畫素點,在偽彩色圖中對應的點越趨向於 紅色;亮度越低,則對應的偽彩色越趨向於 藍色;總體上按照灰度值高低,由紅漸變至藍,中間色為綠色。其對應關係如下圖所示:
圖20
void F_Gray2Color(CvMat* gray_mat, CvMat* color_mat)
{
if(color_mat)
cvZero(color_mat);
int stype = CV_MAT_TYPE(gray_mat->type), dtype = CV_MAT_TYPE(color_mat->type);
int rows = gray_mat->rows, cols = gray_mat->cols;
// 判斷輸入的灰度圖和輸出的偽彩色圖是否大小相同、格式是否符合要求
if (CV_ARE_SIZES_EQ(gray_mat, color_mat) && stype == CV_8UC1 && dtype == CV_8UC3)
{
CvMat* red = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* green = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* blue = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
CvMat* mask = cvCreateMat(gray_mat->rows, gray_mat->cols, CV_8U);
// 計算各彩色通道的畫素值
cvSubRS(gray_mat, cvScalar(255), blue); // blue(I) = 255 - gray(I)
cvCopy(gray_mat, red); // red(I) = gray(I)
cvCopy(gray_mat, green); // green(I) = gray(I),if gray(I) < 128
cvCmpS(green, 128, mask, CV_CMP_GE ); // green(I) = 255 - gray(I), if gray(I) >= 128
cvSubRS(green, cvScalar(255), green, mask);
cvConvertScale(green, green, 2.0, 0.0);
// 合成偽彩色圖
cvMerge(blue, green, red, NULL, color_mat);
cvReleaseMat( &red );
cvReleaseMat( &green );
cvReleaseMat( &blue );
cvReleaseMat( &mask );
}
}
7. 如何將視差資料儲存為 txt 資料檔案以便在 Matlab 中讀取分析?
由於OpenCV本身只支援 xml、yml 的資料檔案讀寫功能,並且其xml檔案與構建網頁資料所用的xml檔案格式不一致,在Matlab中無法讀取。我們可以通過以下方式將視差資料儲存為txt檔案,再匯入到Matlab中。
void saveDisp(const char* filename, const Mat& mat)
{
FILE* fp = fopen(filename, "wt");
fprintf(fp, "%02d/n", mat.rows);
fprintf(fp, "%02d/n", mat.cols);
for(int y = 0; y < mat.rows; y++)
{
for(int x = 0; x < mat.cols; x++)
{
short disp = mat.at<short>(y, x); // 這裡視差矩陣是CV_16S 格式的,故用 short 型別讀取
fprintf(fp, "%d/n", disp); // 若視差矩陣是 CV_32F 格式,則用 float 型別讀取
}
}
fclose(fp);
}
相應的Matlab程式碼為:
function img = txt2img(filename)
data = importdata(filename);
r = data(1); % 行數
c = data(2); % 列數
disp = data(3:end); % 視差
vmin = min(disp);
vmax = max(disp);
disp = reshape(disp, [c,r])'; % 將列向量形式的 disp 重構為 矩陣形式
% OpenCV 是行掃描儲存影像,Matlab 是列掃描儲存影像
% 故對 disp 的重新排列是首先變成 c 行 r 列的矩陣,然後再轉置回 r 行 c 列
img = uint8( 255 * ( disp - vmin ) / ( vmax - vmin ) );
mesh(disp);
set(gca,'YDir','reverse'); % 通過 mesh 方式繪圖時,需倒置 Y 軸方向
axis tight; % 使座標軸顯示範圍與資料範圍相貼合,去除空白顯示區
顯示效果如下:
圖21
(待續……)
.
.
相關文章
- 雙目測距與三維重建的OpenCV實現問題集錦(三)立體匹配與視差計算OpenCV
- 雙目測距與三維重建的OpenCV實現問題集錦(四)三維重建與OpenGL顯示OpenCV
- 雙目測距與三維重建的OpenCV實現問題集錦(二)雙目定標與雙目校正OpenCV
- 雙目測距與三維重建的OpenCV實現問題集錦(一)影象獲取與單目定標OpenCV
- 雙目標定與三維計算:從理論到OpenCV實踐OpenCV
- OpenCV三種立體匹配求視差圖演算法總結OpenCV演算法
- 三維視覺論文閱讀:StereoNet2018雙目立體匹配視覺
- 深度學習 + OpenCV,Python實現實時視訊目標檢測深度學習OpenCVPython
- opencv學習筆記(一)OpenCV筆記
- OpenCV 例項解讀:深度學習的計算與加速OpenCV深度學習
- OpenCV&Qt學習之四——OpenCV 實現人臉檢測與相關知識整理OpenCVQT
- opencv學習(三)——繪圖功能OpenCV繪圖
- OpenCV學習筆記-Harris角點檢測OpenCV筆記
- OpenCV----實現目標識別與分割OpenCV
- Halcon6:三維重建和光度立體視覺視覺
- 計網學習筆記三 MAC與LAN筆記Mac
- 視訊人臉檢測——OpenCV版(三)OpenCV
- 計算機視覺1->opencv4學習指南1 | 環境配置與例程計算機視覺OpenCV
- OpenCV成長之路(9):特徵點檢測與影象匹配OpenCV特徵
- 學習SVM(一) SVM模型訓練與分類的OpenCV實現模型OpenCV
- 利用MKL實現OpenCV的模板匹配(matchTemplate)OpenCV
- 【Svm機器學習篇】Opencv3.4.1與C++實現對分類問題的訓練與預測】機器學習OpenCVC++
- OpenCV學習筆記(4)——mixChannels函式OpenCV筆記函式
- OpenCV學習筆記(5)——normalize函式OpenCV筆記ORM函式
- OpenCV 名稱空間學習筆記OpenCV筆記
- 程式設計師進階之路之面試題與筆試題集錦(三)線上程式設計題程式設計師面試題筆試
- Opencv第三章練習題答案OpenCV
- Halcon缺陷檢測例項轉OpenCV實現(三) 物體凸缺陷檢測OpenCV
- 【opencv學習筆記】001之opencv配置(win10+VS2015+OpenCV3.1.0)OpenCV筆記Win10
- 學習筆記:樹與圖上的計數問題筆記
- Redis設計與實現學習筆記(一)Redis筆記
- opencv學習筆記四十四:移動物件統計OpenCV筆記物件
- Portal開發與配置技巧集錦(三)
- 深入學習OpenCV檢測及分割影象的目標區域OpenCV
- OpenCV學習筆記(六)——對XML和YAML檔案實現I/O操作OpenCV筆記XMLYAML
- OpenCV筆記(3)實現支援向量機(SVM)OpenCV筆記
- 解決ubuntu16.04 opencv2與opencv3共存問題UbuntuOpenCV
- 深度學習求解「三體」問題,計算速度提高一億倍深度學習