使用Python,OpenCV捕获关键事件,并进行视频剪辑

Posted 程序媛一枚~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Python,OpenCV捕获关键事件,并进行视频剪辑相关的知识,希望对你有一定的参考价值。

使用Python,OpenCV捕获关键事件,并进行视频剪辑

上一篇博客介绍了如何使用Python、OpenCV写入视频文件,这一篇将介绍如何做有趣的视频剪辑,而不是保留整个视频文件;

总体目标是构建视频概要,将视频流的最关键,突出和有趣部分提炼成一系列短视频文件。

使用场景有:

  • 访问在区域中检测到的运动物体;
  • 截取入侵者进入房子或公寓的片段;

什么才是视频的关键帧、有趣部分,则完全取决于您的需求和目标;

使用此方法,可以将小时级的视频流素材缩小为多个秒级的有趣事件的小视频,有效地产生视频概要。

1. 效果图

检测画面中的绿色盆,并将其生成git图,如下:
在这里插入图片描述

2. 步骤

  1. 定义关键事件;
  2. 将包含事件的视频片段写入到视频文件;

利用线程以确保在输入流和输出视频剪辑文件中执行I / O时,不会放慢主程序的速度。
利用内置的Python数据结构,如DEQUE队列以按顺序存放关键帧;

3. 源码

3.1 keyclipwriter.py

# 导入必要的包
from collections import deque # 导入队列,FIFO(first in first out)先进先出原则
from threading import Thread
from queue import Queue
import time
import cv2


class KeyClipWriter:
    # bufSize 要在内存缓冲区中缓存的最大帧数
    # timeout 超时时间
    def __init__(self, bufSize=64, timeout=1.0):
        # 存储在内存中要保留的最大缓冲区大小 以及睡眠超时期间
        self.bufSize = bufSize
        self.timeout = timeout

        # 初始化帧的缓冲区(64帧)、Q:待写入的视频帧、视频写入类、 多线程(以避免I/O延迟)
        # recordering boolean类型指示录制是否已启动
        self.frames = deque(maxlen=bufSize)
        self.Q = None
        self.writer = None
        self.thread = None
        self.recording = False

    def update(self, frame):
        # 更新帧缓冲区
        self.frames.appendleft(frame)
        # 如果正在记录,将帧加入队列
        if self.recording:
            self.Q.put(frame)

    def start(self, outputPath, fourcc, fps):
        # 展示正在记录关键事件帧
        # 表明正在录制,启动视频写入类,初始化需要写入视频文件的帧队列
        self.recording = True
        self.writer = cv2.VideoWriter(outputPath, fourcc, fps,
                                      (600,600), True)
        self.Q = Queue()

        # 循环遍历所有帧,并加入队列
        for i in range(len(self.frames), 0, -1):
            self.Q.put(self.frames[i - 1])

        # 开启一个单独的线程写入帧到视频文件
        self.thread = Thread(target=self.write, args=())
        self.thread.daemon = True
        self.thread.start()

    def write(self):
        # 保持循环
        while True:
            # 如果停止了录制,退出线程
            if not self.recording:
                return
            # 判断队列是否已经没有关键事件帧了
            if not self.Q.empty():
                # 获取队列中的下一帧并写入视频文件
                frame = self.Q.get()
                self.writer.write(frame)
            # 如果队列已经空了,sleep超时时间以不浪费cpu周期
            # 使用队列时空闲一会儿尤其重要,队列数据结构是线程安全的,暗示必须在更新内部缓冲区之前获取锁/信号量。
            # 如果不sleep,当缓冲区为空时,然后写入和更新方法将不断为锁而战斗。相反,最好让视频写入类等待一会儿,直到需要写入文件的队列中存在积压的帧。
            else:
                time.sleep(self.timeout)

    def flush(self):
        # 通过刷新所有剩余帧来清空队列
        while not self.Q.empty():
            frame = self.Q.get()
            self.writer.write(frame)

    def finish(self):
        # 表明结束录制、加入线程
        # 刷新队列中的所有帧到视频文件
        # 释放视频写入指针
        self.recording = False
        self.thread.join()
        self.flush()
        self.writer.release()

3.2 save_key_events.py

# USAGE
# python save_key_events.py --output output

#  导入必要的包
from pyimagesearch.keyclipwriter import KeyClipWriter
from imutils.video import VideoStream
import argparse
import datetime
import imutils
import time
import cv2

# 构建命令行参数及解析
# --output 输出视频剪辑文件的路径及名称
# --picamera 是否使用树莓摄像头,默认-1:不是
# --fps 输出视频剪辑的帧率(即每秒的帧数) 默认20
# --codec 输出视频剪辑的四维编解码器 默认MJPG
# --buffer-size 视频剪切类的缓冲区大小 默认32,用于存储来自相机传感器最近轮询帧的内存缓冲区的大小。
#               较大的缓冲大小将允许在“关键事件”中包含在输出视频剪辑中的“关键事件”之前和之后的更多上下文,而较小的缓冲大小将在“关键事件”之前和之后存储更少的帧;
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", required=True,
                help="path to output directory")
ap.add_argument("-p", "--picamera", type=int, default=-1,
                help="whether or not the Raspberry Pi camera should be used")
ap.add_argument("-f", "--fps", type=int, default=20,
                help="FPS of output video")
ap.add_argument("-c", "--codec", type=str, default="MJPG",
                help="codec of output video")
ap.add_argument("-b", "--buffer-size", type=int, default=32,
                help="buffer size of video clip writer")
args = vars(ap.parse_args())

# 初始化视频流,并预热相机传感器2s
print("[INFO] warming up camera...")
vs = VideoStream(usePiCamera=args["picamera"] > 0).start()
time.sleep(2.0)

# 定义绿色球的较低和上边界HSV颜色空间
greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)

# 初始化关键事件剪辑类,
# 初始化用于计算尚未包含任何有趣事件的连续帧数的整数。
kcw = KeyClipWriter(bufSize=args["buffer_size"])
consecFrames = 0

# 保持循环
while True:
    # 获取当前帧,缩放,初始化一个布尔类型值updateConsecFrames表示是否连续帧计数器应该被更新
    frame = vs.read()
    frame = imutils.resize(frame, width=600)
    updateConsecFrames = True

    # 高斯模糊帧,并将其RGB颜色空间转换为HSV颜色空间,因此可以使用颜色阈值
    blurred = cv2.GaussianBlur(frame, (11, 11), 0)
    hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)

    # 为绿色构建一个颜色mask,然后执行一系列腐蚀膨胀操作已移除mask中小斑点
    # cv2.inRange实际进行颜色阈值处理,cv2.erode腐蚀,cv2.dilate膨胀操作
    mask = cv2.inRange(hsv, greenLower, greenUpper)
    mask = cv2.erode(mask, None, iterations=2)
    mask = cv2.dilate(mask, None, iterations=2)

    # 在mask中查找轮廓
    cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # 当发现轮廓时继续处理
    if len(cnts) > 0:
        # 寻找mask中最大的轮廓,计算最小外接圆
        c = max(cnts, key=cv2.contourArea)
        ((x, y), radius) = cv2.minEnclosingCircle(c)
        updateConsecFrames = radius <= 10

        # 只有当外接圆轮廓达到一定值时处理
        if radius > 10:
            # 重置连续帧计数器为0,绘制外接圆轮廓到帧上
            consecFrames = 0
            cv2.circle(frame, (int(x), int(y)), int(radius),
                       (0, 0, 255), 2)

            # 如果还没开始计关键事件帧数,开始记录
            if not kcw.recording:
                timestamp = datetime.datetime.now()
                p = "{}/{}.avi".format(args["output"],
                                       timestamp.strftime("%Y%m%d-%H%M%S"))
                kcw.start(p, cv2.VideoWriter_fourcc(*args["codec"]),
                          args["fps"])

    # 如果帧中没有任何事件,将没有关键事件帧的帧计数
    if updateConsecFrames:
        consecFrames += 1

    # 更新关键事件帧缓存区
    kcw.update(frame)

    # 如果没有关键事件的帧达到一定数量,计数记录剪辑
    if kcw.recording and consecFrames == args["buffer_size"]:
        kcw.finish()

    # 展示帧
    cv2.imshow("Frame", frame)
    key = cv2.waitKey(1) & 0xFF

    # 按下‘q’键,退出循环
    if key == ord("q"):
        break

# 如果还在录制中,结束录制
if kcw.recording:
    kcw.finish()

# 清理工作,释放窗口,释放视频文件写入指针
cv2.destroyAllWindows()
vs.stop()

参考

以上是关于使用Python,OpenCV捕获关键事件,并进行视频剪辑的主要内容,如果未能解决你的问题,请参考以下文章

python opencv捕获摄像头并显示内容

番外4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案

番外4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案

使用 OpenCV Python 从 Android 智能手机捕获视频

Python 检查损坏的视频文件(捕获 OpenCV 错误)

Python OpenCV videocapture 不从源捕获视频