基于Normalized cross correlation(NCC)用来比较两幅图像的相似程度已经是一个常见的图像处理手段。在工业生产环节检测、监控领域对对象检测与识别均有应用。NCC算法可以有效降低光照对图像比较结果的影响。而且NCC最终结果在0到1之间,所以特别容易量化比较结果,只要给出一个阈值就可以判断结果的好与坏。传统的NCC比较方法比较耗时,虽然可以通过调整窗口大小和每次检测的步长矩形部分优化,但是对工业生产检测然后不能达到实时需求,通过积分图像实现预计算,比较模板图像与生产出电子版之间的细微差异,可以帮助企业提高产品质量,减少次品出厂率,把控质量。
什么是NCC - (normalized cross correlation)归一化的交叉相关性,是数学上统计两组数据之间是否有关系的判断方法,貌似搞大数据分析比较流行相关性分析和计算。正常的计算公式如下:
mxn表示窗口大小,这样的计算复杂度就为O(m x n x M x N)。从上面公式就可以看出其中均值和平方和可以通过积分图预计算得到,对于模板和目标图像大小一致的应用场景来说
NCC的计算公式可以表示为如下:
其中根据积分图像可以提前计算出任意窗口大小和与平方和,这样就对
上述两个计算实现了窗口半径无关的常量时间计算,唯一缺少的是下面计算公式
通过积分图像建立起来窗口下面的待检测图像与模板图像的和与平方和以及他们的交叉乘积五个积分图索引之后,这样就完成了整个预计算生成。依靠索引表查找计算结果,NCC就可以实现线性时间的复杂度计算,而且时间消耗近似常量跟窗口半径大小无关,完全可以满足实时对象检测工业环境工作条件。
算法步骤
预计算模板图像和目标图像的积分图
根据输入的窗口半径大小使用积分图完成NCC计算
根据阈值得到匹配或者不匹配区域。
输出结果
为了减小计算量,我们可以要把输入的图像转换为灰度图像,在灰度图像的基础上完成整个NCC计算检测。我们这个给出的基于RGB图像的NCC计算完整代码,读者可以在此基础上修改实现单通道图像检测。
stereo.py
import numpy as np
from scipy.ndimage import filters
def plane_sweep_ncc(im_l,im_r,start,steps,wid):
""" 使用归一化的互相关计算视差图像 """
m,n = im_l.shape
# 保存不同求和值的数组
mean_l = np.zeros((m,n))
mean_r = np.zeros((m,n))
s = np.zeros((m,n))
s_l = np.zeros((m,n))
s_r = np.zeros((m,n))
# 保存深度平面的数组
dmaps = np.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/np.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 = np.zeros((m,n))
mean_r = np.zeros((m,n))
s = np.zeros((m,n))
s_l = np.zeros((m,n))
s_r = np.zeros((m,n))
# 保存深度平面的数组
dmaps = np.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)
main.py
import stereo
import numpy as np
from PIL import Image
import imageio
im_l = np.array(Image.open('F:/python/pic/im4.png').convert('L'),'f')
im_r = np.array(Image.open('F:/python/pic/im5.png').convert('L'),'f')
# 开始偏移,并设置步长
steps = 12
start = 4
# ncc 的宽度
wid = 9
res = stereo.plane_sweep_ncc(im_l,im_r,start,steps,wid)
imageio.imsave('F:/python/pic/f6.png',res)
第一组三个结果的窗口值分别为6,9,12,第二组三个结果使用的窗口值为15,18,24,当窗口值较小时,在平坦无纹理的情况下略有些模糊,可以看出匹配较为敏感,细节点较少,结果精度显得有些低。当窗口值较大时,细节点增多,图片清晰度提升,噪点也同步增多,匹配区分度逐渐提升,另外,当窗口值过大时,匹配点数值不增反减,得出的结果只会显示黑块与白块,得出需根据条件选择窗口值,图片本身属性也会影响结果,不同像素的图片在相同窗口值下得出的结果并不相似