白话文讲计算机视觉-第二讲-cameo类库

上一讲小木我讲解了图片的读取,保存。视频的读取、保存、以及如何调用摄像头。这节课我们要将这些东西给合而为一。怎么合而为一呢,美国的天才,也就是《OPENCV3 计算机视觉 PYTHON语言实现》这本书的作者,他做了一个程序叫做Cameo。这个程序集合了所有的方法。那么这个程序包括两个文件,分别是cameo.pymanager.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

也可以扫描二维码哦!


猜你喜欢

转载自blog.csdn.net/u013631121/article/details/80412111