白話文講計算機視覺-第二講-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

也可以掃描二維碼哦!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章