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

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

![](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/