立体视觉是计算机视觉领域的一个重要课题,它的目的在于重构场景的三维几何信息。立体视觉的研究具有重要的应用价值,其应用包括移动机器人的自主导航系统,航空及遥感测量,工业自动化系统等。
立体视觉的研究方法之一,利用多幅图象来恢复三维信息的方法,它是被动方式的。根据图象获取方式的区别又可以划分成普通立体视觉和通常所称的光流(optical
flow)两大类。普通立体视觉研究的是由两摄像机同时拍摄下的两幅图象,而光流法中研究的是单个摄像机沿任一轨道运动时顺序拍下的两幅或更多幅图象。前者可以看作后者的一个特例,它们具有相同的几何构形,研究方法具有共同点。双目立体视觉是它的一个特例。
其中立体匹配是立体视觉系统的核心,是建立图像间的对应从而计算视差的过程,是极为重要的。
归一化相关性,normalization cross-correlation
,因此简称 N C C NCC NCC。顾名思义,就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置 p p p( p x p_x px, p y p_y py)构建 3 ∗ 3 3*3 3∗3邻域匹配窗口,与目标像素位置 p ′ p' p′( p x + d px+d px+d, p y py py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。
NCC是一种基于统计学计算两组样本数据相关性的算法,其取值范围为[-1, 1]之间,而对图像来说,每个像素点都可以看出是RGB数值,这样整幅图像就可以看成是一个样本数据的集合,如果它有一个子集与另外一个样本数据相互匹配则它的ncc值为1,表示相关性很高,如果是-1则表示完全不相关,基于这个原理,实现图像基于模板匹配识别算法,其中第一步就是要归一化数据。 N C C NCC NCC计算公式如下图所示:
- 其中 N C C ( p , d ) NCC(p,d) NCC(p,d) N C C ( p , d ) N C C ( p , d ) NCC(p,d)NCC(p,d) NCC(p,d)NCC(p,d)得到的值得范围将在 [ − 1 , 1 ] [−1,1] [−1,1]之间 [ − 1 , 1 ] [-1,1] [−1,1]之间 [ − 1 , 1 ] [−1,1] [−1,1]之间。
- W p W_p Wp为之前提到的匹配窗口。
- I 1 ( x , y ) I_1 (x,y) I1(x,y)为原始图像的像素值。
- I 1 ( p x , p y ) I_1 (p x ,p y ) I1(px,py)为原始窗口内像素的均值。
- I 2 ( x + d , y ) I_2 (x+d ,y ) I2(x+d,y)为原始图像在目标图像上对应点位置在xxx方向上偏移ddd后的像素值。
- I 2 ( p x + d , p y ) I_2 (p_x+d ,p_y ) I2(px+d,py)为目标图像匹配窗口像素均值。
- 若 N C C = − 1 NCC=−1 NCC=−1,则表示两个匹配窗口完全不相关,相反,若 N C C = 1 NCC=1 NCC=1时,表示两个匹配窗口相关程度非常高。
如图, T x Tx Tx为双目相机基线, f f f为相机焦距,这些可以通过相机标定步骤得到。而 x r − x l x_r−x_l xr−xl就是视差 d d d。通过公式 z = f ∗ T x d z=\frac{f*T_x}{d} z=df∗Tx可以很简单地得到以左视图为参考系的深度图了。
至此,我们便完成了双目立体匹配。倘若只是用于图像识别,那么到步骤3时已经可以结束了。
参考博文https://blog.csdn.net/qq_38898129/article/details/93074387?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5
立体匹配算法也称为视差算法,目的是求解两幅图片之间的视差值。主要是通过找出每对图像间的对应关系,根据三角测量原理,得到视差图;在获得了视差信息后,根据投影模型很容易地可以得到原始图像的深度信息和三维信息。
影响视差算法的因素:
Foreshortening
)Perspective distortions
)Low texture
)Repetitive/ambiguous patterns
)1. 匹配代价计算: `
匹配代价计算的目的是衡量待匹配像素与候选像素之间的相关性。两个像素无论是否为同名点,都可以通过匹配代价函数计算匹配代价,代价越小则说明相关性越大,是同名点的概率也越大。匹配代价的算法由很多种,本文采用 N C C NCC NCC算法匹配。
`
2. 代价聚合:
代价聚合的根本目的是让代价值能够准确的反映像素之间的相关性。上一步匹配代价的计算往往只会考虑局部信息,通过两个像素邻域内一定大小的窗口内的像素信息来计算代价值,这很容易受到影像噪声的影响,而且当影像处于弱纹理或重复纹理区域,这个代价值极有可能无法准确的反映像素之间的相关性,直接表现就是真实同名点的代价值非最小。
3.视差获取:
对于区域算法来说,在完成匹配代价的叠加以后,视差的获取就很容易了,只需在一定范围内选取叠加匹配代价最优的点( S A D SAD SAD和 S S D SSD SSD取最小值, N C C NCC NCC取最大值)作为对应匹配点,如胜者为王算法 W T A WTA WTA(
Winner-take-all
)。而全局算法则直接对原始匹配代价进行处理,一般会先给出一个能量评价函数,然后通过不同的优化算法来求得能量的最小值,同时每个点的视差值也就计算出来了。
4. 视差细化(亚像素级):
大多数立体匹配算法计算出来的视差都是一些离散的特定整数值,可满足一般应用的精度要求。但在一些精度要求比较高的场合,如精确的三维重构中,就需要在初始视差获取后采用一些措施对视差进行细化,如匹配代价的曲线拟合、图像滤波、图像分割等。
代码:
import stereo
import scipy.misc
from PIL import Image
from pylab import *
from scipy.ndimage import *
def plane_sweep_ncc(im_l, im_r, start, steps, wid):
# 使用归一化的互相关计算视差图像 """
m, n = im_l.shape
# 保存不同求和值的数组
mean_l = zeros((m, n))
mean_r = zeros((m, n))
s = zeros((m, n))
s_l = zeros((m, n))
s_r = zeros((m, n))
# 保存深度平面的数组
dmaps = zeros((m, n, steps))
# 计算图像块的平均值
filters.uniform_filter(im_l, wid, mean_l)
filters.uniform_filter(im_r, wid, mean_r)
# 归一化图像
norm_l = im_l - mean_l
norm_r = im_r - mean_r
# 尝试不同的视差
for displ in range(steps):
# 将左边图像移动到右边,计算加和
filters.uniform_filter(roll(norm_l, -displ - start) * norm_r, wid, s) # 和归一化
filters.uniform_filter(roll(norm_l, -displ - start) * roll(norm_l, -displ - start), wid, s_l)
filters.uniform_filter(norm_r * norm_r, wid, s_r) # 和反归一化
# 保存 ncc 的分数
dmaps[:, :, displ] = s / sqrt(s_l * s_r)
# 为每个像素选取最佳深度
return argmax(dmaps, axis=2)
def plane_sweep_gauss(im_l, im_r, start, steps, wid):
# 使用带有高斯加权周边的归一化互相关计算视差图像 """
m, n = im_l.shape
# 保存不同加和的数组
mean_l = zeros((m, n))
mean_r = zeros((m, n))
s = zeros((m, n))
s_l = zeros((m, n))
s_r = zeros((m, n))
# 保存深度平面的数组
dmaps = zeros((m, n, steps))
# 计算平均值
filters.gaussian_filter(im_l, wid, 0, mean_l)
filters.gaussian_filter(im_r, wid, 0, mean_r)
# 归一化图像
norm_l = im_l - mean_l
norm_r = im_r - mean_r
# 尝试不同的视差
for displ in range(steps):
# 将左边图像移动到右边,计算加和
filters.gaussian_filter(roll(norm_l, -displ - start) * norm_r, wid, 0, s) # 和归一化
filters.gaussian_filter(roll(norm_l, -displ - start) * roll(norm_l, -displ - start), wid, 0, s_l)
filters.gaussian_filter(norm_r * norm_r, wid, 0, s_r) # 和反归一化
# 保存 ncc 的分数
dmaps[:, :, displ] = s / sqrt(s_l * s_r)
# 为每个像素选取最佳深度
return argmax(dmaps, axis=2)
im_l = array(Image.open('G:/QQdata/tsukuba/scene1.row3.col1.ppm').convert('L'), 'f')
im_r = array(Image.open('G:/QQdata/tsukuba/scene1.row3.col5.ppm').convert('L'), 'f')
# 开始偏移,并设置步长
steps = 50
start = 4
# ncc 的宽度
wid = 12
res = stereo.plane_sweep_ncc(im_l, im_r, start, steps, wid)
imsave('depth.png', res)
测试数据集:
测试数据可以从这里下载http://vision.middlebury.edu/stereo/data/scenes2003/本文使用的测试数据是cones
这两组的对比可以看出当窗口值过低时低纹理区域容易出现误匹配,匹配精度较低,并且噪声也很多。随着窗口值的增大,其中错误的匹配的区域也随之慢慢被矫正。不过这只是猜想,接下来继续增大窗口值以用来验证正确性。
随着窗口值增大可以印证上面的判断纹理低的区域错误的匹配基本上得到矫正,但是在信息的细节上越来越模糊可以从上图勾出的红圈与篮圈中看出。
从wid=20可以明显看出,图片中的噪声几乎被矫正完,但随之而然的图片细节鲁棒也越来越差。由此可以得出结论。wid值越小图片对噪声越敏感,在低纹理区会出现很多错误匹配,但细节鲁棒好,值越大噪声鲁棒好,错误匹配随之被纠正但细节随之会被忽略掉,导致图片的精度小。