該系列文章是講解Python OpenCV圖像處理知識,前期主要講解圖像入門、OpenCV基礎用法,中期講解圖像處理的各種算法,包括圖像銳化算子、圖像增強技術、圖像分割等,后期結合深度學習研究圖像識別、圖像分類應用。希望文章對您有所幫助,如果有不足之處,還請海涵~
前面一篇文章介紹了民族服飾及文化圖騰識別,詳細講解圖像點運算,包括灰度化處理、灰度線性變換、灰度非線性變換、閾值化處理。這篇文章將講解兩個重要的算法——傅里葉變換和霍夫變換,萬字長文整理,希望對您有所幫助。同時,該部分知識均為作者查閱資料撰寫總結,並且開設成了收費專欄,為小寶賺點奶粉錢,感謝您的抬愛。當然如果您是在讀學生或經濟拮據,可以私聊我給你每篇文章開白名單,或者轉發原文給你,更希望您能進步,一起加油喔~
PS:寫這篇文章另一個重要的原因是Github資源有作者提交了新的貢獻,發現提交的是霍夫變換,因此作者也總結這篇文章。CSDN博客專欄9比1分成,真的挺不錯的,也希望大家能分享更好的文章。
前文參考:
[Python圖像處理] 一.圖像處理基礎知識及OpenCV入門函數
[Python圖像處理] 二.OpenCV+Numpy庫讀取與修改像素
[Python圖像處理] 三.獲取圖像屬性、興趣ROI區域及通道處理
[Python圖像處理] 四.圖像平滑之均值濾波、方框濾波、高斯濾波及中值濾波
[Python圖像處理] 五.圖像融合、加法運算及圖像類型轉換
[Python圖像處理] 六.圖像縮放、圖像旋轉、圖像翻轉與圖像平移
[Python圖像處理] 七.圖像閾值化處理及算法對比
[Python圖像處理] 八.圖像腐蝕與圖像膨脹
[Python圖像處理] 九.形態學之圖像開運算、閉運算、梯度運算
[Python圖像處理] 十.形態學之圖像頂帽運算和黑帽運算
[Python圖像處理] 十一.灰度直方圖概念及OpenCV繪制直方圖
[Python圖像處理] 十二.圖像幾何變換之圖像仿射變換、圖像透視變換和圖像校正
[Python圖像處理] 十三.基於灰度三維圖的圖像頂帽運算和黑帽運算
[Python圖像處理] 十四.基於OpenCV和像素處理的圖像灰度化處理
[Python圖像處理] 十五.圖像的灰度線性變換
[Python圖像處理] 十六.圖像的灰度非線性變換之對數變換、伽馬變換
[Python圖像處理] 十七.圖像銳化與邊緣檢測之Roberts算子、Prewitt算子、Sobel算子和Laplacian算子
[Python圖像處理] 十八.圖像銳化與邊緣檢測之Scharr算子、Canny算子和LOG算子
[Python圖像處理] 十九.圖像分割之基於K-Means聚類的區域分割
[Python圖像處理] 二十.圖像量化處理和采樣處理及局部馬賽克特效
[Python圖像處理] 二十一.圖像金字塔之圖像向下取樣和向上取樣
[Python圖像處理] 二十二.Python圖像傅里葉變換原理及實現
[Python圖像處理] 二十三.傅里葉變換之高通濾波和低通濾波
[Python圖像處理] 二十四.圖像特效處理之毛玻璃、浮雕和油漆特效
[Python圖像處理] 二十五.圖像特效處理之素描、懷舊、光照、流年以及濾鏡特效
[Python圖像處理] 二十六.圖像分類原理及基於KNN、朴素貝葉斯算法的圖像分類案例
[Python圖像處理] 二十七.OpenGL入門及繪制基本圖形(一)
[Python圖像處理] 二十八.OpenCV快速實現人臉檢測及視頻中的人臉
[Python圖像處理] 二十九.MoviePy視頻編輯庫實現抖音短視頻剪切合並操作
[Python圖像處理] 三十.圖像量化及采樣處理萬字詳細總結(推薦)
[Python圖像處理] 三十一.圖像點運算處理兩萬字詳細總結(灰度化處理、閾值化處理)
文章目錄
在數字圖像處理中,有兩個經典的變換被廣泛應用——傅里葉變換和霍夫變換。其中,傅里葉變換主要是將時間域上的信號轉變為頻率域上的信號,用來進行圖像除噪、圖像增強等處理;霍夫變換主要用來辨別找出物件中的特征,用來進行特征檢測、圖像分析、數位影像處理等處理。
一.圖像傅里葉變換概述
傅里葉變換(Fourier Transform,簡稱FT)常用於數字信號處理,它的目的是將時間域上的信號轉變為頻率域上的信號。隨着域的不同,對同一個事物的了解角度也隨之改變,因此在時域中某些不好處理的地方,在頻域就可以較為簡單的處理。同時,可以從頻域里發現一些原先不易察覺的特征。傅里葉定理指出“任何連續周期信號都可以表示成(或者無限逼近)一系列正弦信號的疊加。”
傅里葉公式(1)如下,其中w表示頻率,t表示時間,為復變函數。它將時間域的函數表示為頻率域的函數f(t)的積分。
傅里葉變換認為一個周期函數(信號)包含多個頻率分量,任意函數(信號)f(t)可通過多個周期函數(或基函數)相加合成。從物理角度理解,傅里葉變換是以一組特殊的函數(三角函數)為正交基,對原函數進行線性變換,物理意義便是原函數在各組基函數的投影。如上圖所示,它是由三條正弦曲線組合成。其函數為(2)所示。
傅里葉變換可以應用於圖像處理中,經過對圖像進行變換得到其頻譜圖。從譜頻圖里頻率高低來表征圖像中灰度變化劇烈程度。圖像中的邊緣信號和噪聲信號往往是高頻信號,而圖像變化頻繁的圖像輪廓及背景等信號往往是低頻信號。這時可以有針對性的對圖像進行相關操作,例如圖像除噪、圖像增強和銳化等。二維圖像的傅里葉變換可以用以下數學公式(3)表達,其中f是空間域(Spatial Domain))值,F是頻域(Frequency Domain)值。
二.圖像傅里葉變換操作
對上面的傅里葉變換有了大致的了解之后,下面通過Numpy和OpenCV分別講解圖像傅里葉變換的算法及操作代碼。
1.Numpy實現傅里葉變換
Numpy中的 FFT包提供了函數 np.fft.fft2()可以對信號進行快速傅里葉變換,其函數原型如下所示,該輸出結果是一個復數數組(Complex Ndarry)。
- fft2(a, s=None, axes=(-2, -1), norm=None)
– a表示輸入圖像,陣列狀的復雜數組
– s表示整數序列,可以決定輸出數組的大小。輸出可選形狀(每個轉換軸的長度),其中s[0]表示軸0,s[1]表示軸1。對應fit(x,n)函數中的n,沿着每個軸,如果給定的形狀小於輸入形狀,則將剪切輸入。如果大於則輸入將用零填充。如果未給定’s’,則使用沿’axles’指定的軸的輸入形狀
– axes表示整數序列,用於計算FFT的可選軸。如果未給出,則使用最后兩個軸。“axes”中的重復索引表示對該軸執行多次轉換,一個元素序列意味着執行一維FFT
– norm包括None和ortho兩個選項,規范化模式(請參見numpy.fft)。默認值為無
Numpy中的fft模塊有很多函數,相關函數如下:
- numpy.fft.fft(a, n=None, axis=-1, norm=None)
計算一維傅里葉變換 - numpy.fft.fft2(a, n=None, axis=-1, norm=None)
計算二維的傅里葉變換 - numpy.fft.fftn()
計算n維的傅里葉變換 - numpy.fft.rfftn()
計算n維實數的傅里葉變換 - numpy.fft.fftfreq()
返回傅里葉變換的采樣頻率 - numpy.fft.shift()
將FFT輸出中的直流分量移動到頻譜中央
下面的代碼是通過Numpy庫實現傅里葉變換,調用np.fft.fft2()快速傅里葉變換得到頻率分布,接着調用np.fft.fftshift()函數將中心位置轉移至中間,最終通過Matplotlib顯示效果圖。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv.imread('test.png', 0)
#快速傅里葉變換算法得到頻率分布
f = np.fft.fft2(img)
#默認結果中心點位置是在左上角,
#調用fftshift()函數轉移到中間位置
fshift = np.fft.fftshift(f)
#fft結果是復數, 其絕對值結果是振幅
fimg = np.log(np.abs(fshift))
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#展示結果
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('(a)原始圖像')
plt.axis('off')
plt.subplot(122), plt.imshow(fimg, 'gray'), plt.title('(b)傅里葉變換處理')
plt.axis('off')
plt.show()
輸出結果如圖2所示,圖2(a)為原始圖像,圖2(b)為頻率分布圖譜,其中越靠近中心位置頻率越低,越亮(灰度值越高)的位置代表該頻率的信號振幅越大。
需要注意,傅里葉變換得到低頻、高頻信息,針對低頻和高頻處理能夠實現不同的目的。同時,傅里葉過程是可逆的,圖像經過傅里葉變換、逆傅里葉變換能夠恢復原始圖像。
下列代碼呈現了原始圖像在變化方面的一種表示:圖像最明亮的像素放到中央,然后逐漸變暗,在邊緣上的像素最暗。這樣可以發現圖像中亮、暗像素的百分比,即為頻域中的振幅AA的強度。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
#讀取圖像
img = cv.imread('Na.png', 0)
#傅里葉變換
f = np.fft.fft2(img)
#轉移像素做幅度普
fshift = np.fft.fftshift(f)
#取絕對值:將復數變化成實數取對數的目的為了將數據變化到0-255
res = np.log(np.abs(fshift))
#展示結果
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(res, 'gray'), plt.title('Fourier Image')
plt.show()
輸出結果如圖3所示,圖3(a)為原始圖像,圖3(b)為頻率分布圖譜。
2.Numpy實現傅里葉逆變換
下面介紹Numpy實現傅里葉逆變換,它是傅里葉變換的逆操作,將頻譜圖像轉換為原始圖像的過程。通過傅里葉變換將轉換為頻譜圖,並對高頻(邊界)和低頻(細節)部分進行處理,接着需要通過傅里葉逆變換恢復為原始效果圖。頻域上對圖像的處理會反映在逆變換圖像上,從而更好地進行圖像處理。
圖像傅里葉變化主要使用的函數如下所示:
- numpy.fft.ifft2(a, n=None, axis=-1, norm=None)
實現圖像逆傅里葉變換,返回一個復數數組 - numpy.fft.fftshift()
fftshit()函數的逆函數,它將頻譜圖像的中心低頻部分移動至左上角 - iimg = numpy.abs(逆傅里葉變換結果)
將復數轉換為0至255范圍
下面的代碼分別實現了傅里葉變換和傅里葉逆變換。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv.imread('Lena.png', 0)
#傅里葉變換
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
res = np.log(np.abs(fshift))
#傅里葉逆變換
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#展示結果
plt.subplot(131), plt.imshow(img, 'gray'), plt.title(u'(a)原始圖像')
plt.axis('off')
plt.subplot(132), plt.imshow(res, 'gray'), plt.title(u'(b)傅里葉變換處理')
plt.axis('off')
plt.subplot(133), plt.imshow(iimg, 'gray'), plt.title(u'(c)傅里葉逆變換處理')
plt.axis('off')
plt.show()
輸出結果如圖4所示,從左至右分別為原始圖像、頻譜圖像、逆傅里葉變換轉換圖像。
3.OpenCV實現傅里葉變換
OpenCV 中相應的函數是cv2.dft()和用Numpy輸出的結果一樣,但是是雙通道的。第一個通道是結果的實數部分,第二個通道是結果的虛數部分,並且輸入圖像要首先轉換成 np.float32 格式。其函數原型如下所示:
- dst = cv2.dft(src, dst=None, flags=None, nonzeroRows=None)
– src表示輸入圖像,需要通過np.float32轉換格式
– dst表示輸出圖像,包括輸出大小和尺寸
– flags表示轉換標記,其中DFT _INVERSE執行反向一維或二維轉換,而不是默認的正向轉換;DFT _SCALE表示縮放結果,由陣列元素的數量除以它;DFT _ROWS執行正向或反向變換輸入矩陣的每個單獨的行,該標志可以同時轉換多個矢量,並可用於減少開銷以執行3D和更高維度的轉換等;DFT _COMPLEX_OUTPUT執行1D或2D實數組的正向轉換,這是最快的選擇,默認功能;DFT _REAL_OUTPUT執行一維或二維復數陣列的逆變換,結果通常是相同大小的復數數組,但如果輸入數組具有共軛復數對稱性,則輸出為真實數組
– nonzeroRows表示當參數不為零時,函數假定只有nonzeroRows輸入數組的第一行(未設置)或者只有輸出數組的第一個(設置)包含非零,因此函數可以處理其余的行更有效率,並節省一些時間;這種技術對計算陣列互相關或使用DFT卷積非常有用
注意,由於輸出的頻譜結果是一個復數,需要調用cv2.magnitude()函數將傅里葉變換的雙通道結果轉換為0到255的范圍。其函數原型如下:
- cv2.magnitude(x, y)
– x表示浮點型X坐標值,即實部
– y表示浮點型Y坐標值,即虛部
該函數最終輸出結果為幅值,即:
下面的代碼是調用cv2.dft()進行傅里葉變換的一個簡單示例。
# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv2.imread('Lena.png', 0)
#傅里葉變換
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
#將頻譜低頻從左上角移動至中心位置
dft_shift = np.fft.fftshift(dft)
#頻譜圖像雙通道復數轉換為0-255區間
result = 20*np.log(cv2.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#顯示圖像
plt.subplot(121), plt.imshow(img, cmap = 'gray')
plt.title(u'(a)原始圖像'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(result, cmap = 'gray')
plt.title(u'(b)傅里葉變換處理'), plt.xticks([]), plt.yticks([])
plt.show()
輸出結果如圖5所示,圖5(a)為原始“Lena”圖,圖5(b)為轉換后的頻譜圖像,並且保證低頻位於中心位置。
4.OpenCV實現傅里葉逆變換
在OpenCV 中,通過函數cv2.idft()實現傅里葉逆變換,其返回結果取決於原始圖像的類型和大小,原始圖像可以為實數或復數。其函數原型如下所示:
- dst = cv2.idft(src[, dst[, flags[, nonzeroRows]]])
– src表示輸入圖像,包括實數或復數
– dst表示輸出圖像
– flags表示轉換標記
– nonzeroRows表示要處理的dst行數,其余行的內容未定義(請參閱dft描述中的卷積示例)
注意,由於輸出的頻譜結果是一個復數,需要調用cv2.magnitude()函數將傅里葉變換的雙通道結果轉換為0到255的范圍。其函數原型如下:
- cv2.magnitude(x, y)
– x表示浮點型X坐標值,即實部
– y表示浮點型Y坐標值,即虛部
該函數最終輸出結果為幅值,即:
下面的代碼是調用cv2.idft()進行傅里葉逆變換的一個簡單示例。
# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv2.imread('Lena.png', 0)
#傅里葉變換
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
dftshift = np.fft.fftshift(dft)
res1= 20*np.log(cv2.magnitude(dftshift[:,:,0], dftshift[:,:,1]))
#傅里葉逆變換
ishift = np.fft.ifftshift(dftshift)
iimg = cv2.idft(ishift)
res2 = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#顯示圖像
plt.subplot(131), plt.imshow(img, 'gray'), plt.title(u'(a)原始圖像')
plt.axis('off')
plt.subplot(132), plt.imshow(res1, 'gray'), plt.title(u'(b)傅里葉變換處理')
plt.axis('off')
plt.subplot(133), plt.imshow(res2, 'gray'), plt.title(u'(b)傅里葉變換逆處理')
plt.axis('off')
plt.show()
輸出結果如圖6所示,圖6(a)為原始“Lena”圖,圖6(b)為傅里葉變換后的頻譜圖像,圖6©為傅里葉逆變換,頻譜圖像轉換為原始圖像的過程。
三.基於傅里葉變換的高通濾波和低通濾波
傅里葉變換的目的並不是為了觀察圖像的頻率分布(至少不是最終目的),更多情況下是為了對頻率進行過濾,通過修改頻率以達到圖像增強、圖像去噪、邊緣檢測、特征提取、壓縮加密等目的。
過濾的方法一般有三種:低通(Low-pass)、高通(High-pass)、帶通(Band-pass)。所謂低通就是保留圖像中的低頻成分,過濾高頻成分,可以把過濾器想象成一張漁網,想要低通過濾器,就是將高頻區域的信號全部拉黑,而低頻區域全部保留。例如,在一幅大草原的圖像中,低頻對應着廣袤且顏色趨於一致的草原,表示圖像變換緩慢的灰度分量;高頻對應着草原圖像中的老虎等邊緣信息,表示圖像變換較快的灰度分量,由於灰度尖銳過度造成。
1.高通濾波器
高通濾波器是指通過高頻的濾波器,衰減低頻而通過高頻,常用於增強尖銳的細節,但會導致圖像的對比度會降低。該濾波器將檢測圖像的某個區域,根據像素與周圍像素的差值來提升像素的亮度。圖7展示了“Lena”圖對應的頻譜圖像,其中心區域為低頻部分。
接着通過高通濾波器覆蓋掉中心低頻部分,將255兩點變換為0,同時保留高頻部分,高通濾波器處理過程如圖8所示。
其中心黑色模板生成的核心代碼如下:
- rows, cols = img.shape
- crow,ccol = int(rows/2), int(cols/2)
- fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
通過高通濾波器將提取圖像的邊緣輪廓,生成如圖9所示圖像。
完整代碼如下所示:
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv.imread('Lena.png', 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
#傅里葉逆變換
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#顯示原始圖像和高通濾波處理圖像
plt.subplot(121), plt.imshow(img, 'gray'), plt.title(u'(a)原始圖像')
plt.axis('off')
plt.subplot(122), plt.imshow(iimg, 'gray'), plt.title(u'(b)結果圖像')
plt.axis('off')
plt.show()
輸出結果如圖10所示,圖10(a)為原始“Na”圖,圖10(b)為高通濾波器提取的邊緣輪廓圖像。它通過傅里葉變換轉換為頻譜圖像,再將中心的低頻部分設置為0,再通過傅里葉逆變換轉換為最終輸出圖像。
2.低通濾波器
低通濾波器是指通過低頻的濾波器,衰減高頻而通過低頻,常用於模糊圖像。低通濾波器與高通濾波器相反,當一個像素與周圍像素的插值小於一個特定值時,平滑該像素的亮度,常用於去燥和模糊化處理。如PS軟件中的高斯模糊,就是常見的模糊濾波器之一,屬於削弱高頻信號的低通濾波器。
下圖展示了“Lena”圖對應的頻譜圖像,其中心區域為低頻部分。如果構造低通濾波器,則將頻譜圖像中心低頻部分保留,其他部分替換為黑色0,最終得到的效果圖為模糊圖像。
那么,如何構造該濾波圖像呢?如圖12所示,濾波圖像是通過低通濾波器和頻譜圖像形成。其中低通濾波器中心區域為白色255,其他區域為黑色0。
低通濾波器主要通過矩陣設置構造,其核心代碼如下:
- rows, cols = img.shape
- crow,ccol = int(rows/2), int(cols/2)
- mask = np.zeros((rows, cols, 2), np.uint8)
- mask[crow-30:crow+30, ccol-30:ccol+30] = 1
通過低通濾波器將模糊圖像的完整代碼如下所示:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#讀取圖像
img = cv2.imread('Na.png', 0)
#傅里葉變換
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
fshift = np.fft.fftshift(dft)
#設置低通濾波器
rows, cols = img.shape
crow,ccol = int(rows/2), int(cols/2) #中心位置
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
#掩膜圖像和頻譜圖像乘積
f = fshift * mask
print(f.shape, fshift.shape, mask.shape)
#傅里葉逆變換
ishift = np.fft.ifftshift(f)
iimg = cv2.idft(ishift)
res = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])
#顯示原始圖像和低通濾波處理圖像
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.axis('off')
plt.subplot(122), plt.imshow(res, 'gray'), plt.title('Result Image')
plt.axis('off')
plt.show()
輸出結果如圖13所示,圖13(a)為原始“Nana”圖,圖13(b)為低通濾波器模糊處理后的圖像。
四.圖像霍夫變換概述
霍夫變換(Hough Transform)是一種特征檢測(Feature Extraction),被廣泛應用在圖像分析、計算機視覺以及數位影像處理。霍夫變換是在1959年由氣泡室(Bubble Chamber)照片的機器分析而發明,發明者Paul Hough在1962年獲得美國專利。現在廣泛使用的霍夫變換是由Richard Duda和Peter Hart在1972年發明,並稱之為廣義霍夫變換。經典的霍夫變換是檢測圖片中的直線,之后,霍夫變換不僅能識別直線,也能夠識別任何形狀,常見的有圓形、橢圓形。1981年,因為Dana H.Ballard的一篇期刊論文“Generalizing the Hough transform to detect arbitrary shapes”,讓霍夫變換開始流行於計算機視覺界。
霍夫變換是一種特征提取技術,用來辨別找出物件中的特征,其目的是通過投票程序在特定類型的形狀內找到對象的不完美實例。這個投票程序是在一個參數空間中進行的,在這個參數空間中,候選對象被當作所謂的累加器空間中的局部最大值來獲得,累加器空間是由計算霍夫變換的算法明確地構建。霍夫變換主要優點是能容忍特征邊界描述中的間隙,並且相對不受圖像噪聲的影響。
最基本的霍夫變換是從黑白圖像中檢測直線,它的算法流程大致如下:給定一個物件和要辨別的形狀的種類,算法會在參數空間中執行投票來決定物體的形狀,而這是由累加空間里的局部最大值來決定。假設存在直線公式如(4)所示,其中m表示斜率,b表示截距。
如果用參數空間表示,則直線為(b, m),但它存在一個問題,垂直線的斜率不存在(或無限大),使得斜率參數m值接近於無限。為此,為了更好的計算,Richard O. Duda和Peter E. Hart在1971年4月,提出了Hesse normal form(Hesse法線式),如公式(5)所示,它轉換為直線的離散極坐標公式。
其中r是原點到直線上最近點的距離,θ是x軸與連接原點和最近點直線之間的夾角,如圖15-14所示。
對於點(x0, y0),可以將通過這個點的一族直線統一定義為公式(6)。因此,可以將圖像的每一條直線與一對參數(r,θ)相關聯,相當於每一對(r0,θ)代表一條通過點的直線(x0, y0),其中這個參數(r,θ)平面被稱為霍夫空間。
然而在實現的圖像處理領域,圖像的像素坐標P(x, y)是已知的,而(r,θ)是需要尋找的變量。如果能根據像素點坐標P(x, y)值繪制每個(r,θ)值,那么就從圖像笛卡爾坐標系統轉換到極坐標霍夫空間系統,這種從點到曲線的變換稱為直線的霍夫變換。變換通過量化霍夫參數空間為有限個值間隔等分或者累加格子。當霍夫變換算法開始,每個像素坐標點P(x, y)被轉換到(r,θ)的曲線點上面,累加到對應的格子數據點,當一個波峰出現時候,說明有直線存在。
如圖15所示,三條正弦曲線在平面相交於一點,該點坐標(r0,θ)表示三個點組成的平面內的直線。這就是使用霍夫變換檢測直線的過程,它追蹤圖像中每個點對應曲線間的交點,如果交於一點的曲線的數量超過了閾值,則認為該交點所代表的參數對(r0,θ)在原圖像中為一條直線。
同樣的原理,可以用來檢測圓,對於圓的參數方程變為如下等式:
其中(a, b)為圓的中心點坐標,r圓的半徑。這樣霍夫參數空間就變成一個三維參數空間。給定圓半徑轉為二維霍夫參數空間,變換相對簡單,也比較常用。
五.圖像霍夫線變換操作
在OpenCV中,霍夫變換分為霍夫線變換和霍夫圓變換,其中霍夫線變換支持三種不同方法——標准霍夫變換、多尺度霍夫變換和累計概率霍夫變換。
- 標准霍夫變換主要有HoughLines()函數實現。
- 多尺度霍夫變換是標准霍夫變換在多尺度下的變換,可以通過HoughLines()函數實現。
- 累計概率霍夫變換是標准霍夫變換的改進,它能在一定范圍內進行霍夫變換,計算單獨線段的方向及范圍,從而減少計算量,縮短計算時間,可以通過HoughLinesP()函數實現。
在OpenCV 中,通過函數HoughLines()檢測直線,並且能夠調用標准霍夫變換(SHT)和多尺度霍夫變換(MSHT),其函數原型如下所示:
- lines = HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])
– image表示輸入的二值圖像
– lines表示經過霍夫變換檢測到直線的輸出矢量,每條直線為(r,θ)
– rho表示以像素為單位的累加器的距離精度
– theta表示以弧度為單位的累加器角度精度
– threshold表示累加平面的閾值參數,識別某部分為圖中的一條直線時它在累加平面中必須達到的值,大於該值線段才能被檢測返回
– srn表示多尺度霍夫變換中rho的除數距離,默認值為0。粗略的累加器進步尺寸為rho,而精確的累加器進步尺寸為rho/srn
– stn表示多尺度霍夫變換中距離精度theta的除數,默認值為0,。如果srn和stn同時為0,使用標准霍夫變換
– min_theta表示標准和多尺度的霍夫變換中檢查線條的最小角度。必須介於0和max_theta之間
– max_theta表示標准和多尺度的霍夫變換中要檢查線條的最大角度。必須介於min_theta和π之間
下面的代碼是調用HoughLines()函數檢測圖像中的直線,並將所有的直線繪制於原圖像中。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#讀取圖像
gray = cv2.imread('judge.png', 0)
#灰度變換
#gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#轉換為二值圖像
edges = cv2.Canny(gray, 50, 150)
#顯示原始圖像
plt.subplot(121), plt.imshow(edges, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫變換檢測直線
lines = cv2.HoughLines(edges, 1, np.pi / 180, 160)
#轉換為二維
line = lines[:, 0, :]
#將檢測的線在極坐標中繪制
for rho,theta in line[:]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
print x0, y0
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
print x1, y1, x2, y2
#繪制直線
cv2.line(gray, (x1, y1), (x2, y2), (255, 0, 0), 2)
#顯示處理圖像
plt.subplot(122), plt.imshow(gray, 'gray'), plt.title('Result Image')
plt.axis('off')
plt.show()
輸出結果如圖16所示,第一幅圖為原始圖像,第二幅檢測出的直線。
使用該方法檢測大樓圖像中的直線如圖17所示,可以發現直線會存在越界的情況。
前面的標准霍夫變換會計算圖像中的每一個點,計算量比較大,另外它得到的是整條線(r,θ),並不知道原圖中直線的端點。接下來使用累計概率霍夫變換,它是一種改進的霍夫變換,調用HoughLinesP()函數實現。
- lines = HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])
– image表示輸入的二值圖像
– lines表示經過霍夫變換檢測到直線的輸出矢量,每條直線具有4個元素的矢量,即(x1, y1)和(x2, y2)是每個檢測線段的端點
– rho表示以像素為單位的累加器的距離精度
– theta表示以弧度為單位的累加器角度精度
– threshold表示累加平面的閾值參數,識別某部分為圖中的一條直線時它在累加平面中必須達到的值,大於該值線段才能被檢測返回
– minLineLength表示最低線段的長度,比這個設定參數短的線段不能被顯示出來,默認值為0
– maxLineGap表示允許將同一行點與點之間連接起來的最大距離,默認值0
下面的代碼是調用HoughLinesP()函數檢測圖像中的直線,並將所有的直線繪制於原圖像中。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#讀取圖像
img = cv2.imread('judge.png')
#灰度轉換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#轉換為二值圖像
edges = cv2.Canny(gray, 50, 200)
#顯示原始圖像
plt.subplot(121), plt.imshow(edges, 'gray'), plt.title(u'(a)原始圖像')
plt.axis('off')
#霍夫變換檢測直線
minLineLength = 60
maxLineGap = 10
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, minLineLength, maxLineGap)
#繪制直線
lines1 = lines[:, 0, :]
for x1,y1,x2,y2 in lines1[:]:
cv2.line(img, (x1,y1), (x2,y2), (255,0,0), 2)
res = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']
#顯示處理圖像
plt.subplot(122), plt.imshow(res), plt.title(u'(b)結果圖像')
plt.axis('off')
plt.show()
輸出結果如圖18所示,圖18(a)為原始圖像,圖18(b)檢測出的直線,它有效地提取了線段的起點和終點。
六.圖像霍夫圓變換操作
霍夫圓變換的原理與霍夫線變換很類似,只是將線的(r,θ)二維坐標提升為三維坐標,包括圓心點(x_center,y_center,r)和半徑r,其數學形式如公式(7)。
從而一個圓的確定需要三個參數,通過三層循環實現,接着尋找參數空間累加器的最大(或者大於某一閾值)的值。隨着數據量的增大,導致圓的檢測將比直線更耗時,所以一般使用霍夫梯度法減少計算量。在OpenCV中,提供了cv2.HoughCircles()函數檢測圓,其原型如下所示:
- circles = HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])
– image表示輸入圖像,8位灰度單通道圖像
– circles表示經過霍夫變換檢測到圓的輸出矢量,每個矢量包括3個元素,即(x, y, radius)
– method表示檢測方法,包括HOUGH_GRADIENT值
– dp表示用來檢測圓心的累加器圖像的分辨率於輸入圖像之比的倒數,允許創建一個比輸入圖像分辨率低的累加器
– minDist表示霍夫變換檢測到的圓的圓心之間的最小距離
– param1表示參數method設置檢測方法的對應參數,對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示傳遞給Canny邊緣檢測算子的高閾值,而低閾值為高閾值的一半,默認值100
– param2表示參數method設置檢測方法的對應參數,對當前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在檢測階段圓心的累加器閾值,它越小,將檢測到更多根本不存在的圓;它越大,能通過檢測的圓就更接近完美的圓形
– minRadius表示圓半徑的最小值,默認值為0
– maxRadius表示圓半徑的最大值,默認值0
下列代碼是檢測圖像中的圓。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#讀取圖像
img = cv2.imread('test01.png')
#灰度轉換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#顯示原始圖像
plt.subplot(121), plt.imshow(gray, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫變換檢測圓
#circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100,
# param1=100, param2=30, minRadius=200, maxRadius=300)
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param2=30)
print(circles1)
#提取為二維
circles = circles1[0, :, :]
#四舍五入取整
circles = np.uint16(np.around(circles))
#繪制圓
for i in circles[:]:
cv2.circle(img, (i[0],i[1]), i[2], (255,0,0), 5) #畫圓
cv2.circle(img, (i[0],i[1]), 2, (255,0,255), 10) #畫圓心
#顯示處理圖像
plt.subplot(122), plt.imshow(img), plt.title('Result Image')
plt.axis('off')
plt.show()
輸出結果如圖19所示,圖19(a)為原始圖像,圖圖19(b)檢測出的圓形,它有效地提取了圓形的圓心和輪廓。
使用下面的函數能有效提取人類眼睛的輪廓,核心函數如下:
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
param1=100, param2=30,
minRadius=160, maxRadius=300)
輸出結果如圖20所示,它提取了三條圓形接近於人體的眼睛。
圖20中顯示了三條曲線,通過不斷優化最大半徑和最小半徑,比如將minRadius設置為160,maxRadius設置為200,將提取更為精准的人體眼睛,如圖21所示。
最終代碼如下:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#讀取圖像
img = cv2.imread('eyes.png')
#灰度轉換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#顯示原始圖像
plt.subplot(121), plt.imshow(gray, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫變換檢測圓
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
param1=100, param2=30,
minRadius=160, maxRadius=200)
print(circles1)
#提取為二維
circles = circles1[0, :, :]
#四舍五入取整
circles = np.uint16(np.around(circles))
#繪制圓
for i in circles[:]:
cv2.circle(img, (i[0],i[1]), i[2], (255,0,0), 5) #畫圓
cv2.circle(img, (i[0],i[1]), 2, (255,0,255), 8) #畫圓心
#顯示處理圖像
plt.subplot(122), plt.imshow(img), plt.title('Result Image')
plt.axis('off')
plt.show()
七.總結
本文主要講解傅里葉變換和霍夫變換。傅里葉變換主要用來進行圖像除噪、圖像增強處理,通過Numpy和OpenCV兩種方法分別進行敘述,並結合代碼加深了讀者的印象;霍夫變換主要用來辨別找出物件中的特征,包括提取圖像中的直線和圓,調用cv2.HoughLines()、cv2.HoughLinesP()和cv2.HoughCircles()實現。
時光嘀嗒嘀嗒的流失,這是我在CSDN寫下的第八篇年終總結,比以往時候來的更早一些。《敏而多思,寧靜致遠》,僅以此篇紀念這風雨兼程的一年,這感恩的一年。列車上只寫了一半,這兩天完成,思遠,思君O(∩_∩)O
2020年8月18新開的“娜璋AI安全之家”,主要圍繞Python大數據分析、網絡空間安全、人工智能、Web滲透及攻防技術進行講解,同時分享CCF、SCI、南核北核論文的算法實現。娜璋之家會更加系統,並重構作者的所有文章,從零講解Python和安全,寫了近十年文章,真心想把自己所學所感所做分享出來,還請各位多多指教,真誠邀請您的關注!謝謝。
(By:Eastmount 2020-12-02 下午6點寫於武漢 http://blog.csdn.net/eastmount/ )
參考文獻,在此感謝這些大佬,共勉!
- [1]岡薩雷斯著. 數字圖像處理(第3版)[M]. 北京:電子工業出版社,2013.
- [2]阮秋琦. 數字圖像處理學(第3版)[M]. 北京:電子工業出版社,2008.
- [3]毛星雲,冷雪飛. OpenCV3編程入門[M]. 北京:電子工業出版社,2015.
- [4]張錚,王艷平,薛桂香等. 數字圖像處理與機器視覺——Visual C++與Matlab實現[M]. 北京:人民郵電出版社,2014.
- [5]百度百科. 傅里葉變換[EB/OL]. (2019.02.05). https://baike.baidu.com/item/傅里葉變換/7119029.
- [6]網易雲課堂_高登教育. Python+OpenCV圖像處理[EB/OL]. (2019-01-15). https://study.163.com/course/courseLearn.htm?courseId=1005317018.
- [7]安安zoe. 圖像的傅里葉變換[EB/OL]. (2018-02-01). https://www.jianshu.com/p/89ce7fdb9e12.
- [8]daduzimama. 圖像的傅里葉變換的迷思----頻譜居中[EB/OL]. (2018-06-07). https://blog.csdn.net/daduzimama/article/details/80597454.
- [9]tenderwx. [數字圖像處理] 傅里葉變換在圖像處理中的應用[EB/OL]. (2016-03-05). https://www.cnblogs.com/tenderwx/p/5245859.html.
- [10]小小貓釣小小魚. 深入淺出的講解傅里葉變換(真正的通俗易懂)[EB/OL]. (2018-02-02).
- [11]https://www.cnblogs.com/h2zZhou/p/8405717.html.
- [12]百度百科. 霍夫變換[EB/OL]. (2018-11-21). https://baike.baidu.com/item/霍夫變換/4647236.
- [13]yuyuntan. 經典霍夫變換(Hough Transform)[EB/OL]. (2018-04-29). https://blog.csdn.net/yuyuntan/article/details/80141392.
- [14]g20040733. 霍夫變換[EB/OL]. (2016-12-07). https://blog.csdn.net/g200407331/article/details/53507784.
- [15]我i智能. Python下opencv使用筆記(十一)(詳解hough變換檢測直線與圓)[EB/OL]. (2015-07-23). https://blog.csdn.net/on2way/article/details/47028969.
- [16]ex2tron. Python+OpenCV教程17:霍夫變換[EB/OL]. (2018-01-10). https://www.jianshu.com/p/34d6dc466e81.
- [17]Daetalus. OpenCV-Python教程(9、使用霍夫變換檢測直線)[EB/OL]. (2013-07-12). https://blog.csdn.net/sunny2038/article/details/9253823.