opencv目標檢測之canny演算法

  • 2019 年 10 月 3 日
  • 筆記

canny

canny的目標有3個

  • 低錯誤率 檢測出的邊緣都是真正的邊緣
  • 定位良好 邊緣上的像素點與真正的邊緣上的像素點距離應該最小
  • 最小響應 邊緣只能標識一次,雜訊不應該標註為邊緣

canny分幾步

  • 濾掉雜訊 比如高斯濾波
  • 計算梯度 比如用索貝爾運算元算出梯度
  • 非極大值抑制
    上一步算出來的邊緣可能比較粗糙,假設邊緣是一條很細的線的話,上面處理完的結果你可以理解為得到一條比較粗的線條,所謂非極大值抑制,就是要在局部像素點中找到變換最劇烈的一個點,這樣就得到了更細的邊緣.
  • 雙閾值檢測和連接邊緣

前面2步我們應該很熟悉了,不熟悉的參考https://www.cnblogs.com/sdu20112013/p/11608469.htmlhttps://www.cnblogs.com/sdu20112013/p/11600436.html

非極大值抑制

在求解梯度這一步,我們可以得到梯度的模長和方向

這一步為我們下面做nms(非極大值抑制)打下了基礎,索貝爾運算元處理後的影像得到的邊緣可能是很粗糙的,反映到影像上也就是邊緣比較寬,我們採用nms把非極大值的點的灰度都置為0,這樣就可以濾掉很多非邊緣的像素點.

如下圖所示,C表示為當前非極大值抑制的點,g1-4為它的8連通鄰域點,圖中藍色線段表示上一步計算得到的角度影像C點的值,即梯度方向,第一步先判斷C灰度值在8值鄰域內是否最大,如是則繼續檢查圖中梯度方向交點dTmp1,dTmp2值是否大於C,如C點大於dTmp1,dTmp2點的灰度值,則認定C點為極大值點,置為1,因此最後生成的影像應為一副二值影像,邊緣理想狀態下都為單像素邊緣.

這一步里有一點需要注意的就是dTmp1,dTmp2,這兩個像素點是不存在的,是通過雙線性插值法算出來的. 在John Canny提出的Canny運算元的論文中,非最大值抑制就只是在0、90、45、135四個梯度方向上進行的,每個像素點梯度方向按照相近程度用這四個方向來代替.實際檢測過程里,為了更準確地過濾出屬於邊緣的像素點,會做雙線性插值得到dTmp1,dTmp2.再去做前面所說的nms過程去判斷一個像素點是否屬於邊緣.
推薦2篇講的比較好的:https://blog.csdn.net/kezunhai/article/details/11620357 https://www.cnblogs.com/techyan1990/p/7291771.html

關於如何得到梯度方向的像素點,如下圖所示

這樣的話就達到了將"粗大的邊緣"過濾地更加細膩.

這一步之後,得到的邊緣還包含很多由雜訊及其他原因造成的假邊緣.

雙閾值檢測和邊緣連接

經過nms以後,已經很接近真實邊緣了.但還是有一些由於雜訊或者別的一些原因造成的假的邊緣.我們通過2個閾值來作進一步的過濾.

Hysteresis: The final step. Canny does use two thresholds (upper and lower): – If a pixel gradient is higher than the upper threshold, the pixel is accepted as an edge .If a pixel gradient value is below the lower threshold, then it is rejected.If the pixel gradient is between the two thresholds, then it will be accepted only if it is connected to a pixel that is above the upper threshold.
Canny recommended a upper:lower ratio between 2:1 and 3:1.

  • 對於梯度大於高閾值的點,認為是真的邊緣上的像素點.
  • 對於梯度小於低閾值的點,認為是假的邊緣像素點,是雜訊造成的,去掉這些點.
  • 對於梯度介於高低閾值之間的點,如果它周圍的鄰域像素點有"真邊緣點"(也就是梯度大於高閾值的點),則認為這點也是"真邊緣點".
    推薦的高低閾值比在2:1到3:1之間

實際工程里,這兩個參數要針對你自己的影像數據去調整,太低有可能造成假邊緣太多,太高有可能造成想要保留的邊緣也被濾掉了.
canny api

參數3,4表示低閾值和高閾值,L2gradient默認false,表示是否用開平方的方式計算梯度的大小.

opencv示例

from __future__ import print_function  import cv2 as cv  import argparse  max_lowThreshold = 100  window_name = 'Edge Map'  title_trackbar = 'Min Threshold:'  ratio = 3  kernel_size = 3  def CannyThreshold(val):      low_threshold = val      #img_blur = cv.blur(src_gray, (3,3))      detected_edges = cv.Canny(src_gray, low_threshold, low_threshold*ratio, kernel_size)      mask = detected_edges != 0      dst = src * (mask[:,:,None].astype(src.dtype))      cv.imshow(window_name, dst)    src = cv.imread("/home/sc/disk/keepgoing/opencv_test/sidetest.jpeg")  src = cv.GaussianBlur(src, (3, 3), 0)  src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)  cv.namedWindow(window_name)  cv.createTrackbar(title_trackbar, window_name , 0, max_lowThreshold, CannyThreshold)  CannyThreshold(0)  cv.waitKey()

注意閾值的不同造成的影響,可以看到閾值很低的時候線條更多,當然"偽邊緣"更多,當閾值很高的時候,"偽邊緣"減少了,但也丟失了更多的細節.所以需要根據自己實際的圖片數據去調參.