銳化處理的主要目的是突出灰度的過度部分,所以提取圖像的邊緣信息對圖像的銳化來說非常重要。我們可以借助空間微分的定義來實現這一目的。定義圖像的一階微分的差分形式為
從定義中就可以看出圖像一階微分的結果在圖像灰度變化緩慢的區域數值較小,而在圖像灰度變化劇烈的區域數值較大,所以這一運算在一定程度可以反應圖像灰度的變化情況。
對圖像的一階微分結果再次微分可以得到圖像的二階微分形式
二階微分可以反應圖像像素變化率的變化,所以對灰度均勻變化的區域沒有影響,而對灰度驟然變化的區域反應效果明顯。
由於數字圖像的邊緣常常存在類似的斜坡過度,所以一階微分時產生較粗的邊緣,而二階微分則會產生以零分開的雙邊緣,所以在增強細節方面二階微分要比一階微分效果好得多。
1.拉普拉斯算子
最簡單的實現二階微分的方法就是拉普拉斯算子,仿照一維二階微分,二維圖像的拉普拉斯算子其定義為
將前面提到的微分形式帶入可以得到離散化的算子
其對應的拉普拉斯模板為
對角線方向也可以加入拉普拉斯算子,加入后算子模板變為
進行圖像銳化時需要將拉普拉斯模板計算得的圖像加到原圖像當中,所以最終銳化公式為
如果濾波器模板中心系數為負,則c值取負,反之亦然。這是因為當模板中心系數為負時,如果計算結果為正,則說明中間像素的灰度小於旁邊像素的灰度,要想使中間像素更為突出,則需要則需要減小中間的像素值,即在原始圖像中符號為正的計算結果。當計算結果為負時道理相同。
由上述原理就可以自定義拉普拉斯濾波器的計算函數了,該函數的輸出為拉普拉斯算子對原圖像的濾波,也就是提取的邊緣信息。函數主體如下
import cv2 as cv import matplotlib.pyplot as plt import math import numpy as np original_image_test1 = cv.imread('test1.pgm',0) # 用原始圖像減去拉普拉斯模板直接計算得到的邊緣信息 def my_laplace_result_add(image, model): result = image - model for i in range(result.shape[0]): for j in range(result.shape[1]): if result[i][j] > 255: result[i][j] = 255 if result[i][j] < 0: result[i][j] = 0 return result def my_laplace_sharpen(image, my_type = 'small'): result = np.zeros(image.shape,dtype=np.int64) # 確定拉普拉斯模板的形式 if my_type == 'small': my_model = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]]) else: my_model = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) # 計算每個像素點在經過高斯模板變換后的值 for i in range(image.shape[0]): for j in range(image.shape[1]): for ii in range(3): for jj in range(3): # 條件語句為判斷模板對應的值是否超出邊界 if (i+ii-1)<0 or (i+ii-1)>=image.shape[0]: pass elif (j+jj-1)<0 or (j+jj-1)>=image.shape[1]: pass else: result[i][j] += image[i+ii-1][j+jj-1] * my_model[ii][jj] return result # 將計算結果限制為正值 def my_show_edge(model): # 這里一定要用copy函數,不然會改變原來數組的值 mid_model = model.copy() for i in range(mid_model.shape[0]): for j in range(mid_model.shape[1]): if mid_model[i][j] < 0: mid_model[i][j] = 0 if mid_model[i][j] > 255: mid_model[i][j] = 255 return mid_model # 調用自定義函數 result = my_laplace_sharpen(original_image_test1, my_type='big') # 繪制結果 fig = plt.figure() fig.add_subplot(131) plt.title('original') plt.imshow(original_image_test1) fig.add_subplot(132) plt.title('edge') plt.imshow(my_show_edge(result)) fig.add_subplot(133) plt.title('sharpen') plt.imshow(my_laplace_result_add(original_image_test1,result)) plt.show()
程序運行得結果為
也可以調用cv2中的庫函數進行拉普拉斯濾波,調用程序如下
import cv2 as cv from matplotlib import pyplot as plt # 用原始圖像減去拉普拉斯模板直接計算得到的邊緣信息 def my_laplace_result_add(image, model): result = image - model for i in range(result.shape[0]): for j in range(result.shape[1]): if result[i][j] > 255: result[i][j] = 255 if result[i][j] < 0: result[i][j] = 0 return result original_image_test1 = cv.imread('test1.pgm',0) # 函數中的參數ddepth為輸出圖像的深度,也就是每個像素點是多少位的。 # CV_16S表示16位有符號數 computer_result = cv.Laplacian(original_image_test1,ksize=3,ddepth=cv.CV_16S) plt.imshow(my_laplace_result_add(original_image_test1, computer_result)) plt.show()
運行結果如圖
可以看出庫函數的運行結果與自定義函數基本一致。
網絡上由另外一種對拉普拉斯模板計算結果的處理方法,就是將計算結果取絕對值,再用源圖像減去取絕對值的結果。用這種方式計算的程序為
import cv2 as cv import matplotlib.pyplot as plt import numpy as np original_image_test1 = cv.imread('test1.pgm',0) def my_laplace_result_add_abs(image, model): for i in range(model.shape[0]): for j in range(model.shape[1]): if model[i][j] < 0: model[i][j] = 0 if model[i][j] > 255: model[i][j] = 255 result = image - model for i in range(result.shape[0]): for j in range(result.shape[1]): if result[i][j] > 255: result[i][j] = 255 if result[i][j] < 0: result[i][j] = 0 return result # 調用自定義函數 result = my_laplace_sharpen(original_image_test1, my_type='big') # 繪制結果 fig = plt.figure() fig.add_subplot(121) plt.title('original') plt.imshow(original_image_test1) fig.add_subplot(122) plt.title('sharpen') plt.imshow(my_laplace_result_add_abs(original_image_test1,result)) plt.show()
運行結果為
雖然這種算法機理不能確定正確,但是效果還可以。
2.非銳化掩蔽
除了通過二階微分的形式提取到圖像邊緣信息,也可以通過原圖像減去一個圖像的非銳化版本來提取邊緣信息,這就是非銳化掩蔽的原理,其處理過程為
- 將原圖像進行模糊處理
- 用原圖像減去模糊圖像得到非銳化版本
- 將非銳化版本按照一定比例系數加到原圖像中,得到銳化圖像。
進行模糊處理時可以使用高斯濾波器等低通濾波器。非銳化掩蔽的自定義函數為
import cv2 as cv import matplotlib.pyplot as plt import numpy as np # 圖像銳化函數 def my_not_sharpen(image, k, blur_size=(5, 5), blured_sigma=3): blured_image = cv.GaussianBlur(image, blur_size, blured_sigma) # model = image - blured_image # 注意不能直接用減法,對於圖像格式結果為負時會自動加上256 model = np.zeros(image.shape, dtype=np.int64) for i in range(image.shape[0]): for j in range(image.shape[1]): model[i][j] = int(image[i][j]) - int(blured_image[i][j]) # 兩個矩陣中有一個不是圖像格式,則結果就不會轉換為圖像格式,如果不放心可以測試一下 sharpen_image = image + k * model # cv.convertScaleAbs計算公式為dst = uchar(|alpha*src+b|) 所以超過255的值會變成255,小於0的值會取絕對值 sharpen_image = cv.convertScaleAbs(sharpen_image) return sharpen_image # 提取圖像邊界信息函數 def my_get_model(image, blur_size=(5, 5), blured_sigma=3): blured_image = cv.GaussianBlur(image, blur_size, blured_sigma) model = np.zeros(image.shape, dtype=np.int64) for i in range(image.shape[0]): for j in range(image.shape[1]): model[i][j] = int(image[i][j]) - int(blured_image[i][j]) model = cv.convertScaleAbs(model) return model if __name__ == '__main__': '''讀取原始圖片''' original_image_test4 = cv.imread('test4.tif', 0) # 獲得圖像邊界信息 edge_image_test4 = my_get_model(original_image_test4) # 獲得銳化圖像 sharpen_image_test4_3 = my_not_sharpen(original_image_test4, 3) # 顯示結果 plt.subplot(131) plt.title('original') plt.imshow(original_image_test4) plt.subplot(132) plt.title('edge') plt.imshow(edge_image_test4) plt.subplot(133) plt.title('sharpen') plt.imshow(sharpen_image_test4_3) plt.show()
用自定義函數進行圖像邊界提取和圖像增強的效果如下
3.梯度
圖像處理的一階微分使用梯度幅值來實現的,二元函數的梯度定義為
由於梯度是多維的,梯度本身並不能作為圖像邊緣的提取值,所以常用梯度的絕對值和或平方和作為幅度值來反應邊緣情況。
我們可以像前面拉普拉斯算子一樣定義一個3×3模板的離散梯度形式
其對應的圖像模板分別為
通過模板計算得到梯度值后,再將x、y方向的梯度絕對值相加或平方和相加,就得到了圖像邊緣的幅度值,再將提取到的幅度值圖像加到原圖像上,就得到了銳化后的圖像。梯度方法的自定義函數為
import cv2 as cv import matplotlib.pyplot as plt import numpy as np # 輸入圖像,輸出提取的邊緣信息 def my_sobel_sharpen(image): result_x = np.zeros(image.shape,dtype=np.int64) result_y = np.zeros(image.shape, dtype=np.int64) result = np.zeros(image.shape, dtype=np.int64) # 確定拉普拉斯模板的形式 my_model_x = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) my_model_y = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # 計算每個像素點在經過高斯模板變換后的值 for i in range(image.shape[0]): for j in range(image.shape[1]): for ii in range(3): for jj in range(3): # 條件語句為判斷模板對應的值是否超出邊界 if (i+ii-1)<0 or (i+ii-1)>=image.shape[0]: pass elif (j+jj-1)<0 or (j+jj-1)>=image.shape[1]: pass else: result_x[i][j] += image[i+ii-1][j+jj-1] * my_model_x[ii][jj] result_y[i][j] += image[i+ii-1][j+jj-1] * my_model_y[ii][jj] result[i][j] = abs(result_x[i][j]) + abs(result_y[i][j]) if result[i][j] > 255: result[i][j] = 255 return result # 將邊緣信息按一定比例加到原始圖像上 def my_result_add(image, model, k): result = image + k * model for i in range(result.shape[0]): for j in range(result.shape[1]): if result[i][j] > 255: result[i][j] = 255 if result[i][j] < 0: result[i][j] = 0 return result if __name__ == '__main__': '''讀取原始圖片''' original_image_test4 = cv.imread('test4.tif', 0) # 獲得圖像邊界信息 edge_image_test4 = my_sobel_sharpen(original_image_test4) # 獲得銳化圖像 sharpen_image_test4 = my_result_add(original_image_test4, edge_image_test4, -0.5) # 顯示結果 plt.subplot(131) plt.title('original') plt.imshow(original_image_test4) plt.subplot(132) plt.title('edge') plt.imshow(edge_image_test4) plt.subplot(133) plt.title('sharpen') plt.imshow(sharpen_image_test4) plt.show()
用梯度進行圖像增強的效果為
4.Canny邊緣檢測
Canny算法是一個綜合類的算法,它包含多個階段,每個階段基本上都可以用前面提到的方法實現。其具體流程如下 。
(1) 降低噪聲
邊緣檢測很容易受噪聲聲的影響,所以在檢測之前先做降噪處理是很有必要的。一般可以用5×5的高斯濾波器進行降噪處理。
(2) 尋找圖像的強度梯度
然后對平滑后的圖像進行水平方向和垂直方向的Sobel核濾波,得到水平方向(Gx)和垂直方向(Gy)的一階導數。這兩幅圖像中,我們可以發現每個像素的邊緣梯度和方向如下:
梯度方向總是垂直於邊緣,它是四角之一,代表垂直、水平和兩個對角線方向。
(3) 非極大抑制
得到梯度大小和方向后,對圖像進行全面掃描,去除非邊界點。在每個像素處,看這個點的梯度是不是周圍具有相同梯 度方向的點中最大的最后的到的會是一個細邊的邊界。之后閾值
(4) 滯后閾值
現在要確定那些邊界才是真正的邊界,為此,我們需要兩個閾值,minVal和maxVal。任何強度梯度大於maxVal的邊都肯定是邊,小於minVal的邊肯定是非邊,所以丟棄。位於這兩個閾值之間的根據它們的連接性對邊緣或非邊緣進行分類(如果介於兩者之間的話,就要看這個點是 否與某個被確定為真正的邊界點相連,如果是就認為它也是邊界點,如果不是就拋棄)
調用cv中canny庫函數的程序示例
import cv2 as cv import matplotlib.pyplot as plt import numpy as np original_image_test4 = cv.imread('test2.tif', 0) # threshold為閾值,L2gradient為2范數,如果不指明則默認使用1范數 edge_image_test4 = cv.Canny(original_image_test4, threshold1=100, threshold2=200, L2gradient=0) plt.subplot(121) plt.title('original') plt.imshow(original_image_test4) plt.subplot(122) plt.title('edge') plt.imshow(edge_image_test4) plt.show()
效果如下