圖像處理之邊緣檢測
圖像處理之邊緣檢測
邊緣檢測
- 邊緣檢測步驟
- 邊緣檢測基礎知識
- 邊緣模型
- 邊緣的數學概念
- 一階導數和二階導數
- 作用
- 計算一階導數
- 計算二階導數
- 邊緣檢測算子
- Roberts算子
- Prewitt算子
- Sobel算子
- 空間濾波器
- 平滑空間濾波器
- 銳化空間濾波器
- 成熟先進的邊緣檢測技術
- Marr-Hildreth邊緣檢測器
- 坎尼邊緣檢測Canny
邊緣檢測步驟
①濾波:邊緣檢測算法主要是基于圖像強度的一階和二階導數,但導數的計算對噪聲很敏感,因此必須使用濾波器來改善與噪聲有關的邊緣檢測器的性能。需要指出,大多數濾波器在降低噪聲的同時也導致了邊緣強度的損失,因此,增強邊緣和降低噪聲之間需要折中。
②增強:增強邊緣的基礎是確定圖像各點鄰域強度的變化值。增強算法可以將鄰域(或局部)強度值有顯著變化的點突顯出來。邊緣增強一般是通過計算梯度幅值來完成的。
③檢測:在圖像中有許多點的梯度幅值比較大,而這些點在特定的應用領域中并不都是邊緣,所以應該用某種方法來確定哪些點是邊緣點。最簡單的邊緣檢測判據是梯度幅值閾值判據。
④定位:如果某一應用場合要求確定邊緣位置,則邊緣的位置可在子像素分辨率上來估計,邊緣的方位也可以被估計出來。在邊緣檢測算法中,前三個步驟用得十分普遍。這是因為大多數場合下,僅僅需要邊緣檢測器指出邊緣出現在圖像某一像素點的附近,而沒有必要指出邊緣的精確位置或方向。
邊緣檢測基礎知識
邊緣模型
邊緣的數學概念
邊緣:圖像上灰度級變化很快的點的集合\red{邊緣:圖像上灰度級變化很快的點的集合}邊緣:圖像上灰度級變化很快的點的集合
1.導數,連續函數上某點斜率,導數越大表示變化率越大,變化率越大的地方就越是“邊緣”,但是在計算機中不常用,因為在斜率90度的地方,導數無窮大,計算機很難表示這些無窮大的東西。
2.微分,連續函數上x變化了dx,導致y變化了dy,dy值越大表示變化的越大,那么計算整幅圖像的微分,dy的大小就是邊緣的強弱了。
微分與導數的關系:dy = f '(x) dx
在連續函數里叫微分,因為圖像是離散的,叫差分,和微分是一個意思,也是求變化率。
差分 :f '(x) = f(x + 1) - f(x),用后一項減前一項。
按先后排列 -f(x) + f(x + 1)
提出系數 [-1, 1] 作為濾波模板,跟原圖 f(x) 做卷積運算就可以檢測邊緣了
Sobel邊緣檢測算子\red{Sobel 邊緣檢測算子}Sobel邊緣檢測算子
用 f '(x) = f(x + 1) - f(x - 1) 近似計算一階差分。
排好序:[-1 * f(x-1),0 * f(x),1 * f(x+1)]
提出系數:[-1, 0, 1]
Prewitt邊緣檢測算子\red{Prewitt 邊緣檢測算子}Prewitt邊緣檢測算子
二維情況下
-1, 0, 1
-1, 0, 1
-1, 0, 1
Laplacian算子\red{Laplacian算子}Laplacian算子
拉普拉斯是用二階差分計算邊緣的,看連續函數的情況下
在一階微分圖中極大值或極小值處,認為是邊緣。
在二階微分圖中極大值和極小值之間的過 0 點,被認為是邊緣。
拉普拉斯算子推導:
一階差分:f '(x) = f(x) - f(x - 1)
二階差分:f '(x) = (f(x + 1) - f(x)) - (f(x) - f(x - 1))
化簡后:f '(x) = f(x - 1) - 2 f(x)) + f(x + 1)
提取前面的系數:[1, -2, 1]
二維的情況下,同理可得
f '(x, y) = -4 f(x, y) + f(x-1, y) + f(x+1, y) + f(x, y-1) + f(x, y+1)
提取各個系數
0, 1, 0
1, -4, 1
0, 1, 0
考慮兩個斜對角的情況
1, 1, 1
1, -8, 1
1, 1, 1
一階導數和二階導數
作用
- 一階導數通常在圖像中產生較粗的邊緣;
- 二階導數對精細細線,如細線、孤立點和噪聲有較強的響應;
- 二階導數在灰度斜坡和灰度臺階過渡出會產生雙邊響應;
- 二階導數的符號可以用于確定邊緣的過渡是從亮到暗還是從暗到亮
并且我們可以得出結論:一階導數的幅度可用于檢測圖像中的某個點是否存在一個邊緣,二階導數可以用于確定一個邊緣像素位于該邊緣的暗的一側還是亮的一側。
那么這是理想情況下的圖片邊緣,如果圖片有噪聲的話,其邊緣函數則為
微弱的可見噪聲對檢測邊緣所用的兩個關鍵導數的嚴重影響的這一事實,是我們應記住的一個重要問題。特別地,在類似于我們剛剛討論的水平的噪聲很可能存在的應用中,使用導數之前的圖像平滑處理是應該認真考慮的問題。
計算一階導數
許多邊緣檢測操作都是基于亮度的一階導數——這樣就得到了原始數據亮度的梯度。使用這個信息我們能夠在圖像的亮度梯度中搜尋峰值。如果 I(x) 表示點 x 的亮度,I′(x) 表示點 x 的一階導數(亮度梯度),這樣我們就會發現:
對于更高性能的圖像處理來說,一階導數能夠通過帶有掩碼的原始數據(1維)卷積計算得到。
計算二階導數
其它一些邊緣檢測操作是基于亮度的二階導數。這實質上是亮度梯度的變化率。在理想的連續變化情況下,在二階導數中檢測過零點將得到梯度中的局部最大值。另一方面,二階導數中的峰值檢測是邊線檢測,只要圖像操作使用一個合適的尺度表示。如上所述,邊線是雙重邊緣,這樣我們就可以在邊線的一邊看到一個亮度梯度,而在另一邊看到相反的梯度。這樣如果圖像中有邊線出現的話我們就能在亮度梯度上看到非常大的變化。為了找到這些邊線,我們可以在圖像亮度的二階導數中尋找過零點。如果 I(x) 表示點 x 的亮度,I′′(x) 表示點 x 亮度的二階導數,那么:
同樣許多算法也使用卷積掩碼快速處理圖像數據:
邊緣檢測算子
一階::Roberts Cross算子,Prewitt算子,Sobel算子, Kirsch算子,羅盤算子;
二階: Marr-Hildreth,在梯度方向的二階導數過零點,Canny算子,Laplacian算子。
Roberts算子
Prewitt算子
Sobel算子
檢測對角邊緣的Prewitt和Sobel模板
空間濾波器
平滑濾波器用于模糊處理和降低噪聲。模糊處理經常用于預處理任務中,例如在目標提取之前去除圖像中的一些瑣碎細節。
平滑空間濾波器
平滑線性濾波器
滑線性空間濾波器的輸出(響應)是包含在濾波器過濾核中像素的簡單平均值,也成為均值濾波。
這種處理的結果降低了圖像灰度的“尖銳”變化。
import cv2
import numpy as npimg = cv2.imread('images/cat.jpg')
cv2.imshow('images',img)
"""
blur—圖像均值平滑濾波
函數原型:blur(src, ksize, dst=None, anchor=None, borderType=None)
src:圖像矩陣
ksize:濾波窗口尺寸
"""
res = cv2.blur(img, (3, 3))
cv2.imshow('res', res)
"""
GaussianBlur—圖像高斯平滑濾波
函數原型:GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
src:圖像矩陣
ksize:濾波窗口尺寸
sigmaX:標準差"""
res2 = cv2.GaussianBlur(img,(3,3),0)
cv2.imshow('res2', res2)
cv2.waitKey()
非線性濾波器
這種濾波器的響應一濾波器過濾核中像素的排序為基礎,然后使用統計排序的結果決定代替中心像素的值。這一類最知名的濾波器是中值濾波器,正如其名,它是將像素領域內灰度的中值代替該像素的值。中值濾波器對處理脈沖噪聲非常有效。
import cv2
import numpy as npimg = cv2.imread('images/lena_1.jpg')
cv2.imshow('img', img)
"""
medianBlur—圖像中值濾波
函數原型:medianBlur(src, ksize, dst=None)
src:圖像矩陣
ksize:濾波窗口尺寸"""
res = cv2.medianBlur(img, 5)
cv2.imshow('res', res)
cv2.waitKey()
銳化空間濾波器
使用二階微分進行圖像銳化——拉普拉斯算子
由于拉普拉斯是一種微分算子,因此其應用強調的是圖像中灰度的突變,并不強調灰度級緩慢變化的區域。將原圖像和拉普拉斯圖像疊加在一起的簡單方法,可以復原背景特性并保持拉普拉斯銳化處理的效果。
如果所使用的定義具有負的中心系數,那么必須將原圖像減去拉普拉斯變換后的圖像而不是加上它,從而得到銳化的結果
import cv2
import numpy as np
img = cv2.imread('images/lena.jpg')
cv2.imshow('img',img)
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
#kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
dst = cv2.filter2D(img, -1, kernel=kernel)
cv2.imshow('res',dst)
cv2.waitKey(0)
使用一階微分對(非線性)圖像銳化——梯度
# coding=utf-8
import cv2img = cv2.imread("images/lena.jpg")
cv2.imshow('img',img)
"""
Sobel函數
Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)
前四個是必須的參數:
第一個參數是需要處理的圖像;
第二個參數是圖像的深度,-1表示采用的是與原圖像相同的深度。目標圖像的深度必須大于等于原圖像的深度;
第三和第四:dx和dy表示的是求導的階數,0表示這個方向上沒有求導,一般為0、1、2。
其后是可選的參數
"""
x = cv2.Sobel(img, -1, 0, 1)
cv2.imshow('res',x)
cv2.waitKey(0)
成熟先進的邊緣檢測技術
Marr-Hildreth邊緣檢測器
Marr-Hildreth算法小結
用一個G(x,y)取樣得到的nxn的高斯高斯低通濾波器對輸入圖像濾波。
-計算由第一步得到的圖像的拉普拉斯
-找到步驟2所有圖像的零交叉
-零交叉是Marr-Hildreth邊緣檢測方法的關鍵特征,實現簡單,并且通常能給出好的結果。
import numpy as np
import matplotlib.pyplot as plt
import cv2def edgesMarrHildreth(img, sigma):"""finds the edges using MarrHildreth edge detection method...:param im : input image:param sigma : sigma is the std-deviation and refers to the spread of gaussian:return:a binary edge image..."""size = int(2 * (np.ceil(3 * sigma)) + 1)x, y = np.meshgrid(np.arange(-size / 2 + 1, size / 2 + 1), np.arange(-size / 2 + 1, size / 2 + 1))normal = 1 / (2.0 * np.pi * sigma ** 2)kernel = ((x ** 2 + y ** 2 - (2.0 * sigma ** 2)) / sigma ** 4) * np.exp(-(x ** 2 + y ** 2) / (2.0 * sigma ** 2)) / normal # LoG filterkern_size = kernel.shape[0]log = np.zeros_like(img, dtype=float)# applying filterfor i in range(img.shape[0] - (kern_size - 1)):for j in range(img.shape[1] - (kern_size - 1)):window = img[i:i + kern_size, j:j + kern_size] * kernellog[i, j] = np.sum(window)log = log.astype(np.int64, copy=False)zero_crossing = np.zeros_like(log)# computing zero crossingfor i in range(log.shape[0] - (kern_size - 1)):for j in range(log.shape[1] - (kern_size - 1)):if log[i][j] == 0:if (log[i][j - 1] < 0 and log[i][j + 1] > 0) or (log[i][j - 1] < 0 and log[i][j + 1] < 0) or (log[i - 1][j] < 0 and log[i + 1][j] > 0) or (log[i - 1][j] > 0 and log[i + 1][j] < 0):zero_crossing[i][j] = 255if log[i][j] < 0:if (log[i][j - 1] > 0) or (log[i][j + 1] > 0) or (log[i - 1][j] > 0) or (log[i + 1][j] > 0):zero_crossing[i][j] = 255# plotting imagesfig = plt.figure()a = fig.add_subplot(1, 2, 1)imgplot = plt.imshow(log, cmap='gray')a.set_title('Laplacian of Gaussian')a = fig.add_subplot(1, 2, 2)imgplot = plt.imshow(zero_crossing, cmap='gray')string = 'Zero Crossing sigma = 'string += (str(sigma))a.set_title(string)plt.show()return zero_crossingimg = cv2.imread('images/17.jpg',0)
img = edgesMarrHildreth(img,4)
坎尼邊緣檢測Canny
Canny目標
-低錯誤率。所有邊緣都應被找到,并且應該沒有偽相應,也就是檢測到的邊緣必須盡可能是真是的邊緣
-邊緣點應被很好的定位。已定位邊緣必須盡可能接近真實邊緣。也就是由檢測器標記為邊緣的點和真實邊緣的中心之間的距離應該最小
-單一的邊緣點響應。對于真實的邊緣點,檢測器僅應返回一個點。也就是真是邊緣周圍的局部最大數應該是最小的。這意味著在僅存一個單一邊緣點到額位置,檢測器不應指出多個邊緣像素。
Canny算法步驟
1、用一個高斯濾波器平滑輸入圖像
2、計算梯度幅度圖像和角度圖像
3、對梯度幅度圖像應用非最大抑制
4、用雙閾值處理和連接分析來檢測并連接邊緣(這相對Marr-Hildreth優化了邊緣的連接使得檢測的邊緣更加完整)
# coding=utf-8
import cv2
import numpy as npimg = cv2.imread("images/17.jpg", 0)
cv2.imshow('img',img)
img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
可以看到,Canny算法明顯提取邊緣的效果要優于Marr-Hildreth算法,對邊緣的連接也做的更好。