1. 程式人生 > 實用技巧 >Python 影象處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術

Python 影象處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術

前文傳送門:

「Python 影象處理 OpenCV (1):入門」

「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 度的影象邊緣。

$$

dx = \left[

\begin{matrix}

-1 & 0\

0 & 1 \

\end{matrix}

\right]

dy = \left[

\begin{matrix}

0 & -1\

1 & 0 \

\end{matrix}

\right]

$$

Roberts 運算元在水平方向和垂直方向的計算公式如下:

$$

d_x(i, j) = f(i + 1, j + 1) - f(i, j)

$$

$$

d_y(i, j) = f(i, j + 1) - f(i + 1, j)

$$

Roberts 運算元畫素的最終計算公式如下:

$$

S = \sqrt{d_x(i, j)^2 + d_y(i, j)^2}

$$

今天的公式都是小學生水平,千萬別再說看不懂了。

實現 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 運算元的模版如下:

$$

dx = \left[

\begin{matrix}

1 & 0 & -1\

1 & 0 & -1\

1 & 0 & -1\

\end{matrix}

\right]

dy = \left[

\begin{matrix}

-1 & -1 & -1\

0 & 0 & 0\

1 & 1 & 1\

\end{matrix}

\right]

$$

在程式碼實現上, 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 運算元的基礎上增加了權重的概念,認為相鄰點的距離遠近對當前畫素點的影響是不同的,距離越近的畫素點對應當前畫素的影響越大,從而實現影象銳化並突出邊緣輪廓。

演演算法模版如下:

$$

dx = \left[

\begin{matrix}

1 & 0 & -1\

2 & 0 & -2\

1 & 0 & -1\

\end{matrix}

\right]

dy = \left[

\begin{matrix}

-1 & -2 & -1\

0 & 0 & 0\

1 & 2 & 1\

\end{matrix}

\right]

$$

Sobel 運算元根據畫素點上下、左右鄰點灰度加權差,在邊緣處達到極值這一現象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向資訊。因為 Sobel 運算元結合了高斯平滑和微分求導(分化),因此結果會具有更多的抗噪性,當對精度要求不是很高時, Sobel 運算元是一種較為常用的邊緣檢測方法。

Sobel 運算元近似梯度的大小的計算公式如下:

$$

G = \sqrt{d_X^2 + d_y^2}

$$

梯度方向的計算公式如下:

$$

\theta = \tan^{-1}(\frac {d_x}{d_y})

$$

如果以上的角度 θ 等於零,即代表影象該處擁有縱向邊緣,左方較右方暗。

在 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 運算元分為四鄰域和八鄰域,四鄰域是對鄰域中心畫素的四方向求梯度,八鄰域是對八方向求梯度。

四鄰域模板如下:

$$

H = \left[

\begin{matrix}

0 & -1 & 0\

-1 & 4 & -1\

0 & -1 & 0\

\end{matrix}

\right]

$$

八鄰域模板如下:

$$

H = \left[

\begin{matrix}

-1 & -1 & -1\

-1 & 4 & -1\

-1 & -1 & -1\

\end{matrix}

\right]

$$

通過模板可以發現,當鄰域內畫素灰度相同時,模板的卷積運算結果為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」進行獲取。

參考

https://blog.csdn.net/Eastmount/article/details/89001702