1. 圖像與原始字節之間的轉換
從概念上講,一個字節能表示0到255的整數。目前,對於多有的實時圖像應用而言,雖然有其他的表示形式,但一個像素通常由每個通道的一個字節表示。
一個OpenCV圖像是.array類型的二維或三維數組。8位的灰度圖像是一個含有字節值的二維數組。一個24位的BGR圖像是一個三維數組,它也包含了字節值。可使用表達式訪問這些值,如image[0,0]或image[0,0,0]。第一個值代表像素的y坐標啊或行,0表示頂部;第二個值是像素的x坐標或列,0表示最左邊;第三個值(如果可用的話)表示顏色通道。如,對於一個左上角有白色像素的8位灰度圖像而言,image[0,0]的值為255. 對於一個左上角有藍色像素的24位BGR圖像而言,image[0,0]是[255,0,0]。
可以用另外一個表示,如image[0,0]或image[0,0]=128,還可表示成image.item((0,0))或image.setitem((0,0),128)。對於單像素操作,第二種表示方式更有效。
若一幅圖像的每個通道為8位,則可將其顯示轉換為標准的一維Python bytearray格式:
byteArray = bytearray(image)
反之,bytearray含有恰當順序的字節,可以通過顯示轉換和重構,得到numpy.array形式的圖像:
garyImage = numpy.array(garyByteArray ).reshape(height, width)
bgrImage = numpy.array(bgrByteArray ).reshape(height, width, 3)
下面介紹將含有隨機字節的bytearray轉換為灰度圖像和BGR圖像:
import cv2 import numpy as np import os # 創建一個120000個隨機字節的數組 randomByteArray = bytearray(os.urandom(120000)) #os.urandom(n) 返回n個隨機byte值的string,作為加密使用 flatNumpyArray = np.array(randomByteArray) # 將數組轉換為400 x 300的灰度圖像 garyImage = flatNumpyArray.reshape(300, 400) cv2.imwrite('randomGary.png', garyImage) # 將數組轉換為400 x 300的彩色圖像 bgrImage = flatNumpyArray.reshape(100, 400, 3) cv2.imwrite('randomColor.png', bgrImage)
運行該程序,將會在程序所在目錄中生成兩張灰度圖像(如下所示)。尺寸分別為400 x 100,400 x 400
使用Python標准的os.urandom()函數可隨機生成原始字節,隨后會把該字節轉換為NumPy數組。需要注意的是,諸如numpy.random.randint(0, 256, 120000).reshape(400, 300)語句也能直接(並且更高效地)隨機生成NumPy數組。使用os.urandom()函數的原因是該語句有助於展示原始字節的轉換。
2. 使用numpy.array訪問圖像數據
加載OpenCV圖像最簡單的方式是使用imread()函數,該函數會返回一幅圖像,這幅圖像是一個數組(根據imread()函數輸入參數的不同,該圖像可能是二維數組,也可能是三維數組)。
y.array結構針對數組操作有很好的優化,它允許某些塊(bulk)操作,這些操作在通常的Python中不可用這些特定的.array操作在OpenCV的圖像處理中會很方便。利用numpy.array函數來轉換數組比用普通的Python數組轉換要快得多。
import cv2 import numpy as np img = cv2.imread('flower.jpg') img[0,0] = [255, 255, 255] cv2.imshow('my image', img) cv2.waitKey()
在圖像左上方會出現一個白點。
假設想要改變一個特定像素的藍色值,numpy.array提供了item()方法。該函數有3個參數:x(或左)位置,y(或頂部)位置以及(x,y)位置的數組索引(注意,在BGR圖像中,某一位置的數據是按B,G,R的順序保存的三元數組),該函數能返回索引函數的值。另一個方法是通過itemset()函數可設置指定像素在指定通道的值(itemset()有兩個參數:一個三元組(x,y和索引)和要設定的值)。如下例子將坐標(150,120)的當前藍色值127變為255
import cv2 import numpy as np img = cv2.imread('flower.jpg') print(img.item(150, 120, 0)) # 打印當前坐標點的藍色值 img.itemset((150, 120, 0), 255) print(img.item(150, 120, 0))
建議使用內置的濾波器和方法來處理整個圖像,上述方法只適合於處理特定的小區域。
下面介紹操作通道:將指定通道(B,G,R)的所有值置為0.(注:通過循環來處理Python數組的效率非常低,應該盡量避免這樣的操作。使用數組索引可以高效地操作像素。像素操作是一個高代價的低效操作,特別是在視頻數據處理時,會發現要等很久才能得到結果。可用索引(indexing)來解決該問題)
以下代碼可將圖像所有的G(綠色)值設為0
import cv2 import numpy as np img = cv2.imread('flower.jpg') img[:, :, 1] = 0 cv2.imshow('my image', img) cv2.waitKey()
運行結果為:
通過NumPy數組的索引訪問原始像素,還可設定感興趣區域(Region Of Interest, ROI)。一旦設定了該區域,就可以執行許多操作,例如,將該區域與變量綁定,然后設定第二個區域,並將第一個區域的值分配給第二個區域(將圖像的一部分拷貝到該圖像的另一個位置):
import cv2 import numpy as np img = cv2.imread('flower.jpg') roi = img[0:100, 0:100] img[100:200, 100:200] = roi # 此處需考慮所用圖像的尺寸,不能超過,並確保兩個區域的大小一樣 cv2.imshow('my image', img) cv2.waitKey()
運行結果為:
此外,還可使用numpy.array來獲得圖像其他屬性。
shape:NumPy返回包含寬度、高度和通道數(如果圖像是彩色的)數組,這在調試圖像類型時很有用;如果圖像是單色或灰度的,將不包含通道值;
size:該屬性是指圖像像素的大小;
datatype:該屬性會得到圖像的數據類型(通常為一個無符號整數類型的變量和該類型占的位數,比如unit8類型)
import cv2 import numpy as np img = cv2.imread('flower.jpg') print(img.shape) print(img.size) print(img.dtype)
運行結果為:
(220, 252, 3)
166320
uint8
3.視頻文件的讀/寫
OpenCV提供了VideoCapture類和VideoWriter類來支持各種格式的視頻文件。支持的格式類型會因系統的不同而變化,但應該都支持AVI格式。在到達視頻文件末尾之前,VideoCapture類可通過read()函數來獲取新的幀,每幀都是一幅基於BGR格式的圖像。
可將一幅圖像傳遞給VideoWriter類的write()函數,該函數會將這幅圖像加到VideoWriter類所指向的文件中。
如下示例讀取AVI文件的幀,並采用YUV顏色編碼將其寫入另一幀中:
import cv2 videoCapture = cv2.VideoCapture('myvideo.avi') fps = videoCapture.get(cv2.CAP_PROP_FPS) size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) videoWriter = cv2.VideoWriter('MyOutputVid.avi', cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size) success, frame = videoCapture.read() while success: # 循環直到所有幀結束 videoWriter.write(frame) success, frame = videoCapture.read()
要特別注意:必須要為VideoWriter類的構造函數指定視頻文件名,這個文件名對應的文件若存在,會被覆蓋。也必須指定視頻編解碼器。編解碼器的可用性根據系統不同而不同。下面是一些常用選項:
cv2.VideoWriter_force('I', '4', '2', '0'):該選項是一個未壓縮的YUV顏色編碼,是4:2:0色度子采樣。這種編碼有很好的兼容性,但會產生較大文件,文件擴展名為.avi。
cv2.VideoWriter_force('P', 'I', 'M', '1'):該選項是MPEG-1編碼類型,文件擴展名為.avi。
cv2.VideoWriter_force('X', 'V', 'I', 'D'):該選項是MPEG-4編碼類型,如果希望得到的視頻大小為平均值,推薦使用此選項,文件擴展名為.avi。
cv2.VideoWriter_force('T', 'H', 'E', 'O'):該選項是Ogg Vorbis,文件擴展名應為.ogv。
cv2.VideoWriter_force('F', 'L', 'V', '1'):該選項是一個Flash視頻,文件擴展名應為.flv。
幀速率和幀大小也必須要指定,因為需要從另一個視頻文件復制視頻幀,這些屬性可以通過VideoCapture類的get()函數得到。
4. 捕獲攝像頭的幀
VideoCapture類可以獲得攝像頭的幀流。但對攝像頭而言,通常不是用視頻的文件名來構造VideoCapture類,而是需要傳遞攝像頭的設備索引(device index)。
下面的例子會捕獲攝像頭10秒的視頻信息,並將其寫入一個AVI文件中:
import cv2 cameraCapture = cv2.VideoCapture(0) fps = 30 size = (int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) videoWriter = cv2.VideoWriter('MyOutputVid.avi', cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size) success, frame = cameraCapture.read() numFramesRemaining = 10 * fps - 1 while success and numFramesRemaining > 0: videoWriter.write(frame) success, frame = cameraCapture.read() numFramesRemaining -= 1 cameraCapture.release()
然而,VideoCapture類的get()方法不能反悔攝像頭幀速率的准確值,它總是返回0。
為了針對攝像頭創建合適的VideoWriter類,要么對幀速率做出假設,要么使用計時器來測量。攝像頭的數量和順序由系統決定,但OpenCV沒有提供任何查詢攝像頭數量和屬性的方法。如果使用無效索引構造了VideoCapture類,就不會得到幀,VideoCapture的read()函數會返回(false, None)。為了不讓read()函數從沒有正確打開的VideoCapture類中獲取數據,可在執行該函數之后使用VideoCapture.isOpened方法做一個判斷,該方法返回一個Boolean值。
當需要同步一組攝像頭或一個多頭攝像頭(例如立體攝像頭或Kinect)時,read()方法就不再適合了,可用grab()和retrive()方法代替它。對於一組攝像頭,可以使用以下代碼:
success0 = cameraCapture0.grab() success1 = cameraCapture1.grab() if success0 and success1: frame0 = cameraCapture0.retrive() frame1 = cameraCapture1.retrive()
5. 在窗口顯示圖像
用imshow()函數實現顯示圖像的操作。imshow()函數有兩個參數:顯示圖像的幀名字以及要顯示的圖像本身。
import cv2 import numpy as np img = cv2.imread('flower.jpg') cv2.imshow('my image', img) cv2.waitKey() cv2.destroyAllWindows() # 釋放由OpenCV創建的所有窗口
6. 在窗口顯示攝像頭幀
OpenCV的namedWindow()、imshow()和DestoryWindow()函數允許指定窗口名來創建、顯示和銷毀(destroy)窗口。此外,任何窗口都可以通過waitKey()函數來獲取鍵盤輸入,通過setMouseCallback()函數來獲取鼠標輸入。以下代碼可實時顯示攝像頭幀:
import cv2 clicked = False def onMouse(event, x, y, flags, param): global clicked if event == cv2.EVENT_LBUTTONUP: clicked = True cameraCapture = cv2.VideoCapture(0) cv2.namedWindow('MyWindow') cv2.setMouseCallback('MyWindow', onMouse) print('showing camera feed. Click window or press any key to stop.') success, frame = cameraCapture.read() while success and cv2.waitKey(1) == -1 and not clicked: cv2.imshow('MyWindow', frame) success, frame = cameraCapture.read()
cv2.destroyWindow('MyWindow')
cameraCapture.release()
waitKey()的參數為等待鍵盤觸發的時間,單位為毫秒,其返回值為-1(表示沒有鍵被按下)或ASCII碼。另外,Python提供了一個標准函數ord(),該函數可以將字符轉換為ASCII碼。(注:在一些系統中,waitKey()的返回值可能比ASCII碼的值更大(在Linux系統中,如果OpenCV使用GTK作為后端的GUI庫,就會出現bug),在所有系統中,可以通過讀取返回值的最后一個字節來保證肢體去ASCII碼,代碼為:
keycode = cv2.waitkey(1)
if keycode != -1:
keycode &= 0xff )
OpenCV的窗口函數和waitKey()函數相互依賴。OpenCV的窗口只有在調用waitKey()函數時才會更新,waitKey()函數只有在OpenCV窗口成為活動窗口時,才能捕獲輸入信息。
鼠標回調函數setMouseCallback()有5個參數,param是可選參數,它是setMouseCallback()函數的第三個參數,默認情況下,該參數是0.回調時間參數可以取如下的值,它們分別對應不同的鼠標事件。
cv2.EVENT_MOUSEMOVE:該事件對應鼠標移動
cv2.EVENT_LBUTTONDOWN:該事件對應鼠標左鍵按下
cv2.EVENT_RBUTTONDOWN:該事件對應鼠標右鍵按下
cv2.EVENT_MBUTTONDOWN:該事件對應鼠標中間鍵按下
cv2.EVENT_LBUTTONUP:該事件對應鼠標左鍵松開
cv2.EVENT_RBUTTONUP:該事件對應鼠標右鍵松開
cv2.EVENT_MBUTTONUP:該事件對應鼠標中間鍵松開
cv2.EVENT_LBUTTONDBLCLK:該事件對應雙擊鼠標左鍵
cv2.EVENT_RBUTTONDBLCLK:該事件對應雙擊鼠標右鍵
cv2.EVENT_MBUTTONDBLCLK:該事件隨影雙擊鼠標中間鍵
鼠標回調的標志參數可能是以下事件的按位組合:
cv2.EVENT_FLAG_LBUTTON:該事件對應按下鼠標左鍵
cv2.EVENT_FLAG_RBUTTON:該事件對應按下鼠標右鍵
cv2.EVENT_FLAG_MBUTTON:該事件對應按下鼠標中間鍵
cv2.EVENT_FLAG_CTRLKEY:該事件對應按下Ctrl鍵
cv2.EVENT_FLAG_SHIFTKEY:該事件對應按下Shift鍵
cv2.EVENT_FLAG_ALTKEY:該事件對應按下Alt鍵