1. 程式人生 > 實用技巧 >[Python影象處理] 三十二.傅立葉變換(影象去噪)與霍夫變換(特徵識別)萬字詳細總結

[Python影象處理] 三十二.傅立葉變換(影象去噪)與霍夫變換(特徵識別)萬字詳細總結

此文轉載自:https://blog.csdn.net/Eastmount/article/details/110487868

該系列文章是講解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.