程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

第二节,使用面向对象实现图像捕获,视频保存

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

一、示例程序

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

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

创建一个cameo目录,在目录下创建managers.pycameo.py文件。

1.1 managers.py

managers.py文件代码如下,实现了CaptureManager类和WindowManager类;

# -*- 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)
            
 

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1459)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示