安徽经工建设集团网站,北京建站推广,wordpress返回最新文章,任县网站建设公司视频防抖是指用于减少摄像机运动对最终视频的影响的一系列方法。摄像机的运动可以是平移#xff08;比如沿着x、y、z方向上的运动#xff09;或旋转#xff08;偏航、俯仰、翻滚#xff09;。 正如你在上面的图片中看到的#xff0c;在欧几里得运动模型中#xff0c;图像…视频防抖是指用于减少摄像机运动对最终视频的影响的一系列方法。摄像机的运动可以是平移比如沿着x、y、z方向上的运动或旋转偏航、俯仰、翻滚。 正如你在上面的图片中看到的在欧几里得运动模型中图像中的一个正方形可以转换为任何其他位置、大小或旋转不同的正方形。它比仿射变换和单应变换限制更严格但对于运动稳定来说足够了因为摄像机在视频连续帧之间的运动通常很小。
1. 识别抖动
寻找帧之间的移动这是算法中最关键的部分。我们将遍历所有的帧并找到当前帧和前一帧之间的移动。欧几里得运动模型要求我们知道两个坐标系中两个点的运动。但是在实际应用中找到50-100个点的运动然后用它们来稳健地估计运动模型。
特征跟踪首先需要识别出易跟踪的特征光滑的区域不利于跟踪而有很多角的纹理区域则比较好。OpenCV有一个快速的特征检测器goodFeaturesToTrack可以检测最适合跟踪的特性。
我们在前一帧中找到好的特征就可以使用Lucas-Kanade光流算法在下一帧中跟踪它们。它是利用OpenCV中的calcOpticalFlowPyrLK函数实现的。
接着估计运动我们已经找到了特征在当前帧中的位置并且我们已经知道了特征在前一帧中的位置。所以我们可以使用这两组点来找到映射前一个坐标系到当前坐标系的刚性欧几里德变换。这是使用函数estimateRigidTransform完成的。 # 检测前一帧的特征点prev_pts cv2.goodFeaturesToTrack(prev_gray,maxCorners200,qualityLevel0.01,minDistance30,blockSize3)# 读下一帧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, fullAffineFalse)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, modesame)# 删除填充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], radiusSMOOTHING_RADIUS)return smoothed_trajectory计算平滑变换 到目前为止我们已经得到了一个平滑的轨迹。在这一步我们将使用平滑的轨迹来获得平滑的变换可以应用到视频的帧来稳定它。
这是通过找到平滑轨迹和原始轨迹之间的差异并将这些差异加回到原始的变换中来完成的。
# 使用累积变换和计算轨迹
trajectory np.cumsum(transforms, axis0)# 创建变量来存储平滑的轨迹
smoothed_trajectory smooth(trajectory)# 计算smoothed_trajectory与trajectory的差值
difference smoothed_trajectory - trajectory# 计算更新的转换数组
transforms_smooth transforms difference3. 将平滑的摄像机运动应用到帧中
现在我们所需要做的就是循环帧并应用我们刚刚计算的变换。如果我们有一个指定为(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 frame4. 实践代码
代码来自[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, modesame)# 删除填充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], radiusSMOOTHING_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,maxCorners200,qualityLevel0.01,minDistance30,blockSize3)# 读下一帧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, fullAffineFalse)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, axis0)# 创建变量来存储平滑的轨迹
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