用OpenCV制作庆祝武汉重启一周年短视频
一、引言
2021年4月8日武汉重启一周年,这是个值得庆祝的日子,作为一个武汉人和一个死宅程序员,老猿也想在这个日子留下点什么。想起武汉长江两岸的灯光秀,顿时有了主意,那就用程序实现一个武汉重启庆祝的灯光秀短视频吧,于是在4月7日晚开始构思和着手开发,4月8日晚终于顺利完成,利用今天周末的时间总结一下。
二、实现思路
2.1、视频内容设计
老猿是个没有艺术细胞的人,因此这个视频内容只能说仅能代表是个视频而已,对最终的内容表现大家就不需要过多评价。
在创作该视频前,老猿对视频进行了简单规划,将创作视频分为片头、视频内容和片尾三部分:
1. 片头:5秒时间,展现一幅黄鹤楼的照片,并带上“武汉重启一周年灯光秀”的标题
2. 视频内容:全长35秒,每隔2秒随机展现一张武汉灯光秀景观图,并在视频中附上向上滚动的文字“热烈庆祝武汉重启一周年!”、“武汉万岁!中国万岁!”,并在视频的左下角和右下角用红绿蓝三色画三条向上晃动的线条表示彩色激光
3. 片尾:25秒,每隔2秒随机展现武汉的一张风景照,并展现“制片:老猿Python”等制作信息。
2.2、开发设计
2.2.1、视频图片处理
视频中用到的图片都来源于互联网,为了减少图片加载的时间和统一初始化,在程序中通过全局变量将片头使用图像、视频内容使用图像、片尾使用图像分别使用了三个全局变量进行保存,其中后两者为列表类型。为了确保视频输出,所有图片都调整到了统一大小。
2.2.2、灯光效果处理
在视频内容部分,左下角和右下角发射的彩色激光,采用在背景图片中根据时间动态绘制彩色线条,实现彩色激光晃动照射的效果,为了限制晃动范围,设定了激光终点的x值的最小值和最大值。激光终点的位置根据时间动态计算,并在到达x值的最小值或最大值时自动回扫。
2.2.3、帧图像的生成
上面介绍的图像处理,全部集中在一个函数中处理,该函数仅带一个参数时间t,判断时间来决定现在生成的内容是片头、内容还是片尾,然后据此来进行帧图像的生成。生成时,需要判断图像是否切换,因此需要记录上一次切换的时间和切换后的图像,确保未达到切换时间前用上次图像作为帧图像的背景,达到切换时间要求后切换新的图像作为后续帧图像生成的背景。为此在该函数中使用了两个全局变量来记录当前帧图像背景图片和上次切换时间。
2.2.4、输出到视频
为了将视频输出到文件,通过VideoWriter_fourcc指定视频输出文件类型,采用OpenCV的VideoWriter类来进行视频输出,通过Open方法指定输出文件,通过write方法将一帧帧视频写入。
三、具体实现
3.1、总流程
1. 加载片头、视频内容、片尾需要使用的图像;
2. 创建新视频文件
3. 按照帧率24、时长65秒,构建24*65张帧图像,并逐一输出到视频文件。
3.2、全局变量初始化
全局变量主要是视频中需要使用的图片,分成片头使用图片hhlImg、灯光秀图片列表lightShowImgList 和片尾风景图片列表whImgList 。另外preImg,preTime用于记录上次切换视频图片的图片和切换时间。
hhlImg = cv2.resize(readImgFile(r'f:\pic\武汉\黄鹤楼.jpg'),(800,600))
lightShowImgList = [cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_桥.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_武汉江边.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_一桥.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_一桥底部.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_一桥远景.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\灯光秀_远桥.jpg'),(800,600))]
whImgList = [cv2.resize(readImgFile(r'f:\pic\武汉\东湖1.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\东湖2.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\东湖樱园樱花.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\武大牌楼.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\武大牌楼远观.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\武大樱花2.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\武大樱园顶高拍照.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\武大樱园门洞.jpg'),(800,600)),
cv2.resize(readImgFile(r'f:\pic\武汉\武大樱园入口.jpg'),(800,600)),cv2.resize(readImgFile(r'f:\pic\武汉\武大老图书馆.jpg'),(800,600))]
preImg,preTime = None,0
3.3、实现给背景图像添加彩色激光照射效果
lightShowImg函数实现给背景图像指定点发射彩色激光的特效,激光发射点固定,由参数lightStartPos指定,终点随参数t在一定范围内变化,终点x坐标受参数minX, maxX控制,同一个发射源的三激光之间的间距受参数distance控制。
def lightShowImg(bg,minX, maxX,distance,lightStartPos,t):
"""
实现在背景图像上添加当前散发彩色激光的处理
:param bg: 背景图像
:param minX:灯光终点的最大x坐标
:param maxX:灯光终点的最小x坐标
:param distance: 不同灯光之间的间距
:param lightStartPos: 灯光发射点
:param t: 时间t
:return: 添加了发射灯光的图像
"""
x = (minX+int(t*200))%(maxX*2) #按时间t计算灯光终点的x坐标,该坐标可以超出背景图像范围
img = np.array(bg)
if x>maxX: #到达最大范围,需要回扫
x = 2*maxX-x
color1,color2,color3 = (255,0,0),(0,255,0),(0,0 ,255 ) #蓝、绿、红三色
cv2.line(img, lightStartPos, (x, 0), color1, 4)
cv2.line(img, lightStartPos, (x + distance, 0), color2, 4)
cv2.line(img, lightStartPos, (x - distance, 0), color3, 4)
return img
3.4、帧图片生成
makeframe函数实现帧图片生成,带一个参数t表示当前帧对应的剪辑时间,在函数内根据剪辑时间来判断是生成片头内容、灯光秀内容还是片尾内容生成t时刻对应帧图像返回:
1. 对于片头,采用黄鹤楼照片作为背景,并在图片中央显示“武汉重启一周年灯光秀”;
2. 对于视频内容,则每2秒从灯光秀图片队列中随机取一张图片作为当前背景图像,并调用lightShowImg增加左下角和右下角的彩色激光效果,同时动态向上滚动显示“热烈庆祝武汉重启一周年”、“武汉万岁!中国万岁!”等标语;
3. 对于片尾,则每2秒从武汉风景图片队列中随机取一张图片作为当前背景图像,同时动态向上滚动显示制作信息。
def makeframe(t):
#生成t时刻的视频帧图像
global preImg,preTime
if t<5:#5秒片头
img = imgAddText(hhlImg,'武汉重启一周年灯光秀',64,(255,0,0),vRefPos='C')
elif t<40:#5-40秒灯光秀
minX, maxX = 200, 1200 # 灯光横向扫射范围
lightStartPos1 = (0, 600) # 灯光1的发射点坐标设置为左下角
lightStartPos2 = (800, 600) # 灯光2的发射点坐标设置为右下角
if (t-preTime)>2 or preImg is None:
img = np.array(random.choice(lightShowImgList))
preImg,preTime = img,t
else:
img = preImg
img = lightShowImg(img, minX, maxX, 80,lightStartPos1, t)
img = lightShowImg(img, minX-100, maxX+100,48, lightStartPos2, t)
t = int((t-4)*40)%img.shape[0]
img = imgAddText(img,'热烈庆祝武汉重启一周年!',48,(0,0,255),vRefPos=-80-t,)
img = imgAddText(img, '武汉万岁!中国万岁!',48,(255,0,255),vRefPos=-t)
else:#片尾
if (t-preTime)>2 or preImg is None:
img = np.array(random.choice(whImgList))
preImg,preTime = img,t
else:
img = preImg
t = int((t - 39) * 20) % img.shape[0]
img = imgAddText(img,"制片:老猿Python",36,(255,255,255),vRefPos=-120-t)
img = imgAddText(img, "https://www.infoq.cn/u/laoyuanpython/publish",24,(255,255,255),vRefPos= -60-t)
img = imgAddText(img, "2021年4月8日", 24, (255,255,255), vRefPos=-t)
return img
3.5、制作视频文件
函数buildVideoByCV用于制作视频文件,按指定格式生成一个新的视频文件,然后循环调用makeframe生成一帧帧图像写入视频文件中。
def buildVideoByCV():
videoMake = cv2.VideoWriter()
fourcc = cv2.VideoWriter_fourcc(*'MP4V') #https://blog.csdn.net/whudee/article/details/108689420
fps = 12
videoMake.open(r"F:\video\lightShowCV.MP4", fourcc, fps, (800,600))
for t in range(65*fps):
img = makeframe(t*1.0/fps)
videoMake.write(img)
print(f'\r视频制作进度:{(t*100.0)/(66*fps):4.2f}%',end='')
videoMake.release()
上述函数构建后只需要调用buildVideoByCV函数即可完成视频制作。
3.6、视频效果

四、小结
本文完整介绍了用Python+OpenCV制作一个庆祝武汉重启一周年的武汉灯光秀短视频的实现思路、过程、关键函数等,有助于理解OpenCV的图像操作、生成视频的实现。用该方案实现的视频是无声的视频,据老猿所知OpenCV无法操作音频,因此如果要给视频添加声音,可以使用其他Python的音视频剪辑库。
