Python OpenCV 365 天學習計劃,與橡皮擦一起進入圖像領(lǐng)域吧。本篇博客是這個系列的第 52 篇。
該系列文章導航參考:https://blog.csdn.net/hihell/category_10688961.html
學在前面
傅里葉變換(Fourier Transform,F(xiàn)T)今天第一次接觸,按照以往的套路,我們依舊是先應(yīng)用起來,然后逐步的迭代學習。
傅里葉變換是對同一個事物觀看角度的變化,不在從時域進行觀看,從頻域進行觀看。這里提及了兩個新詞,時域和頻域,先簡單理解一下,時域,在時間范圍內(nèi)事物發(fā)生的變化,頻域是指的事物的變化頻率,是不是很繞,沒啥問題,先用,先會調(diào) API。
傅里葉原理略過,先說一下應(yīng)用它之后對圖像產(chǎn)生的影響。
- 使用高通濾波器之后,會保留高頻信息,增強圖像細節(jié),例如邊界增強;
- 使用低通濾波器之后,會保留低頻信息,邊界模糊。
傅里葉變換應(yīng)用
原理既然先放下了,那先應(yīng)用起來吧,我們將分別學習 numpy 和 OpenCV 兩種方式實現(xiàn)傅里葉變換。
Numpy 實現(xiàn)傅里葉變換
通過 numpy 實現(xiàn)傅里葉變換用到的函數(shù)是 np.fft.fft2
,函數(shù)原型如下:
fft2(a, s=None, axes=(-2, -1), norm=None)
參數(shù)說明如下:
-
a
:輸入圖像; -
s
:整數(shù)序列,輸出數(shù)組的大小; -
axex
:整數(shù)序列,用于計算FFT
的可選軸; -
norm
:規(guī)范化模式。
有些參數(shù)如果不實際使用,比較難看出結(jié)果,當然函數(shù)的說明,還是 官方手冊 最靠譜。應(yīng)用層面的代碼編寫流程如下:
通過 np.fft.fft2
函數(shù)進行傅里葉變換得到頻率分布,再調(diào)用 np.fft.fftshift
函數(shù)將中心位置轉(zhuǎn)移至中間。
測試代碼如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('test.jpg', 0)
# 快速傅里葉變換算法得到頻率分布,將空間域轉(zhuǎn)化為頻率域
f = np.fft.fft2(img)
# 默認結(jié)果中心點位置是在左上角,通過下述代碼將中心點轉(zhuǎn)移到中間位置
# 將低頻部分移動到圖像中心
fshift = np.fft.fftshift(f)
# fft 結(jié)果是復數(shù), 其絕對值結(jié)果是振幅
result = 20*np.log(np.abs(fshift))
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')
plt.subplot(122)
plt.imshow(result, cmap='gray')
plt.title('result')
plt.axis('off')
plt.show()
這個是很多地方反復提及的案例,在補充一下相關(guān)內(nèi)容。
np.fft.fft2
是一個頻率轉(zhuǎn)換函數(shù),它的第一個參數(shù)是輸入圖像,即灰度圖像。第二個參數(shù)是可選的,它決定輸出數(shù)組的大小。
第二個參數(shù)的大小如果大于輸入圖像的大小,則在計算 FFT 之前用零填充輸入圖像;如果小于輸入圖像,將裁切輸入圖像。如果未傳遞任何參數(shù),則輸出數(shù)組的大小將與輸入的大小相同,測試如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 生成一個圖片
src = np.ones((5, 5), dtype=np.uint8)*100
print(src)
print(src.shape)
f = np.fft.fft2(src,(7,7))
print(f.shape)
print(f)
中心點進行移動之后,可以參考下圖運行結(jié)果,而且可以知道 np.fft.fft2
函數(shù)應(yīng)用之后得到的是復數(shù)矩陣。
后面要進行的操作,有部分數(shù)學知識,暫未搞懂,摘錄如下:
進行完頻率變換之后,就可以構(gòu)建振幅譜了,最后在通過對數(shù)變換來壓縮范圍。
或者可以理解為將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示,補充代碼如下:
fimg = np.log(np.abs(fshift))
最終得到的結(jié)果如下,當然這個是采用的隨機圖,如果換成一張灰度圖,可以驗證如下。
修改之后的代碼如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 生成一個圖片
# src = np.ones((5, 5), dtype=np.uint8)*100
src = cv.imread("./test.jpg", 0)
print("*"*100)
print(src)
print(src.shape)
f = np.fft.fft2(src)
print("*"*100)
print(f)
fshift = np.fft.fftshift(f)
print("*"*100)
print(fshift)
# 將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示
fimg = 20*np.log(np.abs(fshift))
print(fimg)
# 圖像顯示
plt.subplot(121), plt.imshow(src, "gray"), plt.title('origin')
plt.axis('off')
plt.subplot(122), plt.imshow(fimg, "gray"), plt.title('Fourier')
plt.axis('off')
plt.show()
基本結(jié)論:左邊為原始灰度圖像,右邊為頻率分布圖譜,其越靠近中心位置頻率越低,灰度值越高亮度越亮的中心位置代表該頻率的信號振幅越大。
接下來將其進行逆變換,也就是反向操作回去,通過 numpy
實現(xiàn)傅里葉逆變換,它是傅里葉變換的逆操作,將頻譜圖像轉(zhuǎn)換為原始圖像。用到核心函數(shù)與原型分別如下。
np.fft.ifft2
# 實現(xiàn)圖像逆傅里葉變換,返回一個復數(shù)數(shù)組
np.fft.ifft2(a, s=None, axes=(-2, -1), norm=None)
np.fft.ifftshift
# fftshit()函數(shù)的逆函數(shù),它將頻譜圖像的中心低頻部分移動至左上角
np.fft.ifftshift(x, axes=None)
依據(jù)上述內(nèi)容,對傅里葉變換進行逆向的操作,測試代碼如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 生成一個圖片
# src = np.ones((5, 5), dtype=np.uint8)*100
src = cv.imread("./test.jpg", 0)
print("*"*100)
print(src)
print(src.shape)
f = np.fft.fft2(src)
print("*"*100)
print(f)
fshift = np.fft.fftshift(f)
print("*"*100)
print(fshift)
# 將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示
fimg = np.log(np.abs(fshift))
print(fimg)
# 逆傅里葉變換
ifshift = np.fft.ifftshift(fshift)
# 將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示
ifimg = np.log(np.abs(ifshift))
if_img = np.fft.ifft2(ifshift)
origin_img = np.abs(if_img)
# 圖像顯示
plt.subplot(221), plt.imshow(src, "gray"), plt.title('origin')
plt.axis('off')
plt.subplot(222), plt.imshow(fimg, "gray"), plt.title('fourier_img')
plt.axis('off')
plt.subplot(223), plt.imshow(origin_img, "gray"), plt.title('origin_img')
plt.axis('off')
plt.subplot(224), plt.imshow(ifimg, "gray"), plt.title('ifimg')
plt.axis('off')
plt.show()
最終的結(jié)果如下:
如果在上述傅里葉變換之后的頻譜圖像中,增加一個低通濾波,將會得到如下結(jié)果。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 實現(xiàn)傅里葉變換的低通濾波
src = cv.imread("./test.jpg", 0)
f = np.fft.fft2(src)
fshift = np.fft.fftshift(f)
rows, cols = src.shape
crow, ccol = rows//2, cols//2
# 制定掩模,大小和圖像一樣,np.zeros初始化
mask = np.zeros((rows, cols), np.uint8)
# 使中心位置,上下左右距離30,置為1
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
# 掩模與 DFT 后結(jié)果相乘只保留出中間區(qū)域
fshift = fshift*mask
# 逆傅里葉變換
ifshift = np.fft.ifftshift(fshift)
# 將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示
ifimg = np.fft.ifft2(ifshift)
dft_img = np.abs(ifimg)
# 圖像顯示
plt.subplot(121), plt.imshow(src, "gray"), plt.title('origin')
plt.axis('off')
plt.subplot(122), plt.imshow(dft_img, "gray"), plt.title('dft_img')
plt.axis('off')
plt.show()
如果希望實現(xiàn)高通濾波,只需要修改掩模數(shù)據(jù)即可。
mask = np.ones((rows, cols), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0
參考了很多文章,但是一篇文章被反復提及,這里也備注一下:https://zhuanlan.zhihu.com/p/19763358
當然傅里葉變換在官方手冊的內(nèi)容也需要時長進行閱讀一下下,官網(wǎng)地址
OpenCV 實現(xiàn)傅里葉變換
OpenCV 實現(xiàn)傅里葉變換與 numpy 基本一致,核心差異在函數(shù)的使用上,具體區(qū)別如下:
Opencv 中主要通過 cv2.dif
和 cv2.idif
(傅里葉逆變換),在輸入圖像之前需要先轉(zhuǎn)換成把圖像從 np.uint8
轉(zhuǎn)換為 np.float32
格式,其得到的結(jié)果中頻率為 0 的部分會在左上角,要轉(zhuǎn)換到中心位置,通過 shift
變換來實現(xiàn),與 numpy 一致,cv2.dif
返回的結(jié)果是雙通道的(實部,虛部),還需要轉(zhuǎn)換成圖像格式才能展示。
函數(shù) cv2.dft() 原型如下
dst = cv.dft(src[, dst[, flags[, nonzeroRows]]])
參數(shù)說明:
-
src
:輸入圖像,要求np.float32
格式; -
dst
:輸出圖像,雙通道(實部+虛部),大小和類型取決于第三個參數(shù)flags
; -
flags
:表示轉(zhuǎn)換標記,默認為 0,存在多種取值,參見后文; -
nonzeroRows
:默認為 0,暫時不考慮。
flags
取值如下:
-
DFT_INVERSE
:用一維或二維逆變換取代默認的正向變換; -
DFT_SCALE
: 縮放比例標識符,根據(jù)數(shù)據(jù)元素個數(shù)平均求出其縮放結(jié)果,如有 N 個元素,則輸出結(jié)果以 1/N 縮放輸出,常與DFT_INVERSE
搭配使用; -
DFT_ROWS
:對輸入矩陣的每行進行正向或反向的傅里葉變換;此標識符可在處理多種適量的的時候用于減小資源的開銷,這些處理常常是三維或高維變換等復雜操作; -
DFT_COMPLEX_OUTPUT
:對一維或二維的實數(shù)數(shù)組進行正向變換,這樣的結(jié)果雖然是復數(shù)陣列,但擁有復數(shù)的共軛對稱性(CCS),可以以一個和原數(shù)組尺寸大小相同的實數(shù)數(shù)組進行填充,這是最快的選擇也是函數(shù)默認的方法。你可能想要得到一個全尺寸的復數(shù)數(shù)組(像簡單光譜分析等等),通過設(shè)置標志位可以使函數(shù)生成一個全尺寸的復數(shù)輸出數(shù)組; -
DFT_REAL_OUTPUT
:對一維二維復數(shù)數(shù)組進行逆向變換,這樣的結(jié)果通常是一個尺寸相同的復數(shù)矩陣,但是如果輸入矩陣有復數(shù)的共軛對稱性(比如是一個帶有DFT_COMPLEX_OUTPUT
標識符的正變換結(jié)果),便會輸出實數(shù)矩陣。
以上內(nèi)容摘抄網(wǎng)絡(luò),原創(chuàng)人已經(jīng)無法找到,尷尬。
總結(jié)下來就是:
-
DFT_COMPLEX_OUTPUT
:得到一個復數(shù)形式的矩陣; -
DFT_REAL_OUTPUT
:只輸出復數(shù)的實部; -
DFT_INVERSE
:進行傅里葉逆變換; -
DFT_SCALE
:是否除以 MxN (M 行 N 列的圖片,共有有 MxN 個像素點); -
DFT_ROWS
:輸入矩陣的每一行進行傅里葉變換或者逆變換。
最后需要注意的是,輸出的頻譜結(jié)果是一個復數(shù),需要調(diào)用 cv2.magnitude()
函數(shù)將傅里葉變換的雙通道結(jié)果轉(zhuǎn)換為 0 到 255 的范圍。
這個函數(shù)比較簡單,原型和說明如下。
cv2.magnitude
函數(shù)原型:cv2.magnitude(x, y)
- x 表示浮點型 X 坐標值,即實部
- y 表示浮點型 Y 坐標值,即虛部
基礎(chǔ)知識簡單過一遍,直接進行案例的說明。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
src = cv.imread("test.jpg", 0)
# OpneCV傅里葉變換函數(shù)
# 需要將圖像進行一次float轉(zhuǎn)換
result = cv.dft(np.float32(src), flags=cv.DFT_COMPLEX_OUTPUT)
# 將頻譜低頻從左上角移動至中心位置
dft_shift = np.fft.fftshift(result)
# 頻譜圖像雙通道復數(shù)轉(zhuǎn)換為 0-255 區(qū)間
result1 = 20 * np.log(cv.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
# 圖像顯示
plt.subplot(121), plt.imshow(src, 'gray'), plt.title('原圖像')
plt.axis('off')
plt.subplot(122), plt.imshow(result1, 'gray'), plt.title('傅里葉變換')
plt.axis('off')
plt.show()
運行結(jié)果與 numpy 一致。
套用上面的知識,使用 OpenCV 里面的函數(shù)對圖片使用低通濾波。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
src = cv.imread("test.jpg", 0)
# OpneCV傅里葉變換函數(shù)
# 需要將圖像進行一次float轉(zhuǎn)換
result = cv.dft(np.float32(src), flags=cv.DFT_COMPLEX_OUTPUT)
# 將頻譜低頻從左上角移動至中心位置
dft_shift = np.fft.fftshift(result)
# 頻譜圖像雙通道復數(shù)轉(zhuǎn)換為 0-255 區(qū)間
result = 20 * np.log(cv.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
rows, cols = src.shape
crow, ccol = rows//2, cols//2
mask = np.zeros((rows, cols, 2), np.uint8)
mask[int(crow-30):int(crow+30), int(ccol-30):int(ccol+30)] = 1
# LPF(低通濾波)
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv.idft(f_ishift)
img_back = cv.magnitude(img_back[:, :, 0], img_back[:, :, 1])
# 圖像顯示
plt.subplot(131), plt.imshow(src, 'gray'), plt.title('原圖像')
plt.axis('off')
plt.subplot(132), plt.imshow(result, 'gray'), plt.title('傅里葉變換')
plt.axis('off')
plt.subplot(133), plt.imshow(img_back, 'gray'), plt.title('低通濾波之后的圖像')
plt.axis('off')
plt.show()
高通濾波的代碼和運行效果,就交給你自己來實現(xiàn)吧。
橡皮擦的小節(jié)
HPF(高通濾波),LPF(低通濾波)
希望今天的 1 個小時(貌似不太夠)你有所收獲,我們下篇博客見~
相關(guān)閱讀
技術(shù)專欄