今天的想法是用Canny邊緣檢測算法,建立一種可以勾畫出圖像上任何物體的邊緣的算法。
首先,我們來描述一下Canny邊緣檢測器:
Canny邊緣檢測算子是一種邊緣檢測算子,它采用多級算法檢測圖像中廣泛的邊緣。它是由John F. Canny在1986年開發的。Canny還提出了邊緣檢測的計算理論,解釋了該技術的工作原理。
Canny邊緣檢測算法由5個步驟組成:
- 降噪;
- 梯度計算;
- 非最大抑制;
- 雙阈值;
- 滞後邊緣跟蹤。
應用這些步驟後,您将能夠獲得以下結果:
左側的原始圖像 - 右側的已處理圖像
最後值得一提的是,該算法是基于灰度圖像的。因此,在進行上述步驟之前,首先要将圖像轉換為灰度。
降噪由于場景背後涉及的數學主要基于導數(參見步驟2:梯度計算),邊緣檢測結果對圖像噪聲高度敏感。
消除圖像噪聲的一種方法是使用高斯模糊平滑圖像。為此,圖像卷積技術應用高斯核(3x3, 5x5, 7x7等)。核大小取決于預期的模糊效果。基本上,核越小,模糊就越不明顯。在我們的例子中,我們将使用一個5×5的高斯核函數。
大小為(2k 1)×(2k 1)的高斯濾波核的方程為:
高斯濾波器核方程
用于生成Gaussian 5x5内核的Python代碼:
import numpy as np def gaussian_kernel(size, sigma=1): size = int(size) // 2 x, y = np.mgrid[-size:size 1, -size:size 1] normal = 1 / (2.0 * np.pi * sigma**2) g = np.exp(-((x**2 y**2) / (2.0*sigma**2))) * normal return g
應用高斯模糊後,我們得到以下結果:
原始圖像(左) - 帶有高斯濾波器的模糊圖像(sigma = 1.4,核大小為5x5)梯度計算梯度計算步驟通過使用邊緣檢測算子計算圖像的梯度來檢測邊緣強度和方向。
邊緣對應于像素強度的變化。要檢測它,最簡單的方法是應用filters,在兩個方向上突出這種強度變化:水平(x)和垂直(y)
當平滑圖像時,計算導數Ix和Iy。它可以通過分别用Sobel kernels Kx和Ky分别卷積I來實現:
Sobel filters用于兩個方向(水平和垂直)
然後,梯度的幅度G和斜率θ計算如下:
梯度強度和邊緣方向
下面是Sobel濾鏡應用于圖像的方法,以及如何獲得強度和邊緣方向矩陣,Python代碼如下:
from scipy import ndimage def sobel_filters(img): Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32) Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32) Ix = ndimage.filters.convolve(img, Kx) Iy = ndimage.filters.convolve(img, Ky) G = np.hypot(Ix, Iy) G = G / G.max() * 255 theta = np.arctan2(Iy, Ix) return (G, theta)
模糊圖像(左) - 梯度強度(右)
結果幾乎是預期的,我們可以看到,一些邊緣是厚的,另一些是薄的。非最大抑制步驟将有助于我們減輕厚的。
此外,梯度強度水平在0到255之間,這是不均勻的。最終結果的邊緣應具有相同的強度(即白色像素= 255)。
非最大抑制理想情況下,最終的圖像應該有細邊。因此,我們必須執行非最大抑制以使邊緣變細。
原理很簡單:算法遍曆梯度強度矩陣上的所有點,并找到邊緣方向上具有最大值的像素。
讓我們舉一個簡單的例子:
上圖左上角的紅色框表示被處理的梯度強度矩陣的一個強度像素。對應的邊緣方向由橙色箭頭表示,其角度為-pi弧度( /- 180度)。
聚焦左上角的紅色方塊像素
邊緣方向是橙色虛線(從左到右水平)。該算法的目的是檢查在相同方向上的像素是否比被處理的像素強度高或低。在上面的例子中,正在處理像素(i,j),相同方向上的像素用藍色(i, j-1)和(i, j 1)高亮顯示。如果這兩個像素中的一個比正在處理的那個更強,那麼隻保留更強的那個。像素(i, j-1)似乎更強,因為它是白色的(值255)。因此,當前像素(i, j)的強度值設置為0。如果邊緣方向上沒有具有更強值的像素,則保留當前像素的值。
現在讓我們關注另一個例子:
在這種情況下,方向是橙色虛線對角線。因此,該方向上最強的像素是像素(i-1,j 1)。
讓我們總結一下。每個像素有2個主要标準(弧度的邊緣方向和像素強度(0-255之間))。基于這些輸入,非最大抑制步驟是:
- 創建一個初始化為0的矩陣,該矩陣與原始梯度強度矩陣的大小相同;
- 根據角度矩陣的角度值識别邊緣方向;
- 檢查相同方向的像素是否具有比當前處理的像素更高的強度;
- 返回使用非最大抑制算法處理的圖像。
Python代碼如下:
def non_max_suppression(img, D): M, N = img.shape Z = np.zeros((M,N), dtype=np.int32) angle = D * 180. / np.pi angle[angle < 0] = 180 for i in range(1,M-1): for j in range(1,N-1): try: q = 255 r = 255 #angle 0 if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180): q = img[i, j 1] r = img[i, j-1] #angle 45 elif (22.5 <= angle[i,j] < 67.5): q = img[i 1, j-1] r = img[i-1, j 1] #angle 90 elif (67.5 <= angle[i,j] < 112.5): q = img[i 1, j] r = img[i-1, j] #angle 135 elif (112.5 <= angle[i,j] < 157.5): q = img[i-1, j-1] r = img[i 1, j 1] if (img[i,j] >= q) and (img[i,j] >= r): Z[i,j] = img[i,j] else: Z[i,j] = 0 except IndexError as e: pass return Z
結果是相同的圖像,但邊緣更薄。然而,我們仍然可以注意到邊緣亮度的一些變化:一些像素似乎比其他像素更亮,我們将嘗試在最後兩個步驟中彌補這一缺陷。
非最大抑制的結果
雙阈值雙阈值步驟旨在識别3種像素:強,弱和不相關:
- 強像素是指像素的強度如此之高,以至于我們确信它們有助于最終的邊緣。
- 弱像素是具有不足以被視為強的強度值的像素,但是還不足以被認為與邊緣檢測不相關。
- 其他像素被認為與邊緣無關。
現在你可以看到這兩個阈值代表什麼:
- 高阈值用于識别強像素(強度高于高阈值)
- 低阈值用于識别不相關的像素(強度低于低阈值)
- 具有兩個阈值之間的強度的所有像素被标記為弱,滞後機制(下一步驟)将幫助我們識别可被視為強的那些和被認為是不相關的那些。
def threshold(img, lowThresholdRatio=0.05, highThresholdRatio=0.09): highThreshold = img.max() * highThresholdRatio; lowThreshold = highThreshold * lowThresholdRatio; M, N = img.shape res = np.zeros((M,N), dtype=np.int32) weak = np.int32(25) strong = np.int32(255) strong_i, strong_j = np.where(img >= highThreshold) zeros_i, zeros_j = np.where(img < lowThreshold) weak_i, weak_j = np.where((img <= highThreshold) & (img >= lowThreshold)) res[strong_i, strong_j] = strong res[weak_i, weak_j] = weak return (res, weak, strong)
此步驟的結果是隻有2個像素強度值(強弱)的圖像:
非最大抑制圖像(左) - 阈值結果(右)
滞後邊緣跟蹤根據阈值結果,當且僅當被處理像素周圍至少有一個像素為強像素時,滞後由弱像素轉換為強像素構成,如下所述:
def hysteresis(img, weak, strong=255): M, N = img.shape for i in range(1, M-1): for j in range(1, N-1): if (img[i,j] == weak): try: if ((img[i 1, j-1] == strong) or (img[i 1, j] == strong) or (img[i 1, j 1] == strong) or (img[i, j-1] == strong) or (img[i, j 1] == strong) or (img[i-1, j-1] == strong) or (img[i-1, j] == strong) or (img[i-1, j 1] == strong)): img[i, j] = strong else: img[i, j] = 0 except IndexError as e: pass return img
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!