一、更改色彩空間
1.1 目標(biāo)
- 在本教程中,您將學(xué)習(xí)如何將圖像從一種顏色空間轉(zhuǎn)換為另一種顏色空間,例如 BGR ? Gray、BGR ? HSV 等。
- 除此之外,我們還將創(chuàng)建一個(gè)應(yīng)用程序來提取視頻中的彩色對象
- 您將學(xué)習(xí)以下函數(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)換,我們使用標(biāo)記 cv.COLOR_BGR2GRAY 。同樣,對于BGR → HSV,我們使用標(biāo)記 cv.COLOR_BGR2HSV 。要獲取其他標(biāo)記,只需在Python終端中運(yùn)行以下命令:
>>> 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的值與它們進(jìn)行比較,就需要對這些范圍進(jìn)行歸一化處理。
1.3 跟蹤對象
下載代碼:點(diǎn)擊 這里
既然我們已經(jīng)知道如何將BGR圖像轉(zhuǎn)換為HSV,我們就可以用它來提取彩色對象。與BGR色彩空間相比,HSV更容易表示顏色。在我們的應(yīng)用中,我們將嘗試提取一個(gè)藍(lán)色物體。方法如下:
- 提取視頻的每一幀
- 將BGR轉(zhuǎn)換為HSV色彩空間
- 我們對HSV圖像的藍(lán)色范圍進(jìn)行閾值處理
- 現(xiàn)在單獨(dú)提取藍(lán)色對象,我們可以在該圖像上做任何我們想做的事情。
以下是詳細(xì)注釋的代碼:
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中藍(lán)色的范圍
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])
# 對HSV圖像的藍(lán)色閾值進(jìn)行處理
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()
下圖顯示了對藍(lán)色物體的跟蹤:
注意:
圖像中有一些噪點(diǎn)。我們將在后面的章節(jié)中了解如何去除噪點(diǎn)。
這是物體追蹤中最簡單的方法。一旦學(xué)會(huì)了輪廓線的功能,你就可以做很多事情,比如找到物體的中心點(diǎn)并用它來跟蹤物體,在攝像機(jī)前移動(dòng)你的手來繪制圖表,以及其他有趣的事情。
1.4 如何找到要跟蹤的HSV值?
這是在 上發(fā)現(xiàn)的一個(gè)常見問題。其實(shí)非常簡單,您可以使用相同的函數(shù)cv.cvtColor()。無需傳遞圖像,只需傳遞所需的BGR值即可。例如,要查找綠色的HSV值,請?jiān)赑ython終端中執(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 練習(xí)
- 嘗試找到提取多個(gè)彩色對象的方法,例如同時(shí)提取紅色、藍(lán)色和綠色對象。
二、圖像的幾何變換
2.1 目標(biāo)
- 學(xué)習(xí)對圖像應(yīng)用不同的幾何變換,如平移、旋轉(zhuǎn)、仿射變換等。
- 您將看到這些函數(shù):
cv.getPerspectiveTransform
2.2 變換
cv.warpAffine 接收一個(gè)2x3變換矩陣,而cv.warpPerspective 接收一個(gè)3x3變換矩陣作為輸入。
2.2.1 縮放
下載代碼:點(diǎn)擊 這里
縮放就是調(diào)整圖像大小。為此,OpenCV提供了一個(gè)函數(shù)cv.resize() ??梢允謩?dòng)指定圖像大小,也可以指定縮放因子??梢允褂貌煌牟逯捣椒?。首選的插值方法是用于縮小的cv.INTER_AREA ,以及用于縮放的cv.INTER_CUBIC (慢)和cv.INTER_LINEAR 。默認(rèn)情況下,插值方法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 平移
下載代碼:點(diǎn)擊 這里
平移是指物體位置的移動(dòng)。如果知道(x,y)方向上的移動(dòng),并將其設(shè)為 (tx?,ty?)。您可以這樣創(chuàng)建轉(zhuǎn)換矩陣 M:
M=[10?01?tx?ty??]
您可以將其轉(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ù)的第三個(gè)參數(shù)是輸出圖像的大小,其形式應(yīng)為 (width, height)。請記住,width=列數(shù),height=行數(shù)。
請看下面的結(jié)果:
2.2.3 旋轉(zhuǎn)
下載代碼:點(diǎn)擊 這里
圖像旋轉(zhuǎn)一個(gè)角度 θ 是通過變換矩陣實(shí)現(xiàn)的,變換矩陣的形式為:
M=[cosθsinθ??sinθcosθ?]
但是,OpenCV提供了可調(diào)整旋轉(zhuǎn)中心的縮放旋轉(zhuǎn)功能,因此您可以在您喜歡的任何位置進(jìn)行旋轉(zhuǎn)。修改后的變換矩陣如下:
[α?β?βα?(1?α)?center.x?β?center.yβ?center.x+(1?α)?center.y?]
其中:
α=scale?cosθβ=scale?sinθ
要找到這個(gè)變換矩陣,OpenCV提供了一個(gè)函數(shù)cv.getRotationMatrix2D 。請看下面的示例,該示例將圖像以中心為基準(zhǔn)旋轉(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是坐標(biāo)限制。
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é)果吧:
2.2.4 映射變換
下載代碼:點(diǎn)擊 這里
在映射變換中,原始圖像中的所有平行線在輸出圖像中仍然是平行的。要找到變換矩陣,我們需要輸入圖像中的三個(gè)點(diǎn)及其在輸出圖像中的對應(yīng)位置。然后,cv.getAffineTransform 將創(chuàng)建一個(gè)2x3矩陣,并將其傳遞給cv.warpAffine 。
請看下面的示例,以及我選擇的點(diǎn)(用綠色標(biāo)出):
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é)果吧:
2.2.5 透視變換
下載代碼:點(diǎn)擊 這里
透視變換需要一個(gè)3x3變換矩陣。即使在變換后,直線仍然是直線。要找到這個(gè)變換矩陣,需要輸入圖像上的4個(gè)點(diǎn)和輸出圖像上的相應(yīng)點(diǎn)。在這4個(gè)點(diǎn)中,有3個(gè)點(diǎn)不應(yīng)相交。然后就可以通過函數(shù)cv.getPerspectiveTransform 找到變換矩陣。然后利用這個(gè)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é)果:
2.3 額外資源
《計(jì)算機(jī)視覺:算法與應(yīng)用 (Computer Vision: Algorithms and Applications)》- 理查德·塞利斯基 (Richard Szeliski)
三、圖像閾值處理
3.1 目標(biāo)
- 在本教程中,您將學(xué)習(xí)簡單閾值處理、自適應(yīng)閾值處理和Otsu閾值處理。
- 您還將學(xué)習(xí)函數(shù)
cv.threshold 和cv.adaptiveThreshold 。
3.2 簡單閾值處理
下載代碼:點(diǎn)擊 這里
這里的問題很簡單。對每個(gè)像素都應(yīng)用相同的閾值。如果像素值小于閾值,則設(shè)置為0,否則設(shè)置為最大值。函數(shù)cv.threshold 用于應(yīng)用閾值處理。第一個(gè)參數(shù)是源圖像,應(yīng)為灰度圖像。第二個(gè)參數(shù)是用于對像素值進(jìn)行分類的閾值。第三個(gè)參數(shù)是最大值,分配給超過閾值的像素值。OpenCV提供不同類型的閾值處理,由函數(shù)的第四個(gè)參數(shù)給出。如上所述的基本閾值是通過使用cv.THRESH_BINARY 類型完成的。所有簡單閾值類型均為:
- cv.THRESH_BINARY
- cv.THRESH_BINARY_INV
- cv.THRESH_TRUNC
- cv.THRESH_TOZERO
- cv.THRESH_TOZERO_INV
有關(guān)差異,請參閱各類型的文檔。
該方法返回兩個(gè)輸出。第一個(gè)輸出是使用的閾值,第二個(gè)輸出是經(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文檔。
代碼會(huì)產(chǎn)生這樣的結(jié)果:
3.3 自適應(yīng)閾值
下載代碼:點(diǎn)擊 這里
在上一節(jié)中,我們使用一個(gè)全局值作為閾值。但這并非在所有情況下都適用,例如,如果圖像的不同區(qū)域有不同的光照條件。在這種情況下,自適應(yīng)閾值法就能提供幫助。在這里,算法會(huì)根據(jù)像素周圍的一個(gè)小區(qū)域來確定該像素的閾值。這樣,我們就能為同一圖像的不同區(qū)域獲得不同的閾值,從而為光照條件不同的圖像提供更好的效果。
除上述參數(shù)外,cv.adaptiveThreshold 方法還需要三個(gè)輸入?yún)?shù):
- adaptiveMethod決定如何計(jì)算閾值:
cv.ADAPTIVE_THRESH_MEAN_C :閾值是鄰域面積的平均值減去常數(shù)C
cv.ADAPTIVE_THRESH_GAUSSIAN_C :閾值是鄰域值的高斯加權(quán)和減去常數(shù)C
- blockSize決定了鄰近區(qū)域的大小
- C是一個(gè)常數(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é)果:
3.4 Otsu二值化
下載代碼:點(diǎn)擊 這里
在全局閾值法中,我們使用一個(gè)任意選擇的值作為閾值。相比之下,Otsu方法無需選擇數(shù)值,而是自動(dòng)確定。
考慮到圖像中只有兩個(gè)不同的圖像值(雙峰圖像),直方圖中只有兩個(gè)峰值。一個(gè)好的閾值應(yīng)該在這兩個(gè)值的中間。同樣,Otsu方法也能從圖像直方圖中確定一個(gè)最佳的全局閾值。
為此,我們使用了cv.threshold() 函數(shù),并將cv.THRESH_OTSU 作為額外的標(biāo)志傳入。閾值可以任意選擇。然后,算法會(huì)找出最佳閾值,并作為第一個(gè)輸出返回。
請看下面的示例。輸入圖像是一張有噪聲的圖像。在第一種情況下,應(yīng)用值為127的全局閾值。第二種情況是直接應(yīng)用大津閾值。在第三種情況下,首先使用5x5高斯內(nèi)核對圖像進(jìn)行濾波以去除噪聲,然后再應(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é)果:
3.4.1 Otsu二值化是如何實(shí)現(xiàn)的?
本節(jié)演示了大津二值化的Python實(shí)現(xiàn),以說明其實(shí)際工作原理。如果您不感興趣,可以跳過這部分。
由于我們處理的是雙模態(tài)圖像,大津算法試圖找到一個(gè)閾值 (t),使關(guān)系式給出的加權(quán)類內(nèi)方差最?。?/p>
σw2?(t)=q1?(t)σ12?(t)+q2?(t)σ22?(t)
其中:
q1?(t)=i=1∑t?P(i)μ1?(t)=i=1∑t?q1?(t)iP(i)?σ12?(t)=i=1∑t?[i?μ1?(t)]2q1?(t)P(i)??&q2?(t)=q1?(t)=i=t+1∑I?P(i)&μ2?(t)=i=t+1∑I?q2?(t)iP(i)?&σ22?(t)=i=t+1∑I?[i?μ2?(t)]2q2?(t)P(i)??
它實(shí)際上是在兩個(gè)峰值之間找到一個(gè)t值,使兩個(gè)類別的方差都最小。它可以用Python簡單實(shí)現(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
# 計(jì)算最小化函數(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 額外資源
- 《數(shù)字圖像處理 (Digital Image Processing)》,拉斐爾·C·岡薩雷斯 (Rafael C. Gonzalez)
3.5.1 練習(xí)
- Otsu二值化有一些優(yōu)化方法。您可以搜索并實(shí)施。
四、圖像平滑
4.1 目標(biāo)
學(xué)習(xí):
- 使用各種低通濾波器模糊圖像
- 為圖像應(yīng)用自定義濾波器(二維卷積)
4.2 二維卷積(圖像濾波)
下載代碼:點(diǎn)擊 這里
與一維信號一樣,圖像也可以使用各種低通濾波器(LPF)、高通濾波器(HPF)等進(jìn)行濾波。LPF有助于去除噪音、模糊圖像等。HPF濾波器有助于發(fā)現(xiàn)圖像中的邊緣。
OpenCV提供了一個(gè)函數(shù)cv.filter2D() ,用于將核與圖像卷積。例如,我們將嘗試在圖像上使用平均濾波器。一個(gè)5x5的平均濾波核將如下所示:
k=251????11111?11111?11111?11111?11111????
操作過程如下:在一個(gè)像素點(diǎn)上方保留此核,將此核下方的所有25個(gè)像素點(diǎn)相加,取平均值,然后用新的平均值替換中心像素點(diǎn)。圖像中的所有像素都將繼續(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é)果:
4.3 圖像模糊(圖像平滑)
下載代碼:點(diǎn)擊 這里
圖像模糊是通過將圖像與低通濾波器核進(jìn)行卷積來實(shí)現(xiàn)的。它可用于去除噪點(diǎn)。它實(shí)際上是去除圖像中的高頻內(nèi)容(如:噪點(diǎn)、邊緣)。因此,在這一操作中,邊緣會(huì)被模糊一些(也有不模糊邊緣的模糊技術(shù))。OpenCV提供了四種主要的模糊技術(shù)。
4.3.1 平均化
這是通過用歸一化盒狀濾波器對圖像進(jìn)行卷積來實(shí)現(xiàn)的。它只是取核區(qū)域下所有像素的平均值,并替換中心元素。這由函數(shù)cv.blur() 或cv.boxFilter() 完成。有關(guān)內(nèi)核的更多詳情,請查閱文檔。我們應(yīng)該指定內(nèi)核的寬度和高度。一個(gè)3x3歸一化盒狀濾波器看起來如下:
k=91????111?111?111????
注意:
如果不想使用規(guī)范化方框過濾器,請使用cv.boxFilter() 。向函數(shù)傳遞一個(gè)參數(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é)果:
4.3.2 高斯模糊
在這種方法中,使用的不是盒式濾波器,而是高斯核。它是通過函數(shù)cv.GaussianBlur() 完成的。我們應(yīng)指定核的寬度和高度,它們應(yīng)為正奇數(shù)。我們還應(yīng)該指定X和Y方向的標(biāo)準(zhǔn)偏差,分別為sigmaX 和sigmaY 。如果只指定了sigmaX,則sigmaY與sigmaX相同。如果兩者都為零,則根據(jù)內(nèi)核大小計(jì)算。高斯模糊對于去除圖像中的高斯噪點(diǎn)非常有效。
如果需要,可以使用函數(shù)cv.getGaussianKernel() 創(chuàng)建高斯核。
上述代碼可以修改為高斯模糊:
blur = cv.GaussianBlur(img, (5, 5), 0)
結(jié)果:
4.3.3 中值模糊
在這里,函數(shù)cv.medianBlur() 取核區(qū)域下所有像素的中值,并用該中值替換中心元素。這對消除圖像中的椒鹽噪聲非常有效。有趣的是,在上述濾波器中,中心元素是一個(gè)新計(jì)算出的值,可能是圖像中的像素值,也可能是一個(gè)新值。但在中值模糊濾波器中,中心元素總是由圖像中的某個(gè)像素值代替。它能有效減少噪音。其內(nèi)核大小應(yīng)為正奇數(shù)整數(shù)。
在本演示中,我在原始圖像中添加了50%的噪點(diǎn),并應(yīng)用了中值模糊技術(shù)??纯唇Y(jié)果如何:
median = cv.medianBlur(img, 5)
結(jié)果:
4.3.4 雙邊濾波
cv.bilateralFilter() 能有效去除噪點(diǎn),同時(shí)保持邊緣銳利。但與其他濾波器相比,其運(yùn)行速度較慢。我們已經(jīng)看到,高斯濾波器會(huì)獲取像素周圍的鄰域,并找出其高斯加權(quán)平均值。這種高斯濾波器僅是一個(gè)空間函數(shù),即在濾波時(shí)只考慮附近的像素。它不考慮像素是否具有幾乎相同的強(qiáng)度。它不考慮像素是否是邊緣像素。因此,它也會(huì)模糊邊緣,這是我們不希望看到的。
雙邊濾波也需要一個(gè)空間高斯濾波器,但多了一個(gè)像素差函數(shù)的高斯濾波器。空間的高斯函數(shù)確保只有附近的像素才會(huì)被考慮進(jìn)行模糊處理,而強(qiáng)度差的高斯函數(shù)則確保只有那些與中心像素強(qiáng)度相似的像素才會(huì)被考慮進(jìn)行模糊處理。因此,它可以保留邊緣,因?yàn)檫吘壍南袼貢?huì)有較大的強(qiáng)度變化。
下面的示例展示了雙邊濾波器的使用(有關(guān)參數(shù)的詳細(xì)信息,請?jiān)L問文檔)。
blur = cv.bilateralFilter(img, 9, 75, 75)
結(jié)果:
看,表面的紋理消失了,但邊緣還保留著。
4.4 額外資源
- 有關(guān)雙邊濾波的詳細(xì)信息
五、形態(tài)變換
5.1 目標(biāo)
在本章:
- 我們將學(xué)習(xí)不同的形態(tài)學(xué)操作,如腐蝕、膨脹、開運(yùn)算、閉運(yùn)算等。
- 我們將看到不同的函數(shù),如:cv.erode()、cv.dilate()、cv.morphologyEx()等。
5.2 理論
形態(tài)變換是基于圖像形狀的一些簡單操作。它通常在二值圖像上執(zhí)行。它需要兩個(gè)輸入,一個(gè)是原始圖像,另一個(gè)稱為結(jié)構(gòu)元素或內(nèi)核,它決定了操作的性質(zhì)。兩種基本形態(tài)學(xué)運(yùn)算符是腐蝕和膨脹。然后,它的變體形式如開運(yùn)算、閉運(yùn)算、梯度等也會(huì)發(fā)揮作用。我們將借助下圖逐一了解它們:
5.2.1 腐蝕
下載代碼:點(diǎn)擊 這里
腐蝕的基本原理和土壤腐蝕一樣,只是會(huì)腐蝕掉前景物體的邊界(始終盡量保持前景物體為白色)。那么它的作用是什么呢?內(nèi)核在圖像中滑動(dòng)(如同二維卷積)。只有當(dāng)核下的所有像素都是1時(shí),原始圖像中的一個(gè)像素(1或0)才會(huì)被視為1,否則就會(huì)被腐蝕(變?yōu)?)。
因此,根據(jù)內(nèi)核的大小,邊界附近的所有像素都會(huì)被丟棄。這樣,前景物體的厚度或大小就會(huì)減小,或者說圖像中的白色區(qū)域就會(huì)減小。這對于去除小的白噪聲(正如我們在色彩空間章節(jié)中所看到的)、分離兩個(gè)相連的物體等都很有用。
舉例來說,在這里我將使用一個(gè)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)
5.2.2 膨脹
下載代碼:點(diǎn)擊 這里
它與腐蝕正好相反。在這里,如果內(nèi)核下至少有一個(gè)像素為1,則該像素元素為1。因此,它增加了圖像中的白色區(qū)域或前景物體的大小。通常,在去除噪點(diǎn)等情況下,侵蝕之后是擴(kuò)張。因?yàn)榍治g可以去除白色噪點(diǎn),但同時(shí)也會(huì)縮小我們的對象。因此,我們要擴(kuò)張它。由于噪點(diǎn)消失了,它們不會(huì)再出現(xiàn),但我們的對象面積卻增加了。它在連接物體的破碎部分時(shí)也很有用。
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)
5.2.3 開運(yùn)算
下載代碼:點(diǎn)擊 這里
開運(yùn)算是腐蝕后膨脹的另一個(gè)名稱。如上文所述,它在去除噪點(diǎn)方面非常有用。這里我們使用函數(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é)果:
5.2.4 閉運(yùn)算
下載代碼:點(diǎn)擊 這里
閉運(yùn)算與開運(yùn)算、膨脹和腐蝕相反。它適用于封閉前景物體內(nèi)部的小孔或物體上的小黑點(diǎn)。
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é)果:
5.2.5 形態(tài)梯度
下載代碼:點(diǎn)擊 這里
這是圖像擴(kuò)張和侵蝕的區(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é)果:
5.2.6 禮帽
下載代碼:點(diǎn)擊 這里
它是輸入圖像與圖像開口之間的差值。下面是一個(gè)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()
5.2.7 黑帽
下載代碼:點(diǎn)擊 這里
它是輸入圖像與輸入圖像的閉合差。
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é)果:
5.3 結(jié)構(gòu)元素
在前面的示例中,我們借助Numpy手動(dòng)創(chuàng)建了一個(gè)結(jié)構(gòu)元素。它是矩形的。但在某些情況下,您可能需要橢圓形/或圓形的內(nèi)核。為此,OpenCV提供了一個(gè)函數(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 額外資源
- HIPR2形態(tài)操作
六、圖像漸變
6.1 目標(biāo)
在本章中,我們將學(xué)習(xí):
- 查找圖像梯度、邊緣等
- 我們將看到以下函數(shù):
cv.Sobel() 、cv.Scharr() 、cv.Laplacian() 等
6.2 原理
OpenCV提供三種梯度濾波器或高通濾波器:Sobel、Scharr和Laplacian。我們將逐一介紹。
6.2.1 Sobel和Scharr梯度濾波器
Sobel運(yùn)算符是一種高斯平滑加微分的聯(lián)合運(yùn)算,因此對噪點(diǎn)的干擾能力更強(qiáng)。您可以指定導(dǎo)數(shù)的方向,垂直或水平(分別通過參數(shù)yorder 和xorder )。還可以通過參數(shù)ksize 指定核的大小。如果ksize=-1,就會(huì)使用3x3 Scharr濾波器,效果比3x3 Sobel濾波器更好。有關(guān)使用的核,請參閱文檔。
6.2.2 Laplacian(拉普拉斯)梯度濾波器
它通過關(guān)系式 Δsrc=?x2?2src?+?y2?2src? 計(jì)算圖像的拉普拉斯值,其中每個(gè)導(dǎo)數(shù)都是通過Sobel導(dǎo)數(shù)求得的。如果ksize=1,則使用以下核進(jìn)行濾波:
kernel=???010?1?41?010????
6.3 代碼
下載:點(diǎn)擊 這里
下面的代碼在一張圖中顯示了所有運(yùn)算符。所有內(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é)果:
6.4 一個(gè)重要事項(xiàng)!
下載代碼:點(diǎn)擊 這里
在上一個(gè)例子中,輸出數(shù)據(jù)類型是cv.CV_8U 或np.uint8 。但這有一個(gè)小問題。從黑色到白色的轉(zhuǎn)換被視為正斜率(它具有正值),而從白色到黑色的轉(zhuǎn)換被視為負(fù)斜率(它具有負(fù)值)。因此,在將數(shù)據(jù)轉(zhuǎn)換為np.uint8時(shí),所有負(fù)斜率都會(huì)變?yōu)榱?。簡單地說,你會(huì)錯(cuò)過這條邊。
如果要檢測兩條邊,更好的辦法是將輸出數(shù)據(jù)類型保持為更高的形式,如cv.CV_16S 、cv.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é)果:
七、邊緣檢測
7.1 目標(biāo)
在本章中,我們將學(xué)習(xí):
- Canny邊緣檢測的概念
- 用于此功能的OpenCV函數(shù):
cv.Canny()
7.2 原理
Canny邊緣檢測是一種流行的邊緣檢測算法。它是由John F.Canny于1986年開發(fā)。
-
它是一種多階段算法,我們將逐一介紹。
-
降噪
由于邊緣檢測容易受到圖像中噪聲的影響,因此第一步是使用5x5高斯濾波器去除圖像中的噪聲。這一點(diǎn)我們在前面的章節(jié)中已經(jīng)介紹過。
-
查找圖像的強(qiáng)度梯度
然后用Sobel核對平滑后的圖像進(jìn)行水平和垂直方向的濾波,得到水平方向( Gx? )和垂直方向( Gy? )的初導(dǎo)數(shù)。從這兩幅圖像中,我們可以找到每個(gè)像素的邊緣梯度和方向,如下所示:
Edge_gradient(G)=Gx2?+Gy2??Angle(θ)=tan?1(Gx?Gy??)
梯度方向總是垂直于邊緣。它被圓化成四個(gè)角度中的一個(gè),分別代表垂直、水平和兩個(gè)對角線方向。
- 非最大抑制
得到梯度大小和方向后,要對圖像進(jìn)行全面掃描,以去除任何可能不構(gòu)成邊緣的無用像素。為此,要檢查每個(gè)像素在梯度方向上是否是其附近的局部最大值。請看下圖:
A點(diǎn)位于邊緣上(垂直方向)。梯度方向?yàn)檫吘壍姆ň€方向。B點(diǎn)和C點(diǎn)在梯度方向上。因此,A點(diǎn)將與B點(diǎn)和C點(diǎn)一起檢查,看是否形成局部最大值。如果是,則進(jìn)入下一階段,否則將被抑制(置零)。
簡而言之,得到的結(jié)果就是一幅具有“薄邊緣”的二值圖像。
- 滯后閾值
這一階段決定哪些是真正的邊,哪些不是。為此,我們需要兩個(gè)閾值,即minVal 和maxVal 。強(qiáng)度梯度大于maxVal的邊緣肯定是邊緣,而強(qiáng)度梯度小于minVal的邊緣肯定不是邊緣,因此會(huì)被丟棄。位于這兩個(gè)閾值之間的邊緣會(huì)根據(jù)其連接性被歸類為邊緣或非邊緣。如果它們與“確定為邊緣”的像素相連,則被視為邊緣的一部分。否則,它們也會(huì)被丟棄。請看下圖:
邊A高于maxVal,因此被視為“確定邊”。雖然邊C低于maxVal,但它與邊A相連,因此也被視為有效邊,從而得到完整曲線。但是,邊B雖然高于minVal且與邊C在同一區(qū)域,但它與任何“確定邊”都不相連,因此會(huì)被舍棄。因此,我們必須相應(yīng)地選擇minVal和maxVal,以得到正確的結(jié)果,這一點(diǎn)非常重要。
這一階段還能消除小像素噪點(diǎn),前提是邊緣是長線。
因此,我們最終得到的是圖像中的強(qiáng)邊緣。
7.3 OpenCV中的Canny邊緣檢測
下載代碼:點(diǎn)擊 這里
OpenCV將上述所有功能都整合到了一個(gè)函數(shù)中,即cv.Canny() 。我們將看看如何使用它。第一個(gè)參數(shù)是我們的輸入圖像。第二和第三個(gè)參數(shù)分別是minVal 和maxVal 。第四個(gè)參數(shù)是aperture_size 。它是用于查找圖像梯度的Sobel內(nèi)核的大小。最后一個(gè)參數(shù)是L2gradient ,用于指定查找梯度大小的方程。如果該參數(shù)為True,則會(huì)使用上述更精確的方程,否則會(huì)使用以下函數(shù):Edge_gradient(G)=∣Gx?∣+∣Gy?∣ 。默認(rèn)為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é)果:
7.4 額外資源
- 維基百科 - Canny邊緣檢測器
- 《Canny邊緣檢測教程 (Canny Edge Detection Tutorial)》- 比爾·格林 (Bill Green) 2002
7.4.1 練習(xí)
- 編寫一個(gè)小程序來查找Canny邊緣檢測,其閾值可以通過兩個(gè)軌跡條來改變。這樣,你就能了解閾值的影響。
八、圖像金字塔
8.1 目標(biāo)
在本章中:
- 我們將學(xué)習(xí)圖像金字塔
- 我們將使用圖像金字塔創(chuàng)建一種新水果“Orapple”
- 我們將看到這些函數(shù):
cv.pyrUp() 、cv.pyrDown()
8.2 原理
通常,我們使用的是恒定大小的圖像。但在某些情況下,我們需要處理不同分辨率的(相同)圖像。例如,當(dāng)我們在圖像中搜索某個(gè)物體(如人臉)時(shí),我們無法確定該物體在圖像中的大小。在這種情況下,我們需要?jiǎng)?chuàng)建一組具有不同分辨率的相同圖像,并在所有圖像中搜索對象。這組不同分辨率的圖像被稱為圖像金字塔(因?yàn)楫?dāng)它們堆疊在一起時(shí),分辨率最高的圖像在底部,分辨率最低的圖像在頂部,看起來就像一個(gè)金字塔)。
圖像金字塔有兩種。1.高斯金字塔(Gaussian Pyramid) 2.拉普拉斯金字塔(Laplacian Pyramids)
高斯金字塔的高層(低分辨率)是通過移除低層(高分辨率)圖像中的連續(xù)行和列形成的。然后,高層中的每個(gè)像素由底層中的5個(gè)像素以高斯權(quán)重貢獻(xiàn)而成。這樣,M×N 圖像就變成了 M/2×N/2 圖像。這樣,面積就減少到原來的四分之一。這就是所謂的八度空間。同樣的模式會(huì)隨著金字塔的上升而繼續(xù)(即分辨率降低)。同樣,在擴(kuò)展時(shí),每一層的面積都會(huì)變?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個(gè)層次:
現(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,因?yàn)橐坏┙档头直媛?,信息就?huì)丟失。下圖是由上例中最小的圖像與原始圖像對比:
拉普拉斯金字塔由高斯金字塔形成。這沒有專屬函數(shù)。拉普拉斯金字塔圖像僅與邊緣圖像相似。其大部分元素為零。它們用于圖像壓縮。拉普拉斯金字塔中的一個(gè)層級是由高斯金字塔中的該層級與高斯金字塔中其上層級的擴(kuò)展版本之間的差值形成的。拉普拉斯金字塔的兩個(gè)層次如下所示(調(diào)整對比度以增強(qiáng)內(nèi)容):
8.3 使用金字塔進(jìn)行圖像混合
下載代碼:點(diǎn)擊 這里
金字塔的一個(gè)應(yīng)用是圖像混合。例如,在圖像拼接中,您需要將兩幅圖像疊加在一起,但由于圖像之間的不連續(xù)性,效果可能并不理想。在這種情況下,使用金字塔進(jìn)行圖像混合可以實(shí)現(xiàn)無縫混合,而不會(huì)在圖像中留下太多數(shù)據(jù)。一個(gè)經(jīng)典的例子就是橙子和蘋果這兩種水果的混合。請看結(jié)果本身,就會(huì)明白我在說什么:
請查看附加資源中的第一個(gè)參考資料,其中有關(guān)于圖像混合、拉普拉奇金字塔等的完整圖解細(xì)節(jié)。簡單來說,可以這樣做:
- 加載蘋果和橙子的兩幅圖像
- 找到蘋果和橙子的高斯金字塔(在本例中,層次數(shù)為6)
- 從高斯金字塔中找出它們的拉普拉斯金字塔
- 現(xiàn)在將蘋果的左半部分和橙子的右半部分分別加入拉普拉茨金字塔的各個(gè)層中
- 最后,從聯(lián)合圖像金字塔中重建原始圖像。
以下是完整代碼。(為簡單起見,每一步都是單獨(dú)完成的,這可能會(huì)占用更多內(nèi)存。如果你愿意,可以對其進(jìn)行優(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 額外資源
- 圖像混合
|