第一節,初識OpenCV3-圖像的讀、寫、顯、格式轉化等


之前一直在看深度學習,突然用到了對圖像處理的知識,所以過來補充一下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:
  1. cv2.IMREAD_COLOR=1:讀入一副彩色圖像,將圖像轉化為三通道BGR彩色圖像。圖像的透明度會被忽略,這是默認參數;
  2. cv2.IMREAD_GRAYSCALE=0::將加載的圖像轉換為單通道灰度圖;
  3. cv2.IMREAD_UNCHANGED = -1:已經廢除,不再使用;
  4. cv2.IMREAD_ANYDEPTH=2:若載入圖像深度為16位或32為就返回其對應深度,否則將圖像轉換為8位圖像;
  5. cv2.IMREAD_ANYCOLOR=4:保持圖像原格式,可以讀取任意可能的彩色格式;
  6. 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)cv2.waitKey() 
鍵盤綁定函數。需要指出的是它的時間尺度是毫 秒級。函數等待特定的幾毫秒,看是否有鍵盤輸入。特定的幾毫秒之內,如果 按下任意鍵,這個函數會返回按鍵的ASCII 碼值,程序將會繼續運行。如果沒 有鍵盤輸入,返回值為-1,如果我們設置這個函數的參數為0,那它將會無限期的等待鍵盤輸入。它也可以被用來檢測特定鍵是否被按下。
'''
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()      #銷毀所有窗口


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM