使用Python,OpenCV捕获关键事件,并进行视频剪辑
Posted 程序媛一枚~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Python,OpenCV捕获关键事件,并进行视频剪辑相关的知识,希望对你有一定的参考价值。
使用Python,OpenCV捕获关键事件,并进行视频剪辑
上一篇博客介绍了如何使用Python、OpenCV写入视频文件,这一篇将介绍如何做有趣的视频剪辑,而不是保留整个视频文件;
总体目标是构建视频概要,将视频流的最关键,突出和有趣部分提炼成一系列短视频文件。
使用场景有:
- 访问在区域中检测到的运动物体;
- 截取入侵者进入房子或公寓的片段;
什么才是视频的关键帧、有趣部分,则完全取决于您的需求和目标;
使用此方法,可以将小时级的视频流素材缩小为多个秒级的有趣事件的小视频,有效地产生视频概要。
1. 效果图
检测画面中的绿色盆,并将其生成git图,如下:
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捕获关键事件,并进行视频剪辑的主要内容,如果未能解决你的问题,请参考以下文章
番外4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案
番外4. Python OpenCV 中鼠标事件相关处理与常见问题解决方案
使用 OpenCV Python 从 Android 智能手机捕获视频