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