之前一直在看深度學習,突然用到了對圖像處理的知識,所以過來補充一下OpenCV基礎。
就順便從網上了買了一本OpenCV 3計算機視覺這本書,這本書比較薄,但是目前已經夠我用了,在這里就記錄一下我的學習筆記。
一 OpeCV3的安裝
在前面我已經介紹過我的python運行環境,是運行在windos 7操作系統下,安裝的Anaconda集成開發環境。具體安裝步驟可以參考文章第一節,windows下深度學習theano環境搭建。
下面介紹一下如何在Anaconda命令窗口下安裝opencv:
conda install -c https://conda.binstar.org/menpo opencv
打開Anaconda Prompt,輸入python,進入python環境之后,import cv2。不報錯,說明安裝成功。
二 OpenCV基礎
主要介紹下面7塊內容:
- 讀寫圖像文件
- 圖像與原始字節之間的轉換
- 使用numpy.array訪問圖像數據
- 視頻文件的讀寫(復制視頻)
- 捕獲攝像頭的幀,並保存視頻文件
- 窗口顯示圖像
- 在窗口顯示攝像頭幀
為了做測試,我專門創建了一個OpenCV文件夾,在下面放一些測試代碼,並創建了兩個文件夾,叫做video和image分別保存一些視頻和圖像文件:



1.讀寫圖像文件
OpenCV的imread()和imwrite()函數能支持各種靜態圖像文件格式。不同系統支持的文件格式不一樣,但是都支持bmp格式,通常還支持 png,jpeg,tiff格式文件 :
- bmp格式:每個像素每個通道為8位;
- png:每個像素每個通道為8位或者16位。
(1) 使用函數cv2.imread(filename[,flags]) 讀入圖像。
該函數返回我們讀取的圖像數據:
- 第一個參數,這幅圖像應該在此程序的工作路徑,或者給函數提供完整路徑;
- 第二個參數是標志位,要告訴函數應該如何讀取這幅圖片,即指定加載圖片的顏色類型,默認加載類型是cv2.IMREAD_COLOR:
- cv2.IMREAD_COLOR=1:讀入一副彩色圖像,將圖像轉化為三通道BGR彩色圖像。圖像的透明度會被忽略,這是默認參數;
- cv2.IMREAD_GRAYSCALE=0::將加載的圖像轉換為單通道灰度圖;
- cv2.IMREAD_UNCHANGED = -1:已經廢除,不再使用;
- cv2.IMREAD_ANYDEPTH=2:若載入圖像深度為16位或32為就返回其對應深度,否則將圖像轉換為8位圖像;
- cv2.IMREAD_ANYCOLOR=4:保持圖像原格式,可以讀取任意可能的彩色格式;
- cv2.IMREAD_LOAD_GDAL=8:使用文件格式驅動加載圖像,在現階段用處不多。
在使用flags時可能會同時使用多種flags,如果發生沖突,函數將自動采用較小數字值對應的加載方式。如:cv2.IMREAD_COLOR | cv2.IMREAD_ANYCOLOR,則imread()函數將自動載入cv2.IMREAD_COLOR所對應的3通道彩色圖。如果要載入圖像原本的彩色格式和深度,則可以使用: cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH。
也可以利用flags是int類型的變量輸入其他值以達到加載特定圖像格式的目的,但符合一下標准:
- flags > 0:返回一個三通道的彩色圖像;
- flags = 0: 返回灰度圖像 ;
- flags < 0: 返回包含Alpha通道的圖像。
圖像在默認情況下不是從Alpha通道進來的,如果需要載入Alpha通道的話就取負值。
(2)使用函數cv2.imwrite(filename,img[,params])
該函數用於保存圖像:
- 第一個參數,這幅圖像應該保存的工作路徑,或者給函數提供完整路徑;
- 第二個參數是輸入圖像;
- 第三個參數表示為特定保存格式的參數編碼,在一般情況下不需要更改;
(3)使用cv2.cvtColor(src,code[,dst[,dstCn]])函數
用於格式轉換。輸出為輸出圖像即進行顏色空間變換后存儲圖像。我們生活中大多數看到的彩色圖片都是BGR類型,但是在進行圖像處理時,需要用到灰度圖、二值圖、HSV、HSI等顏色制式,opencv提供了cv2.cvtColor()函數來實現這些功能:
- 第一個參數輸入圖像即要進行顏色空間變換的原圖像;
- 第二個參數轉換的代碼或標識,即在此確定將什么制式的圖片轉換成什么制式的圖片;
- 第三個參數為為輸出圖像即進行顏色空間變換后存儲圖像;
- 第四個參數是目標圖像通道數,如果取0,則由src和code決定;
函數的作用是將一個圖像從一個顏色空間轉換到另一個顏色空間,但是從BGR向其他類型轉換時,必須明確指出圖像的顏色通道,在opencv中,其默認的顏色制式排列是BGR而非RGB。所以對於24位顏色圖像來說,前8-bit是藍色,中間8-bit是綠色,最后8-bit是紅色。常見的B,G,R通道的取值范圍為:
- . 0-255 :cv2.CV_8U類型圖片
- . 0-65535: cv2.CV_16U類型圖片
- . 0-1::cv2.CV_32F類型圖片
講到這里有必要介紹一下圖像的數據類型,一張圖片就是一個簡單的numpy數組,數組的數據類型有很多種,相互之間也可以轉換,這些數據類型以及取值范圍如下表所示:
| Data type | Range |
|---|---|
| uint8 | 0 to 255 |
| uint16 | 0 to 65535 |
| uint32 | 0 to 232 |
| float32 | -1 to 1 or 0 to 1 |
| int8 | -128 to 127 |
| int16 | -32768 to 32767 |
| int32 | -231 to 231 - 1 |
一張圖片的像素值范圍是[0,255],因此默認類型是uint8。在上面表中需要注意的是float32類型,它的范圍是[-1,1]或[0,1]之間。詳細內容可以參考:python數字圖像處理(4):圖像數據類型及顏色空間轉換
對於線性變換來說,這些取值范圍是無關緊要的。但是對於非線性轉換,輸入的BGR圖像必須歸一化到其對應的取值范圍來或得最終正確的轉換結果,例如從BGR>L*u*v轉換。如果從一個8-bit類型圖像不經過任何縮放(scaling)直接轉換為32-bit浮點型圖像,函數將會以0-255的取值范圍來取代0-1的取值范圍,所以在使用cvtColor函數之前需要對圖像進行縮放如下:
img *= 1.0/255;
cvtColor(img,cv2.COLOR_BGR2Luv);
如果對8-bit圖像使用cvtColor()函數進行轉換將會由一些信息丟失。函數可以做下面類型的轉換,需要說明的是在opencv2.x時顏色空間轉換code用的宏定義是CV_前綴開頭,而在opencv3.x版本其顏色空間轉換code宏定義更改為COLOR_開頭,而經驗證,2.4.13版本中opencv同事支持這兩種形式的寫法。故下面表格會將兩種code類型同時列出,以供參考:

這里列出的類型並不齊全,但是對於一般的圖像處理已經夠用。需要特別說明的是BGR–>GRAY的轉換是我們常用的轉換格式,其轉換公式如下:

介紹一下BGRA格式圖片,BGRA是代表Blue(藍色),Green(綠色)、Red(紅色)、和Alpha的色彩空間。雖然它有時候被描述為一個顏色空間,但是它其實是BGR模型附加了額外的信息,可以屬於任何一種BGR顏色空間。Alpha參數一般用作不透明度參數,如果一個像素的alpha通道數值為0%,那它就是完全透明的也就是肉眼不可見,而數值為100%則意味着一個完全不透明的像素,傳統的數字圖像就是alpha值為100%.
import cv2
import numpy as np
import os
''' 1.讀寫圖像文件 ''' #通過二維numpy數組創建一個黑色的正方形圖像 img = np.zeros((3,3),dtype=np.uint8) #輸出 每一個像素都是8位整數 范圍0-255 圖片大小為3x3 print(img) #[[0 0 0] # [0 0 0] # [0 0 0]] print(img.shape) #(3, 3) #利用cvtColor()函數將圖像轉換為blue-green-red(BGR)格式 img = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR) #輸出維度為3x3x3 表明圖像大小為3x3 有三個通道 每個像素由一個三元數組表示(B,G,R) print(img) # [[[0 0 0] # [0 0 0] # [0 0 0]] # [[0 0 0] # [0 0 0] # [0 0 0]] # [[0 0 0] # [0 0 0] # [0 0 0]]] print(img.shape) #(3, 3, 3) #把png圖像轉換為jpeg格式 root = '.\image' file = os.path.join(root,'test.bmp') #加載OpenCV圖像最簡單的方式是使用imread函數,該函數會返回一副圖片,這幅圖片是一個數組(根據imread()輸入參數的不同,該圖像可能是一個二維數組,也可能是三維數組) #在默認情況下,即使文件是灰度格式,imread()函數也會返回BGR格式的圖像 #BGR和RGB所表示的色彩空間相同,字節順序相反 img = cv2.imread(file) #讀bmp格式取圖片 cv2.imwrite('test.jpg',img) #保存成jpg格式 #按照指定參數讀取圖片 加載bmp文件作為灰度圖像(丟失顏色信息) gray_image = cv2.imread(file,cv2.IMREAD_GRAYSCALE) #保存為灰度的jpg圖像 cv2.imwrite('test_gray.jpg',gray_image)
2.圖像與原始字節之間的轉換
一個OpenCV圖像一般是numpy.array類型的二維或者三維數組。8位的灰度圖像是一個含有字節值得二維數組,維度為 height x width,24位的BGR是一個三維數組 維度為height x width x channel。
(1)使用函數cv2.imshow(winname,mat)
該函數用來顯示圖像,窗口會自動調整為圖像大小:
- 第一個參數是窗口的名字。
- 第二個參數是要輸出的圖像。
imshow()在用於指定的窗口顯示圖像時,如果窗口用cv2.WINDOW_ATTOSIZE創建,那么顯示圖像原始大小。否則將圖像進行縮放以適合窗口。而imshow()函數縮放圖像取決與圖像深度:
- 如果載入圖像是8位無符號類型(8-bis unsigned),就顯示圖像本身。
- 如果圖像是16位無符號類型(16-bist unsigned)或32位無整型(32-bit integer),則使用像素值除以256.也就是說將像素值范圍在[0,255x266]之間的元素映射到(0,255]范圍內。
- 如果載入圖像是32位浮點型(32-bit floating-point),像素值要乘以255.也就是說像素值范圍在[0,1]映射到[0,255].
''' 2.圖像與原始字節之間的轉換 ''' img = cv2.imread(file) #讀bmp格式取圖片 print('圖像{0}的維度為{1}'.format(file,img.shape)) #顯示的轉換為一維的python bytearray格式 byte_array = bytearray(img) #圖像.\image\test.bmp的維度為(45, 50, 3) print('轉換之后的維度為:',len(byte_array),type(byte_array)) #轉換之后的維度為: 6750 <class 'bytearray'> #bytearray含有恰當順序的字節,可以通過顯示轉換和重構 得到numpy.array形式的圖像 img = np.asarray(byte_array).reshape(45,50,3) cv2.imshow('window 1',img)cv2.waitKey(10) #等候10ms ''' 一個詳細的例子 將含有隨機字節的bytearray轉換為灰度圖像和BGR圖像 ''' #隨機生成120,000個字節的數組 random_byte_array = bytearray(os.urandom(120000)) #生成隨機字節 #轉換成numpy.array類型 byte_array = np.asarray(random_byte_array,dtype=np.uint8) #把數組轉換成 400 x 300的灰度圖像 gray_image = byte_array.reshape(300,400) cv2.imwrite('test_gray.png',gray_image) #把數組轉換成 400 x 100的BGR圖像 bgr_image = byte_array.reshape(100,400,3) cv2.imwrite('test_bgr.png',bgr_image)
3.使用numpy.array訪問圖像數據
下面實現把圖像的一部分復制到另一個位置,然后顯示。
''' 3.使用numpy.array訪問圖像數據 ''' #將BGR圖像在(0,0)處的像素轉化為白像素 img = cv2.imread('test.jpg') img[0,0] = [255,255,255] #img[0,0]操作和下面操作一樣 ''' numpy.array的item(n_hight,n_width,n_channel)方法可以獲取指定索引的值 通道BGR依次對應索引0,1,2 ''' print(img.item(25,26,0)) #獲取25列26行B通道的值 222 img.itemset((25,26,0),155) #設置新的值 print(img.item(25,26,0)) #155 #將指定通道的所有值置為零 img[:,:,1] = 0 #將G通道像素值全部置為零 ''' 1.讀取感興趣區域,並把值賦值給一個變量 2.同理設定第二個區域,賦值給另一個變量 3.將第一個我區域的值賦值給第二個區域 (將圖像一部分拷貝到圖像另一部分) ''' img = cv2.imread('./image/img1.jpg') my_roi = img[800:1000,300:500] img[0:200,200:400] =my_roi cv2.imshow('window 2',img) cv2.waitKey(10) #等候10ms print(img.shape) #(1080, 1920, 3) 圖像的高度,寬度,和通道數 print(img.size) #36220800 圖像的大小 print(img.dtype) #uint8 圖像的數據類型
4.視頻文件的讀寫(復制視頻)
OpenCV提供了VideoCapture類和VideoWriter類來支持各種格式的視頻文件。支持的格式類型會因系統的不同而變化,但是都支持avi格式,在到達視頻文件末尾之前,VideoCapture可通過read()函數來獲取新的幀,每一幀都是一幅基於BGR的圖像。VideoWriter可以通過write()寫入每一幀圖像。
''' 4.視頻文件的讀寫(實現視頻的復制) ''' ''' 將一幅圖像傳遞給VideoWriter類的write()函數,該函數會將這幅圖像加到VideoWriter類所指向的文件中 ''' video_capture = cv2.VideoCapture('./video./2.mp4') #獲取幀速率 fps = video_capture.get(cv2.CAP_PROP_FPS) print('該視頻的幀速率為:',fps) #該視頻的幀速率為: 30.009902511911992 #獲取圖片的寬和高 size =(int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))) print('該視頻每一幀的大小為:',size) #該視頻每一幀的大小為: (544, 960) #1.VideoWriter類的構造函數指定視頻文件名,這個文件名對應的文件若存在,則會被覆 #2.需要指定編解碼器 #3.幀速率 #4.幀大小 video_writer = cv2.VideoWriter('./video/4.avi',cv2.VideoWriter_fourcc('m','p','4','v'),fps,size) success,frame = video_capture.read() cv2.imwrite('./video/frame.jpg',frame) #讀取幀,直至沒有幀可以讀取 while success: #寫入一幀 video_writer.write(frame) #讀取一幀 success,frame = video_capture.read() video_writer.release() video_capture.release()
5.捕獲攝像頭的幀,並保存視頻文件
VedioCapture類可以獲得攝像頭的幀流,但對於攝像頭而言,通常不是用視頻的文件名來構造VideoCapture類, 而是需要傳遞攝像頭的設備索引,VideoCapture類對象的get方法不能返回攝像頭幀速率的准確值,它總是返回0。
''' 5.捕獲攝像頭的幀(保存視頻文件) ''' #獲取攝像頭10s的視頻信息,並將其寫入一個avi文件中 camera_capture = cv2.VideoCapture(0) #OpenCV沒有提供任何查詢攝像頭數量和屬性的方法。如果使用無效的索引構造了VideoCapture類,就得不到幀,read方法 #就會返回 False,None 因此需要在讀取前判斷一下設備是否已經打開 if camera_capture.isOpened(): fps = 30 #假設幀大小 #獲取圖片的寬和高 size =(int(camera_capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(camera_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))) #1.VideoWriter類的構造函數指定視頻文件名,這個文件名對應的文件若存在,則會被覆 #2.需要指定編解碼器 #3.幀速率 #4.幀大小 video_writer = cv2.VideoWriter('./video/camera.avi',cv2.VideoWriter_fourcc('m','p','4','v'),fps,size) success,frame = camera_capture.read() num_frame_remaining = 10*fps - 1 #當前剩余捕獲圖像個數 while success and num_frame_remaining > 0 and cv2.waitKey(1)==-1: #顯示拍照 cv2.imshow('frame',frame) #寫入一幀 video_writer.write(frame) #讀取一幀 success,frame = camera_capture.read() num_frame_remaining -= 1 #釋放 camera_capture.release() video_writer.release()
6.窗口顯示圖像
''' 6 窗口顯示圖像 imshow()函數可以用來實現圖片,但是這個圖片顯示出來后會立即消失,一般我們需要使用waitKey()函數, 傳入等待鍵盤觸發的時間,單位為ms,返回值為-1(表示沒有鍵按下)或者ASCII碼。並且需要調用destoryAllWindows() 釋放OpenCV創建的所有窗口 ''' img = cv2.imread('./image/img2.jpg') cv2.imshow('window 3',img) cv2.waitKey(10)
7.在窗口顯示攝像頭幀
以下代碼實現實時捕獲攝像頭的幀並顯示在窗口,按任何鍵可以退出窗口。
(1)cv2.destroyAllWindows()
該函數可以輕易刪除任何我們建立的窗口。如果你想刪除特定的窗口可以使用cv2.destroyWindow(),在括號內輸入你想刪除的窗口名。
(2)cv2.namedWindow(winname[,flags])
指定窗口名來創建窗口:
- 第一個參數為窗口的名字。
- 第二個參數為窗口屬性。
flags是一個枚舉類型,其由如下參數:
- cv2.WINDOW_NORMAL:可以改變窗口大小(無限制),也可將一個滿屏窗口轉換成常用大小;
- cv2.WINDOW_AUTOSIZE:程序會根據呈現內容自動調整大小且不能手動更改窗口大小;
- cv2.WINDOW_OPENGL:創建支持OpenGL的窗口;
- cv2.WINDOW_FULLSCREEN:創建一個充滿屏幕的窗口;cv2.WINDOW_FREETATIO:圖像將盡可能展開;
- cv2.WINDOW_KEEPRATIO:圖像比例受到約束。
namedWindow()函數是通過指定的名字創建一個作為圖像和進度條顯示的窗口,如果有相同名稱的窗口已經存在,則函數不會重復創建窗口,而是什么都不做。我們可以調用destroyWindows()或者destroyAllWindows()函數來關閉窗口並取消之前分配的與窗口相關的所有內存空間。
''' 7.在窗口顯示攝像頭幀 ''' clicked = False def on_mouse(event,x,y,flag,param): ''' #鼠標事件的回調函數 args: event:回調事件參數,有很多取值,分別對應不同的鼠標事件 param:可選參數,它是setMouseCallback()函數的第三個參數 默認為0 flag:標志參數 如 cv2.EVENT_FLAG_LBUTTON:該事件對應按下鼠標左鍵 x,y:鼠標坐標 ''' global clicked #鼠標左鍵松開 if event == cv2.EVENT_LBUTTONUP: clicked = True camera_capture = cv2.VideoCapture(0) #指定窗口名來創建窗口 cv2.namedWindow('window 4') #設置鼠標事件回調函數 來獲取鼠標輸入 cv2.setMouseCallback('window 4',on_mouse) print('Showing camera feed.Click window or press any key to stop') success,frame = camera_capture.read() #OpenCV窗口只有在調用cv2.waitKey()函數時才會更新,並且waitKey()函數只有在OpenCV窗口成為活動窗口時,才能捕獲輸入信息 while success and cv2.waitKey(1) == -1 and not clicked: cv2.imshow('window 4',frame) success,frame = camera_capture.read() camera_capture.release() cv2.waitKey(10) #等候10ms cv2.destroyAllWindows() #銷毀所有窗口
