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

極客挖掘機發表於2020-06-29

前文傳送門:

「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

相關文章