一、圖像的基本操作
1.1 目標
學(xué)會:
- 訪問像素值并修改它們
- 訪問圖像屬性
- 設(shè)置ROI
- 分割和合并圖像
本節(jié)中幾乎所有的操作都主要與Numpy而不是OpenCV相關(guān)。要使用OpenCV編寫更好的優(yōu)化代碼,需要對Numpy有很好的了解。
(示例將在Python終端中顯示,因為它們中的大多數(shù)只是單行代碼)
1.2 縮放和修改像素值
下載代碼:點擊 這里
讓我們先加載一個彩色圖像:
>>> import numpy as np
>>> import cv2 as cv
>>> img = cv.imread('Ronaldo.png')
>>> assert img is not None, "文件無法讀取,使用os.path.exists()檢查"
可以通過像素的行和列坐標來訪問像素值。對于BGR圖像,它返回一個藍色、綠色和紅色值的數(shù)組。對于灰度圖像,僅返回相應(yīng)的強度。
>>> px = img[100, 100]
>>> print(px)
[25 26 22]
# 僅訪問藍色像素
>>> blue = img[100, 100, 0]
>>> print(blue)
25
您可以用同樣的方法修改像素值。
>>> img[100, 100] = [255, 255, 255]
>>> print(img[100, 100])
[255 255 255]
警告:
Numpy是一個用于快速數(shù)組計算的優(yōu)化庫。因此,簡單地訪問每個像素值并修改它將非常緩慢,并且不鼓勵。
注意:
上面的方法通常用于選擇數(shù)組的一個區(qū)域,比如前5行和后3列。對于單個像素訪問,Numpy數(shù)組方法array.item()和array.itemset()被認為是更好的。
它們總是返回一個標量,但是,如果你想訪問所有的B、G、R值,你需要為每個值分別調(diào)用array.item()。
更好的像素訪問和編輯方法:
# 訪問RED值
>>> img.item(10, 10, 2)
34
# 修改RED值
>>> img.itemset((10, 10, 2), 100)
>>> img.item(10, 10, 2)
100
1.3 圖像屬性
下載代碼:點擊 這里
加載另一個彩色圖像:
>>> import numpy as np
>>> import cv2 as cv
>>> img = cv.imread('Ronaldo2.jpg')
>>> assert img is not None, "文件無法讀取,使用os.path.exists()檢查"
圖像屬性包括行數(shù)、列數(shù)和通道數(shù);圖像數(shù)據(jù)類型;像素數(shù)等。
圖像的形狀通過img.shape 訪問。它返回一個包含行數(shù)、列數(shù)和通道數(shù)的元組(如果圖像是彩色的):
>>> print(img.shape)
(423, 634, 3)
注意:
如果圖像是灰度的,返回的元組只包含行數(shù)和列數(shù),因此這是檢查加載的圖像是灰度還是彩色的好方法。
img.size 訪問的像素總數(shù):
>>> print(img.size)
804546
圖像數(shù)據(jù)類型通過img.dtype 獲?。?/p>
>>> print(img.dtype)
uint8
注意:
img.dtype在調(diào)試時非常重要,因為OpenCV-Python代碼中的大量錯誤都是由無效的數(shù)據(jù)類型引起的。
1.4 圖像和ROI
下載代碼:點擊 這里
有時,您需要對圖像的某些區(qū)域進行檢測。在圖像中檢測眼睛時,首先要對整個圖像進行人臉檢測。獲得人臉后,我們只選擇人臉區(qū)域,然后在該區(qū)域內(nèi)搜索眼睛,而不是搜索整個圖像。
它提高了準確性,因為眼睛總是在臉上 ?? 和性能(因為我們在一個小區(qū)域內(nèi)搜索)。
ROI再次使用Numpy索引獲得。在這里,我選擇了Ronaldo的膝蓋部分,并將其復(fù)制到圖像中的另一個區(qū)域:
>>> ball = img[298:354, 366:418]
>>> img[148:360, 216:424] = ball
查看如下結(jié)果:
1.5 分割和合并圖像通道
有時您需要分別處理圖像的B、G、R通道。在這種情況下,您需要將BGR圖像拆分為單個通道。在其他情況下,您可能需要連接這些單獨的通道以創(chuàng)建BGR映像。您可以通過以下方式簡單地做到這一點:
>>> b, g, r = cv.split(img)
>>> img = cv.merge((b, g, r))
或者
假設(shè)您要將所有紅色像素設(shè)置為零——您不需要首先拆分通道。Numpy索引更快:
警告:
cv.split()是一個代價很高的操作(就時間而言)。所以只在必要時使用它,否則就用Numpy索引。
1.6 為圖像制作邊框(填充)
下載代碼:點擊 這里
如果您想在圖像周圍創(chuàng)建一個邊框,比如一個相框,您可以使用cv.copyMakeBorder() 。但它有更多的卷積運算,零填充等應(yīng)用程序。此函數(shù)采用以下參數(shù):
- src :輸入圖像
- top, bottom, left, right :相應(yīng)方向上的像素數(shù)的邊框?qū)挾?/li>
- borderType :定義要添加的邊框類型的標志。它可以是以下類型:
cv.BORDER_CONSTANT - 添加一個恒定顏色的邊框。該值應(yīng)作為下一個參數(shù)給出。
cv.BORDER_REFLECT - Border將是邊框元素的鏡像反射,如下所示: fedcba|abcdefgh|hgfedcb
cv.BORDER_REFLECT_101 或cv.BORDER_DEFAULT - 與上述相同,但略有變化,如下所示: gfedcb|abcdefgh|gfedcba
cv.BORDER_REPLICATE - 最后一個元素被復(fù)制,如下所示: aaaaaa|abcdefgh|hhhhhhh
cv.BORDER_WRAP - 無法解釋,它看起來像這樣: cdefgh|abcdefgh|abcdefg
- value :如果邊框類型為
cv.BORDER_CONSTANT ,則邊框的顏色
下面是一個示例代碼,演示了所有這些邊框類型,以便更好地理解:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255, 0, 0]
img1 = cv.imread('opencv-logo.png')
assert img1 is not None, "文件無法讀取,使用os.path.exists()檢查"
replicate = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_CONSTANT, value=BLUE)
plt.subplot(231), plt.imshow(img1, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()
請參見下面的結(jié)果。(圖像是用matplotlib顯示的。因此,紅色和藍色通道將互換):
二、圖像的算術(shù)運算
2.1 目標
- 學(xué)習(xí)圖像上的幾種算術(shù)運算,如加法,減法,按位運算等。
- 學(xué)習(xí)這些函數(shù):
cv.add() 、cv.addWeighted() 等。
2.2 圖像疊加
下載代碼:點擊 這里
您可以使用OpenCV函數(shù)cv.add()添加兩個圖像,或者簡單地使用numpy操作res = img1 + img2 。兩個圖像應(yīng)該具有相同的深度和類型,或者第二個圖像可以只是一個標量值。
注意:
OpenCV加法和Numpy加法之間有區(qū)別。OpenCV加法是飽和運算,而Numpy加法是模運算。
例如,請看下面的示例:
>>> x = np.uint8([250])
>>> y = np.uint8([10])
>>> print(cv.add(x, y)) # 250+10 = 260 => 255
[[260.]
[ 0.]
[ 0.]
[ 0.]]
>>> print(x+y) # 250+10 = 260 % 256 = 4
[4]
當你添加兩個圖像時,這將更加明顯。堅持使用OpenCV函數(shù),因為它們會提供更好的結(jié)果。
2.3 圖像混合
下載代碼:點擊 這里
這也是圖像相加,但不同的權(quán)重被賦予圖像,以給出混合或透明的感覺。按照以下公式添加圖像:
g(x)=(1?α)f0?(x)+αf1?(x)
通過改變α的值(0→1),您可以在一個圖像到另一個圖像之間執(zhí)行很酷的過渡。
在這里,我把兩個圖像融合在一起。第一個圖像的權(quán)重為0.7,第二個圖像的權(quán)重為0.3。cv.addWeighted() 將以下等式應(yīng)用于圖像:
dst=α?img1+β?img2+γ
這里γ取0。
import numpy as np
import cv2 as cv
img1 = cv.imread('ml.jpg')
img2 = cv.imread('opencv-logo2.png')
assert img1 is not None, "文件無法讀取,使用os.path.exists()檢查"
assert img2 is not None, "文件無法讀取,使用os.path.exists()檢查"
dst = cv.addWeighted(img1, 0.7, img2, 0.3, 0)
cv.imshow('dst', dst)
cv.waitKey(0)
cv.destroyAllWindows()
請看下面的結(jié)果:
2.4 按位運算
下載代碼:點擊 這里
這包括按位AND、OR、NOT和XOR運算。在提取圖像的任何部分(我們將在接下來的章節(jié)中看到),定義和處理非矩形ROI等時,它們都非常有用。
下面我們將看到一個如何改變圖像特定區(qū)域的示例。
我想把OpenCV徽標放在圖像上方。如果我添加兩個圖像,它會改變顏色。如果我混合它們,我會得到透明的效果。但我希望它不透明。如果它是一個矩形區(qū)域,我可以像上一章那樣使用ROI。
但是OpenCV的標志不是矩形的。所以你可以用按位操作來做,如下所示:
import numpy as np
import cv2 as cv
# 加載兩個圖片
img1 = cv.imread('Ronaldo3.png')
img2 = cv.imread('opencv-logo2.png')
assert img1 is not None, "文件無法讀取,使用os.path.exists()檢查"
assert img2 is not None, "文件無法讀取,使用os.path.exists()檢查"
# 我想把標志放在左上角,所以我創(chuàng)建ROI
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
# 現(xiàn)在創(chuàng)建一個logo蒙版,同時創(chuàng)建其反向蒙版
img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# 現(xiàn)在涂黑ROI中的logo區(qū)域
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
# 從logo圖像中只提取logo區(qū)域。
img2_fg = cv.bitwise_and(img2, img2, mask=mask)
# 在ROI中加入logo并修改主圖像
dst = cv.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst
cv.imshow('res', img1)
cv.waitKey(0)
cv.destroyAllWindows()
請看下面的結(jié)果。左圖顯示了我們創(chuàng)建的掩碼。右圖顯示最終結(jié)果。為了更好地理解,顯示上面代碼中的所有中間圖像,特別是img1_bg和img2_fg。
2.5 額外資源
2.5.1 練習(xí)
- 使用
cv.addWeighted 函數(shù)創(chuàng)建文件夾中圖像的幻燈片放映,圖像之間平滑過渡
三、效能衡量和改進技術(shù)
3.1 目標
在圖像處理中,由于每秒要處理大量操作,因此代碼不僅要提供正確的解決方案,還要以最快的速度提供解決方案。因此,在本章中,你將學(xué)習(xí)
- 衡量代碼性能。
- 提高代碼性能的一些技巧。
- 您將看到這些函數(shù):
cv.getTickCount 、cv.getTickFrequency 等。
除了OpenCV,Python還提供了一個模塊 time ,這有助于測量執(zhí)行時間。另一個模塊 profile 有助于獲得關(guān)于代碼的詳細報告,例如代碼中每個函數(shù)花費了多少時間,函數(shù)被調(diào)用了多少次等。但是,如果您使用IPython,所有這些功能都以用戶友好的方式集成。
我們將看到一些重要的,更多的細節(jié),請查看本節(jié)的“額外資源”。
3.2 使用OpenCV衡量性能
下載代碼:點擊 這里
函數(shù)cv.getTickCount 返回從參考事件(如機器打開的時刻)到調(diào)用該函數(shù)的時刻的時鐘周期數(shù)。因此,如果你在函數(shù)執(zhí)行之前和之后調(diào)用它,你會得到用于執(zhí)行函數(shù)的時鐘周期數(shù)。
cv.getTickFrequency 函數(shù)返回時鐘周期的頻率,或每秒時鐘周期的數(shù)量。因此,要查找以秒為單位的執(zhí)行時間,您可以執(zhí)行以下操作:
e1 = cv.getTickCount()
# 你的代碼操作
e2 = cv.getTickCount()
time = (e2 - e1) / cv.getTickFrequency()
我們將通過下面的示例進行演示。下面的示例使用中值濾波,其內(nèi)核大小為奇數(shù),從5到49不等(不用擔心結(jié)果會是什么樣子——那不是我們的目標):
img1 = cv.imread('Ronaldo.png')
assert img1 is not None, "文件無法讀取,使用os.path.exists()檢查"
e1 = cv.getTickCount()
for i in range(5, 49, 2):
img1 = cv.medianBlur(img1, i)
e2 = cv.getTickCount()
t = (e2 - e1) / cv.getTickFrequency()
print(t)
# 我得到的結(jié)果是 0.8226811s
注意:
你可以用time模塊做同樣的事情。使用time.time()函數(shù)代替cv.getTickCount。然后取兩次的差。
3.3 OpenCV中的默認優(yōu)化
OpenCV的許多函數(shù)都使用SSE2,AVX等進行了優(yōu)化,它還包含未優(yōu)化的代碼。因此,如果我們的系統(tǒng)支持這些功能,我們應(yīng)該利用它們(幾乎所有現(xiàn)代處理器都支持它們)。在編譯時默認啟用它。
因此,如果啟用了OpenCV,它將運行優(yōu)化的代碼,否則將運行未優(yōu)化的代碼。你可以使用cv.useOptimized() 來檢查它是否被啟用/禁用,使用cv. setupOptimized() 來啟用/禁用它。讓我們看一個簡單的例子。
# 檢查是否啟用了優(yōu)化
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# 禁用它
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
可以看出,優(yōu)化后的中值濾波比未優(yōu)化的版本快2倍。如果查看其源代碼,可以發(fā)現(xiàn)中值濾波是經(jīng)過 SIMD 優(yōu)化的。因此,你可以利用這一點在代碼頂部啟用優(yōu)化(記住,默認情況下是啟用的)。
3.4 在IPython中測量性能
有時您可能需要比較兩個類似操作的性能。IPython提供了一個神奇的命令timeit來執(zhí)行此操作。它多次運行代碼以獲得更準確的結(jié)果。同樣,它適用于測量單行代碼。
例如,你知道下面哪種加法運算更好:
x = np.uint8([5])
y = x * x
還是
x = np.uint8([5])
y = np.square(x)
我們將在IPython shell中使用timeit進行驗證。
In [10]: x = 5
In [11]: %timeit y = x ** 2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y = x * x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y = z * z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y \= np.square(z)
1000000 loops, best of 3: 1.16 us per loop
你可以看到,第二種是最快的,它比Numpy快了20倍左右。如果您還考慮陣列創(chuàng)建,它可能會快100倍。很酷吧 (Numpy開發(fā)人員正在處理這個問題)。
注意:
Python標量操作比Numpy標量操作更快。因此,對于包含一個或兩個元素的操作,Python標量優(yōu)于Numpy數(shù)組。當數(shù)組的大小稍微大一點時,Numpy具有優(yōu)勢。
我們再舉一個例子。這一次,我們將比較cv.countNonZero() 和np.count_nonzero() 對同一圖像的性能。
In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop
看,OpenCV函數(shù)比Numpy函數(shù)快了近25倍。
3.5 更多IPython神奇命令
還有其他幾個神奇的命令來測量性能,分析,行分析,內(nèi)存測量等,他們都是有據(jù)可查的。所以這里只提供這些文檔的鏈接。有興趣的讀者可以嘗試一下(參見“額外資源”)。
3.6 性能優(yōu)化技術(shù)
有幾種技術(shù)和編碼方法可以利用Python和Numpy的最大性能。這里只提到相關(guān)的,并提供重要來源的鏈接。這里要注意的主要事情是,首先嘗試以簡單的方式實現(xiàn)算法。
一旦它開始工作,分析它,找到瓶頸,并優(yōu)化它們。
- 盡可能避免在Python中使用循環(huán),尤其是雙循環(huán)、三循環(huán)等,它們天生就很慢。
- 盡可能最大程度地對算法、代碼進行矢量化,因為Numpy和OpenCV針對矢量操作進行了優(yōu)化。
- 利用緩存一致性。
- 除非必要,否則不要復(fù)制數(shù)組。嘗試使用視圖代替。數(shù)組復(fù)制是一項成本高昂的操作。
3.7 額外資源
- Python優(yōu)化技術(shù)
- Scipy講座筆記-高級Numpy
- IPython中的計時和分析
|