前文傳送門:
「Python 影像處理 OpenCV (2):畫素處理與 Numpy 操作以及 Matplotlib 顯示影像」
「Python 影像處理 OpenCV (3):影像屬性、影像感興趣 ROI 區域及通道處理」
「Python 影像處理 OpenCV (4):影像算數運算以及修改顏色空間」
「Python 影像處理 OpenCV (5):影像的幾何變換」
「Python 影像處理 OpenCV (6):影像的閾值處理」
「Python 影像處理 OpenCV (7):影像平滑(濾波)處理」
「Python 影像處理 OpenCV (8):影像腐蝕與影像膨脹」
「Python 影像處理 OpenCV (9):影像處理形態學開運算、閉運算以及梯度運算」
「Python 影像處理 OpenCV (10):影像處理形態學之頂帽運算與黑帽運算」
「Python 影像處理 OpenCV (11):Canny 運算元邊緣檢測技術」
引言
前文介紹了 Canny 運算元邊緣檢測,本篇繼續介紹 Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元等常用邊緣檢測技術。
Roberts 運算元
Roberts 運算元,又稱羅伯茨運算元,是一種最簡單的運算元,是一種利用區域性差分運算元尋找邊緣的運算元。他採用對角線方向相鄰兩象素之差近似梯度幅值檢測邊緣。檢測垂直邊緣的效果好於斜向邊緣,定位精度高,對噪聲敏感,無法抑制噪聲的影響。
1963年, Roberts 提出了這種尋找邊緣的運算元。 Roberts 邊緣運算元是一個 2x2 的模版,採用的是對角方向相鄰的兩個畫素之差。
Roberts 運算元的模板分為水平方向和垂直方向,如下所示,從其模板可以看出, Roberts 運算元能較好的增強正負 45 度的影像邊緣。
Roberts 運算元在水平方向和垂直方向的計算公式如下:
Roberts 運算元畫素的最終計算公式如下:
今天的公式都是小學生水平,千萬別再說看不懂了。
實現 Roberts 運算元,我們主要通過 OpenCV 中的 filter2D()
這個函式,這個函式的主要功能是通過卷積核實現對影像的卷積運算:
def filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)
- src: 輸入影像
- ddepth: 目標影像所需的深度
- kernel: 卷積核
接下來開始寫程式碼,首先是影像的讀取,並把這個影像轉化成灰度影像,這個沒啥好說的:
# 讀取影像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化處理影像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
然後是使用 Numpy 構建卷積核,並對灰度影像在 x 和 y 的方向上做一次卷積運算:
# Roberts 運算元
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv.filter2D(grayImage, cv.CV_16S, kernelx)
y = cv.filter2D(grayImage, cv.CV_16S, kernely)
注意:在進行了 Roberts 運算元處理之後,還需要呼叫convertScaleAbs()函式計算絕對值,並將影像轉換為8點陣圖進行顯示,然後才能進行影像融合:
# 轉 uint8 ,影像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
最後是通過 pyplot 將影像顯示出來:
# 顯示圖形
titles = ['原始影像', 'Roberts運算元']
images = [rgb_img, Roberts]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
最終結果如下:
Prewitt 運算元
Prewitt 運算元是一種一階微分運算元的邊緣檢測,利用畫素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分偽邊緣,對噪聲具有平滑作用。
由於 Prewitt 運算元採用 3 * 3 模板對區域內的畫素值進行計算,而 Robert 運算元的模板為 2 * 2 ,故 Prewitt 運算元的邊緣檢測結果在水平方向和垂直方向均比 Robert 運算元更加明顯。Prewitt運算元適合用來識別噪聲較多、灰度漸變的影像。
Prewitt 運算元的模版如下:
在程式碼實現上, Prewitt 運算元的實現過程與 Roberts 運算元比較相似,我就不多介紹,直接貼程式碼了:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 讀取影像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化處理影像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Prewitt 運算元
kernelx = np.array([[1,1,1],[0,0,0],[-1,-1,-1]],dtype=int)
kernely = np.array([[-1,0,1],[-1,0,1],[-1,0,1]],dtype=int)
x = cv.filter2D(grayImage, cv.CV_16S, kernelx)
y = cv.filter2D(grayImage, cv.CV_16S, kernely)
# 轉 uint8 ,影像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Prewitt = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']
# 顯示圖形
titles = ['原始影像', 'Prewitt 運算元']
images = [rgb_img, Prewitt]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
從結果上來看, Prewitt 運算元影像銳化提取的邊緣輪廓,其效果圖的邊緣檢測結果比 Robert 運算元更加明顯。
Sobel 運算元
Sobel 運算元的中文名稱是索貝爾運算元,是一種用於邊緣檢測的離散微分運算元,它結合了高斯平滑和微分求導。
Sobel 運算元在 Prewitt 運算元的基礎上增加了權重的概念,認為相鄰點的距離遠近對當前畫素點的影響是不同的,距離越近的畫素點對應當前畫素的影響越大,從而實現影像銳化並突出邊緣輪廓。
演算法模版如下:
Sobel 運算元根據畫素點上下、左右鄰點灰度加權差,在邊緣處達到極值這一現象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向資訊。因為 Sobel 運算元結合了高斯平滑和微分求導(分化),因此結果會具有更多的抗噪性,當對精度要求不是很高時, Sobel 運算元是一種較為常用的邊緣檢測方法。
Sobel 運算元近似梯度的大小的計算公式如下:
梯度方向的計算公式如下:
如果以上的角度 θ 等於零,即代表影像該處擁有縱向邊緣,左方較右方暗。
在 Python 中,為我們提供了 Sobel()
函式進行運算,整體處理過程和前面的類似,程式碼如下:
import cv2 as cv
import matplotlib.pyplot as plt
# 讀取影像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化處理影像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Sobel 運算元
x = cv.Sobel(grayImage, cv.CV_16S, 1, 0)
y = cv.Sobel(grayImage, cv.CV_16S, 0, 1)
# 轉 uint8 ,影像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Sobel = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']
# 顯示圖形
titles = ['原始影像', 'Sobel 運算元']
images = [rgb_img, Sobel]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
Laplacian 運算元
拉普拉斯( Laplacian )運算元是 n 維歐幾里德空間中的一個二階微分運算元,常用於影像增強領域和邊緣提取。
Laplacian 運算元的核心思想:判斷影像中心畫素灰度值與它周圍其他畫素的灰度值,如果中心畫素的灰度更高,則提升中心畫素的灰度;反之降低中心畫素的灰度,從而實現影像銳化操作。
在實現過程中, Laplacian 運算元通過對鄰域中心畫素的四方向或八方向求梯度,再將梯度相加起來判斷中心畫素灰度與鄰域內其他畫素灰度的關係,最後通過梯度運算的結果對畫素灰度進行調整。
Laplacian 運算元分為四鄰域和八鄰域,四鄰域是對鄰域中心畫素的四方向求梯度,八鄰域是對八方向求梯度。
四鄰域模板如下:
八鄰域模板如下:
通過模板可以發現,當鄰域內畫素灰度相同時,模板的卷積運算結果為0;當中心畫素灰度高於鄰域內其他畫素的平均灰度時,模板的卷積運算結果為正數;當中心畫素的灰度低於鄰域內其他畫素的平均灰度時,模板的卷積為負數。對卷積運算的結果用適當的衰弱因子處理並加在原中心畫素上,就可以實現影像的銳化處理。
在 OpenCV 中, Laplacian 運算元被封裝在 Laplacian()
函式中,其主要是利用Sobel運算元的運算,通過加上 Sobel 運算元運算出的影像 x 方向和 y 方向上的導數,得到輸入影像的影像銳化結果。
import cv2 as cv
import matplotlib.pyplot as plt
# 讀取影像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化處理影像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Laplacian
dst = cv.Laplacian(grayImage, cv.CV_16S, ksize = 3)
Laplacian = cv.convertScaleAbs(dst)
# 用來正常顯示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']
# 顯示圖形
titles = ['原始影像', 'Laplacian 運算元']
images = [rgb_img, Laplacian]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
最後
邊緣檢測演算法主要是基於影像強度的一階和二階導數,但導數通常對噪聲很敏感,因此需要採用濾波器來過濾噪聲,並呼叫影像增強或閾值化演算法進行處理,最後再進行邊緣檢測。
最後我先使用高斯濾波去噪之後,再進行邊緣檢測:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 讀取影像
img = cv.imread('maliao.jpg')
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
# 灰度化處理影像
gray_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 高斯濾波
gaussian_blur = cv.GaussianBlur(gray_image, (3, 3), 0)
# Roberts 運算元
kernelx = np.array([[-1, 0], [0, 1]], dtype = int)
kernely = np.array([[0, -1], [1, 0]], dtype = int)
x = cv.filter2D(gaussian_blur, cv.CV_16S, kernelx)
y = cv.filter2D(gaussian_blur, cv.CV_16S, kernely)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# Prewitt 運算元
kernelx = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
kernely = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
x = cv.filter2D(gaussian_blur, cv.CV_16S, kernelx)
y = cv.filter2D(gaussian_blur, cv.CV_16S, kernely)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Prewitt = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# Sobel 運算元
x = cv.Sobel(gaussian_blur, cv.CV_16S, 1, 0)
y = cv.Sobel(gaussian_blur, cv.CV_16S, 0, 1)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Sobel = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# 拉普拉斯演算法
dst = cv.Laplacian(gaussian_blur, cv.CV_16S, ksize = 3)
Laplacian = cv.convertScaleAbs(dst)
# 展示影像
titles = ['Source Image', 'Gaussian Image', 'Roberts Image',
'Prewitt Image','Sobel Image', 'Laplacian Image']
images = [rgb_img, gaussian_blur, Roberts, Prewitt, Sobel, Laplacian]
for i in np.arange(6):
plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
示例程式碼
如果有需要獲取原始碼的同學可以在公眾號回覆「OpenCV」進行獲取。