深度信息可以通过计算1幅图像和其它图像的特征位置的像素差获得。视差图和深度图很像,因为视差大的像素离摄像机近,而视差小的像素离摄像机远。按以米为单位来计算摄像机距物体多远需要额外的计算。
根据Matlab教程,计算视差图的标准方法是用简单的块匹配(Block Matching)。我们选择右边图像中的1块小区域,并在左边图像中搜索匹配最近的像素区域。
同理,当搜索右边图像时,我们从和左边图像的模板相同的坐标处开始,向左和向右搜索至最大距离。视差为右边图像的小区域和左边图像的最近匹配区域的中心像素的水平距离。
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张为实验结果的热度图,第1张(从左向右)为SHD的视差图,对物体平滑部分处理得很糟糕。第2张为SAD的视差图,SAD在物体平滑部分处理得最好,物体边缘部分效果也相对较好,但在深度有渐变的地方效果不如NCC。第3张为NCC的视差图。
再换个物体。SHD的效果依然比较乱,但原图后面的木质网格确实有深度起伏,说明SHD对边缘处细节表现得很好;SAD依然很稳定,尤其是物体平滑部分,但无法体现细小的边缘变化。NCC体现细节的效果不如SHD,体现平滑的效果不如SAD,是SHD和SAD的折中选择。
[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