计算机视觉——立体视觉—NCC视差匹配
目录
- 实验原理
- 匹配流程
- 实验代码
- 实验结果
实验原理
视差图计算
深度信息可以通过计算1幅图像和其它图像的特征位置的像素差获得。视差图和深度图很像,因为视差大的像素离摄像机近,而视差小的像素离摄像机远。按以米为单位来计算摄像机距物体多远需要额外的计算。
根据Matlab教程,计算视差图的标准方法是用简单的块匹配(Block Matching)。我们选择右边图像中的1块小区域,并在左边图像中搜索匹配最近的像素区域。同理,当搜索右边图像时,我们从和左边图像的模板相同的坐标处开始,向左和向右搜索至最大距离。视差为右边图像的小区域和左边图像的最近匹配区域的中心像素的水平距离。
原理:
对于原始的图像内任意一个像素点(px,py) (p_x,p_y)(p x,p y)构建一个n×n 的邻域作为匹配窗口。然后对于目标相素位置(px+d,py) (p_x+d, p_y)(px+d,py)同样构建一个n×n n\times nn×n大小的匹配窗口,对两个窗口进行相似度度量,注意这里的d dd有一个取值范围。对于两幅图像来说,在进行NCC NCCNCC计算之前要对图像处理,也就是将两帧图像校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。
NCC计算公式如下图所示:

其中NCC(p,d) NCC(p,d)NCC(p,d)得到的值得范围将在[−1,1]之间 [-1,1]之间[−1,1]之间。
Wp 为之前提到的匹配窗口。
I1(x,y) 为原始图像的像素值。
I1(px,py) 为原始窗口内像素的均值。
I2(x+d,y) 为原始图像在目标图像上对应点位置在x 方向上偏移d后的像素值。
I2(px+d,py) 为目标图像匹配窗口像素均值。
若NCC=−1 ,则表示两个匹配窗口完全不相关,相反,若NCC=1 时,表示两个匹配窗口相关程度非常高。
匹配流程
采集图像:通过标定好的双目相机采集图像,当然也可以用两个单目相机来组合成双目相机。
极线校正:校正的目的是使两帧图像极线处于水平方向,或者说是使两帧图像的光心处于同一水平线上。通过校正极线可以方便后续的NCC 操作。
由标定得到的内参中畸变信息中可以对图像去除畸变。
通过校正函数校正以后得到相机的矫正变换R和新的投影矩阵P,接下来是要对左右视图进行去畸变,并得到重映射矩阵。
特征匹配:这里便是我们利用NCC 做匹配的步骤啦,匹配方法如上所述,右视图中与左视图待测像素同一水平线上相关性最高的即为最优匹配。完成匹配后,我们需要记录其视差d,即待测像素水平方向xl与匹配像素水平方向xr之间的差值d=xr−xl ,最终我们可以得到一个与原始图像尺寸相同的视差图D 。
深度恢复:通过上述匹配结果得到的视差图D ,我们可以很简单的利用相似三角形反推出以左视图为参考系的深度图。计算原理如下图所示:

如图,Tx 为双目相机基线,f 为相机焦距,这些可以通过相机标定步骤得到。而xr−xl 就是视差d 。
通过公式z=f×Txd \d
至此,我们便完成了双目立体匹配。倘若只是用于图像识别,那么到步骤3时已经可以结束了。
实验代码
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
import cv2
from numpy import *
from numpy.ma import array
from scipy.ndimage import filters
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(np.roll(norm_l, -displ - start) * norm_r, wid, s) # 和归一化
filters.uniform_filter(np.roll(norm_l, -displ - start) * np.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 np.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(np.roll(norm_l, -displ - start) * norm_r, wid, 0, s) # 和归一化
filters.gaussian_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, 0, s_l)
filters.gaussian_filter(norm_r*norm_r,wid,0,s_r) # 和反归一化
# 保存 ncc 的分数
dmaps[:,:,displ] = s / np.sqrt(s_l * s_r)
# 为每个像素选取最佳深度
return np.argmax(dmaps, axis=2)
im_l = array(Image.open(r'jia/1.jpg').convert('L'), 'f')
im_r = array(Image.open(r'jia/2.jpg').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 15
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('depth1.png',res)
show()
实验结果

depth=50
depth=100


depth=1000
depth=700
depth=500
depth=300

分析:
上述对比试验的结果可以看出,窗口值depth过小时,匹配代价区分度过低,在低纹理区域容易出现误匹配,匹配精度较低;随着窗口值增大,匹配区分度逐渐清晰,误匹配区域得到矫正,匹配精度随着窗口值增大而变高。但当窗口值过大时,在深度区域容易出现误匹配。因此窗口值的大小应适中,不宜过大也不宜过小。
