Python 影象處理 OpenCV (12): Roberts 運算元、 Prewitt 運算元、 Sobel 運算元和 Laplacian 運算元邊緣檢測技術
阿新 • • 發佈:2020-06-29
![](https://cdn.geekdigging.com/opencv/opencv_header.png)
前文傳送門:
[「Python 影象處理 OpenCV (1):入門」](https://www.geekdigging.com/2020/05/17/5513454552/)
[「Python 影象處理 OpenCV (2):畫素處理與 Numpy 操作以及 Matplotlib 顯示影象」](https://www.geekdigging.com/2020/05/18/4936041986/)
[「Python 影象處理 OpenCV (3):影象屬性、影象感興趣 ROI 區域及通道處理」](https://www.geekdigging.com/2020/05/19/1227329671/)
[「Python 影象處理 OpenCV (4):影象算數運算以及修改顏色空間」](https://www.geekdigging.com/2020/05/21/1757913240/)
[「Python 影象處理 OpenCV (5):影象的幾何變換」](https://www.geekdigging.com/2020/05/23/4331122737/)
[「Python 影象處理 OpenCV (6):影象的閾值處理」](https://www.geekdigging.com/2020/06/03/6651375581/)
[「Python 影象處理 OpenCV (7):影象平滑(濾波)處理」](https://www.geekdigging.com/2020/06/06/8676263283/)
[「Python 影象處理 OpenCV (8):影象腐蝕與影象膨脹」](https://www.geekdigging.com/2020/06/08/5731186312/)
[「Python 影象處理 OpenCV (9):影象處理形態學開運算、閉運算以及梯度運算」](https://www.geekdigging.com/2020/06/11/5023174082/)
[「Python 影象處理 OpenCV (10):影象處理形態學之頂帽運算與黑帽運算」](https://www.geekdigging.com/2020/06/18/9182078666/)
[「Python 影象處理 OpenCV (11):Canny 運算元邊緣檢測技術」](https://www.geekdigging.com/2020/06/25/4009152544/)
## 引言
前文介紹了 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()` 這個函式,這個函式的主要功能是通過卷積核實現對影象的卷積運算:
```python
def filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)
```
* src: 輸入影象
* ddepth: 目標影象所需的深度
* kernel: 卷積核
接下來開始寫程式碼,首先是影象的讀取,並把這個影象轉化成灰度影象,這個沒啥好說的:
```python
# 讀取影象
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 的方向上做一次卷積運算:
```python
# 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點陣圖進行顯示,然後才能進行影象融合:
```python
# 轉 uint8 ,影象融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
```
最後是通過 pyplot 將影象顯示出來:
```python
# 顯示圖形
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()
```
最終結果如下:
![](https://cdn.geekdigging.com/opencv/12/roberts_result.png)
## 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 運算元比較相似,我就不多介紹,直接貼程式碼了:
```python
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()
```
![](https://cdn.geekdigging.com/opencv/12/prewitt_result.png)
從結果上來看, 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()` 函式進行運算,整體處理過程和前面的類似,程式碼如下:
```python
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()
```
![](https://cdn.geekdigging.com/opencv/12/sobel_result.png)
## 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 方向上的導數,得到輸入影象的影象銳化結果。
```python
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()
```
![](https://cdn.geekdigging.com/opencv/12/laplacian_result.png)
## 最後
邊緣檢測演算法主要是基於影象強度的一階和二階導數,但導數通常對噪聲很敏感,因此需要採用濾波器來過濾噪聲,並呼叫影象增強或閾值化演算法進行處理,最後再進行邊緣檢測。
最後我先使用高斯濾波去噪之後,再進行邊緣檢測:
```python
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()
```
![](https://cdn.geekdigging.com/opencv/12/gaussian_after.png)
## 示例程式碼
如果有需要獲取原始碼的同學可以在公眾號回覆「OpenCV」進行獲取。
## 參考
https://blog.csdn.net/Eastmount/article/details/