OpenCV防抖实践及代码解析笔记

视频防抖是指用于减少摄像机运动对最终视频的影响的一系列方法。摄像机的运动可以是平移(比如沿着x、y、z方向上的运动)或旋转(偏航、俯仰、翻滚)。
在这里插入图片描述
正如你在上面的图片中看到的,在欧几里得运动模型中,图像中的一个正方形可以转换为任何其他位置、大小或旋转不同的正方形。它比仿射变换和单应变换限制更严格,但对于运动稳定来说足够了,因为摄像机在视频连续帧之间的运动通常很小。

1. 识别抖动

寻找帧之间的移动,这是算法中最关键的部分。我们将遍历所有的帧,并找到当前帧和前一帧之间的移动。欧几里得运动模型要求我们知道两个坐标系中两个点的运动。但是在实际应用中,找到50-100个点的运动,然后用它们来稳健地估计运动模型。

特征跟踪首先需要识别出易跟踪的特征,光滑的区域不利于跟踪,而有很多角的纹理区域则比较好。OpenCV有一个快速的特征检测器goodFeaturesToTrack,可以检测最适合跟踪的特性。

我们在前一帧中找到好的特征,就可以使用Lucas-Kanade光流算法在下一帧中跟踪它们。它是利用OpenCV中的calcOpticalFlowPyrLK函数实现的。

接着估计运动,我们已经找到了特征在当前帧中的位置,并且我们已经知道了特征在前一帧中的位置。所以我们可以使用这两组点来找到映射前一个坐标系到当前坐标系的刚性(欧几里德)变换。这是使用函数estimateRigidTransform完成的。

    # 检测前一帧的特征点prev_pts = cv2.goodFeaturesToTrack(prev_gray,maxCorners=200,qualityLevel=0.01,minDistance=30,blockSize=3)# 读下一帧success, curr = cap.read()if not success:break# 转换为灰度图curr_gray = cv2.cvtColor(curr, cv2.COLOR_BGR2GRAY)# 计算光流(即轨迹特征点)curr_pts, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, None)# 检查完整性assert prev_pts.shape == curr_pts.shape# 只过滤有效点idx = np.where(status == 1)[0]prev_pts = prev_pts[idx]curr_pts = curr_pts[idx]# 找到变换矩阵# 只适用于OpenCV-3或更少的版本吗# m = cv2.estimateRigidTransform(prev_pts, curr_pts, fullAffine=False)m, inlier = cv2.estimateAffine2D(prev_pts, curr_pts, )

2. 计算帧之间的平滑运动

在前面的步骤中,我们估计帧之间的运动并将它们存储在一个数组中。我们现在需要通过叠加上一步估计的微分运动来找到运动轨迹。

轨迹计算,在这一步,我们将增加运动之间的帧来计算轨迹。我们的最终目标是平滑这条轨迹,可以很容易地使用numpy中的cumsum(累计和)来实现。

​计算平滑轨迹,我们计算了运动轨迹。所以我们有三条曲线来显示运动(x, y,和角度)如何随时间变化。

平滑任何曲线最简单的方法是使用移动平均滤波器(moving average filter)。顾名思义,移动平均过滤器将函数在某一点上的值替换为由窗口定义的其相邻函数的平均值。

def movingAverage(curve, radius):window_size = 2 * radius + 1# 定义过滤器f = np.ones(window_size) / window_size# 为边界添加填充curve_pad = np.lib.pad(curve, (radius, radius), 'edge')# 应用卷积curve_smoothed = np.convolve(curve_pad, f, mode='same')# 删除填充curve_smoothed = curve_smoothed[radius:-radius]# 返回平滑曲线return curve_smootheddef smooth(trajectory):smoothed_trajectory = np.copy(trajectory)# 过滤x, y和角度曲线for i in range(3):smoothed_trajectory[:, i] = movingAverage(trajectory[:, i], radius=SMOOTHING_RADIUS)return smoothed_trajectory

计算平滑变换
到目前为止,我们已经得到了一个平滑的轨迹。在这一步,我们将使用平滑的轨迹来获得平滑的变换,可以应用到视频的帧来稳定它。

这是通过找到平滑轨迹和原始轨迹之间的差异,并将这些差异加回到原始的变换中来完成的。

# 使用累积变换和计算轨迹
trajectory = np.cumsum(transforms, axis=0)# 创建变量来存储平滑的轨迹
smoothed_trajectory = smooth(trajectory)# 计算smoothed_trajectory与trajectory的差值
difference = smoothed_trajectory - trajectory# 计算更新的转换数组
transforms_smooth = transforms + difference

3. 将平滑的摄像机运动应用到帧中

现在我们所需要做的就是循环帧并应用我们刚刚计算的变换。如果我们有一个指定为(x, y, θ \theta θ),的运动,对应的变换矩阵是:

T = [ c o s θ − s i n θ x s i n θ c o s θ y ] T=\begin{bmatrix} cos\theta & -sin \theta & x\\ sin \theta & cos \theta & y \end{bmatrix} T=[cosθsinθsinθcosθxy]

    # 从新的转换数组中提取转换dx = transforms_smooth[i, 0]dy = transforms_smooth[i, 1]da = transforms_smooth[i, 2]# 根据新的值重构变换矩阵m = np.zeros((2, 3), np.float32)m[0, 0] = np.cos(da)m[0, 1] = -np.sin(da)m[1, 0] = np.sin(da)m[1, 1] = np.cos(da)m[0, 2] = dxm[1, 2] = dy# 应用仿射包装到给定的框架frame_stabilized = cv2.warpAffine(frame, m, (w, h))# Fix border artifactsframe_stabilized = fixBorder(frame_stabilized)# 将框架写入文件frame_out = frame_stabilizedout.write(frame_out)

修复边界伪影
当我们稳定一个视频,我们可能会看到一些黑色的边界伪影。这是意料之中的,因为为了稳定视频,帧可能不得不缩小大小。我们可以通过将视频的中心缩小一小部分(例如4%)来缓解这个问题。

下面的fixBorder函数显示了实现。我们使用getRotationMatrix2D,因为它在不移动图像中心的情况下缩放和旋转图像。我们所需要做的就是调用这个函数时,旋转为0,缩放为1.04(也就是提升4%)。

def fixBorder(frame):s = frame.shape# 在不移动中心的情况下,将图像缩放4%T = cv2.getRotationMatrix2D((s[1] / 2, s[0] / 2), 0, 1.04)frame = cv2.warpAffine(frame, T, (s[1], s[0]))return frame

4. 实践代码

代码来自[1]

import numpy as np
import cv2def movingAverage(curve, radius):window_size = 2 * radius + 1# 定义过滤器f = np.ones(window_size) / window_size# 为边界添加填充curve_pad = np.lib.pad(curve, (radius, radius), 'edge')# 应用卷积curve_smoothed = np.convolve(curve_pad, f, mode='same')# 删除填充curve_smoothed = curve_smoothed[radius:-radius]# 返回平滑曲线return curve_smootheddef smooth(trajectory):smoothed_trajectory = np.copy(trajectory)# 过滤x, y和角度曲线for i in range(3):smoothed_trajectory[:, i] = movingAverage(trajectory[:, i], radius=SMOOTHING_RADIUS)return smoothed_trajectorydef fixBorder(frame):s = frame.shape# 在不移动中心的情况下,将图像缩放4%T = cv2.getRotationMatrix2D((s[1] / 2, s[0] / 2), 0, 1.04)frame = cv2.warpAffine(frame, T, (s[1], s[0]))return frame# 尺寸越大,视频越稳定,但对突然平移的反应越小
SMOOTHING_RADIUS = 50# 读取输入视频
cap = cv2.VideoCapture('video1.mp4')# 得到帧数
n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(n_frames)
# exit()
# #我们的测试视频可能读错了1300帧之后的帧
# n_frames = 1300# 获取视频流的宽度和高度
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))# 获取每秒帧数(fps)
fps = cap.get(cv2.CAP_PROP_FPS)# 定义输出视频的编解码器
#fourcc = cv2.VideoWriter_fourcc(*'MJPG')
#fourcc = cv2.VideoWriter_fourcc('M','P','4','V')
fourcc = cv2.VideoWriter_fourcc(*'MP4V')
# 设置输出视频
out = cv2.VideoWriter('video1_1.mp4', fourcc, fps, (w, h))
#out = cv2.VideoWriter('video_out.avi', fourcc, fps, (2 * w, h)) # 2*w用于前后对比
# 读第一帧
_, prev = cap.read()# 将帧转换为灰度
prev_gray = cv2.cvtColor(prev, cv2.COLOR_BGR2GRAY)# 预定义转换numpy矩阵
transforms = np.zeros((n_frames - 1, 3), np.float32)for i in range(n_frames - 2):# 检测前一帧的特征点prev_pts = cv2.goodFeaturesToTrack(prev_gray,maxCorners=200,qualityLevel=0.01,minDistance=30,blockSize=3)# 读下一帧success, curr = cap.read()if not success:break# 转换为灰度图curr_gray = cv2.cvtColor(curr, cv2.COLOR_BGR2GRAY)# 计算光流(即轨迹特征点)curr_pts, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, None)# 检查完整性assert prev_pts.shape == curr_pts.shape# 只过滤有效点idx = np.where(status == 1)[0]prev_pts = prev_pts[idx]curr_pts = curr_pts[idx]# 找到变换矩阵# 只适用于OpenCV-3或更少的版本吗# m = cv2.estimateRigidTransform(prev_pts, curr_pts, fullAffine=False)m, inlier = cv2.estimateAffine2D(prev_pts, curr_pts, )# 提取traslationdx = m[0, 2]dy = m[1, 2]# 提取旋转角da = np.arctan2(m[1, 0], m[0, 0])# 存储转换transforms[i] = [dx, dy, da]# 移到下一帧prev_gray = curr_grayprint("Frame: " + str(i) + "/" + str(n_frames) +" -  Tracked points : " + str(len(prev_pts)))# 使用累积变换和计算轨迹
trajectory = np.cumsum(transforms, axis=0)# 创建变量来存储平滑的轨迹
smoothed_trajectory = smooth(trajectory)# 计算smoothed_trajectory与trajectory的差值
difference = smoothed_trajectory - trajectory# 计算更新的转换数组
transforms_smooth = transforms + difference# 将视频流重置为第一帧
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)# 写入n_frames-1转换后的帧
for i in range(n_frames - 2):# 读下一帧success, frame = cap.read()if not success:break# 从新的转换数组中提取转换dx = transforms_smooth[i, 0]dy = transforms_smooth[i, 1]da = transforms_smooth[i, 2]# 根据新的值重构变换矩阵m = np.zeros((2, 3), np.float32)m[0, 0] = np.cos(da)m[0, 1] = -np.sin(da)m[1, 0] = np.sin(da)m[1, 1] = np.cos(da)m[0, 2] = dxm[1, 2] = dy# 应用仿射包装到给定的框架frame_stabilized = cv2.warpAffine(frame, m, (w, h))# Fix border artifactsframe_stabilized = fixBorder(frame_stabilized)# 将框架写入文件#frame_out = cv2.hconcat([frame, frame_stabilized]) # 合并前后对比视频frame_out = frame_stabilized# 如果图像太大,调整它的大小。if (frame_out.shape[1] > 1920):frame_out = cv2.resize(frame_out, (frame_out.shape[1] / 2, frame_out.shape[0] / 2))#cv2.imshow("Before and After", frame_out)#cv2.waitKey(10)out.write(frame_out)# 发布视频
cap.release()
out.release()
# 关闭窗口
cv2.destroyAllWindows()

5. OepnCV相关知识点

常见视频编码参数

VideoWriter_fourcc()常见的编码参数
参数列表
cv2.VideoWriter_fourcc(‘M’, ‘P’, ‘4’, ‘V’)
MPEG-4编码 .mp4 可指定结果视频的大小
cv2.VideoWriter_fourcc(‘X’,‘2’,‘6’,‘4’)
MPEG-4编码 .mp4 可指定结果视频的大小
cv2.VideoWriter_fourcc(‘I’, ‘4’, ‘2’, ‘0’)
该参数是YUV编码类型,文件名后缀为.avi 广泛兼容,但会产生大文件
cv2.VideoWriter_fourcc(‘P’, ‘I’, ‘M’, ‘I’)
该参数是MPEG-1编码类型,文件名后缀为.avi
cv2.VideoWriter_fourcc(‘X’, ‘V’, ‘I’, ‘D’)
该参数是MPEG-4编码类型,文件名后缀为.avi,可指定结果视频的大小
cv2.VideoWriter_fourcc(‘T’, ‘H’, ‘E’, ‘O’)
该参数是Ogg Vorbis,文件名后缀为.ogv
cv2.VideoWriter_fourcc(‘F’, ‘L’, ‘V’, ‘1’)
该参数是Flash视频,文件名后缀为.flv

cv2.VideoWriter写入为空或打不开的解决方案

一定要用video.release()方法关闭文件。

参考:

[1]. 默凉. opencv-python 视频流光去抖、实时去抖. CSDN博客. 2022.10
[2]. AI算法与图像处理. OpenCV实现视频防抖技术. 知乎. 2020.09

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/152557.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv图像的直方图,二维直方图,直方图均衡化

文章目录 opencv图像的直方图,二维直方图,直方图均衡化一、图像的直方图1、什么是图像的直方图:2、直方图的作用:3、如何绘制图像的直方图:(1)cv::calcHist()函数原型:英文单词 calc…

【C/C++】结构体内存分配问题

规则1:以多少个字节为单位开辟内存 就是说,该结构体最终所占字节大小,是这个单位的整数倍 给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员 哪个基本类型的成员占字节数多,就以它大大小为单位开辟内存 …

【刷题】只出现一次的数字(三种解法)

【刷题】只出现一次的数字 文章目录 【刷题】只出现一次的数字解法异或运算解法一 : 异或运算解法二:集合类Set集合Map集合 链接: https://www.nowcoder.com/share/jump/2008263481696810321082 https://leetcode.cn/problems/single-number/description/ 题目描述 给定一个整…

[PyTorch][chapter 57][WGAN-GP 代码实现]

前言: 下图为WGAN 的效果图: 绿色为真实数据的分布: 8个高斯分布 红色: 为随机产生的数据分布,跟真实分布基本一致 WGAN-GP: 1 判别器D: 最后一层去掉sigmoid 2 生成器G 和判别器D: loss不取log 3 损失函数…

讲讲项目里的仪表盘编辑器(四)分页卡和布局容器组件

讲讲两个经典布局组件的实现 ① 布局容器组件 配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注 定义文件 规定了组件类的类型、标签、图标、默认布局属性、主文件等等。 // index.js import Container from ./container.vue; class ContainerControl extends…

java:代理模式

概念代理模式 概念: 真实对象:被代理的对象,背景的联想总部代理对象:也就是那个西安联想代理商代理模式:代理对象代理真实对象,达到增强真实对象功能的目的 实现方式: 静态代理:有一…

边缘计算网关

一、项目整体框架图 二、项目整体描述 边缘计算网关项目主要实现了智能家居场景和工业物联网场景下设备的数据采集和控制。 整个项目分为三大层:用户接口层、网关层、设备层。 其中用户层通过QT客户端、WEB界面及阿里云提供数据展示和用户接口。 网关使用虚拟机代替…

ArcGIS Engine:实现Shp/Mxd数据的加载、图层的简单查询

本博客参考&#xff1a;BiliBili UP主 <羊羊旸> &#xff1a; Arcgis Engine学习 目录 01 加载控件以及控件的基本信息等调整 02 编写 <菜单-地图控件> 中各个子工具的代码 2.1 加载Shapefile数据-代码 2.2 加载地图文档数据-代码 2.3 获取图层数量-代码 2.…

如何从零开始系统的学习项目管理?

一、项目的概念 根据项目管理协会&#xff08;PMI&#xff09;的定义&#xff0c;项目是指为了创造独特的产品、服务或成果而进行的临时性工作。这意味着项目需要有明确的目标&#xff0c;且不是日常重复性工作。尽管项目是临时性工作&#xff0c;但它所交付的成果可能会持续存…

汽车冲压车间的RFID技术设计解决方案

一、RFID技术的基本原理 RFID技术是一种利用非接触式自动识别的技术&#xff0c;通过将RFID标签放置在被识别物品上&#xff0c;并使用RFID读写器对标签进行扫描和识别&#xff0c;实现对物品的自动识别和追踪。RFID标签分为被动式和主动式两种。被动式标签无内置电源&#xf…

解决远程git服务器路径改变导致本地无法push的问题

解决远程git服务器路径改变导致本地无法push的问题 &#xff08;1&#xff09;第一步&#xff1a;查看git配置 git config -l&#xff08;2&#xff09;第二步&#xff1a;删除远程git地址 git remote remove origin&#xff08;3&#xff09;第三步&#xff1a;再次查看git配…

Vue3 + Ts实现NPM插件 - 定制loading

目录 你的 Loading&#x1f916; 安装&#x1f6f9; 简介苍白请 您移步文档&#xff1a;✈️ 使用方法&#x1f6e0;️ 配置 loading 类型&#x1f3b2; 定制 loading 色彩 &#x1f4a1; 注意事项 前期回顾 你的 Loading 开箱即可用的 loading&#xff0c; 说明&#xff1a;vu…

Java练习题-用冒泡排序法实现数组排序

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;Java练习题 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又…

MySql017——组合查询UNION和UNION ALL

一、UNION作用 可用UNION操作符来组合数条SQL查询。 二、UNION 使用规则 1、UNION的使用很简单。所需做的只是给出每条SELECT语句&#xff0c;在各条语句之间放上关键字UNION。2、UNION必须由两条或两条以上的SELECT语句组成&#xff0c;语句之间用关键字UNION分隔&#xff…

Mac mov转mp4,详细转换步骤

Mac mov转mp4怎么转&#xff1f;视频文件格式为.mov是由Apple公司所开发的特殊格式。因其只能在苹果设备上播放&#xff0c;与他人分享时就会变得困难。为此&#xff0c;我们通常会选择使用MP4这种最受欢迎的视频格式。在日常使用中&#xff0c;MP4成为了大家首选的视频格式。而…

Vulnhub系列靶机-The Planets Earth

文章目录 Vulnhub系列靶机-The Planets: Earth1. 信息收集1.1 主机扫描1.2 端口扫描1.3 目录爆破 2. 漏洞探测2.1 XOR解密2.2 解码 3. 漏洞利用3.1 反弹Shell 4. 权限提升4.1 NC文件传输 Netcat&#xff08;nc&#xff09;文件传输 Vulnhub系列靶机-The Planets: Earth 1. 信息…

【管理运筹学】第 9 章 | 网络计划(1,网络图的组成及绘制)

文章目录 引言一、网络图的组成及绘制1.1 网络图的组成1. 基本要素2. 线路与关键线路3. 网络图的类型 1.2 网络图的绘制1. 画图原则2. 绘图一般步骤 写在最后 引言 大纲里关于网络计划这一章的描述&#xff0c;就两个&#xff0c;一个是基本概念&#xff1a;网络计划、时间参数…

计算机竞赛 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

API网关是什么?

API网关是什么&#xff1f; API网关很多人都知道它的实现原理&#xff0c;但是并不清楚它存在的意义和背景是什么&#xff0c;这里我给大家通俗易懂地讲解下&#xff01;举个例子&#xff0c;假设你正在开发一个电商网站&#xff0c;那么这里会涉及到很多后端的微服务&#xf…

排序算法之【归并排序】

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…