NCC(normalized cross correlation)算法,归一化互相关匹配法,是基于图像灰度信息的匹配方法。是用来比较两幅图像的相似程度已经是一个常见的图像处理手段。
图像匹配的方法主要有三种:基于灰度,基于特征,基于变换域。
NCC算法可以有效降低光照对图像比较结果的影响。而且NCC最终结果在0到1之间,所以特别容易量化比较结果,只要给出一个阈值就可以判断结果的好与坏。传统的NCC比较方法比较耗时,虽然可以通过调整窗口大小和每次检测的步长矩形部分优化,但是对工业生产检测然后不能达到实时需求,通过积分图像实现预计算,比较模板图像与生产出电子版之间的细微差异,可以帮助企业提高产品质量,减少次品出厂率,把控质量。
对于原始的图像内任意一个像素点(px,py) 构建一个n×n 的邻域作为匹配窗口。然后对于目标相素位置(px+d,py) 同样构建一个n×n大小的匹配窗口,对两个窗口进行相似度度量,注意这里的d有一个取值范围。对于两幅图像来说,在进行NCC NCCNCC计算之前要对图像处理,也就是将两帧图像校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。
在数学上NCC代表着归一化的交叉相关性,是数学上统计两组数据之间是否有关系的判断方法,经常用于大数据分析比较流行相关性分析和计算。
通过公式z = f * Tx / d可以很简单地得到以左视图为参考系的深度图了。
至此,我们便完成了双目立体匹配。倘若只是用于图像识别,那么到步骤3时已经可以结束了。
图片来源:http://vision.middlebury.edu/stereo/data/scenes2003/newdata/teddy/im6.png
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
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:\testdata\jihe\a1.jpg').convert('L'), 'f')
im_r = array(Image.open(r'D:\testdata\jihe\a2.jpg').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 10
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('deptha10.png',res)
show()
由上图的实验结果可以看到,当随着窗口大小不断增大时,可以看到视差图的噪点不断减少,并且图片的细节也不断变少,实验图片中含有的具体细节都有被一定的平滑抹去掉,但由于窗口增大后的边缘逐渐模糊,也更有利于我们判断物体边缘。所以可以显而易见地得出对于wid的取值,我们应该要视情况而定,一般取适中即可,不可过大或过小。
并且,在匹配算法是我们追求的更多是对于位置特征的准确性,需要较多的细节,所以wid取值可以偏小,大约为10即可。