Python과 OpenCV – 25 : Fourier Transform

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_transforms/py_fourier_transform/py_fourier_transform.html#fourier-transform 입니다.

퓨리에 변환이란 어떤 입력 신호를 다수의 sin과 cos 함수의 합으로 변환한 것을 의미합니다. sin과 cos 함수의 합이므로 그래프를 그려보면 어떤 반복적인 주기를 가지고 있을 것입니다. 이러한 퓨리에 변환을 이미지에 적용할 수 있는데, 이미지에 적용한 퓨리에 변환의 결과를 얻는 것을 2D DFT(Discrete Fourier Transform)이라고 합니다. 또한 이렇게 얻은 퓨리에 변환 결과를 이용해 다시 역으로 이미지를 얻는 것을 역 퓨리에 변환이라고 합니다. 퓨리에 변환은 Numpy와 OpenCV 모두에서 함수로 제공합니다. 먼저 Numpy에서 제공하는 퓨리에 변환에 대해서 살펴보면..

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

img = cv2.imread('./data/messi5.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, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

실행 결과는 다음과 같습니다.

왼쪽은 입력 이미지이고 오른쪽은 이미지에 대한 푸리에 변환의 결과를 시각화한 것입니다. 6번 코드의 np.fft.fft2 함수가 이미지에 대한 푸리에 변환을 실행하는 것이고, 7번 코드의 np.fft.fftshift 함수는 푸리에 변환의 결과를 중심으로 이동시킵니다. 푸리에 변환은 반복적인 주기를 갖으므로 이처럼 그 결과를 중심으로 이동시키는 것이 시각화 관점에서 좋습니다. 그리고 8번 코드는 푸리에 변환의 결과의 값을 증폭시킵니다. 워낙 푸리에 변환의 값이 작기때문에 이를 시각화하면 두드러진 특징을 살펴볼 수 없어 값을 증폭시킵니다.

이제 푸리에 변환의 결과를 변경해서, 변경된 푸리에 변환을 통해 이미지로 역 변환시키는 코드를 살펴보겠습니다.

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

img = cv2.imread('./data/messi5.jpg',0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)

rows,cols = img.shape
crow,ccol = (int)(rows/2),(int)(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, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(img_back, cmap = '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()

푸리에 변환 결과에 어떤 변경을 가했는지 언급하면, 11번 코드에서 푸리에 변환 결과 이미지의 중심을 기준으로 좌우로 30픽셀 범위 영역의 값을 0으로 변경하고 있습니다. 이렇게 변경된 푸리에 변환 결과를 통해 역 변환시키는 코드가 12번과 13번 코드입니다. 결과는 다음과 같습니다.

이미지의 Edge를 추출한 결과가 도출되었습니다. 다음으로 OpenCV를 통한 푸리에 변환에 대해 살펴보겠습니다.

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

img = cv2.imread('./data/messi5.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, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

실행 결과는 아래와 같고 Numpy를 통한 푸리에 변환과 동일합니다.

이제 여기서 푸리에 변환 결과에 어떤 변경을 가하고 난뒤 푸리에 역변환을 통해 이미지를 얻어 보겠습니다. 이번 변경은 앞서서 가운데 영역을 0으로 설정했는데, 이번에는 1로 설정합니다.

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

img = cv2.imread('./data/messi5.jpg',0)

dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows, cols = img.shape
crow,ccol = int(rows/2), int(cols/2)

# create a mask first, center square is 1, remaining all zeros
mask = np.zeros((rows,cols,2),np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1

# apply mask and inverse 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, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

실행 결과는 다음과 같습니다.

이미지에 블러링 필터가 적용된 것과 같습니다. 그렇다면 왜 푸리에 변환과 그 역변환을 통해 원래의 이미지가 Edge 추출이나 Blur 효과가 적용되는지 직관적으로 이해하기 위해 다음 예제를 살펴보겠습니다.

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

# simple averaging filter without scaling parameter
mean_filter = np.ones((3,3))

# creating a guassian filter
x = cv2.getGaussianKernel(5,10)
gaussian = x*x.T

# different edge detecting filters
# scharr in x-direction
scharr = np.array([[-3, 0, 3],
                   [-10,0,10],
                   [-3, 0, 3]])
# sobel in x direction
sobel_x= np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]])
# sobel in y direction
sobel_y= np.array([[-1,-2,-1],
                   [0, 0, 0],
                   [1, 2, 1]])
# laplacian
laplacian=np.array([[0, 1, 0],
                    [1,-4, 1],
                    [0, 1, 0]])

filters = [mean_filter, gaussian, laplacian, sobel_x, sobel_y, scharr]
filter_name = ['mean_filter', 'gaussian','laplacian', 'sobel_x', \
                'sobel_y', 'scharr_x']
fft_filters = [np.fft.fft2(x) for x in filters]
fft_shift = [np.fft.fftshift(y) for y in fft_filters]
mag_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(mag_spectrum[i],cmap = 'gray')
    plt.title(filter_name[i]), plt.xticks([]), plt.yticks([])

plt.show()

위의 코드를 실행해보면 아래와 같은데.. 각 행렬에 대해 푸리에 변환을 수행해 그 결과를 살펴볼 수 있습니다.

처음 Numpy 방식에서는 푸리에 변환에 위의 예제 결과에서의 laplacian으로 변경했고, 두번째 OpenCV 방식에서는 mean_filter나 gaussian으로 변경하여 푸리에 역변환을 수행했다는 것을 상기할 수 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다