本文主要內容來自於 OpenCV-Python 教程 的 核心操作 部分,這個部分的主要內容如下:
-
學習讀取和編輯像素值,使用圖像 ROI 和其它的基本操作。
-
對圖像執行算術運算
-
獲得解決方案很重要。但是以最快的方式獲得它更重要。學習檢查代碼的速度,優化代碼等。
目標
學習:
- 訪問像素值並修改它們
- 訪問圖像屬性
- 設置感興趣區域 (ROI)
- 分割和合並圖像
本節中幾乎所有的操作都主要與 Numpy 有關,而不是 OpenCV。使用 OpenCV 編寫更好的優化的代碼需要良好的 Numpy 知識。
訪問及修改像素值
讓我們先加載一幅彩色圖像:
#!/use/bin/env python
import numpy as np
import cv2 as cv
if __name__ == "__main__":
cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
filepath = cv.samples.findFile("messi5.jpg")
img = cv.imread(filepath)
我們可以根據像素點的行和列座標訪問像素的值。對於 BGR 圖像,它返回一個藍色、綠色和紅色值的數組。對於灰度圖像,只返回相應的亮度。
px = img[100, 100]
print(px)
print(px.__class__.__name__)
blue = img[100, 100, 0]
print(blue)
這幾行代碼對應的輸出如下:
[157 166 200]
ndarray
157
彩色圖像像素點的值是由一個 Numpy 的 ndarray 表示的。我們還可以以相同的方式修改像素值。
img[100, 100] = [255, 255, 255]
print(img[100, 100])
這幾行代碼對應的輸出如下:
[255 255 255]
警告 Numpy 是一個爲快速數組計算高度優化的庫。因此,簡單地訪問每個像素值並對其進行修改將非常緩慢,並且不鼓勵這樣做。
注意 上面的方法通常用於選擇數組的一個區域,比如開始的 5 行和最後的 3 列。對於單獨的像素訪問,Numpy 數組方法,
array.item()
和array.itemset()
被認爲是更好的選擇。它們總是返回一個標量,然而,如果想要訪問所有的 B,G,R 值,則將需要爲每個值分別調用array.item()
。
更好的像素訪問和編輯方法:
# accessing RED value
red_value = img.item(10, 10, 2)
print(red_value)
# modifying RED value
img.itemset((10, 10, 2), 100)
red_value = img.item(10, 10, 2)
print(red_value)
這幾行代碼對應的輸出如下:
59
100
訪問圖像的屬性
圖像屬性包括行數、列數和通道數;圖像數據的類型;像素的個數等等。
一幅圖像的形狀可以通過 img.shape 訪問。它返回行數、列數和通道數的元組(如果圖像是彩色的):
print(img.shape)
這行代碼對應的輸出如下:
(342, 548, 3)
注意 如果圖像是灰度圖,則返回的元組只包含行數和列數,因而這是一種檢查加載的圖像是灰度圖還是彩色圖的好方法。
像素值的總個數通過 img.size
訪問,它是行數、列數和通道數三者的乘積,而不是行數和列數兩者的乘積:
print(img.size)
totoal_pixels = img.shape[0] * img.shape[1] * img.shape[2]
print(totoal_pixels)
這幾行代碼對應的輸出如下:
562248
562248
圖像的數據類型通過 img.dtype
獲取:
print(img.dtype)
這行代碼對應的輸出如下:
uint8
注意
img.dtype
在調試時非常重要,因爲 OpenCV-Python 代碼中的大量錯誤都是由無效的數據類型引起的。
圖像 ROI
有時,我們將不得不使用某些圖像區域。對於圖像中的眼睛檢測,首先對整個圖像進行人臉檢測。當獲得人臉時,我們只選擇人臉區域並在其中搜索眼睛,而不是搜索整個圖像。它提高了精度(因爲眼睛總是在臉上:D)和性能(因爲我們在一個更小的區域內搜索)。
使用 Numpy 索引再次獲得 ROI。這裏我們選擇足球,並把它拷貝到圖像的另一個區域:
ball = img[280:340, 330:390]
img[273:333, 100:160] = ball
上面方括號中逗號前面的數字表示選取的區域的行的範圍,即區域的垂直方向的範圍,後面的數字表示選取的區域的列的範圍,即區域的水平方向的範圍。檢查結果如下:
分割和合並圖像通道
有時我們需要分別處理一幅圖像的 B,G,R 通道。在這種情況下,我們需要把 BGR 圖像分割爲單獨的通道。在其它情況下,我們可能需要合併這些單獨的通道,並創建 BGR 圖像。我們可以通過以下方式簡單地做到這一點:
b, g, r = cv.split(img)
img = cv.merge((b, r, g))
這裏有意沒有按照原來的數據格式合併數據,而是把所有像素點的綠色通道和紅色通道的值做了交換。此外,分割獲得的單個色彩通道的值可以作爲一幅灰度圖來繪製。
或者:
b = img[:, :, 0]
假設我們想要把所有像素的紅色通道值都設置爲 0 —— 我們不需要先分割通道。Numpy 的索引更快:
img[:, :, 2] = 0
警告 cv.split() 是一項代價高昂的操作(就時間而言)。因此只在需要的時候使用它。否則使用 Numpy 的索引。
爲圖像製作邊框(填充)
如果要在圖像周圍創建邊框,例如相框,可以使用 cv.copyMakeBorder()。 但它在卷積運算、零填充等方面有更多應用。此函數接收以下參數:
-
src - 輸入圖像
-
top,bottom,left,right - 相應方向上邊框的以像素爲單位的寬度。
-
borderType - 定義了添加何種邊框的標記。它可以是以下類型:
- cv.BORDER_CONSTANT - 添加一個常量彩色邊框。該值應該由下一個參數給出。
- cv.BORDER_REFLECT - 邊框將是邊框元素的鏡像,如下所示:fedcba|abcdefgh|hgfedcb
- cv.BORDER_REFLECT_101 或 cv.BORDER_DEFAULT - 與上面的相同,但有一點輕微的改變:像這樣:gfedcb|abcdefgh|gfedcba
- cv.BORDER_REPLICATE - 最後一個元素在整個過程中被複制,像這樣:aaaaaa|abcdefgh|hhhhhhh
- cv.BORDER_WRAP - 無法解釋,它看起來像這樣:cdefgh|abcdefgh|abcdefg
-
value - 如果邊框類型是 cv.BORDER_CONSTANT 這個是邊框的顏色
下面這段代碼演示了所有這些邊框類型,以使我們獲得更好的理解。
def border_type():
BLUE = [255, 0, 0]
cv.samples.addSamplesDataSearchPath("/media/data/my_multimedia/opencv-4.x/samples/data")
filepath = cv.samples.findFile("opencv-logo.png")
img1 = cv.imread(filepath)
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)
isolated = cv.copyMakeBorder(img1, 10, 10, 10, 10, cv.BORDER_ISOLATED)
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.subplots_adjust(wspace=0.4, hspace=0.4)
plt.show()
這裏用到的示例圖像文件同樣在 OpenCV 的示例數據中,因而先查找這個文件的完整路徑並加載。圖像由 matplotlib 顯示。因而 RED 和 BLUE 通道將會被交換 。用 matplotlib 畫圖時,爲了防止不同圖之間相互遮蓋,這裏通過 plt.subplots_adjust(wspace=0.4, hspace=0.4)
對子圖做了一些調整。
來看下最終的結果:
參考文檔
Done.