1. 圖像與原始字節(jié)之間的轉(zhuǎn)換
從概念上講,一個字節(jié)能表示0到255的整數(shù)。目前,對于多有的實時圖像應用而言,雖然有其他的表示形式,但一個像素通常由每個通道的一個字節(jié)表示。
一個OpenCV圖像是.array類型的二維或三維數(shù)組。8位的灰度圖像是一個含有字節(jié)值的二維數(shù)組。一個24位的BGR圖像是一個三維數(shù)組,它也包含了字節(jié)值??墒褂帽磉_式訪問這些值,如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位,則可將其顯示轉(zhuǎn)換為標準的一維Python bytearray格式:
byteArray = bytearray(image)
反之,bytearray含有恰當順序的字節(jié),可以通過顯示轉(zhuǎn)換和重構,得到numpy.array形式的圖像:
garyImage = numpy.array(garyByteArray ).reshape(height, width)
bgrImage = numpy.array(bgrByteArray ).reshape(height, width, 3)
下面介紹將含有隨機字節(jié)的bytearray轉(zhuǎn)換為灰度圖像和BGR圖像:
import cv2import numpy as npimport os# 創(chuàng)建一個120000個隨機字節(jié)的數(shù)組randomByteArray = bytearray(os.urandom(120000)) #os.urandom(n) 返回n個隨機byte值的string,作為加密使用flatNumpyArray = np.array(randomByteArray)# 將數(shù)組轉(zhuǎn)換為400 x 300的灰度圖像garyImage = flatNumpyArray.reshape(300, 400)cv2.imwrite('randomGary.png', garyImage)# 將數(shù)組轉(zhuǎn)換為400 x 300的彩色圖像bgrImage = flatNumpyArray.reshape(100, 400, 3)cv2.imwrite('randomColor.png', bgrImage)
運行該程序,將會在程序所在目錄中生成兩張灰度圖像(如下所示)。尺寸分別為400 x 100,400 x 400
使用Python標準的os.urandom()函數(shù)可隨機生成原始字節(jié),隨后會把該字節(jié)轉(zhuǎn)換為NumPy數(shù)組。需要注意的是,諸如numpy.random.randint(0, 256, 120000).reshape(400, 300)語句也能直接(并且更高效地)隨機生成NumPy數(shù)組。使用os.urandom()函數(shù)的原因是該語句有助于展示原始字節(jié)的轉(zhuǎn)換。
2. 使用numpy.array訪問圖像數(shù)據(jù)
加載OpenCV圖像最簡單的方式是使用imread()函數(shù),該函數(shù)會返回一幅圖像,這幅圖像是一個數(shù)組(根據(jù)imread()函數(shù)輸入?yún)?shù)的不同,該圖像可能是二維數(shù)組,也可能是三維數(shù)組)。
y.array結(jié)構針對數(shù)組操作有很好的優(yōu)化,它允許某些塊(bulk)操作,這些操作在通常的Python中不可用這些特定的.array操作在OpenCV的圖像處理中會很方便。利用numpy.array函數(shù)來轉(zhuǎn)換數(shù)組比用普通的Python數(shù)組轉(zhuǎn)換要快得多。
import cv2import numpy as npimg = cv2.imread('flower.jpg')img[0,0] = [255, 255, 255]cv2.imshow('my image', img)cv2.waitKey()
在圖像左上方會出現(xiàn)一個白點。
假設想要改變一個特定像素的藍色值,numpy.array提供了item()方法。該函數(shù)有3個參數(shù):x(或左)位置,y(或頂部)位置以及(x,y)位置的數(shù)組索引(注意,在BGR圖像中,某一位置的數(shù)據(jù)是按B,G,R的順序保存的三元數(shù)組),該函數(shù)能返回索引函數(shù)的值。另一個方法是通過itemset()函數(shù)可設置指定像素在指定通道的值(itemset()有兩個參數(shù):一個三元組(x,y和索引)和要設定的值)。如下例子將坐標(150,120)的當前藍色值127變?yōu)?55
import cv2import numpy as npimg = cv2.imread('flower.jpg')print(img.item(150, 120, 0)) # 打印當前坐標點的藍色值img.itemset((150, 120, 0), 255)print(img.item(150, 120, 0))
建議使用內(nèi)置的濾波器和方法來處理整個圖像,上述方法只適合于處理特定的小區(qū)域。
下面介紹操作通道:將指定通道(B,G,R)的所有值置為0.(注:通過循環(huán)來處理Python數(shù)組的效率非常低,應該盡量避免這樣的操作。使用數(shù)組索引可以高效地操作像素。像素操作是一個高代價的低效操作,特別是在視頻數(shù)據(jù)處理時,會發(fā)現(xiàn)要等很久才能得到結(jié)果??捎盟饕?indexing)來解決該問題)
以下代碼可將圖像所有的G(綠色)值設為0
import cv2import numpy as npimg = cv2.imread('flower.jpg')img[:, :, 1] = 0cv2.imshow('my image', img)cv2.waitKey()
運行結(jié)果為:
通過NumPy數(shù)組的索引訪問原始像素,還可設定感興趣區(qū)域(Region Of Interest, ROI)。一旦設定了該區(qū)域,就可以執(zhí)行許多操作,例如,將該區(qū)域與變量綁定,然后設定第二個區(qū)域,并將第一個區(qū)域的值分配給第二個區(qū)域(將圖像的一部分拷貝到該圖像的另一個位置):
import cv2import numpy as npimg = cv2.imread('flower.jpg')roi = img[0:100, 0:100]img[100:200, 100:200] = roi # 此處需考慮所用圖像的尺寸,不能超過,并確保兩個區(qū)域的大小一樣cv2.imshow('my image', img)cv2.waitKey()
運行結(jié)果為:
此外,還可使用numpy.array來獲得圖像其他屬性。
shape:NumPy返回包含寬度、高度和通道數(shù)(如果圖像是彩色的)數(shù)組,這在調(diào)試圖像類型時很有用;如果圖像是單色或灰度的,將不包含通道值;
size:該屬性是指圖像像素的大??;
datatype:該屬性會得到圖像的數(shù)據(jù)類型(通常為一個無符號整數(shù)類型的變量和該類型占的位數(shù),比如unit8類型)
import cv2import numpy as npimg = cv2.imread('flower.jpg')print(img.shape)print(img.size)print(img.dtype)
運行結(jié)果為:
(220, 252, 3)
166320
uint8
3.視頻文件的讀/寫
OpenCV提供了VideoCapture類和VideoWriter類來支持各種格式的視頻文件。支持的格式類型會因系統(tǒng)的不同而變化,但應該都支持AVI格式。在到達視頻文件末尾之前,VideoCapture類可通過read()函數(shù)來獲取新的幀,每幀都是一幅基于BGR格式的圖像。
可將一幅圖像傳遞給VideoWriter類的write()函數(shù),該函數(shù)會將這幅圖像加到VideoWriter類所指向的文件中。
如下示例讀取AVI文件的幀,并采用YUV顏色編碼將其寫入另一幀中:
import cv2videoCapture = 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: # 循環(huán)直到所有幀結(jié)束 videoWriter.write(frame) success, frame = videoCapture.read()
要特別注意:必須要為VideoWriter類的構造函數(shù)指定視頻文件名,這個文件名對應的文件若存在,會被覆蓋。也必須指定視頻編解碼器。編解碼器的可用性根據(jù)系統(tǒng)不同而不同。下面是一些常用選項:
cv2.VideoWriter_force('I', '4', '2', '0'):該選項是一個未壓縮的YUV顏色編碼,是4:2:0色度子采樣。這種編碼有很好的兼容性,但會產(chǎn)生較大文件,文件擴展名為.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()函數(shù)得到。
4. 捕獲攝像頭的幀
VideoCapture類可以獲得攝像頭的幀流。但對攝像頭而言,通常不是用視頻的文件名來構造VideoCapture類,而是需要傳遞攝像頭的設備索引(device index)。
下面的例子會捕獲攝像頭10秒的視頻信息,并將其寫入一個AVI文件中:
import cv2cameraCapture = cv2.VideoCapture(0)fps = 30size = (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 - 1while success and numFramesRemaining > 0: videoWriter.write(frame) success, frame = cameraCapture.read() numFramesRemaining -= 1cameraCapture.release()
然而,VideoCapture類的get()方法不能反悔攝像頭幀速率的準確值,它總是返回0。
為了針對攝像頭創(chuàng)建合適的VideoWriter類,要么對幀速率做出假設,要么使用計時器來測量。攝像頭的數(shù)量和順序由系統(tǒng)決定,但OpenCV沒有提供任何查詢攝像頭數(shù)量和屬性的方法。如果使用無效索引構造了VideoCapture類,就不會得到幀,VideoCapture的read()函數(shù)會返回(false, None)。為了不讓read()函數(shù)從沒有正確打開的VideoCapture類中獲取數(shù)據(jù),可在執(zhí)行該函數(shù)之后使用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()函數(shù)實現(xiàn)顯示圖像的操作。imshow()函數(shù)有兩個參數(shù):顯示圖像的幀名字以及要顯示的圖像本身。
import cv2import numpy as npimg = cv2.imread('flower.jpg')cv2.imshow('my image', img)cv2.waitKey()cv2.destroyAllWindows() # 釋放由OpenCV創(chuàng)建的所有窗口
6. 在窗口顯示攝像頭幀
OpenCV的namedWindow()、imshow()和DestoryWindow()函數(shù)允許指定窗口名來創(chuàng)建、顯示和銷毀(destroy)窗口。此外,任何窗口都可以通過waitKey()函數(shù)來獲取鍵盤輸入,通過setMouseCallback()函數(shù)來獲取鼠標輸入。以下代碼可實時顯示攝像頭幀:
import cv2clicked = Falsedef onMouse(event, x, y, flags, param): global clicked if event == cv2.EVENT_LBUTTONUP: clicked = TruecameraCapture = 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()的參數(shù)為等待鍵盤觸發(fā)的時間,單位為毫秒,其返回值為-1(表示沒有鍵被按下)或ASCII碼。另外,Python提供了一個標準函數(shù)ord(),該函數(shù)可以將字符轉(zhuǎn)換為ASCII碼。(注:在一些系統(tǒng)中,waitKey()的返回值可能比ASCII碼的值更大(在Linux系統(tǒng)中,如果OpenCV使用GTK作為后端的GUI庫,就會出現(xiàn)bug),在所有系統(tǒng)中,可以通過讀取返回值的最后一個字節(jié)來保證肢體去ASCII碼,代碼為:
keycode = cv2.waitkey(1)
if keycode != -1:
keycode &= 0xff )
OpenCV的窗口函數(shù)和waitKey()函數(shù)相互依賴。OpenCV的窗口只有在調(diào)用waitKey()函數(shù)時才會更新,waitKey()函數(shù)只有在OpenCV窗口成為活動窗口時,才能捕獲輸入信息。
鼠標回調(diào)函數(shù)setMouseCallback()有5個參數(shù),param是可選參數(shù),它是setMouseCallback()函數(shù)的第三個參數(shù),默認情況下,該參數(shù)是0.回調(diào)時間參數(shù)可以取如下的值,它們分別對應不同的鼠標事件。
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:該事件隨影雙擊鼠標中間鍵
鼠標回調(diào)的標志參數(shù)可能是以下事件的按位組合:
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鍵
聯(lián)系客服