1. 程式人生 > >opencv目標檢測之canny演算法

opencv目標檢測之canny演算法

canny

canny的目標有3個

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

canny分幾步

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

前面2步我們應該很熟悉了,不熟悉的參考https://www.cnblogs.com/sdu20112013/p/11608469.html 和 https://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()

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