2018-7-3
好几天没来了,上周五按照书中程序,手打一份代码跑了一下,今天分析总结一下。
实现的功能:
1、从摄像头读取视频流,将其在窗口中实时显示
2、使用键盘按键可以实现截图、录取视频、退出的功能
首先这个程序将视频I/O流代码与应用程序代码分离。
I/O流接口分为:CaptureManager类和WindowManager类
CaptureManager:读取新的帧,将帧分派到一个或多个输出中(如图想、视频和窗口)
WindowManager:以面向对象的形式处理窗口和事件
import cv2, time
from 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)
# self._t1 = None
# self._t2 = None
def run(self):
"""Running the main loop."""
self._windowManager.createWindow()
while self._windowManager.isWindowCreated:
# self._t1 = time.time()
self._captureManager.enterFrame()
frame = self._captureManager.frame
# TODO:Filter the frame
self._captureManager.exitFrame()
self._windowManager.processEvents()
# self._t2 = time.time()
# print(self._t2 - self._t1)
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: # table
if not self._captureManager.isWritingVideo:
print("2222")
self._captureManager.startWritingVideo('screencast111.avi')
else:
print("23333")
self._captureManager.stopWritingVideo()
elif keycode == 27: # escape
self._windowManager.destroyWindow()
if __name__ == "__main__":
Cameo().run()
__init__
,创建WindowManager、CaptureManager两个类的实例对象run
函数 enterFrame()
和exitFrame()
。同时不停扫描键盘输入。onKeypress
函数 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
# python3整形没有限制大小,所以python3没有long类型
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:
self._success, 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
#################################
# 添加 enterFrame() 和 exitFrame #
#################################
def enterFrame(self):
"""Capture the next frame. is any."""
# 第一步,检查是否有之前帧存在
assert not self._enteredFrame, \
'previous enterFrame() had no matching extiFrame()'
if self._capture is not None:
self._enteredFrame = self._capture.grab()
def exitFrame(self):
"""Draw to the window.写入文件,释放帧"""
# 检查是否有捕获的帧是可获取的
# getter可能获取并缓存帧
if self._frame is None:
self._enteredFrame = False
return
# 更新帧估计和相关变量
if self._framesElapsed == 0:
self._startTime = time.time()
else:
timeElapsed = time.time() - self._startTime
self._fpsEstimate = self._framesElapsed / timeElapsed
self._fpsEstimate += 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('I', '4', '2', '0')):
"""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._videoWriter = None
self._videoFilename = None
self._videoEncoding = 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
fps = 13
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)
#####################
# 添加Windowmanager #
#####################
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)
这部分先来看CaptureManager
类:
__init__(self, capture, previewWindowManager = None, shouldMirrorPreview = False)
CaptureManager
类被定义了三个参数,分别为输入的视频流,输出的窗口以及镜像参数。
1.通道channel
self._channel = 0
@property
def channel(self):
return self._channel
@channel.setter
def channel(self, value):
if self._channel != value:
self._channel = value
self._frame = None
2.帧frame
self._frame = None
@property
def frame(self):
if self._enteredFrame and self._frame is None:
_, self._frame = self._capture.retrieve()
return self._frame
3.enterFrame()
def enterFrame(self):
"""Capture the next frame. is any."""
# 第一步,检查是否有之前帧存在
assert not self._enteredFrame, 'previous enterFrame() had no matching extiFrame()'
if self._capture is not None:
self._enteredFrame = self._capture.grab()
4.exitFrame()
实现了从当前通道获取图像,估计帧速率,显示图像,执行暂停的请求,写入(保存)图像。
def exitFrame(self):
"""Draw to the window.写入文件,释放帧"""
# 检查是否有捕获的帧是可获取的
# getter可能获取并缓存帧
if self._frame is None:
self._enteredFrame = False
return
# 更新帧估计和相关变量
if self._framesElapsed == 0:
self._startTime = time.time()
else:
timeElapsed = time.time() - self._startTime
self._fpsEstimate = self._framesElapsed / timeElapsed
self._fpsEstimate += 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
5.其他文件的写入
def writeImage(self, filename):
"""Write the next exited frame to an image file."""
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
def stopWritingVideo(self):
"""Stop writing exited frames to a video file."""
self._videoWriter = None
self._videoFilename = None
self._videoEncoding = 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
# fps = 13
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)
后面的WindowManager
比较好理解,不用多说。
#####################
# 添加Windowmanager #
#####################
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)
目前觉得比较难的
1.将程序分解,分框架
2.每一部分需要考虑到执行到该部分,某些变量应该是什么样(True or False)
3.代码逻辑性稍稍好一些