上一講小木我講解了圖片的讀取,保存。視頻的讀取、保存、以及如何調用攝像頭。這節課我們要將這些東西給合而爲一。怎麼合而爲一呢,美國的天才,也就是《OPENCV3 計算機視覺 PYTHON語言實現》這本書的作者,他做了一個程序叫做Cameo。這個程序集合了所有的方法。那麼這個程序包括兩個文件,分別是cameo.py和manager.py,接下來我展示一下:
# cameo.py
# cameo.py
import cv2
from1 managers import WindowManager, CaptureManager
class Cameo(object):
def __init__(self):
self._windowManager = WindowManager('Cameo',
self.onKeypress)
self._captureManager = CaptureManager(
cv2.VideoCapture(0), self._windowManager, True)
def run(self):
"""Run the main loop."""
self._windowManager.createWindow()
while self._windowManager.isWindowCreated:
self._captureManager.enterFrame()
frame = self._captureManager.frame
if frame is not None:
# TODO: Filter the frame (Chapter 3).
pass
self._captureManager.exitFrame()
self._windowManager.processEvents()
def onKeypress(self, keycode):
"""Handle a keypress.
space -> Take a screenshot.
tab -> Start/stop recording a screencast.
escape -> Quit.
"""
if keycode == 32: # space
self._captureManager.writeImage('screenshot.png')
elif keycode == 9: # tab
if not self._captureManager.isWritingVideo:
self._captureManager.startWritingVideo(
'screencast.avi')
else:
self._captureManager.stopWritingVideo()
elif keycode == 27: # escape
self._windowManager.destroyWindow()
if __name__=="__main__":
Cameo().run()
#---------------------------------------
#manager.py
import cv2 import numpy import time class CaptureManager(object): def __init__(self, capture, previewWindowManager = None, shouldMirrorPreview = False): self.previewWindowManager = previewWindowManager self.shouldMirrorPreview = shouldMirrorPreview 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 = int(0) self._fpsEstimate = None @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: # As of OpenCV 3.0, VideoCapture.retrieve() no longer supports # the channel argument. # _, self._frame = self._capture.retrieve(channel = self.channel) _, 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 enterFrame() had no matching exitFrame()' if self._capture is not None: self._enteredFrame = self._capture.grab() def exitFrame(self): """Draw to the window. Write to files. Release the frame.""" # Check whether any 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 = 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 file.""" self._imageFilename = filename def startWritingVideo( self, filename, encoding = cv2.VideoWriter_fourcc('M','J','P','G')): """Start writing exited frames to a video file.""" self._videoFilename = filename self._videoEncoding = encoding def stopWritingVideo(self): """Stop writing exited frames to a video file.""" self._videoFilename = 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's FPS is unknown so use an estimate. if self._framesElapsed < 20: # Wait until more frames elapse so that the # estimate is more 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) 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 createWindow(self): cv2.namedWindow(self._windowName) self._isWindowCreated = True def show(self, frame): cv2.imshow(self._windowName, frame) 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) #---------------------------------------
我們這樣乍眼一看,太特麼的長了吧,看不懂呀。其實你們不是看不懂,而是太長了看着頭痛罷了。如果大家對這個程序沒興趣的話,直接複製粘貼用就行了,根本沒什麼必要讀懂。那麼怎麼用呢,我們把這兩者py文件放到一塊,然後執行cameo.py即可,執行後,打開筆記本的攝像頭,並且在屏幕上顯示。如果我們想退出,按ESC鍵、我們想抓取一幀攝像頭的截圖,那麼我們按空格鍵即可,它會圖片保存在同PY文件的目錄下,名字叫screenshot.png。如果我們想錄像,我們按一下TAB就會開始錄像,再按一下TAB就會結束錄像,並且把錄像保存在同PY文件的目錄下,名字叫screencast.avi。
我給大家展示一下效果:
很棒吧,截圖就按空格,錄像就按TAB即可。
然而,我們既然有程序,而且是開源的,我們爲啥不看?直接用倒是可以,但是瞭解一下程序木有壞處吧?恩恩,下面小木我就來講解一下這個程序吧!
首先我們來到cameo.py這個文件中,我們看45-47行(若大家行數找不到或者對不上,直接複製代碼到notepad,然後搜索一下相關字就行),也就是最後兩行:
if __name__=="__main__":
Cameo().run()
我們按一下運行,就會執行這條語句,執行之後,我們首先初始化Cameo這個類,然後執行run方法。
我們初始化這個方法是執行6-10行:
def __init__(self):
self._windowManager = WindowManager('Cameo',
self.onKeypress)
self._captureManager = CaptureManager(
cv2.VideoCapture(0), self._windowManager, True)
我們初始化時候,第一步,創建一個WindowManager的實例,這個窗口名字叫Cameo,接受鍵盤按鍵(也就是錄像或截圖)的監控的函數叫做onKeypress,這個函數後面講解。
第二步,我們創建一個CaptureManager的實例,這個實例中,第一個參數是獲取攝像頭設備,如果只有一個攝像頭括號裏面就爲0,如果多個,就輸入第幾個攝像頭的標號數字即可。第二個參數是把上面的WindowsManager實例_windowManager傳給CaptureManager的實例_captureManager。第三個參數是是否鏡像,如果True就是鏡像,因爲我們照鏡子,鏡子裏面的人是相反的,所以我們應該水平翻轉一下,給它變正。
初始化完成之後,我們就要執行Run語句了,也就是cameo.py中的12-24行:
def run(self):
"""Run the main loop."""
self._windowManager.createWindow()
while self._windowManager.isWindowCreated:
self._captureManager.enterFrame()
frame = self._captureManager.frame
if frame is not None:
# TODO: Filter the frame (Chapter 3).
pass
self._captureManager.exitFrame()
self._windowManager.processEvents()
下面我把run函數中的語句用()表示,便於區分是這個函數的內容還是其它函數的內容
(1)我們首先用WindowsManager的實例_windowManager創建一個窗口,用來在屏幕上面顯示我們的攝像頭內容。我們先判斷一下是否窗口創建完畢。
createWindow()函數如下,也就是manager.py中的156-158行:
def createWindow(self):
cv2.namedWindow(self._windowName)
self._isWindowCreated = True
我們把_windowName中傳入的名稱,也就是cameo寫入,然後把_isWindowCreated屬性改爲True。
(2)如果創建完畢的話進入循環,如果沒建立好窗口的話,直接就退出程序運行。我們按照創建完畢繼續來說。創建完畢後我們進入循環,並且執行CaptureManager的實例_captureManager的enterFrame函數。
也就是manager.py中的54-62行:
def enterFrame(self):
"""Capture the next frame, if any."""
# But first, check that any previous frame was exited.
assert not self._enteredFrame, \
'previous enterFrame() had no matching exitFrame()'
if self._capture is not None:
self._enteredFrame = self._capture.grab()
這些語句的意思是如果_capture不爲空的話,抓取攝像頭中的一幀。_capture指的是我們當初CaptureManager類實例_captureManager打開的攝像頭capture。
(3)抓取之後我們執行循環中的frame = self._captureManager.frame,把上面捕獲的幀的圖像傳入到變量frame中。
(4)執行CaptureManager中的exitFrame()函數,這個函數的用途是計算攝像頭每秒鐘的幀數,並且把攝像頭捕獲的幀放入窗口cameo中顯示。
exitFrame()這個函數在manager.py的64-99行:
def exitFrame(self):
"""Draw to the window. Write to files. Release the frame."""
# Check whether any grabbed frame is retrievable.
# The getter may retrieve and cache the frame.
if self.frame is None:
self._enteredFrame = False
return
這個函數首先先判斷一下,是否剛纔在enterFrame中真的抓取到一幀了,如果抓取到繼續,沒抓取到,退出函數,執行循環中的下一句 self._windowManager.processEvents()。我們按照抓取到的算,那麼就繼續往下執行exitFrame。但是有一點要說的是判斷語句中frame是一個函數:
def frame(self):
if self._enteredFrame and self._frame is None:
# As of OpenCV 3.0, VideoCapture.retrieve() no longer supports
# the channel argument.
# _, self._frame = self._capture.retrieve(channel = self.channel)
_, self._frame = self._capture.retrieve()
return self._frame
在這個函數中,我們首先判斷一下是否抓取圖片成功,如果成功的話,把成功的圖片的數據導入到_frame這個變量中,使用的方法是retrieve()。
我們把_frame添加東西后,它就不爲none了,那麼就繼續往下執行exitFrame:
# Update the FPS estimate and related variables.
if self._framesElapsed == 0:
self._startTime = time.time()
else:
timeElapsed = time.time() - self._startTime
self._fpsEstimate = self._framesElapsed / timeElapsed
self._framesElapsed += 1
到這裏了,我們做的是計算每秒鐘的幀數,我們第一次的話,framesElapsed爲0,那麼我們就記錄一下這個時候的時間。當我們第二次執行這一部分的時候,也就是抓取完成第二幀後,我們用這兩幀的時間相減,然後除以一個2,第三次執行這部分時候,我們用第三次時間與第一次相減,然後除以一個3,以此類推,我們就估算出了FPS(每秒幀數),而且這個估算是實時更新的。這個時間我們記錄到_fpsEstimate裏面。我們假設我們是第一次執行這個語句,執行完畢後,我們進入下面:
# 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
這部分是判斷是否按下了空格鍵,如果按下了,就把我們按下時候剛好_enterFrame捕獲的那一幀數據保存到硬盤中。如果沒有截圖,就跳過。
# Write to the video file, if any.
self._writeVideoFrame()
這部分是判斷是否按下了TAB,如果按下了,就把我們按下時候剛好_enterFrame捕獲的那一幀數據保存到硬盤中,然後每一次循環的時候都把_enterFrame保存起來,直到我們又按了一下TAB鍵,我們就不再保存了。這樣好多幀的圖片會放到一個爲avi格式的文件當中,說白了就是一個視頻。
# Release the frame.
self._frame = None
self._enteredFrame = False
最後我們把當前幀的圖像給全部變爲空,結束這個語句。
(5)回到主函數的循環,執行self._windowManager.processEvents()。這個函數如下:
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)
它的作用是監控鍵盤,看看它按下我們之前說的那些按鍵沒有,如果按下的話,就執行相應的操作。
(6)當我們執行完畢之後,我們返回循環的頭部,然後繼續執行循環,直到我們按下ESC鍵爲止。
到此爲止,我們的cameo類庫就講完了。我們還要額外補充幾點:
(1)如何監控鍵盤的:
監控鍵盤在循環語句的最後一句,當我們執行這句話的時候,我們要是鍵盤按下鍵或者沒有按下鍵,都會產生一個數據,沒按下是-1,按下了就是一個ASCII碼,我們然後把這個碼轉換爲GTK格式,然後傳入keypressCallback中,也就是我們傳入Windowsmanager類中的函數:
def onKeypress(self, keycode):
"""Handle a keypress.
space -> Take a screenshot.
tab -> Start/stop recording a screencast.
escape -> Quit.
"""
if keycode == 32: # space
self._captureManager.writeImage('screenshot.png')
elif keycode == 9: # tab
if not self._captureManager.isWritingVideo:
self._captureManager.startWritingVideo(
'screencast.avi')
else:
self._captureManager.stopWritingVideo()
elif keycode == 27: # escape
self._windowManager.destroyWindow()
我們在這裏判斷一下是否鍵入了ESC、TAB、空格其中之一,然後執行相應的方法。
(3)這個函數中還有什麼其它的方法:
def show(self, frame):
cv2.imshow(self._windowName, frame)
我們剛纔講的全是攝像頭攝像,但是這裏面類似上面的方法,也可以用於打開圖片,可以打開視頻,但是沒啥用,因爲我們直接用OPENCV自帶的類實現要比它方便。
好了,這樣我們的cameo的類庫就講完了,其實我們這裏面忽略的很多方法沒有講,我想我根本沒有必要講解了吧,自己看看應該可以看懂,要是真心看不懂,歡迎大家發評論提問我,我會爲大家一一解答。
我們OPENCV的基礎部分全部都講完了,之後我們要學習一些過濾的方法,比如腐蝕膨脹啥的,大家可以繼續跟進我的講座!
————————————————
如果對我的課程感興趣的話,歡迎關注小木希望學園-微信公衆號:
mutianwei521
也可以掃描二維碼哦!