轉自:http://www.cnblogs.com/heleifz/archive/2013/10/27/3391225.html
這個函數matlab中處理的時候,檢測二維碼時能夠去除很多雜點,其調用的邊緣檢測算法中就包含這個方法,留着備用吧.該算法的原理如下:
通過對梯度圖進行非極大值抑制,得到梯度圖的掩膜圖,然后該圖上為1並且原前景點的梯度值大於設置的閾值時,才會保留。
前段時間做了一個車牌檢測識別的項目,我的任務是將MATLAB中的算法移植成C++代碼。在車牌區域提取的過程中,用到了水平方向的Sobel算子檢測垂直邊緣,一開始我直接把MATLAB中的
bw = edge(I, 'sobel', 'vertical'); |
語句改寫成OpenCV中的
cv::Mat sobel_kernel = (cv::Mat_<float>(3,3) << -0.125, 0, 0.125, -0.25, 0, 0.25, -0.125, 0, 0.125); cv::Mat edges; cv::filter2D(gray_img, edges, gray_img.type(), sobel_kernel); |
之后,整個檢測算法產生了一些意想不到的輸出。追根溯源,我發現問題的根源就是在這個邊緣檢測步驟里:MATLAB的edge函數產生的是一個細化的二值邊緣,而OpenCV中輸出的是模板卷積后的浮點型的梯度值,若直接對其閾值化,將產生一個粗邊緣,如下圖所示(從左到右分別為edge函數輸出邊緣,OpenCV中直接使用Sobel算子及閾值化產生的邊緣,原圖)
研究了一下edge的實現代碼,我發現這么一個函數
computeEdgesWithThinning函數實現了非極大值抑制和閾值化的效果,這個函數的實現方式已經被MATLAB封裝,無法查看。一番波折之后,我模擬出一個效果基本一致的細化及閾值化算法(默認的閾值T為4乘以每個點梯度的模的平方的均值):
設 M(i, j) 為某點的梯度的模的平方 M(i, j) 大於閾值 T 且: 若 M(i, j) > M(i - 1, j) 且 M(i, j) > M(i + 1, j) 或者 M(i, j) > M(i, j - 1) 且 M(i, j) > M(i, j + 1) 則將輸出邊緣圖像的 (i, j) 位置設為 1
簡要地說,就是判斷一個點的梯度是否是水平或者垂直方向的上的局部極大值,當然,梯度值首先得大於閾值。經過實驗,加上這個非極大值抑制的步驟后,輸出圖片與MATLAB的edge函數產生的邊緣圖片基本一致,下面整個邊緣檢測加細化的MATLAB實現代碼(只檢測垂直的邊緣)
function e = sobel_thin(img) op = fspecial('sobel') / 8; x_mask = op'; a = im2double(img); scale = 4; bx = imfilter(a,x_mask,'replicate'); b = bx.*bx; cutoff = 4 * mean2(b); [m, n] = size(b); for r = 1 : m for c=1 : n if ((c - 1) < 1) b1 = true; else b1 = (b(r, c - 1) <= b(r, c)); end if (c + 1) > n b2 = true; else b2 = (b(r, c) > b(r, c + 1)); end if ((r - 1) < 1) b3 = true; else b3 = (b(r - 1, c) <= b(r, c)); end if ((r+1) > m) b4 = true; else b4 = (b(r, c) > b(r + 1, c)); end e(r, c) = (b(r, c) > cutoff) & ((b1 & b2) | (b3 & b4)); end end |