【OpenCV】Cameo

2018-7-3
好几天没来了,上周五按照书中程序,手打一份代码跑了一下,今天分析总结一下。

实现的功能:
1、从摄像头读取视频流,将其在窗口中实时显示
2、使用键盘按键可以实现截图、录取视频、退出的功能

首先这个程序将视频I/O流代码与应用程序代码分离。
I/O流接口分为:CaptureManager类和WindowManager类
CaptureManager:读取新的帧,将帧分派到一个或多个输出中(如图想、视频和窗口)
WindowManager:以面向对象的形式处理窗口和事件

Cameo.py

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()
  1. 定义初始化函数__init__,创建WindowManager、CaptureManager两个类的实例对象
  2. 定义run函数
    (1)创建一个窗口
    (2)执行循环:不停地捕获下一帧,存入文件,释放帧。也就是不停地调用enterFrame()exitFrame()。同时不停扫描键盘输入。
  3. 定义onKeypress函数
    ① space(32):截屏
    ② tab(9):录视频
    ③escape(27):退出,关闭窗口

managers.py

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.代码逻辑性稍稍好一些

你可能感兴趣的:(opencv,Python)