立体视觉——固定窗口的视差图计算

立体视觉——固定窗口的视差图计算


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的折中选择。


立体视觉——固定窗口的视差图计算_第1张图片


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


你可能感兴趣的:(Matlab)