⚠️這個系列是自己瞎翻的,文法很醜,跳着跳着撿重要的部分翻,翻錯了不負責,就這樣。
⚠️基於3.4.3,Basic Operations on Images,附原文。
目標
學會:
- 獲取像素並且修改他們
- 獲取圖像屬性
- 設置感興趣區域(ROI) (譯者注:ROI啥意思?看這裏。)
- 拆分以及合併圖像(譯者注:這裏有歧義,看完這節,應該是指拆分合並圖像的彩色信號通道而不是圖像本身)
幾乎這部分所有的操作和Numpy相關的程度都超過了和OpenCV本身相關的程度。要用OpenCV寫出更優雅的代碼,Numpy的知識是不可或缺的。
*( 既然大多數的代碼都只有一行,所以示例就在Python終端上展示。(譯者注:以下很多代碼擡行有>>>符號,實際上它們並不是代碼的一部分而是Python終端顯示的一部分。) )*
獲取並且修改像素值
讓咱們先加載一張圖像:
>>> import numpy as np
>>> import cv2 as cv
>>> img = cv.imread('messi5.jpg')
你可以通過這個像素點的行和列座標獲取像素值。它會爲一個BGR圖像返回一個藍,綠,紅數值的數組。爲灰度圖像只會返回一個對應強度。
>>> px = img[100,100]
>>> print( px )
[157 166 200]
# accessing only blue pixel
>>> blue = img[100,100,0]
>>> print( blue )
157
你可以用同樣的方式修改像素值。
>>> img[100,100] = [255,255,255]
>>> print( img[100,100] )
[255 255 255]
警告
Numpy是一個爲了快速計算數組而優化過的第三方庫。所以簡單的獲取遍歷每一個像素值並且修改它會非常的慢而且顯得沒勁。
提示
以上方法是通常用於選擇一個數組表示的區域。比如說前5行和後3列。要獲取一個單獨的像素點,最好考慮使用Numpy的array方法、array.item() 和 array.itemset()。但他們總是返回一個數值,如果你想要獲取所有的B、G、R值,你需要分別爲他們全部調用array.item()方法。
更好的獲取並且編輯像素點的方法:
# accessing RED value
>>> img.item(10,10,2)
59
# modifying RED value
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100
獲取圖像屬性
圖像屬性包括行(像素)數、列(像素)數、(色彩信號)通道、圖像格式類型、像素點總數量等等。
圖像的形狀通過img.shape方法來獲取。它返回一個由行(像素)數、列(像素)數、(色彩信號)通道(如果是彩圖的話)組成的元組。
>>> print( img.shape )
(342, 548, 3)
提示
如果一張圖像是灰度圖像,這個元組只包括行(像素)數和列(像素)數,因此這是個很好的方法用於檢測圖像是灰度圖片還是彩色圖片。
像素點總數量可以通過img.size來獲取:
>>> print( img.size )
562248
圖像格式類型可以用img.dtype來獲取:
>>> print( img.dtype )
uint8
提示
當你debug的時候,img.dtype這方法是非常重要的,因爲OpenCV-Python代碼中大量的錯誤都是由無效的數據類型引起的。
圖像的感興趣區域
有時候,你必須和圖像的某個特定區域較勁。我們用肉眼看圖的時候,首先咱們會掃一輪圖片來個人臉檢測,一旦當我們發現一張人臉,我們就僅僅選擇人臉的區域來尋找眼睛,而不是在整張圖裏去找眼睛。這種方案提升了準確率(因爲眼睛一定在臉上)也提升了執行效率(因爲我們只需要在一小片區域搜索)。
ROI感興趣區除此之外還用在Numpy索引上。現在我正在選擇一個足球,並且把它拷貝到圖像的另外一個區域。
>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball
看看下面的結果。
拆分以及合併圖像的彩色信號通道
有時你需要使用一張圖像拆分後的B,G,R通道。在這種情況下,你需要把一張BGR圖像拆成單獨的彩色信號通道。你可以通過以下的代碼來輕鬆搞定:
>>> b,g,r = cv.split(img)
>>> img = cv.merge((b,g,r))
或者
>>> 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
- cv.BORDER_CONSTANT - 添加一個常量的顏色邊框,數值應該被給在下個參數裏。
- 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')
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()
看下面的結果。(圖像是在matplotlib裏顯示的,因此紅色和藍色的信號通道被互換了。(譯者注:前面的章節有提到matplotlib和OpenCV的不同))