Python OpenCV 圖像處理之傅里葉變換,取經(jīng)之旅第 52 篇

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ù)矩陣。

2021030314083394[1].png

后面要進行的操作,有部分數(shù)學知識,暫未搞懂,摘錄如下:
進行完頻率變換之后,就可以構(gòu)建振幅譜了,最后在通過對數(shù)變換來壓縮范圍。
或者可以理解為將復數(shù)轉(zhuǎn)為浮點數(shù)進行傅里葉頻譜圖顯示,補充代碼如下:

fimg = np.log(np.abs(fshift))

最終得到的結(jié)果如下,當然這個是采用的隨機圖,如果換成一張灰度圖,可以驗證如下。


20210303141546732[1].png

修改之后的代碼如下:

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()
20210303141819801[1].png

基本結(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é)果如下:


20210303144507537[1].png

如果在上述傅里葉變換之后的頻譜圖像中,增加一個低通濾波,將會得到如下結(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()
20210303145503695[1].png

如果希望實現(xiàn)高通濾波,只需要修改掩模數(shù)據(jù)即可。

mask = np.ones((rows, cols), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0
20210303150052835[1].png

參考了很多文章,但是一篇文章被反復提及,這里也備注一下:https://zhuanlan.zhihu.com/p/19763358
當然傅里葉變換在官方手冊的內(nèi)容也需要時長進行閱讀一下下,官網(wǎng)地址

OpenCV 實現(xiàn)傅里葉變換

OpenCV 實現(xiàn)傅里葉變換與 numpy 基本一致,核心差異在函數(shù)的使用上,具體區(qū)別如下:
Opencv 中主要通過 cv2.difcv2.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 一致。


20210303151927980[1].png

套用上面的知識,使用 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()
20210303152137812[1].png

高通濾波的代碼和運行效果,就交給你自己來實現(xiàn)吧。

橡皮擦的小節(jié)

HPF(高通濾波),LPF(低通濾波)
希望今天的 1 個小時(貌似不太夠)你有所收獲,我們下篇博客見~

相關(guān)閱讀


技術(shù)專欄

  1. Python 爬蟲 100 例教程,超棒的爬蟲教程,立即訂閱吧
  2. Python 爬蟲小課,精彩 9 講

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內(nèi)容