一个多视图成像的特殊例子是立体视觉(或者立体成像),即使用两台只有水平(向 一侧)偏移的照相机观测同一场景。当照相机的位置如上设置,两幅图像具有相同 的图像平面,图像的行是垂直对齐的,那么称图像对是经过矫正的。该设置在机器 人学中很常见,常被称为立体平台。
归一化相关性,简称NCC,就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置p(px,py)构建n*n邻域匹配窗口,与目标像素位置p’(px+d,py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。相关程度的度量方式由如下式子定义:
上式中的变量需要解释一下:其中p点表示图像I1待匹配像素坐标(px,py),d表示在图像I2被查询像素位置在水平方向上与px的距离。如下图所示:
左边为图像I1,右边为图像I2。图像I1,蓝色方框表示待匹配像素坐标(px,py),图像I2蓝色方框表示坐标位置为(px,py),红色方框表示坐标位置(px+d,py)。
上述公式表示度量两个匹配窗口之间的相关性,通过归一化将匹配结果限制在 [-1,1]的范围内,可以非常方便得到判断匹配窗口相关程度:
若NCC = -1,则表示两个匹配窗口完全不相关,相反,若NCC = 1时,表示两个匹配窗口相关程度非常高。
假设有校正过的两帧图像I1,、I2,由上述NCC计算流程的描述可知,对图像I1一个待匹配像素构建n*n匹配窗口,在图像I2极线上对每一个像素构建匹配窗口与待匹配像素匹配窗口计算相关性,相关性最高的视为最优匹配。
双目立体匹配流程如下:
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)
1)不同图像的匹配结果
小结:比较两幅细节特征不同的图像的匹配结果图可以看出:
1.两幅图像中的物体远近距离关系都有通过颜色亮度变换表现出来,越高亮的区域则表示物体离摄像点更近,越暗沉的区域则表示物体离摄像点更远
2.在第一张图像中,雕塑的面部特征没有清晰的变现出来,这或许是因为雕塑的面部特征比如耳朵、眼睛、嘴巴等等是趋近平面的,左右视差不明显导致
3.在第二张图像中,台灯的匹配展示特别复杂紊乱,这是因为台灯表面因为光线明亮变化区域较大,导致特征匹配时左右视图的匹配点对应了多个错误匹配
不同ncc 的宽度结果图像:
wid = 3
wid = 5
wid = 7
wid = 9
wid = 12
小结:通过比对几组不同的窗口值可以看出:
1.当wid值很小,如wid=3时,图像的匹配结果变得特别模糊、尖锐,像红框中表现出来的,线条变得很生硬,无法识别出物体特征,这是因为wid值过小,窗口变成3*3的大小,这样使得匹配点不够精准,在极线上有太多相似点匹配,造成了误差。
2.当wid=5,7时,如图像中红框表示的,图像中物体的边缘变得逐渐清晰,不会有太多类似噪声点的情况,同时,在蓝框中表示的台灯特征变得越加清晰,这是因为窗口变得更加大,所包含的数据更多,使得匹配点更加精准,图像更加平滑
3.当wid值很大,如wid=12时,结果图像开始丢失一些边缘信息,如红框和蓝框标识所示,图像中人物雕塑的边缘变得过于平滑,缺少棱角,这是因为窗口变得太大,相邻的两个匹配点的窗口中的值容易出现重叠,也就是有一些同样的像素点被包含进特征匹配算法中,冲淡了匹配点的数值,导致掩盖掉了一些细节信息
1.NCC视差匹配方法,能够有效的标示出图像中物体的远近位置关系,但是其受到很多方面因素的约束,例如在拍摄图像时,明亮光线、角度变换等等物理因素容易导致拍摄的物体反光强弱不同,这对特征匹配结果有着较大的影响,以及在图像中的物体是透明,或者角度变换、遮挡等因素时,NCC算法容易受到干扰。
2.不同的窗口值会直接影响匹配结果,过大容易丢失细节信息、过小容易无法识别出物体特征,产生无用的噪声点,匹配代价区分度过低,在低纹理区域容易出现误匹配,匹配精度较低,在实验中发现,wid = 9是比较适合的窗口值。