【精选优质专栏推荐】
- 《AI 技术前沿》 —— 紧跟 AI 最新趋势与应用
- 《网络安全新手快速入门(附漏洞挖掘案例)》 —— 零基础安全入门必看
- 《BurpSuite 入门教程(附实战图文)》 —— 渗透测试必备工具详解
- 《网安渗透工具使用教程(全)》 —— 一站式工具手册
- 《CTF 新手入门实战教程》 —— 从题目讲解到实战技巧
- 《前后端项目开发(新手必知必会)》 —— 实战驱动快速上手
每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。
文章目录

前言
当你使用 OpenCV 时,你最常处理的是图像。然而,你可能会发现将多张图像制作成动画很有用。快速连续显示图像可能会带来不同的洞察,或者通过引入时间轴来可视化你的工作会更容易。
在这篇文章中,你将看到如何在 OpenCV 中创建视频剪辑。作为示例,你还将学习一些基本的图像操作技巧来创建图像。具体而言,你将学习:
-
如何将图像作为 numpy 数组进行操作
-
如何使用 OpenCV 函数操作图像
-
如何在 OpenCV 中创建视频文件
这篇文章分为两个部分:
-
Ken Burns 效果
-
写入视频
Ken Burns 效果
你将通过参考其他文章来创建许多图像。也许是为了可视化你机器学习项目的进展,或者展示某个计算机视觉技术如何处理你的图像。为了简化操作,你将对输入图像做最简单的处理:裁剪。
本文的任务是创建 Ken Burns 效果。这是一种以电影制片人 Ken Burns 命名的平移和缩放技巧:
不是在屏幕上展示一张大且静态的照片,而是使用 Ken Burns 效果裁剪某个细节,然后在图像上平移。
—— 维基百科,“Ken Burns effect”
让我们看看如何在 Python 中使用 OpenCV 创建 Ken Burns 效果。我们从一张图像开始,例如下面的鸟类照片,你可以从维基百科下载:
https://upload.wikimedia.org/wikipedia/commons/b/b7/Hooded_mountain_tanager_%28Buthraupis_montana_cucullata%29_Caldas.jpg

这张图片的分辨率是 4563×3042。用 OpenCV 打开这张图片非常简单:
import cv2
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
cv2.imshow("bird", img)
cv2.waitKey(0)
OpenCV 读取的图像 img 确实是一个形状为 (3042, 4563, 3) 的 numpy 数组,并且数据类型为 uint8(8 位无符号整数),因为这是彩色图像,每个像素用 0 到 255 之间的 BGR 值表示。
Ken Burns 效果是缩放和平移。视频中的每一帧都是原图像的裁剪(然后再缩放以填满屏幕)。给定一个 numpy 数组来裁剪图像很容易,因为 numpy 已经提供了切片语法:
cropped = img[y0:y1, x0:x1]
图像是一个三维 numpy 数组。前两个维度分别表示高度和宽度(与矩阵坐标设置方式相同)。因此你可以使用 numpy 切片语法来获取垂直方向从 𝑦0 到 𝑦1、以及水平方向从 𝑥0 到 𝑥1 的像素(记住,在矩阵中坐标从上到下、从左到右编号)。
裁剪图片意味着把一张大小为 𝑊 × 𝐻 的图片变为更小的 𝑊′ × 𝐻′。为了制作视频,你希望创建固定尺寸的帧。因此裁剪得到的 𝑊′ × 𝐻′ 的图像需要被缩放。此外,为避免失真,裁剪后的图像也需要保持预定义的宽高比。
如果你自己实现缩放,你可以定义一个新的 numpy 数组,然后逐像素计算并填充值。像素值的计算方式很多,例如线性插值或直接复制最近像素。如果尝试自己实现缩放,你会发现虽然不难,但相当繁琐。因此更简单的方法是使用 OpenCV 的原生函数,例如:
resized = cv2.resize(cropped, dsize=target_dim, interpolation=cv2.INTER_LINEAR)
cv2.resize() 函数接收图像和目标尺寸(一个像素宽度和高度的元组),并返回新的 numpy 数组。你可以指定缩放算法,上述是使用线性插值,通常看起来效果不错。
这些基本上就是你能在 OpenCV 中操作图像的方式,即:
- 直接操作 numpy 数组。这适用于需要逐像素处理的简单任务
- 使用 OpenCV 函数。这适合需要整体操作图像或逐像素操作效率太低的复杂任务
有了这些,你可以构建你的 Ken Burns 动画。流程如下:
-
给定一张图片(最好是高分辨率的),你需要通过指定起始和结束的焦点坐标来定义平移路径。同时你也需要定义起始和结束的缩放比例。
-
你有预定义的视频时长和 FPS(帧率)。总帧数等于时长乘以 FPS。
-
对于每一帧,计算裁剪坐标,然后将裁剪后的图像缩放到视频的目标分辨率。
-
所有帧准备好之后,将其写入视频文件。
让我们从常量开始:假设我们要创建 2 秒钟的 720p 视频(分辨率 1280×720),帧率为 25 FPS(较低但视觉上可接受)。平移将从图像左侧 40%、顶部 60% 的中心位置开始,结束于图像左侧 50%、顶部 50% 的中心位置。缩放将从原图的 70% 开始,逐渐缩放到 100%。
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0
你将会裁剪图像很多次来生成帧(确切来说,2×25=50 帧)。因此创建一个裁剪函数是有益的:
def crop(img, x, y, w, h):
x0, y0 = max(0, x-w//2), max(0, y-h//2)
x1, y1 = x0+w, y0+h
return img[y0:y1, x0:x1]
这个裁剪函数接收图像、以像素坐标表示的目标中心点,以及裁剪的宽高。裁剪会确保不会越界,因此用了两个 max()。裁剪通过 numpy 的切片语法实现。
如果认为当前时间点是整个时长的 𝛼%,即可使用仿射变换计算缩放级别和平移位置。以平移中心的相对位置(宽高百分比)来表示,仿射变换为:
rx = end_center[0]*alpha + start_center[0]*(1-alpha)
ry = end_center[1]*alpha + start_center[1]*(1-alpha)
其中 alpha 在 0 到 1 之间。相同地,缩放级别为:
scale = end_scale*alpha + start_scale*(1-alpha)
给定原始图像尺寸和 scale,你可以通过乘法计算裁剪尺寸。但由于原图的宽高比可能与视频不一致,你应根据视频宽高比调整裁剪尺寸。假设图像数组为 img,缩放比例为 scale,裁剪尺寸可计算为:
orig_shape = img.shape[:2]
if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
h = int(orig_shape[0]*scale)
w = int(h * video_dim[0] / video_dim[1])
else:
w = int(orig_shape[1]*scale)
h = int(w * video_dim[1] / video_dim[0])
以上代码是在比较图像与视频的宽高比,并以缩放比例作用于较受限的边,再按目标宽高比推算另一边。
确定需要多少帧后,你可以使用 for 循环,通过 numpy 的 linspace() 获取不同的 alpha 值来创建每一帧。完整代码如下:
import cv2
import numpy as np
imgfile = "Hooded_mountain_tanager_(Buthraupis_montana_cucullata)_Caldas.jpg"
video_dim = (1280, 720)
fps = 25
duration = 2.0
start_center = (0.4, 0.6)
end_center = (0.5, 0.5)
start_scale = 0.7
end_scale = 1.0
img = cv2.imread(imgfile, cv2.IMREAD_COLOR)
orig_shape = img.shape[:2]
def crop(img, x, y, w, h):
x0, y0 = max(0, x-w//2), max(0, y-h//2)
x1, y1 = x0+w, y0+h
return img[y0:y1, x0:x1]
num_frames = int(fps * duration)
frames = []
for alpha in np.linspace(0, 1, num_frames):
rx = end_center[0]*alpha + start_center[0]*(1-alpha)
ry = end_center[1]*alpha + start_center[1]*(1-alpha)
x = int(orig_shape[1]*rx)
y = int(orig_shape[0]*ry)
scale = end_scale*alpha + start_scale*(1-alpha)
# determined how to crop based on the aspect ratio of width/height
if orig_shape[1]/orig_shape[0] > video_dim[0]/video_dim[1]:
h = int(orig_shape[0]*scale)
w = int(h * video_dim[0] / video_dim[1])
else:
w = int(orig_shape[1]*scale)
h = int(w * video_dim[1] / video_dim[0])
# crop, scale to video size, and save the frame
cropped = crop(img, x, y, w, h)
scaled = cv2.resize(cropped, dsize=video_dim, interpolation=cv2.INTER_LINEAR)
frames.append(scaled)
# write to MP4 file
vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)
for frame in frames:
vidwriter.write(frame)
vidwriter.release()
最后几行展示了如何使用 OpenCV 写入视频。你创建一个 VideoWriter 对象,并指定 FPS 和分辨率。然后逐帧写入,最后释放对象以关闭文件。
生成的视频类似如下。预览如下:

预览已创建的视频。查看此内容需要受支持的浏览器。
写入视频
从上一节的示例中,你已经看到我们如何创建一个 VideoWriter 对象:
vidwriter = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, video_dim)
与写入图像文件(如 JPEG 或 PNG)不同,OpenCV 创建的视频格式并不是从文件名推断出来的。它由第二个参数指定,即 FourCC,它是由四个字符组成的编码。你可以在以下网址找到 FourCC 代码及其对应的视频格式列表:
https://fourcc.org/codecs.php
然而,并非所有的 FourCC 都能使用。这是因为 OpenCV 使用 FFmpeg 工具来创建视频。你可以使用以下命令查看 FFmpeg 支持的视频格式列表:
ffmpeg -codecs
确保 ffmpeg 命令与 OpenCV 所使用的版本一致。还要注意,上述命令的输出只会告诉你 ffmpeg 支持哪些格式,而不是对应的 FourCC 代码。你需要从其他地方查找代码,例如前述的网址。
要检查你是否能使用某个特定的 FourCC 代码,你必须实际尝试并查看 OpenCV 是否抛出异常:
try:
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter('temp.mkv', fourcc, 30, (640, 480))
assert writer.isOpened()
print("Supported")
except:
print("Not supported")
总结
在这篇文章中,你学习了如何在 OpenCV 中创建视频。创建的视频是由一系列帧组成的(即没有音频)。每一帧都是固定大小的图像。作为示例,你学习了如何对图片应用 Ken Burns 效果,具体来说,你应用了:
-
使用 numpy 切片语法裁剪图像的技巧
-
使用 OpenCV 函数调整图像大小的技巧
-
使用仿射变换计算缩放和平移参数并为视频创建帧
最后,你使用 OpenCV 中的 VideoWriter 对象将这些帧写入视频文件。

被折叠的 条评论
为什么被折叠?



