使用FFT进行Matlab模板匹配

时间:2015-09-19 05:10:41

标签: matlab image-processing fft template-matching cross-correlation

我正在使用Matlab中的傅立叶域中的模板匹配。这是我的图像(艺术家是DeviantArt上的RamalamaCreatures):

https://cs.stackexchange.com/questions/2824/most-efficient-known-priority-queue-for-inserts possum.jpg

我的目标是在负鼠的耳朵周围放置一个边界框,就像这个例子(我使用normxcorr2进行模板匹配):

possum_ear.jpg

这是我正在使用的Matlab代码:

clear all; close all;

template = rgb2gray(imread('possum_ear.jpg'));
background = rgb2gray(imread('possum.jpg'));

%% calculate padding
bx = size(background, 2); 
by = size(background, 1);
tx = size(template, 2); % used for bbox placement
ty = size(template, 1);

%% fft
c = real(ifft2(fft2(background) .* fft2(template, by, bx)));

%% find peak correlation
[max_c, imax]   = max(abs(c(:)));
[ypeak, xpeak] = find(c == max(c(:)));
figure; surf(c), shading flat; % plot correlation 

%% display best match
hFig = figure;
hAx  = axes;
position = [xpeak(1)-tx, ypeak(1)-ty, tx, ty];
imshow(background, 'Parent', hAx);
imrect(hAx, position);

代码未按预期运行 - 它未识别正确的区域。这是失败的结果 - 错误的区域被装箱:  Goal: possum ear bounded

这是失败匹配的相关性的表面图: failed template matching

希望你能帮忙!谢谢。

2 个答案:

答案 0 :(得分:23)

您在代码中执行的操作实际上根本不是关联。您正在使用模板和执行卷积与输入图像。如果你从傅里叶变换中回忆起来,两个信号的频谱相乘就相当于时间/空间域中两个信号的卷积。

基本上,您正在做的是您将模板用作内核并使用它来过滤图像。然后,您将找到此输出的最大响应以及模板所在的位置。响应被装箱的地方是有意义的,因为该区域完全是白色的,并且使用模板作为具有完全白色区域的内核将给出非常大的响应,这就是为什么它最有可能确定该区域是最大的响应。具体来说,该区域将具有许多高值(约255左右),并且自然地使用模板补丁进行卷积,并且由于操作是加权和,该区域将给出非常大的输出。因此,如果您在图像的暗区使用模板,则输出会很小 - 这是假的,因为模板也包含暗像素。

但是,您当然可以使用傅立叶变换来定位模板的位置,但我建议您使用Phase Correlation代替。基本上,您不是计算两个光谱的乘法,而是计算交叉功率谱。频域中两个信号之间的交叉功率谱R定义为:

来源:Wikipedia

GaGb是原始图片和频域中的模板,*是共轭。 o是所谓的Hadamard产品或元素产品。我还想指出,这一部分的分子和分母的划分也是元素方面的。使用交叉功率谱,如果在此处找到产生绝对最大响应的(x,y)位置,则模板应位于背景图像中。

因此,您只需更改计算"相关性的代码行。这样它就可以计算出交叉功率谱。但是,我想指出一些非常重要的事情。执行normxcorr2时,相关性将从图像的左上角开始。模板匹配从此位置开始,并与一个窗口进行比较,该窗口是左上角为原点的模板大小。找到模板匹配的位置时,该位置相对于匹配窗口的左上角。计算normxcorr2后,传统上会添加最大响应的一半行和一半列,以找到中心位置

因为我们或多或少地使用FFT /频域进行模板匹配(滑动窗口,相关等)的相同操作,所以当您在此相关数组中找到峰值时,也必须采取考虑到这一点。但是,您对imrect调用模板匹配位置周围的矩形的调用无论如何都会在边界框的左上角进行,因此无需在此处进行偏移。因此,我们将稍微修改该代码,但如果想要找到匹配的中心位置,则在以后使用此代码时请记住偏移逻辑。

我也修改了你的代码,直接从StackOverflow读取图像,以便它可以重现:

clear all; close all;

template = rgb2gray(imread('http://i.stack.imgur.com/6bTzT.jpg'));
background = rgb2gray(imread('http://i.stack.imgur.com/FXEy7.jpg'));

%% calculate padding
bx = size(background, 2); 
by = size(background, 1);
tx = size(template, 2); % used for bbox placement
ty = size(template, 1);

%% fft
%c = real(ifft2(fft2(background) .* fft2(template, by, bx)));

%// Change - Compute the cross power spectrum
Ga = fft2(background);
Gb = fft2(template, by, bx);
c = real(ifft2((Ga.*conj(Gb))./abs(Ga.*conj(Gb))));

%% find peak correlation
[max_c, imax]   = max(abs(c(:)));
[ypeak, xpeak] = find(c == max(c(:)));
figure; surf(c), shading flat; % plot correlation    

%% display best match
hFig = figure;
hAx  = axes;

%// New - no need to offset the coordinates anymore
%// xpeak and ypeak are already the top left corner of the matched window
position = [xpeak(1), ypeak(1), tx, ty];
imshow(background, 'Parent', hAx);
imrect(hAx, position);

有了这个,我得到以下图像:

enter image description here

当显示交叉功率谱的表面图时,我也得到以下信息:

enter image description here

有明确定义的峰值,其余输出的响应非常小。这实际上是相关的一个属性,很明显,最大值的位置是明确定义的,这是模板所在的位置。

希望这有帮助!

答案 1 :(得分:1)

最终还是用python实现了相同的想法,与@rayryeng使用scipy.fftpack.fftn() / ifftn()函数的想法相似,并且在相同的目标和模板图像上具有以下结果:

import numpy as np
import scipy.fftpack as fp
from skimage.io import imread
from skimage.color import rgb2gray, gray2rgb
import matplotlib.pylab as plt
from skimage.draw import rectangle_perimeter

im = 255*rgb2gray(imread('http://i.stack.imgur.com/FXEy7.jpg'))    # target
im_tm = 255*rgb2gray(imread('http://i.stack.imgur.com/6bTzT.jpg')) # template

# FFT 
F = fp.fftn(im)                   
F_tm = fp.fftn(im_tm, shape=im.shape)

# compute the best match location
F_cc = F * np.conj(F_tm)
c = (fp.ifftn(F_cc/np.abs(F_cc))).real
i, j = np.unravel_index(c.argmax(), c.shape)
print(i, j)
# 214 317

# draw rectangle around the best match location
im2 = (gray2rgb(im)).astype(np.uint8)
rr, cc = rectangle_perimeter((i,j), end=(i + im_tm.shape[0], j + im_tm.shape[1]), shape=im.shape)
for x in range(-2,2):
    for y in range(-2,2):
        im2[rr + x, cc + y] = (255,0,0)

# show the output image
plt.figure(figsize=(10,10))
plt.imshow(im2)
plt.axis('off')
plt.show()

enter image description here

此外,以下动画显示了在将鸟的模板图像定位在从一群鸟的视频中提取的一组(目标)帧内时获得的结果。

enter image description here

需要注意的一件事:输出在很大程度上取决于要与模板匹配的对象的大小和形状的相似性,如果与模板图像的相似性很差,则模板可能不是完全匹配。