(三)OpenCV中的图像处理之图像变换及模板匹配

时间:2023-02-07 13:34:15

注释:本文翻译自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括对原文档种错误代码的纠正

3.10 OpenCV中的图像变换

第一节:傅里叶变换(Fourier Transform)

1.目标

  • 使用OpenCV查找图像的傅里叶变换
  • 利用Numpy中可用的FFT函数
  • 傅里叶变换的一些应用
  • 我们将学习以下函数:cv2.dft()、cv2.idft()等

2.原理

傅里叶变换用于分析各种滤波器的频率特性。对于图像,使用2D离散傅里叶变换(DFT)来查找频域。称为快速傅里叶变换(FFT)的快速算法用于计算DFT。有关这些细节可以在任何图像处理或信号处理教科书中找到。请参考其它资源部份。

对于一个正弦信号,(三)OpenCV中的图像处理之图像变换及模板匹配 ,我们可以说f是信号的频率,如果取其频率域,我们可以在f处看到一个尖峰。如果信号被采样以形成离散信号,我们得到相同的频域。但是在范围 (三)OpenCV中的图像处理之图像变换及模板匹配(三)OpenCV中的图像处理之图像变换及模板匹配 (三)OpenCV中的图像处理之图像变换及模板匹配你可以将图像视为在两个方向上采样的信号。因此,在X方向和Y方向进行傅里叶变换将为你提供图像的频率表示。

更直观地说,对于正弦信号,如果幅度在短时间内变化得如此之快,则可以说它是高频信号。如果变化缓慢,则是低频信号。你可以将相同的想法扩展到图像。图像中振幅的变化幅度如何?在边缘点,或噪音?所以我们可以说,边缘和噪音是图像中的高频内容,如果振幅没有太大的变化,则它是低频分量。(一些链接被添加到附加资源中,通过示例直观地解释频率转换)。

现在我们将看到如何找到傅里叶变换。

3.Numpy中的傅里叶变换

首先,我们将看到如何使用Numpy查找傅里叶变换。Numpy有一个FFT软件包来做到这一点。np.fft.fft2()为我们提供了频率变换,这将是一个复杂的数组。它的第一个参数是灰度的输入图像,第二个参数是可选的,它决定输出数组的大小。如果它大于输入图像的大小,则在计算FFT之前,输入图像用0填充。如果它小于输入图像,输入图像将被裁剪。如果没有参数传递,则输出数组大小将与输入相同。

现在一旦得到结果,零频率分量(直流分量)将位于左上角。如果要将它置于中心,则需要在两个方向上将结果移动 (三)OpenCV中的图像处理之图像变换及模板匹配这只是通过np.fft.fft2()完成的(分析起来更容易)。一旦你找到频率变换,就可以找到幅度谱。

示例如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('5.jpg', 0)
f = np.fft.fft2(img)  # 快速傅里叶变换算法得到频率分布
fshift = np.fft.fftshift(f)  # 默认结果中心点位置是在左上角,转移到中间位置
magnitude_spectrum = 20 * np.log(np.abs(fshift))  # 结果是复数,求绝对值才是振幅

# 结果展示
plt.subplot(121), plt.imshow(img, 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

结果:

(三)OpenCV中的图像处理之图像变换及模板匹配

看,你可以在中心看到更多更白的区域,显示低频内容更多。

所以你找到了频率变换,现在你可以在频域中做一些操作,如高通滤波和重构图像,即找到逆DFT。为此,你只需通过使用尺寸为60*60的矩形窗口进行遮盖来移除低频。然后使用np.fft.ifftshit()应用反向偏移,以便DC分量再次出现在左上角。然后使用np.ifft2()函数来找到反FFT。结果也是一个复杂的数字。你可以拿它的绝对值。


import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('5.jpg', 0)
f = np.fft.fft2(img)  # 快速傅里叶变换算法得到频率分布
fshift = np.fft.fftshift(f)  # 默认结果中心点位置是在左上角,转移到中间位置
magnitude_spectrum = 20 * np.log(np.abs(fshift))  # 结果是复数,求绝对值才是振幅

# 找到频域变换后,可以在频域中做一些操作,如高通滤波和重构图像,即找到逆DFT
rows, cols = img.shape
crow, ccol = rows / 2, cols / 2
fshift[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)

plt.subplot(131), plt.imshow(img, 'gray')
plt.title("Input Image"), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img_back, 'gray')
plt.title('Image After HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])

plt.show()

结果:

(三)OpenCV中的图像处理之图像变换及模板匹配

结果显示高通滤波是边缘检测操作。这就是我们在Image Gradients章节看到的。这也表明大部分图像数据存在于频谱的低频区域。无论如何,我们已经看到如何在Numpy中找到DFT,IDFT等。现在让我们看看如何在OpenCV中完成它。

如果仔细观察结果,尤其是JET颜色中的最后一幅图像,可以看到一些工作,它显示了一些波纹状结构,它被称为震荡效应。这是由于我们用于掩蔽的矩形窗口引起的。这个蒙版被转换为正弦形状,导致了问题。所以矩形窗口不用于过滤。更好的选择是高斯窗口。


4.OpenCV中的傅里叶变换

OpenCV为此提供了cv2.dft()cv2.idft().它返回与以前相同的结果,但具有两个通道。第一个通道将具有结果的实际部分,第二个通道将具有结果的虚部。输入图像应首先转换为np.float32.我们将看到如何去做。

注意:你也可以使用cv2.cartToPolar(),它可以一次性返回幅值和相位。

所以,现在我们必须做逆DFT。在之前的会话中,我们创建了一个HPF,这次我们将看到如何去除图像中的高频内容,即我们将LPF应用于图像。它实际上模糊了图像,为此,我们首先在低频处创建一个高值(1)的掩膜,即我们通过LF内容,在HF区域传递0.


# -*- coding: utf-8 -*-
'''
OpenCV中的傅里叶变换:
1.cv2.dft()和cv2.idft(),输入图像应先转换成np.float32
attention:也可以使用cv2.cartToPolar(),可以一次性返回幅值和相位
          cv2.dft()和cv2.idft()比Numpy对应的函数更快,但是Numpy函数更友好。
'''

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('5.jpg', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

magnitude_spectrum = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
'''
plt.subplot(121), plt.imshow(img, 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
'''

rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)  # 这里还是必须转换为整数哦

# 首先创建一个掩膜,中心点为1,其余值为0
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow - 30:crow + 30, ccol - 30: ccol + 30] = 1

# 应用掩膜和逆DFT
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)

img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])
plt.subplot(121), plt.imshow(img, 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_back, 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])

plt.show()

结果:

(三)OpenCV中的图像处理之图像变换及模板匹配


5.DFT的性能优化

对于某些数组大小,DFT计算的性能更好。当阵列大小是2的幂时,它是最快的。大小为2,3和5的乘积的数组也被相当有效地处理。因此,如果你担心代码的性能,可以在查找DFT之前将数组的大小修改为任何最佳大小(通过填充零)。对于OpenCV,你必须手动填充零。但对于Numpy,你指定了FFT计算的新大小,并且它会自动为你填充零。

那么我们如何寻找这个最佳尺寸?OpenCV为此专门提供了一个函数cv2.getOptimalDFTSize().它适用于cv2.dft()np.fft.fft2()。让我们使用Python的命令好%timeit来检查它们的性能。

In [16]: img = cv2.imread('messi5.jpg',0)
In [17]: rows,cols = img.shape
In [18]: print rows,cols
342 548
 
In [19]: nrows = cv2.getOptimalDFTSize(rows)
In [20]: ncols = cv2.getOptimalDFTSize(cols)
In [21]: print nrows, ncols
360 576

看,(342,548)被修改为(360,576).现在让我们用零来填充(对于OpenCV)并且找到它们的DFT计算性能。你可以通过创建一个新的空数组并将数据复制给它,或者使用cv2.copyMakeBorder()来完成。

nimg = np.zeros((nrows,ncols))
nimg[:rows,:cols] = img

或者:

right = ncols - cols
bottom = nrows - rows
bordertype = cv2.BORDER_CONSTANT #just to avoid line breakup in PDF file
nimg = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)

现在我们来计算Numpy函数的DFT性能比较:

In [22]: %timeit fft1 = np.fft.fft2(img)
10 loops, best of 3: 40.9 ms per loop
In [23]: %timeit fft2 = np.fft.fft2(img,[nrows,ncols])
100 loops, best of 3: 10.4 ms per loop

它显示了四倍的加速,现在我们尝试OpenCV函数。

In [24]: %timeit dft1= cv2.dft(np.float32(img),flags=cv2.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 13.5 ms per loop
In [27]: %timeit dft2= cv2.dft(np.float32(nimg),flags=cv2.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 3.11 ms per loop

它也显示了四倍加速。你还可以看到OpenCV函数比Numpy函数大约快倍。这也可以针对逆FFT进行测试。对你来说是一个练习。

6.为什么拉普拉斯算子是高通滤波器

在论坛上提出了类似的问题。问题是,为什么拉普拉斯是高通滤波器?为什么Sobel是HPF,等等。第一个答案就是傅里叶变换。只需对拉普拉斯算子进行傅里叶变换即可获得更高的FFT大小。分析它:

# -*- coding: utf-8 -*-
'''
集中不同滤波器的对比:均值滤波,高斯滤波、scharr滤波、sobel_x,sobel_y,拉普拉斯算子
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt

#简单的均值滤波器没有参数
mean_filter=np.ones((3,3))

#创建一个高斯滤波器
x=cv2.getGaussianKernel(5,10)
gaussian=x*x.T

#不同的边缘检测滤波器
#x方向上的scharr
scharr=np.array([[-3,0,3],
                 [-10,0,10],
                 [-3,0,3]])

#x方向上的sobel
sobel_x=np.array([[-1,0,1],
                  [-2,0,2],
                  [-1,0,1]])

#y方向上的sobel
sobel_y=np.array([[-1,-2,-1],
                  [0,0,0],
                  [1,2,1]])

#拉普拉斯算子
laplacian=np.array([[0,1,0],
                    [1,-4,1],
                    [0,1,0]])

filters=[mean_filter,gaussian,scharr,sobel_x,sobel_y,laplacian]
filter_name=['mean_filter','gaussian','scharr','sobel_x','sobel_y','laplacian']
fft_filter=[np.fft.fft2(x) for x in filters]
fft_shift=[np.fft.fftshift(y) for y in fft_filter]
#地图谱
map_spectrum=[np.log(np.abs(z)+1) for z in fft_shift]

for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(map_spectrum[i],'gray')
    plt.title(filter_name[i]),plt.xticks([]),plt.yticks([])

plt.show()

结果:

(三)OpenCV中的图像处理之图像变换及模板匹配

从图像中,可以看到每个内核阻塞的频率区域以及它传递的区域。从这些信息中,我们可以说出为什么每个内核都是HPF或LPF。


3.11 模板匹配

1.目标

  • 使用模板匹配查找图像中的对象
  • 学会这些函数:cv2.matchTemplate()、cv2.minMaxLoc()

2.原理

模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。OpenCV为此提供了函数cv2.matchTemplate().它只是将模板图像滑过输入图像(如2D卷积),并比较模板图像下的输入图像的模板和补丁。OpenCV中实现了集中比较方法。(你可以查看文档了解更多详情)。它返回有一个灰度图像,其中每个像素表示该像素的领域和模板匹配的程度。

如果输入 图像的大小是(W*H)和模板图像的大小(w*h),则输出图像的大小为(W-w+1,H-h+1)。一旦得到结果,就可以使用cv2.minMaxLoc()函数来查找最大值/最小值。将它作为矩形的左上角,并将(w,h)作为矩形的宽度和高度。该矩形是你的模板区域。

注意:如果你使用cv2.TM_SQDIFF作为比较方法,最小值会给出最佳匹配。


3.OpenCV中的模板匹配

在这个栗子中将在图像中搜索下面同学的这张脸,所以创建模板如下:

(三)OpenCV中的图像处理之图像变换及模板匹配

我们将尝试所有比较方法,以便我们可以看到它们的结果如何:

# -*- coding: utf-8 -*-
'''
OpenCV中的模板匹配
1.cv2.matchTemplate()、cv2.minMaxLoc()
2.OpenCV中实现了集中比较方法,返回一个灰度图像,其中每个像素表示该像素的领域和模板匹配的程度
3.输入图像大小若是(w*h),则输出图像大小为(W-w+1,H-h+1).一旦得到结果,可以使用cv2.minMaxLoc()函数来查找最大值和最小值
下面的栗子:在图像中搜索一张脸
可以看到cv2.TM_CCORR的结果并不如预期那样好
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('1.jpg', 0)
img2 = img.copy()
template = cv2.imread('template.jpg', 0)

w, h = template.shape[::-1]
# 6中类型的比较
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
           'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']

for meth in methods:
    img = img2.copy()
    method = eval(meth)
    # 应用模板匹配
    res = cv2.matchTemplate(img, template, method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

    # 如果方法是cv2.TM_SQDIFF或cv2.TM_SQDIFF_NORMED
    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
        top_left = min_loc
    else:
        top_left = max_loc

    bottom_right = (top_left[0] + w, top_left[1] + h)
    cv2.rectangle(img, top_left, bottom_right, 255, 2)

    plt.subplot(121), plt.imshow(res, 'gray')
    plt.title('Matching Result'), plt.xticks([]), plt.yticks([])
    plt.subplot(122), plt.imshow(img, 'gray')
    plt.title('Detected Point'), plt.xticks([]), plt.yticks([])
    plt.suptitle(meth)

    plt.show()

结果:

cv2.TM_CCOEFF

(三)OpenCV中的图像处理之图像变换及模板匹配

cv2.TM_CCOEFF_NORMED

(三)OpenCV中的图像处理之图像变换及模板匹配

cv2.TM_CCORR

(三)OpenCV中的图像处理之图像变换及模板匹配

cv2.TM_CCORR_NORMED

(三)OpenCV中的图像处理之图像变换及模板匹配

cv2.TM_SQDIFF

(三)OpenCV中的图像处理之图像变换及模板匹配

cv2.TM_SQDIFF_NORMED

(三)OpenCV中的图像处理之图像变换及模板匹配

你可以看到cv2.TM_CCORR的结果并不像我们预期的那样好。


4.多目标的模板匹配

在上一节中,我们搜索了梅西的脸部图像,该图像只在图像中出想过一次。假设你正在搜索一个多次出现的对象,cv2.minMaxLoc()不会给你所有的位置。在这种情况下,我们将使用阈值。所以在这个例子中,我们将使用游戏Mario的屏幕截图,并在其中找到硬币。

# -*- coding: utf-8 -*-
'''
多目标模板匹配
'''

import cv2
import numpy as np
from matplotlib import pyplot as plt

img_rgb = cv2.imread('2.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('temp.jpg', 0)
w, h = template.shape[::-1]

res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
    cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)

cv2.imwrite('res.jpg', img_rgb)
test = cv2.imread('2.jpg')
cv2.imshow('original', test)
cv2.imshow('res', img_rgb)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()

结果:

(三)OpenCV中的图像处理之图像变换及模板匹配