OpenCV图像处理(下) 边缘检测+模板匹配+霍夫变换
OpenCV圖像處理(下)
- 一、邊緣檢測
- 1. 原理
- 2. Sobel檢測算子
- 2.1 方法
- 2.2 應用
- 3. Laplacian算子
- 4. Canny邊緣檢測
- 4.1 原理
- 4.2 應用
- 二、模版匹配和霍夫變換
- 1. 模板匹配
- 1.1 原理
- 1.2 實現
- 2. 霍夫變換
- 2.1 原理
- (1)原理
- (2)實現流程
- 2.2 霍夫線檢測
- 2.3 霍夫圓檢測[了解]
- (1)原理
- (2)API
- (3)實現
一、邊緣檢測
1. 原理
邊緣檢測是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。邊緣的表現形式如下圖所示:
圖像邊緣檢測大幅度地減少了數據量,并且剔除了可以認為不相關的信息,保留了圖像重要的結構屬性。有許多方法用于邊緣檢測,它們的絕大部分可以劃分為兩類:基于搜索和基于零穿越。
- 基于搜索:通過尋找圖像一階導數中的最大值來檢測邊界,然后利用計算結果估計邊緣的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法是Sobel算子和Scharr算子。
- 基于零穿越:通過尋找圖像二階導數零穿越來尋找邊界,代表算法是Laplacian算子。
2. Sobel檢測算子
Sobel邊緣檢測算法比較簡單,實際應用中效率比canny邊緣檢測效率要高,但是邊緣不如Canny檢測的準確,但是很多實際應用的場合,sobel邊緣卻是首選,Sobel算子是高斯平滑與微分操作的結合體,所以其抗噪聲能力很強,用途較多。尤其是效率要求較高,而對細紋理不太關心的時候。
2.1 方法
對于不連續的函數,一階導數可以寫作:
或
所以有:
假設要處理的圖像為II,在兩個方向求導:
- 水平變化: 將圖像II 與奇數大小的模版進行卷積,結果為GxG_xGx??? 。比如,當模板大小為3時, GxG_xGx?為:
- 垂直變化: 將圖像II與奇數大小的模板進行卷積,結果為GyG_yGy???。比如,當模板大小為3時, GyG_yGy?為
在圖像的每一點,結合以上兩個結果求出:
統計極大值所在的位置,就是圖像的邊緣。
注意:當內核大小為3時, 以上Sobel內核可能產生比較明顯的誤差, 為解決這一問題,我們使用Scharr函數,但該函數僅作用于大小為3的內核。該函數的運算與Sobel函數一樣快,但結果卻更加精確,其計算方法為:
2.2 應用
利用OpenCV進行sobel邊緣檢測的API是:
Sobel_x_or_y = cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delta, borderType)參數:
- src:傳入的圖像
- ddepth: 圖像的深度
- dx和dy: 指求導的階數,0表示這個方向上沒有求導,取值為0、1。
- ksize: 是Sobel算子的大小,即卷積核的大小,必須為奇數1、3、5、7,默認為3。
注意:如果ksize=-1,就演變成為3x3的Scharr算子。 - scale:縮放導數的比例常數,默認情況為沒有伸縮系數。
- borderType:圖像邊界的模式,默認值為cv2.BORDER_DEFAULT。
Sobel函數求完導數后會有負值,還有會大于255的值。而原圖像是uint8,即8位無符號數,所以Sobel建立的圖像位數不夠,會有截斷。因此要使用16位有符號的數據類型,即cv2.CV_16S。處理完圖像后,再使用cv2.convertScaleAbs()函數將其轉回原來的uint8格式,否則圖像無法顯示。
Sobel算子是在兩個方向計算的,最后還需要用cv2.addWeighted( )函數將其組合起來
Scale_abs = cv2.convertScaleAbs(x) # 格式轉換函數 result = cv2.addWeighted(src1, alpha, src2, beta) # 圖像混合示例:
import cv2 as cv import numpy as np from matplotlib import pyplot as plt # 1 讀取圖像 img = cv.imread('./image/horse.jpg',0) # 2 計算Sobel卷積結果 x = cv.Sobel(img, cv.CV_16S, 1, 0) y = cv.Sobel(img, cv.CV_16S, 0, 1) # 3 將數據進行轉換 Scale_absX = cv.convertScaleAbs(x) # convert 轉換 scale 縮放 Scale_absY = cv.convertScaleAbs(y) # 4 結果合成 result = cv.addWeighted(Scale_absX, 0.5, Scale_absY, 0.5, 0) # 5 圖像顯示 plt.figure(figsize=(10,8),dpi=100) plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原圖') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(result,cmap = plt.cm.gray),plt.title('Sobel濾波后結果') plt.xticks([]), plt.yticks([]) plt.show()將上述代碼中計算sobel算子的部分中將ksize設為-1,就是利用Scharr進行邊緣檢測。
x = cv.Sobel(img, cv.CV_16S, 1, 0, ksize = -1) y = cv.Sobel(img, cv.CV_16S, 0, 1, ksize = -1)3. Laplacian算子
Laplacian是利用二階導數來檢測邊緣 。 因為圖像是 “2維”, 我們需要在兩個方向求導,如下式所示:
那不連續函數的二階導數是:
那使用的卷積核是:
API:
參數:
- Src: 需要處理的圖像,
- Ddepth: 圖像的深度,-1表示采用的是原圖像相同的深度,目標圖像的深度必須大于等于原圖像的深度;
- ksize:算子的大小,即卷積核的大小,必須為1,3,5,7。
示例:
import cv2 as cv import numpy as np from matplotlib import pyplot as plt # 1 讀取圖像 img = cv.imread('./image/horse.jpg',0) # 2 laplacian轉換 result = cv.Laplacian(img,cv.CV_16S) Scale_abs = cv.convertScaleAbs(result) # 3 圖像展示 plt.figure(figsize=(10,8),dpi=100) plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原圖') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(Scale_abs,cmap = plt.cm.gray),plt.title('Laplacian檢測后結果') plt.xticks([]), plt.yticks([]) plt.show()4. Canny邊緣檢測
Canny 邊緣檢測算法是一種非常流行的邊緣檢測算法,是 John F. Canny 于 1986年提出的,被認為是最優的邊緣檢測算法。
4.1 原理
Canny邊緣檢測算法是由4步構成,分別介紹如下:
- 第一步:噪聲去除
由于邊緣檢測很容易受到噪聲的影響,所以首先使用5?55*55?5高斯濾波器去除噪聲,在圖像平滑那一章節中已經介紹過。
- 第二步:計算圖像梯度
對平滑后的圖像使用 Sobel 算子計算水平方向和豎直方向的一階導數(Gx 和 Gy)。根據得到的這兩幅梯度圖(Gx 和 Gy)找到邊界的梯度和方向,公式如下:
如果某個像素點是邊緣,則其梯度方向總是垂直與邊緣垂直。梯度方向被歸為四類:垂直,水平,和兩個對角線方向。
- 第三步:非極大值抑制
在獲得梯度的方向和大小之后,對整幅圖像進行掃描,去除那些非邊界上的點。對每一個像素進行檢查,看這個點的梯度是不是周圍具有相同梯度方向的點中最大的。如下圖所示:
A點位于圖像的邊緣,在其梯度變化方向,選擇像素點B和C,用來檢驗A點的梯度是否為極大值,若為極大值,則進行保留,否則A點被抑制,最終的結果是具有“細邊”的二進制圖像。
- 第四步:滯后閾值
現在要確定真正的邊界。 我們設置兩個閾值: minVal 和 maxVal。 當圖像的灰度梯度高于 maxVal 時被認為是真的邊界, 低于 minVal 的邊界會被拋棄。如果介于兩者之間的話,就要看這個點是否與某個被確定為真正的邊界點相連,如果是就認為它也是邊界點,如果不是就拋棄。如下圖:
如上圖所示,A 高于閾值 maxVal 所以是真正的邊界點,C 雖然低于 maxVal 但高于 minVal 并且與 A 相連,所以也被認為是真正的邊界點。而 B 就會被拋棄,因為低于 maxVal 而且不與真正的邊界點相連。所以選擇合適的 maxVal 和 minVal 對于能否得到好的結果非常重要。
4.2 應用
在OpenCV中要實現Canny檢測使用的API:
canny = cv2.Canny(image, threshold1, threshold2)參數:
- image:灰度圖,
- threshold1: minval,較小的閾值將間斷的邊緣連接起來
- threshold2: maxval,較大的閾值檢測圖像中明顯的邊緣
示例:
import cv2 as cv import numpy as np from matplotlib import pyplot as plt # 1 圖像讀取 img = cv.imread('./image/horse.jpg',0) # 2 Canny邊緣檢測 lowThreshold = 0 max_lowThreshold = 100 canny = cv.Canny(img, lowThreshold, max_lowThreshold) # 3 圖像展示 plt.figure(figsize=(10,8),dpi=100) plt.subplot(121),plt.imshow(img,cmap=plt.cm.gray),plt.title('原圖') plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(canny,cmap = plt.cm.gray),plt.title('Canny檢測后結果') plt.xticks([]), plt.yticks([]) plt.show()二、模版匹配和霍夫變換
1. 模板匹配
1.1 原理
所謂的模板匹配,就是在給定的圖片中查找和模板最相似的區域,該算法的輸入包括模板和圖片,整個任務的思路就是按照滑窗的思路不斷的移動模板圖片,計算其與圖像中對應區域的匹配度,最終將匹配度最高的區域選擇為最終的結果。
實現流程:
- 準備兩幅圖像:
1.原圖像(I):在這幅圖中,找到與模板相匹配的區域
2.模板(T):與原圖像進行比對的圖像塊
- 滑動模板圖像和原圖像進行比對:
將模板塊每次移動一個像素 (從左往右,從上往下),在每一個位置,都計算與模板圖像的相似程度。
-
對于每一個位置將計算的相似結果保存在結果矩陣(R)中。如果輸入圖像的大小(WxH)且模板圖像的大小(wxh),則輸出矩陣R的大小為(W-w + 1,H-h + 1)將R顯示為圖像,如下圖所示:
-
獲得上述圖像后,查找最大值所在的位置,那么該位置對應的區域就被認為是最匹配的。對應的區域就是以該點為頂點,長寬和模板圖像一樣大小的矩陣。
1.2 實現
我們使用OpenCV中的方法實現模板匹配。
API:
res = cv.matchTemplate(img,template,method)參數:
- img: 要進行模板匹配的圖像
- Template :模板
- method:實現模板匹配的算法,主要有:
- 平方差匹配(CV_TM_SQDIFF):利用模板與圖像之間的平方差進行匹配,最好的匹配是0,匹配越差,匹配的值越大。
- 相關匹配(CV_TM_CCORR):利用模板與圖像間的乘法進行匹配,數值越大表示匹配程度較高,越小表示匹配效果差。
- 利用相關系數匹配(CV_TM_CCOEFF):利用模板與圖像間的相關系數匹配,1表示完美的匹配,-1表示最差的匹配。
完成匹配后,使用cv.minMaxLoc()方法查找最大值所在的位置即可。如果使用平方差作為比較方法,則最小值位置是最佳匹配位置。
示例:
在該案例中,載入要搜索的圖像和模板,圖像如下所示:
模板如下所示:
通過matchTemplate實現模板匹配,使用minMaxLoc定位最匹配的區域,并用矩形標注最匹配的區域。
import cv2 as cv import numpy as np from matplotlib import pyplot as plt # 1 圖像和模板讀取 img = cv.imread('./image/wulin2.jpeg') template = cv.imread('./image/wulin.jpeg') h,w,l = template.shape # 2 模板匹配 # 2.1 模板匹配 res = cv.matchTemplate(img, template, cv.TM_CCORR) # 2.2 返回圖像中最匹配的位置,確定左上角的坐標,并將匹配位置繪制在圖像上 min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res) # 使用平方差時最小值為最佳匹配位置 # top_left = min_loc top_left = max_loc bottom_right = (top_left[0] + w, top_left[1] + h) cv.rectangle(img, top_left, bottom_right, (0,255,0), 2) # 3 圖像顯示 plt.imshow(img[:,:,::-1]) plt.title('匹配結果'), plt.xticks([]), plt.yticks([]) plt.show()拓展:模板匹配不適用于尺度變換,視角變換后的圖像,這時我們就要使用關鍵點匹配算法,比較經典的關鍵點檢測算法包括SIFT和SURF等,主要的思路是首先通過關鍵點檢測算法獲取模板和測試圖片中的關鍵點;然后使用關鍵點匹配算法處理即可,這些關鍵點可以很好的處理尺度變化、視角變換、旋轉變化、光照變化等,具有很好的不變性。
2. 霍夫變換
霍夫變換常用來提取圖像中的直線和圓等幾何形狀,如下圖所示:
2.1 原理
(1)原理
在笛卡爾坐標系中,一條直線由兩個點A=(x1,y1)A=(x_1,y_1)A=(x1?,y1?)和B=(x2,y2)B=(x_2,y_2)B=(x2?,y2?)確定,如下圖所示:
將直線y=kx+q可寫成關于(k,q)(k,q)的函數表達式:
對應的變換通過圖形直觀的表示下:
變換后的空間我們叫做霍夫空間。即:笛卡爾坐標系中的一條直線,對應于霍夫空間中的一個點。反過來,同樣成立,霍夫空間中的一條線,對應于笛卡爾坐標系中一個點,如下所示:
我們再來看下A、B兩個點,對應于霍夫空間的情形:
在看下三點共線的情況:
可以看出如果在笛卡爾坐標系的點共線,那么這些點在霍夫空間中對應的直線交于一點。
如果不止存在一條直線時,如下所示:
我們選擇盡可能多的直線匯成的點,上圖中三條直線匯成的A、B兩點,將其對應回笛卡爾坐標系中的直線:
到這里我們似乎已經完成了霍夫變換的求解。但如果像下圖這種情況時:
上圖中的直線是x=2x=2x=2,那(k,q)怎么確定呢?
為了解決這個問題,我們考慮將笛卡爾坐標系轉換為極坐標。
在極坐標下是一樣的,極坐標中的點對應于霍夫空間的線,這時的霍夫空間是不在是參數(k,q)(k,q)(k,q)的空間,而是(ρ,θ)(\rho,\theta)(ρ,θ)的空間,ρ\rhoρ是原點到直線的垂直距離,θ\thetaθ表示直線的垂線與橫軸順時針方向的夾角,垂直線的角度為0度,水平線的角度是180度。
我們只要求得霍夫空間中的交點的位置,即可得到原坐標系下的直線。
(2)實現流程
假設有一個大小為100*?100的圖片,使用霍夫變換檢測圖片中的直線,則步驟如下所示:
-
直線都可以使用(ρ,θ)(\rho,\theta)(ρ,θ) 表示,首先創建一個2D數組,我們叫做累加器,初始化所有值為0,行表示ρ\rhoρ ,列表示θ\thetaθ 。
-
該數組的大小決定了結果的準確性,若希望角度的精度為1度,那就需要180列。對于ρ\rhoρ,最大值為圖片對角線的距離,如果希望精度達到像素級別,行數應該與圖像的對角線的距離相等。
-
取直線上的第一個點(x,y)(x,y)(x,y),將其帶入直線在極坐標中的公式中,然后遍歷θ\thetaθ的取值:0,1,2,…,180,分別求出對應的ρ\rhoρ值,如果這個數值在上述累加器中存在相應的位置,則在該位置上加1.
-
取直線上的第二個點,重復上述步驟,更新累加器中的值。對圖像中的直線上的每個點都直線以上步驟,每次更新累加器中的值。
-
搜索累加器中的最大值,并找到其對應的(ρ,θ)(\rho,\theta)(ρ,θ),就可將圖像中的直線表示出來。
2.2 霍夫線檢測
在OpenCV中做霍夫線檢測是使用的API是:
cv.HoughLines(img, rho, theta, threshold)參數:
- img: 檢測的圖像,要求是二值化的圖像,所以在調用霍夫變換之前首先要進行二值化,或者進行Canny邊緣檢測
- rho、theta: ρ\rhoρ 和θ\thetaθ的精確度
- threshold: 閾值,只有累加器中的值高于該閾值時才被認為是直線。
霍夫線檢測的整個流程如下圖所示,這是在stackflow上一個關于霍夫線變換的解釋:
示例:
檢測下述圖像中的直線:
import numpy as np import random import cv2 as cv import matplotlib.pyplot as plt # 1.加載圖片,轉為二值圖 img = cv.imread('./image/rili.jpg')gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) edges = cv.Canny(gray, 50, 150)# 2.霍夫直線變換 lines = cv.HoughLines(edges, 0.8, np.pi / 180, 150) # 3.將檢測的線繪制在圖像上(注意是極坐標噢) for line in lines:rho, theta = line[0]a = np.cos(theta)b = np.sin(theta)x0 = a * rhoy0 = b * rhox1 = int(x0 + 1000 * (-b))y1 = int(y0 + 1000 * (a))x2 = int(x0 - 1000 * (-b))y2 = int(y0 - 1000 * (a))cv.line(img, (x1, y1), (x2, y2), (0, 255, 0)) # 4. 圖像顯示 plt.figure(figsize=(10,8),dpi=100) plt.imshow(img[:,:,::-1]),plt.title('霍夫變換線檢測') plt.xticks([]), plt.yticks([]) plt.show()2.3 霍夫圓檢測[了解]
(1)原理
圓的表示式是:
其中aa和bb表示圓心坐標,rr表示圓半徑,因此標準的霍夫圓檢測就是在這三個參數組成的三維空間累加器上進行圓形檢測,此時效率就會很低,所以OpenCV中使用霍夫梯度法進行圓形的檢測。
霍夫梯度法將霍夫圓檢測范圍兩個階段,第一階段檢測圓心,第二階段利用圓心推導出圓半徑。
-
圓心檢測的原理:圓心是圓周法線的交匯處,設置一個閾值,在某點的相交的直線的條數大于這個閾值就認為該交匯點為圓心。
-
圓半徑確定原理:圓心到圓周上的距離(半徑)是相同的,確定一個閾值,只要相同距離的數量大于該閾值,就認為該距離是該圓心的半徑。
原則上霍夫變換可以檢測任何形狀,但復雜的形狀需要的參數就多,霍夫空間的維數就多,因此在程序實現上所需的內存空間以及運行效率上都不利于把標準霍夫變換應用于實際復雜圖形的檢測中。霍夫梯度法是霍夫變換的改進,它的目的是減小霍夫空間的維度,提高效率。
(2)API
在OpenCV中檢測圖像中的圓環使用的是API是:
circles = cv.HoughCircles(image, method, dp, minDist, param1=100, param2=100, minRadius=0,maxRadius=0 )參數:
-
image:輸入圖像,應輸入灰度圖像
-
method:使用霍夫變換圓檢測的算法,它的參數是CV_HOUGH_GRADIENT
-
dp:霍夫空間的分辨率,dp=1時表示霍夫空間與輸入圖像空間的大小一致,dp=2時霍夫空間是輸入圖像空間的一半,以此類推
-
minDist為圓心之間的最小距離,如果檢測到的兩個圓心之間距離小于該值,則認為它們是同一個圓心
-
param1:邊緣檢測時使用Canny算子的高閾值,低閾值是高閾值的一半。
-
param2:檢測圓心和確定半徑時所共有的閾值
-
minRadius和maxRadius為所檢測到的圓半徑的最小值和最大值
返回:
- circles:輸出圓向量,包括三個浮點型的元素——圓心橫坐標,圓心縱坐標和圓半徑
(3)實現
由于霍夫圓檢測對噪聲比較敏感,所以首先對圖像進行中值濾波。
import cv2 as cv import numpy as np import matplotlib.pyplot as plt # 1 讀取圖像,并轉換為灰度圖 planets = cv.imread("./image/star.jpeg") gay_img = cv.cvtColor(planets, cv.COLOR_BGRA2GRAY) # 2 進行中值模糊,去噪點 img = cv.medianBlur(gay_img, 7) # 3 霍夫圓檢測 circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT, 1, 200, param1=100, param2=30, minRadius=0, maxRadius=100) # 4 將檢測結果繪制在圖像上 for i in circles[0, :]: # 遍歷矩陣每一行的數據# 繪制圓形cv.circle(planets, (i[0], i[1]), i[2], (0, 255, 0), 2)# 繪制圓心cv.circle(planets, (i[0], i[1]), 2, (0, 0, 255), 3) # 5 圖像顯示 plt.figure(figsize=(10,8),dpi=100) plt.imshow(planets[:,:,::-1]),plt.title('霍夫變換圓檢測') plt.xticks([]), plt.yticks([]) plt.show()總結
以上是生活随笔為你收集整理的OpenCV图像处理(下) 边缘检测+模板匹配+霍夫变换的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: xpath helper
- 下一篇: RedHat下载安装JDK的方法(方法二