小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Python OpenCV #4

 羊玉wngbx 2024-07-07

一、更改色彩空間

1.1 目標

  • 在本教程中,您將學習如何將圖像從一種顏色空間轉(zhuǎn)換為另一種顏色空間,例如 BGR ? Gray、BGR ? HSV 等。
  • 除此之外,我們還將創(chuàng)建一個應(yīng)用程序來提取視頻中的彩色對象
  • 您將學習以下函數(shù):cv.cvtColor()、cv.inRange()等。

1.2 更改色彩空間

OpenCV 中提供了 150 多種色彩空間轉(zhuǎn)換方法。但我們將只研究兩種,它們是使用最廣泛的兩種:BGR ? Gray和BGR ? HSV。

對于色彩轉(zhuǎn)換,我們使用函數(shù) cv.cvtColor(input_image, flag),其中flag決定轉(zhuǎn)換類型。

對于BGR Gray轉(zhuǎn)換,我們使用標記 cv.COLOR_BGR2GRAY。同樣,對于BGR HSV,我們使用標記 cv.COLOR_BGR2HSV。要獲取其他標記,只需在Python終端中運行以下命令:

>>> import cv2 as cv
>>> flags = [i for i in dir(cv) if i.startswith('COLOR_')]
>>> print( flags )

注意:

對于HSV,色調(diào)范圍是[0,179],飽和度范圍是[0,255],數(shù)值范圍是[0,255]。不同的軟件使用不同的比例。因此,如果要將OpenCV的值與它們進行比較,就需要對這些范圍進行歸一化處理。

1.3 跟蹤對象

下載代碼:點擊 這里

既然我們已經(jīng)知道如何將BGR圖像轉(zhuǎn)換為HSV,我們就可以用它來提取彩色對象。與BGR色彩空間相比,HSV更容易表示顏色。在我們的應(yīng)用中,我們將嘗試提取一個藍色物體。方法如下:

  • 提取視頻的每一幀
  • 將BGR轉(zhuǎn)換為HSV色彩空間
  • 我們對HSV圖像的藍色范圍進行閾值處理
  • 現(xiàn)在單獨提取藍色對象,我們可以在該圖像上做任何我們想做的事情。

以下是詳細注釋的代碼:

import cv2 as cv
import numpy as np

cap = cv.VideoCapture(0)

while True:

    # 提取視頻的每一幀
    _, frame = cap.read()

    # 將BGR轉(zhuǎn)換為HSV
    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)

    # 定義HSV中藍色的范圍
    lower_blue = np.array([110,50,50])
    upper_blue = np.array([130,255,255])

    # 對HSV圖像的藍色閾值進行處理
    mask = cv.inRange(hsv, lower_blue, upper_blue)

    # 按位與掩碼和原始圖像
    res = cv.bitwise_and(frame,frame, mask= mask)

    cv.imshow('frame',frame)
    cv.imshow('mask',mask)
    cv.imshow('res',res)
    k = cv.waitKey(5) & 0xFF
    if k == 27:
        break
 
cv.destroyAllWindows()

下圖顯示了對藍色物體的跟蹤:

Python OpenCV #4 - OpenCV中的圖像處理(上)_python

注意:

圖像中有一些噪點。我們將在后面的章節(jié)中了解如何去除噪點。

這是物體追蹤中最簡單的方法。一旦學會了輪廓線的功能,你就可以做很多事情,比如找到物體的中心點并用它來跟蹤物體,在攝像機前移動你的手來繪制圖表,以及其他有趣的事情。

1.4 如何找到要跟蹤的HSV值?

這是在 上發(fā)現(xiàn)的一個常見問題。其實非常簡單,您可以使用相同的函數(shù)cv.cvtColor()。無需傳遞圖像,只需傳遞所需的BGR值即可。例如,要查找綠色的HSV值,請在Python終端中執(zhí)行以下命令:

>>> green = np.uint8([[[0,255,0 ]]])
>>> hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)
>>> print( hsv_green )
[[[ 60 255 255]]]

現(xiàn)在,您可以將[H-10, 100,100]和[H+10, 255, 255]分別作為下限和上限。除了這種方法,你還可以使用任何圖像編輯工具,如GIMP或任何在線轉(zhuǎn)換器來找到這些值,但不要忘記調(diào)整HSV范圍。

1.5 額外資源

1.5.1 練習

  1. 嘗試找到提取多個彩色對象的方法,例如同時提取紅色、藍色和綠色對象。

二、圖像的幾何變換

2.1 目標

  • 學習對圖像應(yīng)用不同的幾何變換,如平移、旋轉(zhuǎn)、仿射變換等。
  • 您將看到這些函數(shù):cv.getPerspectiveTransform

2.2 變換

cv.warpAffine接收一個2x3變換矩陣,而cv.warpPerspective接收一個3x3變換矩陣作為輸入。

2.2.1 縮放

下載代碼:點擊 這里

縮放就是調(diào)整圖像大小。為此,OpenCV提供了一個函數(shù)cv.resize()??梢允謩又付▓D像大小,也可以指定縮放因子。可以使用不同的插值方法。首選的插值方法是用于縮小的cv.INTER_AREA,以及用于縮放的cv.INTER_CUBIC(慢)和cv.INTER_LINEAR。默認情況下,插值方法cv.INTER_LINEAR用于所有大小調(diào)整。您可以使用以下任一方法調(diào)整輸入圖像的大小:

import numpy as np
import cv2 as cv
 
img = cv.imread('Ronaldo.png')
assert img is not None, 'file could not be read, check with os.path.exists()'

# 使用這種
res = cv.resize(img, None, fx=2, fy=2, interpolation = cv.INTER_CUBIC)

# 或者這種
height, width = img.shape[:2]
res = cv.resize(img, (2*width, 2*height), interpolation = cv.INTER_CUBIC)

2.2.2 平移

下載代碼:點擊 這里

平移是指物體位置的移動。如果知道(x,y)方向上的移動,并將其設(shè)為 (tx,ty)。您可以這樣創(chuàng)建轉(zhuǎn)換矩陣 M

M=[10tx01ty]

您可以將其轉(zhuǎn)換為np.float32類型的Numpy數(shù)組,然后將其傳遞給cv.warpAffine()函數(shù)。請看下面的示例,平移至(100,50):

import numpy as np
import cv2 as cv

img = cv.imread('Ronaldo.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
rows, cols = img.shape

M = np.float32([[1, 0, 100], [0, 1, 50]])
dst = cv.warpAffine(img, M, (cols, rows))

cv.imshow('img', dst)
cv.waitKey(0)
cv.destroyAllWindows()

警告:

cv.warpAffine()函數(shù)的第三個參數(shù)是輸出圖像的大小,其形式應(yīng)為 (width, height)。請記住,width=列數(shù),height=行數(shù)。

請看下面的結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_02

2.2.3 旋轉(zhuǎn)

下載代碼:點擊 這里

圖像旋轉(zhuǎn)一個角度 θ 是通過變換矩陣實現(xiàn)的,變換矩陣的形式為:

M=[cosθ?sinθsinθcosθ]

但是,OpenCV提供了可調(diào)整旋轉(zhuǎn)中心的縮放旋轉(zhuǎn)功能,因此您可以在您喜歡的任何位置進行旋轉(zhuǎn)。修改后的變換矩陣如下:

[αβ(1?α)?center.x?β?center.y?βαβ?center.x+(1?α)?center.y]

其中:

α=scale?cosθβ=scale?sinθ

要找到這個變換矩陣,OpenCV提供了一個函數(shù)cv.getRotationMatrix2D。請看下面的示例,該示例將圖像以中心為基準旋轉(zhuǎn)90度,且不做任何縮放。

import cv2 as cv
import numpy as np

img = cv.imread('Ronaldo.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
rows, cols = img.shape
 
# cols-1和rows-1是坐標限制。
M = cv.getRotationMatrix2D(((cols-1)/2.0, (rows-1)/2.0), 90, 1)
dst = cv.warpAffine(img, M, (cols, rows))

cv.imshow('img', dst)
cv.waitKey(0)

看看結(jié)果吧:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_03

2.2.4 映射變換

下載代碼:點擊 這里

在映射變換中,原始圖像中的所有平行線在輸出圖像中仍然是平行的。要找到變換矩陣,我們需要輸入圖像中的三個點及其在輸出圖像中的對應(yīng)位置。然后,cv.getAffineTransform將創(chuàng)建一個2x3矩陣,并將其傳遞給cv.warpAffine。

請看下面的示例,以及我選擇的點(用綠色標出):

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

img = cv.imread('drawing.png')
assert img is not None, 'file could not be read, check with os.path.exists()'
rows, cols, ch = img.shape
 
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
 
M = cv.getAffineTransform(pts1, pts2)
 
dst = cv.warpAffine(img, M, (cols, rows))
 
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

看看結(jié)果吧:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_04

2.2.5 透視變換

下載代碼:點擊 這里

透視變換需要一個3x3變換矩陣。即使在變換后,直線仍然是直線。要找到這個變換矩陣,需要輸入圖像上的4個點和輸出圖像上的相應(yīng)點。在這4個點中,有3個點不應(yīng)相交。然后就可以通過函數(shù)cv.getPerspectiveTransform找到變換矩陣。然后利用這個3x3變換矩陣應(yīng)用cv.warpPerspective。

請看下面的代碼:

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

img = cv.imread('sudoku.png')
assert img is not None, 'file could not be read, check with os.path.exists()'
rows, cols, ch = img.shape

pts1 = np.float32([[83, 97], [546, 77], [41, 569], [576, 577]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
 
M = cv.getPerspectiveTransform(pts1, pts2)
 
dst = cv.warpPerspective(img, M, (300, 300))
 
plt.subplot(121), plt.imshow(img), plt.title('Input')
plt.subplot(122), plt.imshow(dst), plt.title('Output')
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_05

2.3 額外資源

《計算機視覺:算法與應(yīng)用 (Computer Vision: Algorithms and Applications)》- 理查德·塞利斯基 (Richard Szeliski)

三、圖像閾值處理

3.1 目標

  • 在本教程中,您將學習簡單閾值處理、自適應(yīng)閾值處理和Otsu閾值處理。
  • 您還將學習函數(shù)cv.thresholdcv.adaptiveThreshold。

3.2 簡單閾值處理

下載代碼:點擊 這里

這里的問題很簡單。對每個像素都應(yīng)用相同的閾值。如果像素值小于閾值,則設(shè)置為0,否則設(shè)置為最大值。函數(shù)cv.threshold用于應(yīng)用閾值處理。第一個參數(shù)是源圖像,應(yīng)為灰度圖像。第二個參數(shù)是用于對像素值進行分類的閾值。第三個參數(shù)是最大值,分配給超過閾值的像素值。OpenCV提供不同類型的閾值處理,由函數(shù)的第四個參數(shù)給出。如上所述的基本閾值是通過使用cv.THRESH_BINARY類型完成的。所有簡單閾值類型均為:

  • cv.THRESH_BINARY
  • cv.THRESH_BINARY_INV
  • cv.THRESH_TRUNC
  • cv.THRESH_TOZERO
  • cv.THRESH_TOZERO_INV

有關(guān)差異,請參閱各類型的文檔。

該方法返回兩個輸出。第一個輸出是使用的閾值,第二個輸出是經(jīng)過閾值處理的圖像。

這段代碼比較了不同的簡單閾值處理類型:

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

img = cv.imread('gradient.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray', vmin=0, vmax=255)
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

注意:

為了繪制多幅圖像,我們使用了plt.subplot()函數(shù)。更多詳情請查看matplotlib文檔。

代碼會產(chǎn)生這樣的結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_python_06

3.3 自適應(yīng)閾值

下載代碼:點擊 這里

在上一節(jié)中,我們使用一個全局值作為閾值。但這并非在所有情況下都適用,例如,如果圖像的不同區(qū)域有不同的光照條件。在這種情況下,自適應(yīng)閾值法就能提供幫助。在這里,算法會根據(jù)像素周圍的一個小區(qū)域來確定該像素的閾值。這樣,我們就能為同一圖像的不同區(qū)域獲得不同的閾值,從而為光照條件不同的圖像提供更好的效果。

除上述參數(shù)外,cv.adaptiveThreshold方法還需要三個輸入?yún)?shù):

  • adaptiveMethod決定如何計算閾值:
    • cv.ADAPTIVE_THRESH_MEAN_C:閾值是鄰域面積的平均值減去常數(shù)C
    • cv.ADAPTIVE_THRESH_GAUSSIAN_C:閾值是鄰域值的高斯加權(quán)和減去常數(shù)C
  • blockSize決定了鄰近區(qū)域的大小
  • C是一個常數(shù),從鄰近像素的平均值或加權(quán)和中減。

下面的代碼比較了全局閾值處理和自適應(yīng)閾值處理對不同光照度圖像的處理效果:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
 
img = cv.imread('sudoku.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
img = cv.medianBlur(img, 5)
 
ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)
th3 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)
 
titles = ['Original Image', 'Global Thresholding (v = 127)', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in range(4):
    plt.subplot(2, 2, i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_07

3.4 Otsu二值化

下載代碼:點擊 這里

在全局閾值法中,我們使用一個任意選擇的值作為閾值。相比之下,Otsu方法無需選擇數(shù)值,而是自動確定。

考慮到圖像中只有兩個不同的圖像值(雙峰圖像),直方圖中只有兩個峰值。一個好的閾值應(yīng)該在這兩個值的中間。同樣,Otsu方法也能從圖像直方圖中確定一個最佳的全局閾值。

為此,我們使用了cv.threshold()函數(shù),并將cv.THRESH_OTSU作為額外的標志傳入。閾值可以任意選擇。然后,算法會找出最佳閾值,并作為第一個輸出返回。

請看下面的示例。輸入圖像是一張有噪聲的圖像。在第一種情況下,應(yīng)用值為127的全局閾值。第二種情況是直接應(yīng)用大津閾值。在第三種情況下,首先使用5x5高斯內(nèi)核對圖像進行濾波以去除噪聲,然后再應(yīng)用大津閾值處理??纯丛胍暨^濾是如何改善結(jié)果的。

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

img = cv.imread('noisy.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'

# 全局閾值
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)

# Otsu閾值
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)

# 高斯濾波后的Otsu閾值
blur = cv.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)

# 繪制所有圖像及其直方圖
images = [
 img, 0, th1,
 img, 0, th2,
 blur, 0, th3
]
titles = [
 'Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)', 
 'Original Noisy Image', 'Histogram', 'Otsu\'s Thresholding', 
 'Gaussian filtered Image', 'Histogram', 'Otsu\'s Thresholding'
]
 
for i in range(3):
    plt.subplot(3, 3, i*3+1), plt.imshow(images[i*3], 'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i*3+2), plt.hist(images[i*3].ravel(), 256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i*3+3), plt.imshow(images[i*3+2], 'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_08

3.4.1 Otsu二值化是如何實現(xiàn)的?

本節(jié)演示了大津二值化的Python實現(xiàn),以說明其實際工作原理。如果您不感興趣,可以跳過這部分。

由于我們處理的是雙模態(tài)圖像,大津算法試圖找到一個閾值 (t),使關(guān)系式給出的加權(quán)類內(nèi)方差最?。?/p>

σw2(t)=q1(t)σ12(t)+q2(t)σ22(t)

其中:

q1(t)=i=1tP(i)&q2(t)=q1(t)=i=t+1IP(i)μ1(t)=i=1tiP(i)q1(t)&μ2(t)=i=t+1IiP(i)q2(t)σ12(t)=i=1t[i?μ1(t)]2P(i)q1(t)&σ22(t)=i=t+1I[i?μ2(t)]2P(i)q2(t)

它實際上是在兩個峰值之間找到一個t值,使兩個類別的方差都最小。它可以用Python簡單實現(xiàn)如下:

import cv2 as cv
import numpy as np

img = cv.imread('noisy.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
blur = cv.GaussianBlur(img, (5, 5), 0)

# 求normalized_histogram及其累積分布函數(shù)
hist = cv.calcHist([blur], [0], None, [256], [0, 256])
hist_norm = hist.ravel()/hist.sum()
Q = hist_norm.cumsum()

bins = np.arange(256)

fn_min = np.inf
thresh = -1

for i in range(1, 256):
    p1, p2 = np.hsplit(hist_norm, [i]) # 可能性
    q1, q2 = Q[i], Q[255]-Q[i] # 類的總和
    if q1 < 1.e-6 or q2 < 1.e-6:
        continue
    b1, b2 = np.hsplit(bins, [i]) # 權(quán)重

    # 尋找均值和方差
    m1, m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
    v1, v2 = np.sum(((b1-m1)**2)*p1)/q1, np.sum(((b2-m2)**2)*p2)/q2

    # 計算最小化函數(shù)
    fn = v1*q1 + v2*q2
    if fn < fn_min:
        fn_min = fn
        thresh = i
 
# 用OpenCV函數(shù)求otsu的閾值
ret, otsu = cv.threshold(blur, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)
print('{} {}'.format(thresh, ret))

3.5 額外資源

  1. 《數(shù)字圖像處理 (Digital Image Processing)》,拉斐爾·C·岡薩雷斯 (Rafael C. Gonzalez)

3.5.1 練習

  1. Otsu二值化有一些優(yōu)化方法。您可以搜索并實施。

四、圖像平滑

4.1 目標

學習:

  • 使用各種低通濾波器模糊圖像
  • 為圖像應(yīng)用自定義濾波器(二維卷積)

4.2 二維卷積(圖像濾波)

下載代碼:點擊 這里

與一維信號一樣,圖像也可以使用各種低通濾波器(LPF)、高通濾波器(HPF)等進行濾波。LPF有助于去除噪音、模糊圖像等。HPF濾波器有助于發(fā)現(xiàn)圖像中的邊緣。

OpenCV提供了一個函數(shù)cv.filter2D(),用于將核與圖像卷積。例如,我們將嘗試在圖像上使用平均濾波器。一個5x5的平均濾波核將如下所示:

k=125[1111111111111111111111111]

操作過程如下:在一個像素點上方保留此核,將此核下方的所有25個像素點相加,取平均值,然后用新的平均值替換中心像素點。圖像中的所有像素都將繼續(xù)執(zhí)行此操作。試試這段代碼并檢查結(jié)果:

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

img = cv.imread('opencv_logo.png')
assert img is not None, 'file could not be read, check with os.path.exists()'

kernel = np.ones((5, 5), np.float32)/25
dst = cv.filter2D(img, -1, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(dst), plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_09

4.3 圖像模糊(圖像平滑)

下載代碼:點擊 這里

圖像模糊是通過將圖像與低通濾波器核進行卷積來實現(xiàn)的。它可用于去除噪點。它實際上是去除圖像中的高頻內(nèi)容(如:噪點、邊緣)。因此,在這一操作中,邊緣會被模糊一些(也有不模糊邊緣的模糊技術(shù))。OpenCV提供了四種主要的模糊技術(shù)。

4.3.1 平均化

這是通過用歸一化盒狀濾波器對圖像進行卷積來實現(xiàn)的。它只是取核區(qū)域下所有像素的平均值,并替換中心元素。這由函數(shù)cv.blur()cv.boxFilter()完成。有關(guān)內(nèi)核的更多詳情,請查閱文檔。我們應(yīng)該指定內(nèi)核的寬度和高度。一個3x3歸一化盒狀濾波器看起來如下:

k=19[111111111]

注意:

如果不想使用規(guī)范化方框過濾器,請使用cv.boxFilter()。向函數(shù)傳遞一個參數(shù)normalize=False。

查看下面的示例演示,內(nèi)核大小為5x5:

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

img = cv.imread('opencv_logo.png')
assert img is not None, 'file could not be read, check with os.path.exists()'

blur = cv.blur(img, (5, 5))

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blur), plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_python_10

4.3.2 高斯模糊

在這種方法中,使用的不是盒式濾波器,而是高斯核。它是通過函數(shù)cv.GaussianBlur()完成的。我們應(yīng)指定核的寬度和高度,它們應(yīng)為正奇數(shù)。我們還應(yīng)該指定X和Y方向的標準偏差,分別為sigmaXsigmaY。如果只指定了sigmaX,則sigmaY與sigmaX相同。如果兩者都為零,則根據(jù)內(nèi)核大小計算。高斯模糊對于去除圖像中的高斯噪點非常有效。

如果需要,可以使用函數(shù)cv.getGaussianKernel()創(chuàng)建高斯核。

上述代碼可以修改為高斯模糊:

blur = cv.GaussianBlur(img, (5, 5), 0)

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_python_11

4.3.3 中值模糊

在這里,函數(shù)cv.medianBlur()取核區(qū)域下所有像素的中值,并用該中值替換中心元素。這對消除圖像中的椒鹽噪聲非常有效。有趣的是,在上述濾波器中,中心元素是一個新計算出的值,可能是圖像中的像素值,也可能是一個新值。但在中值模糊濾波器中,中心元素總是由圖像中的某個像素值代替。它能有效減少噪音。其內(nèi)核大小應(yīng)為正奇數(shù)整數(shù)。

在本演示中,我在原始圖像中添加了50%的噪點,并應(yīng)用了中值模糊技術(shù)??纯唇Y(jié)果如何:

median = cv.medianBlur(img, 5)

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_12

4.3.4 雙邊濾波

cv.bilateralFilter()能有效去除噪點,同時保持邊緣銳利。但與其他濾波器相比,其運行速度較慢。我們已經(jīng)看到,高斯濾波器會獲取像素周圍的鄰域,并找出其高斯加權(quán)平均值。這種高斯濾波器僅是一個空間函數(shù),即在濾波時只考慮附近的像素。它不考慮像素是否具有幾乎相同的強度。它不考慮像素是否是邊緣像素。因此,它也會模糊邊緣,這是我們不希望看到的。

雙邊濾波也需要一個空間高斯濾波器,但多了一個像素差函數(shù)的高斯濾波器??臻g的高斯函數(shù)確保只有附近的像素才會被考慮進行模糊處理,而強度差的高斯函數(shù)則確保只有那些與中心像素強度相似的像素才會被考慮進行模糊處理。因此,它可以保留邊緣,因為邊緣的像素會有較大的強度變化。

下面的示例展示了雙邊濾波器的使用(有關(guān)參數(shù)的詳細信息,請訪問文檔)。

blur = cv.bilateralFilter(img, 9, 75, 75)

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_13

看,表面的紋理消失了,但邊緣還保留著。

4.4 額外資源

  1.  有關(guān)雙邊濾波的詳細信息

五、形態(tài)變換

5.1 目標

在本章:

  • 我們將學習不同的形態(tài)學操作,如腐蝕、膨脹、開運算、閉運算等。
  • 我們將看到不同的函數(shù),如:cv.erode()、cv.dilate()、cv.morphologyEx()等。

5.2 理論

形態(tài)變換是基于圖像形狀的一些簡單操作。它通常在二值圖像上執(zhí)行。它需要兩個輸入,一個是原始圖像,另一個稱為結(jié)構(gòu)元素或內(nèi)核,它決定了操作的性質(zhì)。兩種基本形態(tài)學運算符是腐蝕和膨脹。然后,它的變體形式如開運算、閉運算、梯度等也會發(fā)揮作用。我們將借助下圖逐一了解它們:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_14

5.2.1 腐蝕

下載代碼:點擊 這里

腐蝕的基本原理和土壤腐蝕一樣,只是會腐蝕掉前景物體的邊界(始終盡量保持前景物體為白色)。那么它的作用是什么呢?內(nèi)核在圖像中滑動(如同二維卷積)。只有當核下的所有像素都是1時,原始圖像中的一個像素(1或0)才會被視為1,否則就會被腐蝕(變?yōu)?)。

因此,根據(jù)內(nèi)核的大小,邊界附近的所有像素都會被丟棄。這樣,前景物體的厚度或大小就會減小,或者說圖像中的白色區(qū)域就會減小。這對于去除小的白噪聲(正如我們在色彩空間章節(jié)中所看到的)、分離兩個相連的物體等都很有用。

舉例來說,在這里我將使用一個5x5的內(nèi)核,內(nèi)核中滿是1。讓我們來看看它是如何工作的:

import cv2 as cv
import numpy as np

img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
erosion = cv.erode(img, kernel, iterations=1)

cv.imshow('Erosion', erosion)
cv.waitKey(0)

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_15

5.2.2 膨脹

下載代碼:點擊 這里

它與腐蝕正好相反。在這里,如果內(nèi)核下至少有一個像素為1,則該像素元素為1。因此,它增加了圖像中的白色區(qū)域或前景物體的大小。通常,在去除噪點等情況下,侵蝕之后是擴張。因為侵蝕可以去除白色噪點,但同時也會縮小我們的對象。因此,我們要擴張它。由于噪點消失了,它們不會再出現(xiàn),但我們的對象面積卻增加了。它在連接物體的破碎部分時也很有用。

import cv2 as cv
import numpy as np

img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
dilation = cv.dilate(img, kernel, iterations = 1)

cv.imshow('Dilation', dilation)
cv.waitKey(0)

Python OpenCV #4 - OpenCV中的圖像處理(上)_python_16

5.2.3 開運算

下載代碼:點擊 這里

開運算是腐蝕后膨脹的另一個名稱。如上文所述,它在去除噪點方面非常有用。這里我們使用函數(shù)cv.morphologyEx()

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

img = cv.imread('j_2.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(opening), plt.title('Opening')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_17

5.2.4 閉運算

下載代碼:點擊 這里

閉運算與開運算、膨脹和腐蝕相反。它適用于封閉前景物體內(nèi)部的小孔或物體上的小黑點。

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

img = cv.imread('j_3.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(closing), plt.title('Closing')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_18

5.2.5 形態(tài)梯度

下載代碼:點擊 這里

這是圖像擴張和侵蝕的區(qū)別。

結(jié)果看起來就像物體的輪廓。

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

img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(gradient), plt.title('Gradient')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_python_19

5.2.6 禮帽

下載代碼:點擊 這里

它是輸入圖像與圖像開口之間的差值。下面是一個9x9內(nèi)核的例子。

結(jié)果:

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

img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(tophat), plt.title('Top Hat')
plt.xticks([]), plt.yticks([])
plt.show()

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_20

5.2.7 黑帽

下載代碼:點擊 這里

它是輸入圖像與輸入圖像的閉合差。

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

img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
kernel = np.ones((5, 5), np.uint8)
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)

plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(blackhat), plt.title('Black Hat')
plt.xticks([]), plt.yticks([])
plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_21

5.3 結(jié)構(gòu)元素

在前面的示例中,我們借助Numpy手動創(chuàng)建了一個結(jié)構(gòu)元素。它是矩形的。但在某些情況下,您可能需要橢圓形/或圓形的內(nèi)核。為此,OpenCV提供了一個函數(shù)cv.getStructuringElement()。只需傳入內(nèi)核的形狀和大小,就能得到所需的內(nèi)核。

# 矩形內(nèi)核
>>> cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]], dtype=uint8)
 
# 橢圓形內(nèi)核
>>> cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0]], dtype=uint8)
 
# 十字形內(nèi)核
>>> cv.getStructuringElement(cv.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0],
       [1, 1, 1, 1, 1],
       [0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0]], dtype=uint8)

5.4 額外資源

  1.  HIPR2形態(tài)操作

六、圖像漸變

6.1 目標

在本章中,我們將學習:

  • 查找圖像梯度、邊緣等
  • 我們將看到以下函數(shù):cv.Sobel()、cv.Scharr()、cv.Laplacian()

6.2 原理

OpenCV提供三種梯度濾波器或高通濾波器:Sobel、Scharr和Laplacian。我們將逐一介紹。

6.2.1 Sobel和Scharr梯度濾波器

Sobel運算符是一種高斯平滑加微分的聯(lián)合運算,因此對噪點的干擾能力更強。您可以指定導(dǎo)數(shù)的方向,垂直或水平(分別通過參數(shù)yorderxorder)。還可以通過參數(shù)ksize指定核的大小。如果ksize=-1,就會使用3x3 Scharr濾波器,效果比3x3 Sobel濾波器更好。有關(guān)使用的核,請參閱文檔。

6.2.2 Laplacian(拉普拉斯)梯度濾波器

它通過關(guān)系式 Δsrc=?2src?x2+?2src?y2 計算圖像的拉普拉斯值,其中每個導(dǎo)數(shù)都是通過Sobel導(dǎo)數(shù)求得的。如果ksize=1,則使用以下核進行濾波:

kernel=[0101?41010]

6.3 代碼

下載:點擊 這里

下面的代碼在一張圖中顯示了所有運算符。所有內(nèi)核都是5x5大小。輸出圖像的深度為-1,以獲得np.uint8類型的結(jié)果。

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

img = cv.imread('dave.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'

laplacian = cv.Laplacian(img, cv.CV_64F)
sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=5)

plt.subplot(2, 2, 1), plt.imshow(img, cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 2), plt.imshow(laplacian, cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 3), plt.imshow(sobelx, cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2, 2, 4), plt.imshow(sobely, cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.show()

結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_22

6.4 一個重要事項!

下載代碼:點擊 這里

在上一個例子中,輸出數(shù)據(jù)類型是cv.CV_8Unp.uint8。但這有一個小問題。從黑色到白色的轉(zhuǎn)換被視為正斜率(它具有正值),而從白色到黑色的轉(zhuǎn)換被視為負斜率(它具有負值)。因此,在將數(shù)據(jù)轉(zhuǎn)換為np.uint8時,所有負斜率都會變?yōu)榱?。簡單地說,你會錯過這條邊。

如果要檢測兩條邊,更好的辦法是將輸出數(shù)據(jù)類型保持為更高的形式,如cv.CV_16Scv.CV_64F等,取其絕對值,然后再轉(zhuǎn)換回cv.CV_8U。下面的代碼演示了水平Sobel濾波器的操作步驟和結(jié)果差異。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
 
img = cv.imread('box.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
 
# 輸出數(shù)據(jù)類型 = cv.CV_8U
sobelx8u = cv.Sobel(img, cv.CV_8U, 1, 0, ksize=5)
 
# 輸出數(shù)據(jù)類型 = cv.CV_64F,然后取其絕對值并轉(zhuǎn)換為cv.CV_8U
sobelx64f = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
 
plt.subplot(1, 3, 1), plt.imshow(img, cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 2), plt.imshow(sobelx8u, cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 3, 3), plt.imshow(sobel_8u, cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
 
plt.show()

請看下面的結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_23

七、邊緣檢測

7.1 目標

在本章中,我們將學習:

  • Canny邊緣檢測的概念
  • 用于此功能的OpenCV函數(shù):cv.Canny()

7.2 原理

Canny邊緣檢測是一種流行的邊緣檢測算法。它是由John F.Canny于1986年開發(fā)。

  1. 它是一種多階段算法,我們將逐一介紹。

  2. 降噪
    由于邊緣檢測容易受到圖像中噪聲的影響,因此第一步是使用5x5高斯濾波器去除圖像中的噪聲。這一點我們在前面的章節(jié)中已經(jīng)介紹過。

  3. 查找圖像的強度梯度
    然后用Sobel核對平滑后的圖像進行水平和垂直方向的濾波,得到水平方向( Gx )和垂直方向( Gy )的初導(dǎo)數(shù)。從這兩幅圖像中,我們可以找到每個像素的邊緣梯度和方向,如下所示:

    Edge_gradient(G)=Gx2+Gy2Angle(θ)=tan?1(GyGx)

梯度方向總是垂直于邊緣。它被圓化成四個角度中的一個,分別代表垂直、水平和兩個對角線方向。

  1. 非最大抑制
    得到梯度大小和方向后,要對圖像進行全面掃描,以去除任何可能不構(gòu)成邊緣的無用像素。為此,要檢查每個像素在梯度方向上是否是其附近的局部最大值。請看下圖:
    Python OpenCV #4 - OpenCV中的圖像處理(上)_python_24

A點位于邊緣上(垂直方向)。梯度方向為邊緣的法線方向。B點和C點在梯度方向上。因此,A點將與B點和C點一起檢查,看是否形成局部最大值。如果是,則進入下一階段,否則將被抑制(置零)。

簡而言之,得到的結(jié)果就是一幅具有“薄邊緣”的二值圖像。

  1. 滯后閾值
    這一階段決定哪些是真正的邊,哪些不是。為此,我們需要兩個閾值,即minValmaxVal。強度梯度大于maxVal的邊緣肯定是邊緣,而強度梯度小于minVal的邊緣肯定不是邊緣,因此會被丟棄。位于這兩個閾值之間的邊緣會根據(jù)其連接性被歸類為邊緣或非邊緣。如果它們與“確定為邊緣”的像素相連,則被視為邊緣的一部分。否則,它們也會被丟棄。請看下圖:
    Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_25

邊A高于maxVal,因此被視為“確定邊”。雖然邊C低于maxVal,但它與邊A相連,因此也被視為有效邊,從而得到完整曲線。但是,邊B雖然高于minVal且與邊C在同一區(qū)域,但它與任何“確定邊”都不相連,因此會被舍棄。因此,我們必須相應(yīng)地選擇minVal和maxVal,以得到正確的結(jié)果,這一點非常重要。

這一階段還能消除小像素噪點,前提是邊緣是長線。

因此,我們最終得到的是圖像中的強邊緣。

7.3 OpenCV中的Canny邊緣檢測

下載代碼:點擊 這里

OpenCV將上述所有功能都整合到了一個函數(shù)中,即cv.Canny()。我們將看看如何使用它。第一個參數(shù)是我們的輸入圖像。第二和第三個參數(shù)分別是minValmaxVal。第四個參數(shù)是aperture_size。它是用于查找圖像梯度的Sobel內(nèi)核的大小。最后一個參數(shù)是L2gradient,用于指定查找梯度大小的方程。如果該參數(shù)為True,則會使用上述更精確的方程,否則會使用以下函數(shù):Edge_gradient(G)=Gx+Gy 。默認為False。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
 
img = cv.imread('Ronaldo.png', cv.IMREAD_GRAYSCALE)
assert img is not None, 'file could not be read, check with os.path.exists()'
edges = cv.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()

請看下面的結(jié)果:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_26

7.4 額外資源

  1.  維基百科 - Canny邊緣檢測器
  2.  《Canny邊緣檢測教程 (Canny Edge Detection Tutorial)- 比爾·格林 (Bill Green) 2002

7.4.1 練習

  1. 編寫一個小程序來查找Canny邊緣檢測,其閾值可以通過兩個軌跡條來改變。這樣,你就能了解閾值的影響。

八、圖像金字塔

8.1 目標

在本章中:

  • 我們將學習圖像金字塔
  • 我們將使用圖像金字塔創(chuàng)建一種新水果“Orapple”
  • 我們將看到這些函數(shù):cv.pyrUp()、cv.pyrDown()

8.2 原理

通常,我們使用的是恒定大小的圖像。但在某些情況下,我們需要處理不同分辨率的(相同)圖像。例如,當我們在圖像中搜索某個物體(如人臉)時,我們無法確定該物體在圖像中的大小。在這種情況下,我們需要創(chuàng)建一組具有不同分辨率的相同圖像,并在所有圖像中搜索對象。這組不同分辨率的圖像被稱為圖像金字塔(因為當它們堆疊在一起時,分辨率最高的圖像在底部,分辨率最低的圖像在頂部,看起來就像一個金字塔)。

圖像金字塔有兩種。1.高斯金字塔(Gaussian Pyramid) 2.拉普拉斯金字塔(Laplacian Pyramids)

高斯金字塔的高層(低分辨率)是通過移除低層(高分辨率)圖像中的連續(xù)行和列形成的。然后,高層中的每個像素由底層中的5個像素以高斯權(quán)重貢獻而成。這樣,M×N 圖像就變成了 M/2×N/2 圖像。這樣,面積就減少到原來的四分之一。這就是所謂的八度空間。同樣的模式會隨著金字塔的上升而繼續(xù)(即分辨率降低)。同樣,在擴展時,每一層的面積都會變?yōu)樵瓉淼?倍。我們可以使用cv.pyrDown()cv.pyrUp()函數(shù)找到高斯金字塔。

import cv2 as cv

img = cv.imread('Ronaldo.png')
assert img is not None, 'file could not be read, check with os.path.exists()'
lower_reso = cv.pyrDown(img)

cv.imshow('ori', img)
cv.imshow('img', lower_reso)
cv.waitKey(0)

下面是圖像金字塔的2個層次:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_27

現(xiàn)在,您可以使用cv.pyrUp()函數(shù)放大圖像金字塔。

import cv2 as cv

img = cv.imread('Ronaldo.png')
assert img is not None, 'file could not be read, check with os.path.exists()'
higher_reso = cv.pyrUp(img)

cv.imshow('ori', img)
cv.imshow('img', higher_reso)
cv.waitKey(0)

請記住,higher_reso2不等于higher_reso,因為一旦降低分辨率,信息就會丟失。下圖是由上例中最小的圖像與原始圖像對比:

Python OpenCV #4 - OpenCV中的圖像處理(上)_.net_28

拉普拉斯金字塔由高斯金字塔形成。這沒有專屬函數(shù)。拉普拉斯金字塔圖像僅與邊緣圖像相似。其大部分元素為零。它們用于圖像壓縮。拉普拉斯金字塔中的一個層級是由高斯金字塔中的該層級與高斯金字塔中其上層級的擴展版本之間的差值形成的。拉普拉斯金字塔的兩個層次如下所示(調(diào)整對比度以增強內(nèi)容):

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_29

8.3 使用金字塔進行圖像混合

下載代碼:點擊 這里

金字塔的一個應(yīng)用是圖像混合。例如,在圖像拼接中,您需要將兩幅圖像疊加在一起,但由于圖像之間的不連續(xù)性,效果可能并不理想。在這種情況下,使用金字塔進行圖像混合可以實現(xiàn)無縫混合,而不會在圖像中留下太多數(shù)據(jù)。一個經(jīng)典的例子就是橙子和蘋果這兩種水果的混合。請看結(jié)果本身,就會明白我在說什么:

Python OpenCV #4 - OpenCV中的圖像處理(上)_閾值處理_30

請查看附加資源中的第一個參考資料,其中有關(guān)于圖像混合、拉普拉奇金字塔等的完整圖解細節(jié)。簡單來說,可以這樣做:

  1. 加載蘋果和橙子的兩幅圖像
  2. 找到蘋果和橙子的高斯金字塔(在本例中,層次數(shù)為6)
  3. 從高斯金字塔中找出它們的拉普拉斯金字塔
  4. 現(xiàn)在將蘋果的左半部分和橙子的右半部分分別加入拉普拉茨金字塔的各個層中
  5. 最后,從聯(lián)合圖像金字塔中重建原始圖像。

以下是完整代碼。(為簡單起見,每一步都是單獨完成的,這可能會占用更多內(nèi)存。如果你愿意,可以對其進行優(yōu)化)。

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

A = cv.imread('apple.png')
B = cv.imread('orange.png')
assert A is not None, 'file could not be read, check with os.path.exists()'
assert B is not None, 'file could not be read, check with os.path.exists()'

# 為A生成高斯金字塔
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpA.append(G)

# 為B生成高斯金字塔
G = B.copy()
gpB = [G]
for i in range(6):
    G = cv.pyrDown(G)
    gpB.append(G)

# 為A生成拉普拉斯金字塔
lpA = [gpA[5]]

for i in range(5, 0, -1):
    GE = cv.pyrUp(gpA[i])
    GE.resize((gpA[i-1].shape[0], gpA[i-1].shape[1], gpA[i-1].shape[2]))
    L = cv.subtract(gpA[i-1], GE)
    lpA.append(L)

# 為B生成拉普拉斯金字塔
lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv.pyrUp(gpB[i])
    GE.resize((gpA[i-1].shape[0], gpA[i-1].shape[1], gpA[i-1].shape[2]))
    L = cv.subtract(gpB[i-1], GE)
    lpB.append(L)

# 現(xiàn)在在每一層添加圖像的左右兩半
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)

# 現(xiàn)在重構(gòu)
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv.pyrUp(ls_)
    ls_.resize((LS[i].shape[0], LS[i].shape[1], LS[i].shape[2]))
    ls_ = cv.add(ls_, LS[i])

# 圖像與直接連接的每一半
real = np.hstack((A[:, :cols//2], B[:, cols//2:]))

A = A[:, :, ::-1]
B = B[:, :, ::-1]
real = real[:, :, ::-1]
ls_ = ls_[:, :, ::-1]

plt.subplot(221), plt.imshow(A)
plt.title('Apple'), plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(B)
plt.title('Orange'), plt.xticks([]), plt.yticks([])
plt.subplot(223), plt.imshow(real)
plt.title('Direct Connection'), plt.xticks([]), plt.yticks([])
plt.subplot(224), plt.imshow(ls_)
plt.title('Pyramid Blending'), plt.xticks([]), plt.yticks([])
 
plt.show()

8.4 額外資源

  1.  圖像混合

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多