[OpenCV-Python] OpenCV 中的圖像處理 部分 IV (三)


部分 IV
OpenCV 中的圖像處理

OpenCV-Python 中文教程(搬運)目錄

 

19 Canny 邊緣檢測

 

目標
  • 了解 Canny 邊緣檢測的概念
  • 學習函數 cv2.Canny()


19.1 原理
  Canny 邊緣檢測是一種非常流行的邊緣檢測算法,是 John F.Canny 在1986 年提出的。它是一個有很多步構成的算法,我們接下來會逐步介紹。


19.1.1 噪聲去除
  由於邊緣檢測很容易受到噪聲影響,所以第一步是使用 5x5 的高斯濾波器去除噪聲,這個前面我們已經學過了。


19.1.2 計算圖像梯度
  對平滑后的圖像使用 Sobel 算子計算水平方向和豎直方向的一階導數(圖像梯度)(Gx 和 Gy)。根據得到的這兩幅梯度圖(Gx 和 Gy)找到邊界的梯度和方向,公式如下:
      Edge\_Gradient \; (G) = \sqrt{G_x^2 + G_y^2}

Angle \; (\theta) = \tan^{-1} \bigg(\frac{G_y}{G_x}\bigg)
梯度的方向一般總是與邊界垂直。梯度方向被歸為四類:垂直,水平,和兩個對角線。


19.1.3 非極大值抑制
  在獲得梯度的方向和大小之后,應該對整幅圖像做一個掃描,去除那些非邊界上的點。對每一個像素進行檢查,看這個點的梯度是不是周圍具有相同梯度方向的點中最大的。如下圖所示:

      Non-Maximum Suppression

現在你得到的是一個包含“窄邊界”的二值圖像。


19.1.4 滯后閾值
  現在要確定那些邊界才是真正的邊界。這時我們需要設置兩個閾值:minVal 和 maxVal。當圖像的灰度梯度高於 maxVal 時被認為是真的邊界,那些低於 minVal 的邊界會被拋棄。如果介於兩者之間的話,就要看這個點是否與某個被確定為真正的邊界點相連,如果是就認為它也是邊界點,如果不是就拋棄。如下圖:

      Hysteresis Thresholding
A 高於閾值 maxVal 所以是真正的邊界點,C 雖然低於 maxVal 但高於minVal 並且與 A 相連,所以也被認為是真正的邊界點。而 B 就會被拋棄,因為他不僅低於 maxVal 而且不與真正的邊界點相連。所以選擇合適的 maxVal和 minVal 對於能否得到好的結果非常重要。
在這一步一些小的噪聲點也會被除去,因為我們假設邊界都是一些長的線段。


19.2 OpenCV 中的 Canny 邊界檢測
  在 OpenCV 中只需要一個函數:cv2.Canny(),就可以完成以上幾步。
讓我們看如何使用這個函數。這個函數的第一個參數是輸入圖像。第二和第三個分別是 minVal 和 maxVal。第三個參數設置用來計算圖像梯度的 Sobel卷積核的大小,默認值為 3。最后一個參數是 L2gradient,它可以用來設定求梯度大小的方程。如果設為 True,就會使用我們上面提到過的方程,否則使用方程:Edge\_Gradient \; (G) = |G_x| + |G_y|. 代替,默認值為 False。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi5.jpg',0)
edges = cv2.Canny(img,100,200)

plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()

結果:

我的結果

    

官方結果

     Canny Edge Detection


更多資源
1. Canny edge detector at Wikipedia
2. Canny Edge Detection Tutorial by Bill Green, 2002.
練習
1. 寫一個小程序,可以通過調節滑動條來設置閾值 minVal 和 maxVal 進而來進行 Canny 邊界檢測。這樣你就會理解閾值的重要性了。


20 圖像金字塔


目標
  • 學習圖像金字塔
  • 使用圖像創建一個新水果:“橘子蘋果”
  • 將要學習的函數有:cv2.pyrUp(),cv2.pyrDown()。


20.1 原理
  一般情況下,我們要處理是一副具有固定分辨率的圖像。但是有些情況下,我們需要對同一圖像的不同分辨率的子圖像進行處理。比如,我們要在一幅圖像中查找某個目標,比如臉,我們不知道目標在圖像中的尺寸大小。這種情況下,我們需要創建創建一組圖像,這些圖像是具有不同分辨率的原始圖像。我們把這組圖像叫做圖像金字塔(簡單來說就是同一圖像的不同分辨率的子圖集合)。如果我們把最大的圖像放在底部,最小的放在頂部,看起來像一座金字塔,故而得名圖像金字塔。
  有兩類圖像金字塔:高斯金字塔和拉普拉斯金字塔。
  高斯金字塔的頂部是通過將底部圖像中的連續的行和列去除得到的。頂部圖像中的每個像素值等於下一層圖像中 5 個像素的高斯加權平均值。這樣操作一次一個 MxN 的圖像就變成了一個 M/2xN/2 的圖像。所以這幅圖像的面積就變為原來圖像面積的四分之一。這被稱為 Octave。連續進行這樣的操作我們就會得到一個分辨率不斷下降的圖像金字塔。我們可以使用函數cv2.pyrDown() 和 cv2.pyrUp() 構建圖像金字塔。
  函數 cv2.pyrDown() 從一個高分辨率大尺寸的圖像向上構建一個金子塔(尺寸變小,分辨率降低)。

img = cv2.imread('messi5.jpg')
lower_reso = cv2.pyrDown(higher_reso)

下圖是一個四層的圖像金字塔。

    Gaussian Pyramid

函數 cv2.pyrUp() 從一個低分辨率小尺寸的圖像向下構建一個金子塔(尺寸變大,但分辨率不會增加)。

higher_reso2 = cv2.pyrUp(lower_reso)

    Gaussian Pyramid

  你要記住的是是 higher_reso2 和 higher_reso 是不同的。因為一旦使用 cv2.pyrDown(),圖像的分辨率就會降低,信息就會被丟失。下圖就是從 cv2.pyrDown() 產生的圖像金字塔的(由下到上)第三層圖像使用函數cv2.pyrUp() 得到的圖像,與原圖像相比分辨率差了很多。
  拉普拉斯金字塔可以有高斯金字塔計算得來,公式如下:
    

拉普拉金字塔的圖像看起來就像邊界圖,其中很多像素都是 0。他們經常被用在圖像壓縮中。下圖就是一個三層的拉普拉斯金字塔:

    Laplacian Pyramid


20.2 使用金字塔進行圖像融合
  圖像金字塔的一個應用是圖像融合。例如,在圖像縫合中,你需要將兩幅圖疊在一起,但是由於連接區域圖像像素的不連續性,整幅圖的效果看起來會很差。這時圖像金字塔就可以排上用場了,他可以幫你實現無縫連接。這里的一個經典案例就是將兩個水果融合成一個,看看下圖也許你就明白我在講什么了。
    Pyramid Blending
你可以通過閱讀后邊的更多資源來了解更多關於圖像融合,拉普拉斯金字塔的細節。
實現上述效果的步驟如下:
  1. 讀入兩幅圖像,蘋果和句子
  2. 構建蘋果和橘子的高斯金字塔(6 層)
  3. 根據高斯金字塔計算拉普拉斯金字塔
  4. 在拉普拉斯的每一層進行圖像融合(蘋果的左邊與橘子的右邊融合)
  5. 根據融合后的圖像金字塔重建原始圖像。
下圖是摘自《學習 OpenCV》展示了金子塔的構建,以及如何從金字塔重建原始圖像的過程。
整個過程的代碼如下。(為了簡單,每一步都是獨立完成的,這回消耗更多、的內存,如果你願意的話可以對他進行優化)

import cv2
import numpy as np,sys

A = cv2.imread('apple.jpg')
B = cv2.imread('orange.jpg')

# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in xrange(6):
    G = cv2.pyrDown(G)
    gpA.append(G)

# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in xrange(6):
    G = cv2.pyrDown(G)
    gpB.append(G)

# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in xrange(5,0,-1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i-1],GE)
    lpA.append(L)

# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in xrange(5,0,-1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i-1],GE)
    lpB.append(L)

# Now add left and right halves of images in each level
LS = []
for la,lb in zip(lpA,lpB):
    rows,cols,dpt = la.shape
    ls = np.hstack((la[:,0:cols/2], lb[:,cols/2:]))
    LS.append(ls)

# now reconstruct
ls_ = LS[0]
for i in xrange(1,6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_, LS[i])

# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))

cv2.imwrite('Pyramid_blending2.jpg',ls_)
cv2.imwrite('Direct_blending.jpg',real)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM