zoukankan      html  css  js  c++  java
  • 第二节,使用面向对象实现图像捕获,视频保存

    上一节我们讲解了图像的读取,以及视频读写,摄像头读写的基本操作。但是我们在做测试的时候是使用的面向过程的方法。为了提高模块化水平和扩展性,这一节我们采用面向过程方式实现。

    该程序主要包括三个部分,分别为:

    • CaptureManager类,主要负责提取视频流。 该类可以用来读取新的帧(来自视频文件或者摄像头),并将帧分配到一个或者多个输出中,这些输出包括静止的图像文件、视频文件以及窗口。主要包括两个函数,enter_frame()函数和exit_frame()函数,这两个函数是成对执行的。enter_frame()负责捕获一帧图像,只有执行了exit_frame()才会把当前捕获的一帧图像获取出来,并分配到一个或者多个输出中,然后才能捕获下一帧,不然enter_frame()不会捕获新的帧。
    • WindoeManager类,用于窗口管理的类,可以用于创建一个显示图像的窗口,并捕获键盘和鼠标事件
    • Cameo类,该类会使用以上两个类,并主要有两个函数on_key_press()和run()。run()函数会执行主循环处理帧和on_key_press()事件。on_key_press()负责捕获按键消息,并处理不同的任务,比如按空格键可以截屏,按tab键可以启动/停止截屏(一个视频记录),按esc键可以退出应用程序等等。

    managers.py文件代码如下:

    # -*- coding: utf-8 -*-
    """
    Created on Sat Apr 14 12:27:01 2018
    
    """
    
    '''
    OPenCV3 计算机视觉 笔记
    第二章 : 处理文件,摄像头和图形用户界面
    Cameo项目(人脸跟踪和图像处理)
    '''
    
    '''
    采用面向对象设计方法
    定义了CaptureManager类和WindowManager类
    
    '''
    
    import cv2
    import numpy  as np
    import time
    
    class CaptureManager(object):
        '''
        定义一个CaptureManager类,作为一个高级的I/O流接口,用于提取视频流
        该类可以用来读取新的帧(来自视频文件或者摄像头),并将帧分配到一个或者多个输出中,这些输出包括
        静止的图像文件、视频文件以及窗口.
        在应用程序的主循环的每一次迭代通常应调用enter_frame()和exit_frame()函数
        
                    
        param:
            __channel:通道的初始值默认为0,只有在多个摄像头的情况下,通道初始值可能非0
            __entered_frame: 捕获的一帧图像是否还存在(是否读取了),如果已经读取了,设置为False,否则设置为True
            __frame:如果图像捕获成功,保存该帧图像  该属性保存的是在当前通道下调用enter_frame()函数时对应的图像
            __image_file_name:捕获的一帧图像  保存路径  如果不为None,表示正在保存当前帧图片
            __video_file_name:捕获的视频  保存路径 如果不为None,表示正在保存当前帧图片到视频文件中
            __video_encoding:视频文件的编码格式
            __video_writer:VideoWriter对象,用于保存写入视频文件
            
            __fps_estimate:估计帧速率   __fps_estimate=__frames_elapsed/花费时间
            __start_time:开始捕获的时间
            __frames_elapsed:当前捕获的总帧数
        '''
        
        def __init__(self,capture,preview_window_manager= None,should_mirror_preview = False):
            '''
            构造函数
            
            args:
                capture:VideoCapture对象,用于读取视频文件或者捕获摄像头图像  例如capture = cv2.VideoCapture(0)
                preview_window_manager:预处理窗口管理器,WindowManager对象,如果设置了该参数,在调用enter_frame()函数
                                        时会把当前捕获帧图像显示在指定的窗体上。
                should_mirror_preview:是否在指定窗口上镜像显示(水平翻转)
               
    
            '''
            
            self.preview_window_manager = preview_window_manager
            self.should_mirror_preview = should_mirror_preview
        
            #定义私有属性    
            self.__capture = capture
            self.__channel = 0       
            self.__entered_frame = False
            self.__frame = None
            self.__image_file_name = None
            self.__video_file_name = None
            self.__video_encoding = None
            self.__video_writer = None
            
            self.__start_time =None
            self.__frames_elapsed = int(0)
            self.__fps_estimate = None
            
        #将一个getter()方法变成属性(和C#中的get访问器类似)
        @property
        def channel(self):
            return self.__channel
            
    
        #将一个setter()方法变成属性(和C#中的set访问器类似)
        @channel.setter
        def channel(self,value):
            if self.__channel != value:
                self.__channel = value
                self.__frame = None
                
        @property
        def frame(self):
            '''
            如果一帧图像捕获成功,对该帧进行解码,并保存该帧图像
            返回捕获的图片
            '''
            if self.__entered_frame and self.__frame is None:
                #对捕回的帧解码,取回捕获的图像
                _,self.__frame = self.__capture.retrieve()
            return self.__frame
        
        @property
        def is_writing_image(self):
            '''
            判断是否正在保存图像
            '''
            return self.__image_file_name is not None
        
        @property
        def is_writing_video(self):
            '''
            判断是否正在保存视频
            '''
            return self.__video_file_name is not None
        
        
        '''
        注意:enter_frame()函数和exit_frame()函数是成对执行的
        enter_frame()负责捕获一帧图像,只有执行了exit_frame()才会把当前捕获的一帧图像获取出来,然后才能捕获
        下一帧,不然enter_frame()不会捕获新的帧
        
        '''
        def enter_frame(self):
            '''
            开始捕获下一帧图像,并设置捕获捕获的图像状态?已经读取?或者没有读取
            只能(同步)捕获一帧,而且会推迟从一个通道的获取,以便随后能从属性frame中读取(即执行exit_frame()函数)
            (即捕获了一帧图像之后,只有读取之后,才能捕获下一帧,不然会推迟捕获下一帧)
            '''
            #检查上一帧图像是否还存在(是否读取了),如果已经读取了,则可以捕获下一帧
            assert not self.__entered_frame,'上一次执行了enter_frame(),还没有执行exit_frame()函数'
            
            #对于一组摄像头,开始捕获一帧新的图像
            if self.__capture is not None:
                self.__entered_frame = self.__capture.grab()
                
        
        def exit_frame(self):
            '''
            从当前通道获取图像,估计帧速率,通过窗口管理器显示图像,执行暂停的请求,从而向文件中写入图像(如果指定文件路径)
            '''       
            #获取和读取已经捕获的一帧图片,然后设置标志位__entered_frame,以便读取下一帧
            if self.frame is None:
                self.__entered_frame = False
                return
            
            
            #估计帧速率  当前总帧数 / 花费时间
            if self.__frames_elapsed == 0:
                self.__start_time = time.time()
            else:
                time_elapsed = time.time() - self.__start_time        
                self.__fps_estimate = self.__frames_elapsed / time_elapsed        
            self.__frames_elapsed += 1
            
            
            #如果指定了窗口管理器 在窗口中显示图像
            if self.preview_window_manager is not None:
                #是否在窗口中镜像显示(水平翻转)
                if self.should_mirror_preview:
                    mirrored_framed = np.fliplr(self.__frame).copy()
                    self.preview_window_manager.show(mirrored_framed)
                else:
                    self.preview_window_manager.show(self.__frame)
                    
                    
            #把判断是否指定图片路径 如果指定把图像写入文件
            if self.is_writing_image:
                cv2.imwrite(self.__image_file_name,self.__frame)
                self.__image_file_name = None        
            
            
            #把每一帧图像写入视频文件
            self.__write_video_frame()
            
            
            #清空标志位 释放帧
            self.__entered_frame = False
            self.__frame = None
            
        
        def write_image(self,filename):
            '''
            指定每一帧图像的写入路径,实际的写入操作会推迟到下一次调用exit_frame()函数
            
            args:
                filename:图片文件路径
            '''
            self.__image_file_name = filename
            
            
        def start_write_video(self,filename,encoding=cv2.VideoWriter_fourcc('m','p','4','v')):
            '''
            指定每一帧图像写入的视频路径,实际的写入操作会推迟到下一次调用exit_frame()函数
            
            args:
                filename:视频文件路径
                encoding:视频编码格式
            '''
            self.__video_file_name = filename
            self.__video_encoding = encoding
            
            
        def stop_write_video(self):
            '''
            停止把每一帧图像写入指定视频文件
            清空一些相关参数
            '''
            if self.__video_writer is not  None:
                self.__video_writer.release()
                
            self.__video_file_name = None
            self.__video_encoding = None
            self.__video_writer = None
            
        
        def __write_video_frame(self):
             '''
             把每一帧图像都写入视频文件
             可以创建或者像视频文件追加内容
             '''
             #判断是否指定了视频路径
             if not self.is_writing_video:
                 return
             
             #判断VideoWriter对象是否存在,不存在,重新创建一个用于写视频,并初始化相关参数
             if self.__video_writer is None:
                #获取帧速率
                fps = self.__capture.get(cv2.CAP_PROP_FPS)
                #如果获取不到,则使用估计值
                if fps == 0.0:
                    #等待捕获了很多图像后,才使用估计值,这样会更稳定
                    if self.__frames_elapsed < 20:
                        return 
                    else:
                        fps = self.__fps_estimate
                        
                #获取图像尺寸     
                size =(int(self.__capture.get(cv2.CAP_PROP_FRAME_WIDTH)),
                   int(self.__capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
                
                
                #1.VideoWriter类的构造函数指定视频文件名,这个文件名对应的文件若存在,则会被覆
                #2.需要指定编解码器
                #3.帧速率
                #4.帧大小
                self.__video_writer = cv2.VideoWriter(self.__video_file_name,self.__video_encoding,fps,size)
                
             #把一帧图像写入视频文件
             self.__video_writer.write(self.__frame)
            
        def release(self):
            '''
            释放对象
            '''
            if self.__capture is not None:
                self.__capture.release()
                
            if self.__video_writer is not None:
                self.video_writes.release()
            
                
    class  WindowManager(object):
        '''
        定义一个窗口管理器类 用于抽象窗口和键盘,鼠标
        
        Parma :
            __window_name:该窗口名称
            __is_window_created:表示该窗口是否已经被创建 存在True,不存在False
        '''
        def __init__(self,window_name,key_press_call_back = None,mouse_call_back = None):
            '''
            构造函数 初始化参数
            
            args:
                window_name:指定准备创建的窗口名字
                key_press_call_back:键盘事件回调函数 该函数有一个参数,即按下的键盘的ASCII码
                mouse_call_back:鼠标事件回调函数        
                    鼠标事件的回调函数5个参数    (event,x,y,flag,param)
                    args:
                        event:回调事件参数,有很多取值,分别对应不同的鼠标事件
                        param:可选参数,它是setMouseCallback()函数的第三个参数 默认为0
                        flag:标志参数 如 cv2.EVENT_FLAG_LBUTTON:该事件对应按下鼠标左键
                        x,y:鼠标坐标
            '''
            self.key_press_call_back = key_press_call_back
            self.mouse_call_back = mouse_call_back
            
                
            self.__window_name =window_name
            self.__is_window_created = False
            
            if self.mouse_call_back is not None:
                #设置鼠标事件回调函数 来获取鼠标输入
                cv2.setMouseCallback(window_name,self.mouse_call_back)
            
                    
        @property
        def is_window_created(self):
            '''
            判断窗口是否已经被创建
            '''        
            return self.__is_window_created
        
        def create_window(self):
            '''
            创建一个窗口
            '''
            #创建一个指定名字的窗口
            cv2.namedWindow(self.__window_name)
            self.__is_window_created = True
            
        def show(self,frame):
            '''
            在窗口中显示一帧图像
            
            args:
                frame:一帧图像
            '''
            cv2.imshow(self.__window_name,frame)
            
        def destory_window(self):
            '''
            销毁该窗口
            '''
            cv2.destroyWindow(self.__window_name)
            self.__is_window_created = False
            
        def process_event(self):
            '''
            该函数用于处理键盘事件
            '''
            #等待1ms
            keycode = cv2.waitKey(1)
            #有按键按下(keycode != -1)或者有按键回调函数
            if self.key_press_call_back is not None and keycode != -1:
                #抛弃GTK的非ASCII信息
                keycode &= 0xff
                #执行键盘事件回调函数
                self.key_press_call_back(keycode)
                
     

    cameo.py文件代码如下:

    # -*- coding: utf-8 -*-
    """
    Created on Sat Apr 14 12:27:01 2018
    
    """
    
    '''
    OPenCV3 计算机视觉 笔记
    第二章 : 处理文件,摄像头和图形用户界面
    Cameo项目(人脸跟踪和图像处理)
    '''
    
    '''
    采用面向对象设计方法
    定义了Cameo类
    
    '''
    
    import cv2
    from  managers import CaptureManager,WindowManager
    import time
    import os
    
    
    class Cameo(object):
        def __init__(self):
            #创建窗口管理器和视频捕获管理器
            self.__window_manager = WindowManager('Cameo',self.on_key_press)
            self.__capture_manager = CaptureManager(cv2.VideoCapture(0),self.__window_manager,True)
            
        def run(self):
            '''
            运行主循环
            '''
            #创建窗口
            self.__window_manager.create_window()
                   
            
            #循环捕获每一帧图像,并显示 直至窗口销毁
            while self.__window_manager.is_window_created:
                
                #获取一帧图像 并在指定的窗口显示
                self.__capture_manager.enter_frame()
                frame = self.__capture_manager.frame            
              
                '''
                这里可以对这一帧图像进行处理
                '''
                
                
                self.__capture_manager.exit_frame()            
                #执行键盘回调函数
                self.__window_manager.process_event()
              
            #销毁对象
            self.__capture_manager.release()               
    
            
        def on_key_press(self,keycode):
            '''
            键盘事件回调函数
            
            space -> 截屏
            tab -> 开始/结束视频录制
            escape->销毁窗体 并退出run()循环 
            '''
            #获取当前时间
            cur_time = time.strftime('%Y-%m-%d %H_%M_%S',time.localtime(time.time()))
            
            dir_name = './cameo'
            #文件保存目录
            if not os.path.isdir(dir_name):
                os.mkdir(dir_name)
            
            cur_time = dir_name + '/' + cur_time
            
          
            if keycode == 32:  #space
                #保存图片
                self.__capture_manager.write_image(cur_time+'_screenshot.jpg')
                print('截屏成功!')
    
            elif keycode == 9:  #tab
                if not self.__capture_manager.is_writing_video:
                    #开始保存视频
                    self.__capture_manager.start_write_video(cur_time+'_.avi')
                    print('开始录制视频!')
                else:
                    #停止保存视频
                    self.__capture_manager.stop_write_video()
                    print('视频录制结束!')
            
            elif keycode == 27:  #escape
                self.__window_manager.destory_window()
                print('关闭窗体!')
            
            
    if __name__ == '__main__':
        cameo = Cameo()
        cameo.run()
            
  • 相关阅读:
    Linux yum命令重装mysql
    Java多线程编程<一>
    Java内存区域与内存溢出异常
    实现一个线程安全的Queue队列
    Java 原始数据类型转换
    对象-关系映射ORM(Object Relational Mapping)(转)
    2进制,16进制,BCD,ascii,序列化对象相互转换
    Apache MINA 框架之默认session管理类实现
    Struts.properties(转)
    vue常用插件-数字滚动效果vue-count-to
  • 原文地址:https://www.cnblogs.com/zyly/p/8835056.html
Copyright © 2011-2022 走看看