OpenCV3計算機視覺Python語言實現(二):處理檔案、攝像頭和圖形使用者介面
- 2.1 基本I/O指令碼
- 2.1.1 讀/寫影象檔案
- 2.1.2 影象和原始位元組之間的轉換
- 2.1.3使用numpy.array()訪問影象資料
- 2.1.4 視訊檔案的讀寫
- 2.1.5 捕獲攝像頭的幀
- 2.1.6 在視窗顯示影象
- 2.1.7 在視窗顯示攝像頭幀
- 2.2 Cameo專案(人臉跟蹤和影象處理)
- 2.3 Cameo-面向物件設計
安裝
pip install wheel
pip install ****.whl
大多數常用的OpenCV函式都在cv2模組中。可能會遇到其他基於cv2.cv模組的,這些都是傳統版本,Python模組被成為cv2並不是表示該模組針對OpenCV2.x.x版本的,而是因為該模組引入了一個更好的API介面,它們採用了面向物件的程式設計方式,這與以前的cv模組有所不同,以前的cv模組更多采用面向過程化的程式設計方式。
2.1 基本的I/O指令碼
2.1.1 讀/寫影象檔案
OpenCV的imread()函式和imwrite()函式能支援各種靜態影象檔案。不管哪種格式,每個畫素都會有一個值,但不同格式的表示畫素的方式不同。如:通過numpy陣列來建立一個黑色的正方形影象:
img=numpy.zeros((4,2),dtype=numpy.uint8)
# dtype=uint8表示每個畫素都由一個8位整數來表示,即每個畫素值的範圍是0~255
cv2.cvtColor函式:將該影象轉換成BGR模式或者RGB模式
import cv2
imgBGR=cv2.cvtColor(img,cv2.COLOR _GRAY2BGR)
print imgBGR,imgBGR.shape
# img.shape():返回HxWxC.img為4行2列的二維陣列。imgBGR.shape=(4,2,3),4頁*2行*3列,其中每一個行的3列分別代表BGR值或BGR值.
取值img[0,0,0]或img[0][0]:第一個值表示y座標或行,0表示頂部;第二個值表示畫素的x座標或者列;第三個值表示顏色通道
# 影象的讀取、顯示、儲存
import cv2
img=cv2.imread('lena.jpg')
# 讀取第二個引數表示以什麼方式讀入,將影象以灰度影象讀入:cv2.imread('./lena.jpg',cv2.IMREAD_GRAYSCALE)第二個引數省略時,預設Opencv以BGR的格式讀入
cv2.imshow('lena_img',img)
cv2.waitKey(0)
# cv2.waitKey(0)是一個鍵盤繫結函式。需要指出的是它的時間尺度是毫秒級。函式等待特定的幾毫秒,看是否有鍵盤輸入。特定的幾毫秒之內,如果按下任意鍵,這個函式會返回按鍵的ASCII碼,程式會繼續執行。如果沒有鍵盤輸入,返回為-1,如果我們設定這個函式的引數為0,那麼會無限期的等待鍵盤輸入。它也可以被用來檢測特定鍵是都被按下。
cv2.destoryAllWindows()
# 關閉所有開啟的視窗。也可以通過cv2.destoryWindows(windowsName)來關閉指定的視窗
cv2.imwrite('lena2.png',img)
# imwrite()要求影象為BGR或者灰度格式
2.1.2 影象和原始位元組之間的轉換:bytearray()函式
若一幅影象的通道有8位,則可將其顯示轉換成標準的一維Python bytearray格式:
import numpy as np
byteArray=bytearray(image)
print len(byteArray) # 輸出為HxWxC
# 由位元組轉換成影象
grayImage=np.array(byteArray).reshape(height,width)
bgrImage=np.array(byteArray).reshape(height,width,3)
# 隨機生成位元組
import os
randomByteArray=bytearray(os.urandom(120000))
flatNumpyArray=numpy.array(randomByteArray) # OpenCV影象是.array型別的二維或者三維陣列,因此轉換成array"
bgrImage=flatNumpyArray.reshape(200,200,3)
cv2.imshow("bgrImage",bgrImage)
2.1.3使用numpy.array()訪問影象資料
訪問陣列中特定位置的值使用np.array()提供的item(),設定特定位置的值:itemset()
import cv2
import numpy as np
lenaImage = cv2.imread('lena.jpg')
print lenaImage.item(150, 120, 0)// 訪問特定位置
lenaImage.itemset((150, 120, 0), 255) // 設定特定位置的值
通過迴圈處理陣列的效率很低,採用索引的方式來解決這個問題:
import cv2
import numpy as np
img = cv2.imread('lena.jpg') img[:,:,1]=0
img[:,:,1] = 0 // 將所有的G通道設定為0
img[0:100,0:100,:] // 得到影象從左上角開始100*100的區域三個通道值
img[0,0]=[0,0,0] // 將黑色BGR值賦給影象第0行第0列的三通道值
其他屬性
print img.shape // 返回影象H,W,C的陣列
print img.size // 返回影象畫素的大小
print img.dtype // 得到影象的資料型別(通常為一個無符號整數型別的變數和該型別佔的位數,比如uint8)
2.1.4 視訊檔案的讀寫
OpenCV提供了VideoCapture和VideoWriter類來支援各種視訊的讀寫。在達到視訊檔案末尾之前,VideoCapture都會通過read()來獲取新的一幀,每一幀都是基於BGR格式的影象;
可將一幅影象傳遞給VideoWriter類的write()函式,該函式會將這副影象加到VideoWriter類所指向的檔案。
import cv2
videoCapture = cv2.VideoCapture('meipai.mp4')
fps = videoCapture.get(cv2.CAP_PROP_FPS) # 幀速率,fps幀/秒
size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print size # (854,480)注意此時的size為WxH
videoWriter = cv2.VideoWriter('meipai.avi', cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, size)
success, frame = videoCapture.read()
print frame.shape # (480,854,3) 此時為讀取到的每一張圖片大小HxWxC
while success: # success為bool型,當讀到末尾的時候為空
videoWriter.write(frame)
success, frame = videoCapture.read()
videoWriter類的建構函式需要指定視訊編解碼器。編解碼器根據系統的不同而不同,一下是常用的一些選項:
- cv2.VideoWriter_fourcc('I','4','2','0'):該選項是一個未壓縮的YUV顏色編碼,是4:2:0色度子取樣。這種編碼有很好的相容性,但會產生較大的檔案,副檔名為avi
- cv2.VideoWriter_fourcc('P','I','M','1'):該選項是MPEG-1編碼型別,副檔名為avi
- cv2.VideoWriter_fourcc('X', 'V', 'I', 'D'):MPEG-4編碼,如果希望得到的視訊大小為平均值,推薦使用此選項,副檔名avi
- cv2.VideoWriter_fourcc('T', 'H', 'E', 'O'):該選項是Ogg Vorbis,副檔名是avi
- cv2.VideoWriter_fourcc('F', 'L', 'V', '1'):該選項是一個Flash視訊,副檔名為.flv
2.1.5 捕獲攝像頭的幀
import cv2
cameraCapture = cv2.VideoCapture(0)
size = (int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fps = 30 # 假定幀速率為30
videoWriter = cv2.VideoWriter("MyOutputVideo.avi", \
cv2.VideoWriter_fourcc("I", "4", "2", "0"), fps, size)
success, frame = cameraCapture.read()
numFrameRemaining = 10 * fps - 1
while success and numFrameRemaining > 0:
videoWriter.write(frame)
success, frame = cameraCapture.read()
numFrameRemaining -= 1
cameraCapture.release()
2.1.6 在視窗顯示影象
2.1.7 在視窗顯示攝像頭幀
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 not clicked and cv2.waitKey(1) == -1:#沒達到停止條件時
cv2.imshow("MyWindow", frame)
success, frame = cameraCapture.read()
cv2.destroyAllWindows()
cameraCapture.release()
- cv2.waitKey():獲取鍵盤輸入,引數為等待鍵觸發的時間,單位毫秒,其返回值為-1(表示沒有被鍵盤按下)或ASCII碼。
- setMouseCallback()獲取滑鼠輸入
opencv的視窗函式和waitKey函式相互以阿里。Opencv的視窗只有在呼叫waitKey函式時才會更新,waitkey函式只有在opencv視窗成為活動視窗時才能捕獲輸入資訊。
在一些系統中,waitKey()的返回值可能比ASCII碼的值更大(在Linux系統中,如果Opencv使用GTK作為後端的GUI庫,就會出現一個眾所周知的bug
在所有的系統中,可以通過讀取返回值的最後一個位元組來保證只提取ASCII碼,具體程式碼如下:
keycode = cv2.waitKey(1)
if keycode != -1:
keycode &= 0xFF
2.2-2.3 cameo專案(人臉跟蹤和影象處理)
1.manager.py
manager.py
# -*-coding:utf-8-*-
import cv2
from cv2 import *
import numpy
import time
class CaptureManager(object):
def __init__(self, capture, previewWindowManager=None, \
shouldMirrorPreview=False):
self.previewWindowManager = previewWindowManager
self.shouldMirrorPreview = shouldMirrorPreview
# 注意大多數成員(member)變數為非公有變數,這類變數名前會加一個下劃線進行標識。
self._capture = capture
self._channel = 0
self._enteredFrame = False
self._frame = None
self._imageFilename = None
self._videoFilename = None
self._videoEncoding = None
self._videoWriter = None
self._startTime = None
self._framesElapsed = long(0)
self._fpsEstimate = None
# 關於@符號的含義可以參考連結:https://www.zhihu.com/question/26930016
@property
def channel(self):
return self._channel
@channel.setter
def channel(self, value):
if self._channel != value:
self._channel = value
self._frame = None
@property
def frame(self):
if self._enteredFrame and self._frame is None:
_, self._frame = self._capture.retrieve()
return self._frame
@property
def isWritingImage(self):
return self._imageFilename is not None
@property
def isWritingVideo(self):
return self._videoFilename is not None
def enterFrame(self):
"""Capture the next frame,if any."""
# But first ,check that any previous frame was exited.
assert not self._enteredFrame, 'previous enteredFrame() had no matching exitFrame()'
if self._capture is not None:
self._enteredFrame = self._capture.retrieve()
def exitFrame(self):
"""Draw to the window,write to files,Release the frame."""
# Check whether ant grabbed frame is retrievable.
# The getter may retrieve and cache the frame
if self._frame is None:
self._enteredFrame = False
return
# Update the FPS estimate and related variables
if self._framesElapsed == 0:
self._startTime = time.time()
else:
timeElapsed = time.time() - self._startTime
self._fpsEstimate = int(self._framesElapsed / timeElapsed)
self._framesElapsed += 1
# Draw to the Window,if any.
if self.previewWindowManager is not None:
if self.shouldMirrorPreview:
mirroredFrame = numpy.fliplr(self._frame).copy()
self.previewWindowManager.show(mirroredFrame)
else:
self.previewWindowManager.show(self._frame)
# Write to the image file,if any.
if self.isWritingImage:
cv2.imwrite(self._imageFilename, self._frame)
self._imageFilename = None
# Write to the video file,if any.
self._writeVideoFrame()
# Release the Frame
self._frame = None
self._enteredFrame = False
def writeImage(self, filename):
"""write the next exited frame to an image frame"""
self._imageFilename = filename
def startWritingVideo(self, filename, encoding=cv2.VideoWriter_fourcc("I", "4", "2", "0")):
"""start writing exited frames to a video file"""
self._videoFilename = filename
self._videoEncoding = encoding
print self._videoEncoding
def stopWritingVideo(self):
"""Stop writing exited frames to a video file"""
self._imageFilename = None
self._videoEncoding = None
self._videoWriter = None
def _writeVideoFrame(self):
if not self.isWritingVideo:
return
if self._videoWriter is None:
fps = self._capture.get(cv2.CAP_PROP_FPS)
if fps == 0.0:
# the capture fps is unknow ,so ues an estimate.
if self._framesElapsed < 20:
# wait until more frames elapse so that the estimate is stable
return
else:
fps = self._fpsEstimate
size = (int(self._capture.get(cv2.CAP_PROP_FRAME_WIDTH)), \
int(self._capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
# self._videoWriter = cv2.VideoWriter(self._videoFilename, self._videoEncoding, fps, size)
print "self._videoEncoding:",self._videoEncoding
self._videoWriter = cv2.VideoWriter(self._videoFilename, self._videoEncoding, fps, size)
print self._frame
self._videoWriter.write(self._frame)
class WindowManager(object):
def __init__(self, windowName, keypressCallback=None):
self.keypressCallback = keypressCallback
self._windowName = windowName
self._isWindowCreated = False
@property
def isWindowCreated(self):
return self._isWindowCreated
def createdWindow(self):
cv2.namedWindow(self._windowName)
self._isWindowCreated = True
def show(self, frame):
cv2.imshow(self._windowName, frame)
# cv2.waitKey(1)
def destroyWindow(self):
cv2.destroyWindow(self._windowName)
self._isWindowCreated = False
def processEvents(self):
keycode = cv2.waitKey(1)
if self.keypressCallback is not None and keycode != -1:
# Discard any non-ASCII info encoded by GTK
keycode &= 0xFF
self.keypressCallback(keycode)
2.cameo.py
import cv2
from managers import WindowManager, CaptureManager
import filters
class Cameo(object):
def __init__(self):
self._windowManger = WindowManager('Cameo', self.onKeypress)
self._captureManger = CaptureManager(cv2.VideoCapture(0), self._windowManger, True)
self._curveFilter=filters.BlurFilter()
def run(self):
"""Run the main loop"""
self._windowManger.createdWindow()
while self._windowManger.isWindowCreated:
self._captureManger.enterFrame()
frame = self._captureManger.frame
filters.strokeEdges(frame,frame)
self._curveFilter.apply(frame,frame)
self._captureManger.exitFrame()
self._windowManger.processEvents()
def onKeypress(self, keycode):
"""Handle a keypress.
space -> Take a screenshot
tab -> Start/stop recoding a screenshot
escape -> Quit
"""
if keycode == 32: # space
self._captureManger.writeImage("./screenshot.png")
elif keycode == 9: # tab
if not self._captureManger.isWritingVideo:
self._captureManger.startWritingVideo('./screenshot.avi')
else:
self._captureManger.stopWritingVideo()
elif keycode == 27: # escape
self._windowManger.destroyWindow()
if __name__ == '__main__':
Cameo().run()
這裡出現一個問題,錄製的視訊大小不為0,但是時長總是0.不知道是什麼原因?每一幀都被後一幀覆蓋掉了?還不懂,還望懂的小夥伴不吝賜教。