天天看點

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

文章目錄

  • Canny算子
    • 非極大值抑制
    • 非極大值抑制中的插值
    • 滞後門檻值
  • 實際應用
    • 直接使用Canny算子
    • 使用膨脹
    • 先門檻值分割

Canny算子

上一篇說到,我在一個小項目裡需要在一幅圖像中提取一根試管裡的兩種液體的截面。為了達到這個目的使用傳統圖像裡的區域分割技術,實際上就是想把這個圖像分成兩類,然後再找到這個兩個類的邊界。

上一張最後提到,我是使用一種拟合的方法來做的邊界的判斷,後來突然想到,opencv裡面提供了現成的方法:邊緣檢測的Canny算子,直接就可以提取圖像的邊界。

Canny算子在官網上有介紹:

  • 調用方式:edges = cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
  • 參數一:輸入圖像,為二值圖像。
  • 參數二:門檻值1,和門檻值2一樣,是用于控制邊緣檢測的度的,後面在詳細過程說明中來描述。
  • 參數三:門檻值2
  • 參數四:apertureSize,sobel算子的卷積大小。sobel算子用于計算圖像的梯度,參考sobel算子的文章(https://blog.csdn.net/pcgamer/article/details/127942102?spm=1001.2014.3001.5502)
  • 參數五:L2gradient,是否使用二級梯度計算。實際上就是使用Laplacian算子進行二階梯度計算,同樣可以參考上面的那片文章。
  • 傳回值就是一個隻有邊緣資訊的二值圖像。

Canny算子的一般介紹中,主要提到了一下幾個步驟:

  • 噪聲去除
  • 計算梯度
  • 非極大值抑制
  • 滞後門檻值

非極大值抑制

噪聲去除這個步驟一般是采用高斯模糊在做的,這個在濾波的那一篇中已經提到過,這裡就不多說了,可以參考:

https://blog.csdn.net/pcgamer/article/details/124989015?spm=1001.2014.3001.5502

計算梯度之前也提過了,這裡也不多說。

是以從非極大值抑制這裡繼續。

非極大值抑制,用人話說就是隻留下最大值。那麼,這裡有幾個問題:

  • 為什麼留下最大值?
  • 留下什麼最大值?就是這個最大值怎麼定義?
  • 怎麼留下最大值?

首先回答為什麼的問題,在圖像進行中,一般來說,邊緣就是像素值變化最大的地方,這個很容易了解。

是以第二個問題也很簡單,這裡的最大值就是上文提到的梯度最大。而且是在梯度方向上梯度最大。

在寫清晰度的那片文章中提到了梯度方向的計算方式,其實可以了解成目前邊緣方向的法線放下那個,有點繞吧,用幾個圖來說明一下:

首先,梯度方向定義為 a r c t a n ( g x g y ) arctan(\frac{g_x}{g_y}) arctan(gy​gx​​),也就是

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

放大到一整張圖中:

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

黑線指向的方向就是梯度的方向。

那麼上面說到非極大值抑制,就是要确定某一個點在這條梯度方向是是附近(局部)的最大值。一般來說,這個附近就是旁邊的一個像素值。

非極大值抑制中的插值

雖然就是比較這個梯度方向上的三個像素點,但是這不是能直接比較的。

  • 首先,上面的梯度方向不是固定的,隻有當這個梯度方向恰好是45度的整數倍時,才恰好對應到一個像素點:
    python的opencv操作記錄12——Canny算子使用Canny算子實際應用
    • 可以從圖中看到,如果是梯度方向是45,225的時候,中心像素點就是和對角線上的兩個像素點比較(右上和左下)
    • 如果是梯度方向是135,315的時候,中心像素點就是和對角線上的兩個像素點比較(左上和右下)
    • 如果是0,180,就是和左右兩個像素點做比較
    • 如果是90和270度,就是和上下兩個像素點做比較。
    • 上面的幾種情況都是比較巧合的情況,但是如果不是這些角度呢?
  • 如果不是這些角度,就需要進行插值了,見下圖:
    python的opencv操作記錄12——Canny算子使用Canny算子實際應用

    比如上面的dTemp1這個點,很明顯不是一個實際存在的像素點,這個像素點可以被稱作一個亞像素(sub pixel),這個點的像素值可以根據旁邊的兩個實際像素點來插值計算,在canny算子中,插值方法就是普通的線性插值:

    這個亞像素的dTemp1的像素值就是:

    d T e m p 1 = p 1 + L d T e m p 1 − p 1 p 2 − p 1 dTemp1 = p1 + \frac{L_{dTemp1} - p1}{p2-p1} dTemp1=p1+p2−p1LdTemp1​−p1​

    上面的 L d T e m p 1 L_{dTemp1} LdTemp1​可以根據梯度方向的 g x g y \frac{g_x}{g_y} gy​gx​​來計算出來

    同樣,下面的dTemp2也可以通過插值方法計算出來。

  • 插值計算完成後,就可以完成非極大值抑制的比較計算了。如果中心像素是最大的,那麼就保留,不是極大值,則指派為0。
  • 以上面的邏輯完成整張圖像的計算,就完成了非極大值抑制這個過程的計算。

滞後門檻值

完成非極大值抑制後,邊緣已經精細了很多了,但是還不保證所有留下的像素點都是邊緣,是以最後一步是通過門檻值來控制這些留下的梯度值(從非極大值抑制出來的資料已經不是像素值,而是梯度值矩陣),單獨通過一個門檻值來控制過于簡單粗暴,是以Canny算子用了兩個。。。。

  • 如果高于T1,也就是兩個門檻值中較高的門檻值,則保留。
  • 如果低于T2,也就是兩個門檻值中較低的門檻值,則丢棄。
  • 中間的怎麼辦呢?從所有的高于T1的梯度值出發,如果能連接配接的上的中間值,則保留,否則就丢棄。
  • 一般來說T1 = 2T2

完成上面的滞後門檻值計算後,就完成了Canny算子的邊緣檢測(當然最後是輸出像素值的二值圖像)

實際應用

直接使用Canny算子

不管三七二十一,我把之前的圖轉換成灰階圖後,直接調用Canny算子:

img = cv2.imread("./images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    edge = cv2.Canny(grey, 120, 54)

    cv2.imshow("origin", grey)
    cv2.imshow("result", edge)
           

效果非常的糟糕:

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

使用膨脹

因為Canny算子中的高斯模糊針對的是微小的高斯噪聲,這個圖像中的噪聲都是大塊的噪聲,是以我就想着用膨脹函數試一下:

img = cv2.imread("./images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 做一把膨脹
    kernel_e = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    bin_clo = cv2.dilate(grey, kernel_e, iterations=10)

    edge = cv2.Canny(bin_clo, 120, 54)

    cv2.imshow("origin", grey)
    cv2.imshow("result", edge)
           

效果好了一點:

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

先門檻值分割

但是還是去的不幹淨,而且最上面的分界線沒有弄完成,突然想到上一次用大津法的門檻值分割基本已經完成了前景和背景的分割,再加上一次門檻值分割,用膨脹再清除一把,最後再做邊緣檢測:

img = cv2.imread("./images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 如果大于門檻值,指派為255,小于門檻值,指派為0
    # ret, binary = cv2.threshold(grey, 60, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    #
    # 做一把腐蝕
    kernel_e = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    bin_clo = cv2.dilate(grey, kernel_e, iterations=10)

    edge = cv2.Canny(bin_clo, 120, 54)

    cv2.imshow("origin", grey)
    cv2.imshow("result", edge)
           

效果妥妥的:

python的opencv操作記錄12——Canny算子使用Canny算子實際應用

大功告成!