前几天在看CCTV9纪录频道的时候,看到一个印象比较深刻的过场预告动画,如下视频所示(可能需要缓冲一会,有点慢)。
感觉挺好看的,作为程序员的毛病,就想着能不能自己实现个差不多类似的效果(当然没有这么好看了),所以便有了这篇博客。 简单分析一下这段动画,其实是有多个视频播放窗口,各窗口之间存在一定的时差,从而造成了这样的效果。 所以可以采用OpenCV对一段视频进行读取,然后处理,即可获得类似的效果。 废话不多说,直接上代码如下。
1.代码
# coding=utf-8
import cv2
import numpy as np
# 读取多少帧
RANGE = 500
# 不同窗口之间相差的帧数
INTERVAL = 4
# 窗口个数
VIDEO_NUM = 8
# 窗口间X方向偏移量
OFFSET_X = 25
# 窗口间Y方向偏移量
OFFSET_Y = 25
# 原视频X方向缩放比例
SCALE_RATIO_X = 0.3
# 原视频Y方向缩放比例
SCALE_RATIO_Y = 0.3
# Step 1 打开、读取视频
cap = cv2.VideoCapture("F:\\video.mp4")
fps = cap.get(5)
frames = []
for i in range(RANGE):
cap.set(cv2.CAP_PROP_POS_FRAMES, i)
ret, frame = cap.read()
if frame is None:
continue
else:
resize_img = cv2.resize(frame, None, fx=SCALE_RATIO_X, fy=SCALE_RATIO_Y, interpolation=cv2.INTER_CUBIC)
frames.append((resize_img[:, :, 0], resize_img[:, :, 1], resize_img[:, :, 2]))
print ((i + 1.0) / RANGE) * 100, "%"
cap.release()
# 解析视频
video = []
for num in range(VIDEO_NUM):
temp_f = []
for i in range(num * INTERVAL, frames.__len__() - INTERVAL * (VIDEO_NUM - num)):
frame = frames[i]
temp_f.append(frame)
print temp_f.__len__()
video.append(temp_f)
# 逐帧渲染
imgs_join = []
new_height = int(video[0][0][0].shape[0] + (VIDEO_NUM + 1) * OFFSET_Y)
new_width = int(video[0][0][0].shape[1] + (VIDEO_NUM + 1) * OFFSET_X)
old_height = video[0][0][0].shape[0]
old_width = video[0][0][0].shape[1]
for i in range(video[0].__len__()):
join = np.zeros([new_height, new_width, 3], np.uint8) + 255
for num in range(video.__len__()):
join[-old_height - (VIDEO_NUM - num) * OFFSET_Y:-(VIDEO_NUM - num) * OFFSET_Y,
-old_width - (VIDEO_NUM - num) * OFFSET_X:-(VIDEO_NUM - num) * OFFSET_X,
0] = video[num][i][0]
join[-old_height - (VIDEO_NUM - num) * OFFSET_Y:-(VIDEO_NUM - num) * OFFSET_Y,
-old_width - (VIDEO_NUM - num) * OFFSET_X:-(VIDEO_NUM - num) * OFFSET_X,
1] = video[num][i][1]
join[-old_height - (VIDEO_NUM - num) * OFFSET_Y:-(VIDEO_NUM - num) * OFFSET_Y,
-old_width - (VIDEO_NUM - num) * OFFSET_X:-(VIDEO_NUM - num) * OFFSET_X,
2] = video[num][i][2]
imgs_join.append(join)
# 输出视频
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter("output.avi", fourcc, fps, (new_width, new_height))
for item in imgs_join:
out.write(item)
cv2.imshow("frame", item)
cv2.waitKey(50)
out.release()
在上述代码中,核心部分就是渲染这一步,但思路还是很简单的。 在上一步中,已经按照指定的时间差读取了不同的视频序列,渲染这一步就是需要把每一帧所需要用到的影像绘制到当前帧。 这里绘制的时候有顺序问题,早绘的内容会被晚绘的内容给覆盖掉,因此应该先绘较慢的那些帧,再绘较快的那些帧。 虽然整个代码看起来有点复杂,其实稍微看看就能明白了。 之所以觉得复杂是因为这里全部采用的是参数化运算,这样好处是不依赖于某个具体的视频或设置,可以在代码一开始的超参数中随意修改。
2.演示效果
简单演示效果如下,视频可能稍微慢一点,稍微等一下。
当然和原版过场动画相比优点辣眼睛,但也还凑合吧。生成的视频会保存在脚本运行目录下,叫output.avi
。
本文作者原创,未经许可不得转载,谢谢配合