邊緣檢測
對圖像進行邊緣檢測之前,一般都需要先進行降噪(可調用GaussianBlur函數)。
Sobel算子 與 Scharr算子
都是一個離散微分算子 (discrete differentiation operator),用來計算圖像灰度函數的近似梯度。結合了高斯平滑和微分求導。Sobel算子與Scharr算子的內核不同,Sobel內核產生誤差比較明顯,Scharr更為准確一些。
Sobel算子的計算步驟:
- 在兩個方向求導:將原圖分別與兩個3x3的內核進行卷積計算,得到Gx與Gy
- 在圖像的每一點,結合Gx與Gy求出近似 梯度 :
或者
(簡單公式)
/// 求 X方向梯度 //Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT ); Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_x, abs_grad_x ); /// 求Y方向梯度 //Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT ); Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT ); convertScaleAbs( grad_y, abs_grad_y ); /// 合並梯度(近似) addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
- src_gray: 在本例中為輸入圖像,元素類型 CV_8U
- grad_x/grad_y: 輸出圖像.
- ddepth: 輸出圖像的深度,設定為 CV_16S 避免外溢。
- x_order: x 方向求導的階數。
- y_order: y 方向求導的階數。
Laplace算子
計算的是二階導數。由於 Laplacian使用了圖像梯度,它內部調用了 Sobel 算子。Laplacian 算子 的定義:
Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT ); convertScaleAbs( dst, abs_dst );
- src_gray: 輸入圖像。
- dst: 輸出圖像
- ddepth: 輸出圖像的深度。 因為輸入圖像的深度是 CV_8U ,這里我們必須定義 ddepth = CV_16S 以避免外溢。
- kernel_size: 內部調用的 Sobel算子的內核大小,此例中設置為3。
- scale, delta 和 BORDER_DEFAULT: 使用默認值。
Canny邊緣檢測
被很多人認為是邊緣檢測的 最優算法。在Sober算子步驟后添加以下步驟:
- 非極大值 抑制。 這一步排除非邊緣像素, 僅僅保留了一些細線條(候選邊緣)。
- 滯后閾值: 最后一步,Canny 使用了滯后閾值,滯后閾值需要兩個閾值(高閾值和低閾值):
- 如果某一像素位置的幅值超過 高 閾值, 該像素被保留為邊緣像素。
- 如果某一像素位置的幅值小於 低 閾值, 該像素被排除。
- 如果某一像素位置的幅值在兩個閾值之間,該像素僅僅在連接到一個高於 高 閾值的像素時被保留。
Canny( origin_gray_image, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
具體使用方法見此處范例。
Hough變換
Hough線變換
執行Hough線變換之前需執行高斯模糊(降噪)+Canny邊緣檢測。Hough線變換以Canny邊緣檢測的輸出(二值圖)為輸入。
一條直線可由參數 極徑和極角表示:
當x0與y0定下來之后,rθ隨着θ變化而變化,可在平面 -
中畫出相應曲線(是一條正弦曲線),一對(x0, y0)確定一條曲線。(x1,y1)與(x2, y2)相交於(r0,
0)表示以這兩個點作直線可由(r0,
0)表示:
一條直線能夠通過在平面 -
尋找交於一點的曲線數量來 檢測. 越多曲線交於一點也就意味着這個交點表示的直線由更多的點組成. 一般來說我們可以通過設置直線上點的 閾值 來定義多少條曲線交於一點我們才認為 檢測 到了一條直線.
OpenCV實現了以下兩種霍夫線變換:
- 標准Hough線變換 HoughLines :提供一組參數對
的集合來表示檢測到的直線
- 統計概率Hough線變換 HoughLinesP :效率更高的Hough變換,輸出檢測到的直線的端點
// 標准Hough線變換 vector<Vec2f> lines; HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 ); // 畫出檢測的直線 for( size_t i = 0; i < lines.size(); i++ ) { float rho = lines[i][0], theta = lines[i][1]; Point pt1, pt2; double a = cos(theta), b = sin(theta); double x0 = a*rho, y0 = b*rho; pt1.x = cvRound(x0 + 1000*(-b)); pt1.y = cvRound(y0 + 1000*(a)); pt2.x = cvRound(x0 - 1000*(-b)); pt2.y = cvRound(y0 - 1000*(a)); line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA); } // 統計Hough線變換 vector<Vec4i> lines; HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );
- dst: 邊緣檢測的輸出圖像. 它應該是個灰度圖 (但事實上是個二值化圖)
- lines: 儲存着檢測到的直線的參數對
的容器
- rho : 參數極徑
以像素值為單位的分辨率. 我們使用 1 像素.
- theta: 參數極角
以弧度為單位的分辨率. 我們使用 1度 (即CV_PI/180)
- threshold: 要”檢測” 一條直線所需最少的的曲線交點
- 標准Hough變換:
- srn and stn: 參數默認為0. 查缺OpenCV參考文獻來獲取更多信息.
- 統計Hough變換:
- minLinLength: 能組成一條直線的最少點的數量. 點數量不足的直線將被拋棄.
- maxLineGap: 能被認為在一條直線上的亮點的最大距離.
Hough圓變換
原理與線變換相似,在三維的“圓心點x, y還有半徑r”空間中找交點。
由於在三維空間的計算量大大增加的原因, 標准霍夫圓變化很難被應用到實際中,OpenCV實現的是一個比標准霍夫圓變換更為靈活的檢測方法: 霍夫梯度法, 也叫2-1霍夫變換(21HT)。它的原理依據是圓心一定是在圓上的每個點的模向量上, 這些圓上點模向量的交點就是圓心, 霍夫梯度法的第一步就是找到這些圓心, 這樣三維的累加平面就又轉化為二維累加平面. 第二部根據所有候選中心的邊緣非0像素對其的支持程度來確定半徑.
/// Convert it to gray cvtColor( src, src_gray, CV_BGR2GRAY ); /// Reduce the noise so we avoid false circle detection GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 ); vector<Vec3f> circles; /// Apply the Hough Transform to find the circles HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 ); /// Draw the circles detected for( size_t i = 0; i < circles.size(); i++ ) { Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); // circle center circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 ); // circle outline circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 ); }
- src_gray: 輸入圖像 (灰度圖)
- circles: 存儲下面三個參數:
集合的容器來表示每個檢測到的圓.
- CV_HOUGH_GRADIENT: 指定檢測方法. 現在OpenCV中只有霍夫梯度法
- dp = 1: 累加器圖像的反比分辨率
- min_dist = src_gray.rows/8: 檢測到圓心之間的最小距離
- param_1 = 200: Canny邊緣函數的高閾值
- param_2 = 100: 圓心檢測閾值.
- min_radius = 0: 能檢測到的最小圓半徑, 默認為0.
- max_radius = 0: 能檢測到的最大圓半徑, 默認為0