目录
一、基本原理
NCC,顾名思义,就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置p(px,py)构建33邻域匹配窗口,与目标像素位置p’(px+d,py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。相关程度的度量方式由如下式子定义:
p点表示图像I1待匹配像素坐标(px,py),d表示在图像I2被查询像素位置在水平方向上与px的距离。如下图所示:
左边为图像I1,右边为图像I2。图像I1,蓝色方框表示待匹配像素坐标(px,py),图像I2蓝色方框表示坐标位置为(px,py),红色方框表示坐标位置(px+d,py)。
Wp表示以待匹配像素坐标为中心的匹配窗口,通常为3*3匹配窗口。
没有上划线的I1表示匹配窗口中某个像素位置的像素值,带上划线的I1表示匹配窗口所有像素的均值。I2同理。
上述公式表示度量两个匹配窗口之间的相关性,通过归一化将匹配结果限制在 [-1,1]的范围内,可以非常方便得到判断匹配窗口相关程度:
若NCC = -1,则表示两个匹配窗口完全不相关,相反,若NCC = 1时,表示两个匹配窗口相关程度非常高。
二、NCC匹配过程
(1)采集图像:通过标定好的双目相机采集图像,当然也可以用两个单目相机来组合成双目相机。
(2)极线校正:校正的目的是使两帧图像极线处于水平方向,或者说是使两帧图像的光心处于同一水平线上。通过校正极线可以方便后续的NCC操作。
a.由标定得到的内参中畸变信息中可以对图像去除畸变。
b.通过校正函数校正以后得到相机的矫正变换R和新的投影矩阵P,接下来是要对左右视图进行去畸变,并得到重映射矩阵。
(3)特征匹配:这里便是我们利用NCC做匹配的步骤啦,匹配方法如上所述,右视图中与左视图待测像素同一水平线上相关性最高的即为最优匹配。完成匹配后,我们需要记录其视差d,即待测像素水平方向xl与匹配像素水平方向xr之间的差值,最终我们可以得到一个与原始图像尺寸相同的视差图DDD。
(4)深度恢复:通过上述匹配结果得到的视差图DDD,我们可以很简单的利用相似三角形反推出以左视图为参考系的深度图。计算原理如下图所示:
如图,为双目相机基线,f为相机焦距,这些可以通过相机标定步骤得到。而
就是视差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'D:/JMU/computer_vision/SIFTcode/NCC/3.png').convert('L'), 'f')
im_r = array(Image.open(r'D:/JMU/computer_vision/SIFTcode/NCC/4.png').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 11
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('depth.png',res)
show()
四、实验结果截图与分析
原图:
(1)窗口大小为3时
(2)窗口大小为5时
(3)窗口大小为7时
(4)窗口大小为9时
(5)窗口大小为11时
(6)窗口大小为20时
(7)窗口大小为30时
原图:
(1)窗口大小为3时
(2)窗口大小为5时
(3)窗口大小为6时
(4)窗口大小为7时
(5)窗口大小为9时
(6)窗口大小为11时
(7)窗口大小为20时
(8)窗口大小为30时
分析:根据上图可以知道,当我们修改窗口大小时,匹配结果也会发生变化。当窗口大小设置过小时,如窗口大小等于3时,匹配点的匹配错误较高,图像较模糊,我们能从中得到的信息也较少;当窗口大小逐渐增大时,图像也逐渐变得清晰,匹配点的匹配正确程度也逐渐增加,我们从中能得到的信息也逐渐增大,但是由上图也可以知道,当窗口大小过大时,图像虽然依旧越来越清晰,但是匹配点的匹配正确程度却又降低了许多,出现了许多的匹配错误点,当然也有许多的地方渐渐的变成了白色。因此,我们窗口大小应该保持在一个适中的范围较好。
五、问题
原因是两张图片的像素大小不一样导致的,所以最终对图像进行了裁剪。