立體視覺——固定窗口的視差圖計算

立體視覺——固定窗口的視差圖計算


1. 視差圖計算[1]


深度信息可以通過計算1幅圖像和其它圖像的特徵位置的像素差獲得。視差圖和深度圖很像,因爲視差大的像素離攝像機近,而視差小的像素離攝像機遠。按以米爲單位來計算攝像機距物體多遠需要額外的計算。
根據Matlab教程,計算視差圖的標準方法是用簡單的塊匹配(Block Matching)。我們選擇右邊圖像中的1塊小區域,並在左邊圖像中搜索匹配最近的像素區域。
同理,當搜索右邊圖像時,我們從和左邊圖像的模板相同的座標處開始,向左和向右搜索至最大距離。視差爲右邊圖像的小區域和左邊圖像的最近匹配區域的中心像素的水平距離。

(1)塊比較

如何尋找“最近匹配塊”呢?簡單的方法叫差的絕對值的和(SAD)。在計算深度圖之前,先將兩幅圖像轉換爲灰度圖像(像素值爲0到255)。


(2)圖像修正

注意到我們僅在水平方向而沒在垂直方向上搜索匹配塊。這裏用到的圖像都是已修正的,左圖的特徵將會在右圖同一像素行上。


(3)搜索範圍和方向

塊匹配算法要求我們指定從模板位置器我們想搜索多遠,這應該基於希望在圖像中找到的最大視差。


(4)模板大小

更大的模板產生的深度圖噪聲小,然而計算代價高。模板太大會丟失物體邊緣上的細節。


(5)圖像邊緣上的模板形狀

裁剪模板至最大。默認模板大小爲7x7個像素。但對於左上角(行1,列1),我們不能包含填滿模板,所以只用4*4的模板。對2行1列的像素,我們用5*4的模板。塊大小同理。


(6)子像素估計

塊匹配計算得到的視差值爲整數,對應像素偏移。如果可能在最近匹配塊和它的鄰居間插入來微調視差值至“子像素”位置。Matlab教程裏說道:“之前我們僅用最小代價的位置作爲視差,但現在我們考慮最小代價和兩個相鄰代價值。我們用這3個值擬合拋物線,並解析拋物線的最小值來獲得子像素的位置。”即拋物線的橫軸爲像素橫座標,縱軸爲代價,代價最低的點應該是拋物線的極點。實際計算得到的極點是估計值。d2爲最近匹配塊的視差(像素偏移)且d_est爲實際視差的子像素估計值。C2爲最近匹配塊的SAD值(代價),且C1和C3分別是左邊鄰居和右邊鄰居的SAD值。有:
d_est=d2-(C3-C1)/(2*(C1-2C2+C3))


(7)平滑和圖像金字塔

這是本篇未涉及的兩個主題。
第1個是通過考慮相鄰像素的視差來提高視差圖的精度。採用動態規劃。
第2個是圖像金字塔來加速塊匹配的過程。它涉及到對圖像下采樣來來在粗糙尺度上快速搜索,然後在更細尺度上提精搜索。


2. Matlab代碼實現


Chris Mccormick用Matlab實現固定窗口根據SAD的最小值來匹配像素點的立體視覺。這位大哥雖然只實現了SAD匹配部分,但他對圖像邊緣處的窗口處理得很好:在窗口邊緣時會將塊窗口的尺寸縮小,即左右圖像的兩個塊窗口縮小後作匹配,這樣保持了圖像的完整性。子像素估計實驗時覺得效果沒有明顯的改善,可以去掉這部分計算[1]。


Siddhant Ahuja用SAD,SSD,NCC和SHD實現固定窗口的立體視覺匹配,同時給出了計算壞像素點百分比和RMS的函數接口,但大多數情況下壞像素點百分比很高,因爲很當允許誤差的閾值爲1時,計算得到的視差值大多不滿足和實際視差值只差1個像素單位。但他把左右圖都割掉了一大塊,視差範圍或窗口尺寸越大割掉得越多。有可能他認爲小於指定視差範圍的匹配是不太可信的,不過圖像右側邊緣確實有部分會出現誤匹配[1]。SAD和SSD計算出來的匹配索引一模一樣,因爲代價排序時其絕對值較大的索引一定等於平方值較大的索引。同時SSD計算比SAD慢很多,不推薦用;NCC的效果也不錯;SHD計算複雜。總的來說這幾種方法中推薦用SAD和NCC。NCC的公式Mathworks給出了[4],這裏沒有歸一化的步驟,因爲灰度值範圍是[0,255]。用SHD時注意將圖像中的像素數據類型從double轉成uint8[5]。

Dsp Tian根據鏈接[2]的匹配方法作了公式化的總結[3]。


我以Chris Mccormick的代碼爲框架增加Siddhant Ahuja提到的方法,或者說在Siddhant Ahuja的基礎上考慮了圖像邊緣的塊匹配。


clc;
clear;

%% 加載2張立體圖像
left = imread('left.png');
right = imread('right.png');
sizeI = size(left);

% 顯示覆合圖像
zero = zeros(sizeI(1), sizeI(2));
channelRed = left(:,:,1);
channelBlue = right(:,:,3);
composite = cat(3, channelRed, zero, channelBlue);

figure(1);
subplot(2,2,1);
imshow(left);
axis image;
title('Left Image');

subplot(2,2,2);
imshow(right);
axis image;
title('Right Image');

subplot(2,2,3);
imshow(composite);
axis image;
title('Composite Image');

%% 基本的塊匹配

% 通過估計子像素的塊匹配計算視差
disp('運行基本的塊匹配~');

% 啓動定時器
tic();

% 平均3個顏色通道值將RGB圖像轉換爲灰度圖像
leftI = mean(left, 3);
rightI = mean(right, 3);


% SHD
bitsUint8 = 8;
leftI = im2uint8(leftI./255.0);
rightI = im2uint8(rightI./255.0);


% DbasicSubpixel將保存塊匹配的結果,元素值爲單精度32位浮點數
DbasicSubpixel = zeros(size(leftI), 'single');

% 獲得圖像大小
[imgHeight, imgWidth] = size(leftI);

% 視差範圍定義離第1幅圖像中的塊位置多少像素遠來搜索其它圖像中的匹配塊。對於大小爲450x375的圖像,視差範圍爲50是合適的
disparityRange = 50;

% 定義塊匹配的塊大小
halfBlockSize = 5;
blockSize = 2 * halfBlockSize + 1;

% 對於圖像中的每行(m)像素
for (m = 1 : imgHeight)
    	
	% 爲模板和塊設置最小/最大塊邊界
	% 比如:第1行,minr = 1 且 maxr = 4
    minr = max(1, m - halfBlockSize);
    maxr = min(imgHeight, m + halfBlockSize);
	
    % 對於圖像中的每列(n)像素
    for (n = 1 : imgWidth)
        
        % 爲模板設置最小/最大邊界
        % 比如:第1列,minc = 1 且 maxc = 4
		minc = max(1, n - halfBlockSize);
        maxc = min(imgWidth, n + halfBlockSize);
        
        % 將模板位置定義爲搜索邊界,限制搜索使其不會超出圖像邊界 
		% 'mind'爲能夠搜索至左邊的最大像素數;'maxd'爲能夠搜索至右邊的最大像素數
		% 這裏僅需要向右搜索,所以mind爲0
		% 對於要求雙向搜索的圖像,設置mind爲max(-disparityRange, 1 - minc)
		mind = 0; 
        maxd = min(disparityRange, imgWidth - maxc);

		% 選擇右邊的圖像塊用作模板
        template = rightI(minr:maxr, minc:maxc);
		
		% 獲得本次搜索的圖像塊數
		numBlocks = maxd - mind + 1;
		
		% 創建向量來保存塊偏差
		blockDiffs = zeros(numBlocks, 1);
        
		% 計算模板和每塊的偏差
		for (i = mind : maxd)
		
			%選擇左邊圖像距離爲'i'處的塊
			block = leftI(minr:maxr, (minc + i):(maxc + i));
		
			% 計算塊的基於1的索引放進'blockDiffs'向量
			blockIndex = i - mind + 1;
		    
            %{
            % NCC(Normalized Cross Correlation)
            ncc = 0;
            nccNumerator = 0;
            nccDenominator = 0;
            nccDenominatorRightWindow = 0;
            nccDenominatorLeftWindow = 0;
            %}
            
            % 計算模板和塊間差的絕對值的和(SAD)作爲結果
            for (j = minr : maxr)
                for (k = minc : maxc)
                    %{
                    % SAD(Sum of Absolute Differences)
                    blockDiff = abs(rightI(j, k) - leftI(j, k + i));
                    % SSD(Sum of Squared Differences)
                    % blockDiff = (rightI(j, k) - leftI(j, k + i)) * (rightI(j, k) - leftI(j, k + i));
                    blockDiffs(blockIndex, 1) = blockDiffs(blockIndex, 1) + blockDiff;
                    %}
                    
                    %{
                    % NCC
                    nccNumerator = nccNumerator + (rightI(j, k) * leftI(j, k + i));
                    nccDenominatorLeftWindow = nccDenominatorLeftWindow + (leftI(j, k + i) * leftI(j, k + i));
                    nccDenominatorRightWindow = nccDenominatorRightWindow + (rightI(j, k) * rightI(j, k));
                    %}
                end
            end
            %{
            % SAD
            blockDiffs(blockIndex, 1) = sum(sum(abs(template - block)));
            %}
            
            %{
            % NCC
            nccDenominator = sqrt(nccDenominatorRightWindow * nccDenominatorLeftWindow);
            ncc = nccNumerator / nccDenominator;
            blockDiffs(blockIndex, 1) = ncc;
            %}
            

            % SHD(Sum of Hamming Distances)
            blockXOR = bitxor(template, block);
            distance = uint8(zeros(maxr - minr + 1, maxc - minc + 1));
            for (k = 1 : bitsUint8)
                distance = distance + bitget(blockXOR, k);
            end
            blockDiffs(blockIndex, 1) = sum(sum(distance));

		end
		
		% SAD值排序找到最近匹配(最小偏差),這裏僅需要索引列表

        % SAD/SSD/SHD
        [temp, sortedIndeces] = sort(blockDiffs, 'ascend');

        %{
        % NCC
        [temp, sortedIndeces] = sort(blockDiffs, 'descend');
        %}
        % 獲得最近匹配塊的基於1的索引
		bestMatchIndex = sortedIndeces(1, 1);
		
        % 將該塊基於1的索引恢復爲偏移量
		% 這是基本的塊匹配產生的最後的視差結果
		d = bestMatchIndex + mind - 1;
		
        %{
		% 通過插入計算視差的子像素估計
		% 子像素估計要求用左右邊的塊, 所以如果最佳匹配塊在搜索窗的邊緣則忽略估計
		if ((bestMatchIndex == 1) || (bestMatchIndex == numBlocks))
			% 忽略子像素估計並保存初始視差值
			DbasicSubpixel(m, n) = d;
		else
			% 取最近匹配塊(C2)的SAD值和最近的鄰居(C1和C3)
			C1 = blockDiffs(bestMatchIndex - 1);
			C2 = blockDiffs(bestMatchIndex);
			C3 = blockDiffs(bestMatchIndex + 1);
			
			% 調整視差:估計最佳匹配位置的子像素位置
			DbasicSubpixel(m, n) = d - (0.5 * (C3 - C1) / (C1 - (2 * C2) + C3));
        end
        %}
        DbasicSubpixel(m, n) = d;
    end

	% 每10行更新過程
	if (mod(m, 10) == 0)
		fprintf('圖像行:%d / %d (%.0f%%)\n', m, imgHeight, (m / imgHeight) * 100);
    end		
end

% 顯示計算時間
elapsed = toc();
fprintf('計算視差圖花費 %.2f min.\n', elapsed / 60.0);

%% 顯示視差圖
fprintf('顯示視差圖~\n');

% 切換到圖像4
subplot(2,2,4);
% 第2個參數爲空矩陣,從而告訴imshow用數據的最小/最大值,並且映射數據範圍來顯示顏色
imshow(DbasicSubpixel, []);

% 去掉顏色圖會顯示灰度視差圖
colormap('jet');
colorbar;

% 指定視差圖的最小/最大值
%caxis([0 disparityRange]);

%設置顯示的標題
title(strcat('Basic block matching, Sub-px acc., Search left, Block size = ', num2str(blockSize)));

%% 誤匹配像素的百分比

thresh = 1;

computedDisparityMap = double(DbasicSubpixel);
groundTruthDisparityMap = double(imread('disp6.png'));

scale = max(max(groundTruthDisparityMap)) / max(max(computedDisparityMap));
computedDisparityMap = computedDisparityMap * scale;

numPixels=0;
perBADMatch=0.0;

tic;
for (i = 1 + halfBlockSize : imgHeight - halfBlockSize)
    for(j = 1 + halfBlockSize : imgWidth - halfBlockSize - disparityRange)
        if(groundTruthDisparityMap(i,j) ~= 0)
            if(abs(computedDisparityMap(i,j) - groundTruthDisparityMap(i,j)) > thresh)
                perBADMatch = perBADMatch + 1;
            end
            numPixels = numPixels + 1;
        end
    end
end
perBADMatch = perBADMatch / numPixels

timeTaken = toc;
fprintf('計算誤匹配像素的百分比花費 %.2f min.\n', elapsed / 60.0);


3. 實驗效果分析


下面3張爲實驗結果的熱度圖,第1張(從左向右)爲SHD的視差圖,對物體平滑部分處理得很糟糕。第2張爲SAD的視差圖,SAD在物體平滑部分處理得最好,物體邊緣部分效果也相對較好,但在深度有漸變的地方效果不如NCC。第3張爲NCC的視差圖。




再換個物體。SHD的效果依然比較亂,但原圖後面的木質網格確實有深度起伏,說明SHD對邊緣處細節表現得很好;SAD依然很穩定,尤其是物體平滑部分,但無法體現細小的邊緣變化。NCC體現細節的效果不如SHD,體現平滑的效果不如SAD,是SHD和SAD的折中選擇。




4. 參考鏈接


[1] https://chrisjmccormick.wordpress.com/2014/01/10/stereo-vision-tutorial-part-i/
[2] https://siddhantahuja.wordpress.com/tag/matlab-code/
[3] http://www.cnblogs.com/tiandsp/archive/2013/04/07/3006372.html
[4] http://www.mathworks.com/help/images/ref/normxcorr2.html
[5] http://blog.sina.com.cn/s/blog_7e4c1a090100ymx9.html


發佈了54 篇原創文章 · 獲贊 48 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章