一、背景和需求
老猿在某平台开了个 Moviepy 音视频剪辑的专栏,最近有博友咨询能否实现给视频加雪花飘落特效,答案是肯定能,但要达到比较好的效果则需要花点时间。为此老猿利用周末的时间,理了下实现的思路,利用 Python+Moviepy+OpenCV 实现了一个给任意视频加雪花飘落特效的 Python 视频处理应用。
二、实现原理
要给视频加雪花特效,是基于以下原理来实现的:
每个视频都是由一个个视频帧构成,每个视频帧都是一副静态的图像,通过视频帧的连续显示形成动态视频;
实现视频雪花飘落,就是在视频的每帧图像中添加雪花,并在前后相连的视频帧中变化雪花的位置,形成雪花下飘带横向移动的效果;
雪花的图片本身是一个矩形,矩形内有黑色背景和白色的雪花,在将雪花图片添加到视频帧时,需要确保黑色部分不会遮盖帧图像的内容,只有白色的雪花前景色才可以遮挡帧图像内容。这就需要通过图像的阈值处理确得到雪花图像的二值图,用该二值图及其补图作为掩膜,二值图作为雪花原始图片与自身与运算的掩码来获取雪花图片的前景色,补图作为帧图片雪花对应位置的子图与子图自身与运算的掩膜来获取雪花黑色背景部分对应的帧图像作为背景色;
对视频帧调用雪花融合图像的函数进行动态融合雪花的处理
Moviepy 的 fl_image 是视频剪辑基类 VideoClip 的方法,该方法用于对视频的帧图像进行变换,其参数包括一个对帧图像进行变换的函数 image_func,具体变换由应用实现对图像进行变换处理的一个函数,然后将该函数作为 image_func 的值参传入 fl_image,Moviepy 就会调用该函数完成对视频每帧图像的处理生成新的剪辑
三、具体实现
3.1、雪花图片
本次实现的案例对应的雪花图片为:
文件名为:f:\pic\snow.jpg
。上述图片的雪花是标准的雪花图像,但图像比较大,在视频中直接展示这么大的雪花就很假,老猿经过测试,发现将其缩小到原图像的五分之一以内比较象真正的雪花。
3.2、实现流程
3.3、关键实现
3.3.1、初始化雪花
图片的雪花是固定大小的,而真正的雪花大小是不同的,为了模拟真正的雪花效果,需要有各种不同大小和角度的雪花,为此每片雪花需要根据图片雪花图像进行随机的大小和角度调整。
一帧图像中的雪花数量至少是几十到几百片,如果每次融合图像时,都需要从图片雪花图像进行大小和旋转角度的变换,是非常消耗系统的性能的,影响视频的生成耗时。
为了提升处理性能,老猿只在程序开始初始化时一次批量生产各种不同大小、不同旋转角度的各种雪花,后续程序生成雪花时,直接从批量生成的雪花中取一个作为要生成的雪花,而不用每次从基本的雪花图像开始进行变换。
生成各种雪花形状的示例代码:
# -*- coding: utf-8 -*-
import cv2,random
import numpy as np
from opencvPublic import addImgToLargeImg,readImgFile,rotationImg
snowShapesList = [] #雪花形状列表
snowObjects=[] #图片中要显示的所有雪花对象
def initSnowShapes():
"""
从文件中读入雪花图片,并进行不同尺度的缩小和不同角度的旋转从而生成不同的雪花形状,这些雪花形状保存到全局列表中snowShapesList
"""
global snowShapesList
imgSnow = readImgFile(r'f:\pic\snow.jpg')
imgSnow = cv2.resize(imgSnow, None, fx=0.3, fy=0.3) #图片文件中的雪花比较大,需要缩小才能象自然的雪花形象
minFactor,maxFactor = 50,100 #雪花大小在imgSnow的0.5-1倍之间变化
for factor in range(minFactor,maxFactor,5): #每次增加5%大小
f = factor*0.01
imgSnowSize = cv2.resize(imgSnow, None, fx=f, fy=f)
for ange in range(0,360,5):#雪花0-360之间旋转,每次旋转角度增加5°
imgRotate = rotationImg(imgSnowSize,ange)
snowShapesList.append(imgRotate)
复制代码
3.3.2、产生一排雪花
每帧图像除了保留上帧图像中未飘落出图像范围的雪花外,同时还会从顶部生成一排数量随机的雪花,形成生生不息的雪花。下面是从顶部初始化生成一排雪花的代码:
def generateOneRowSnows(width,count):
"""
产生一排雪花对象,每个雪花随机从snowShapesList取一个、横坐标位置随机、纵坐标初始为0
:param width: 背景图像宽度
:param count: 希望的雪花数
:y:当前行对应的竖直坐标
:return:一个包含产生的多个雪花对象信息的列表,每个列表的元素代表一个雪花对象,雪花对象包含三个信息,在snowShapesList的索引号、初始x坐标、初始y坐标(才生成固定为0)
"""
global snowShapesList
line = []
picCount = len(snowShapesList)
for loop in range(count):
imgId = random.randint(0,picCount-1)
xPos = random.randint(0,width-1)
line.append((imgId,xPos,0))
return line
复制代码
3.3.3、将所有雪花对象融合到背景图像
上帧图像中的雪花在当前帧中需要随机下落一定位置,并在一定幅度内横向漂移,当有雪花落到图像底部之下时,需要释放对应对象以节省资源。
def addSnowEffectToImg(img):
"""
将所有snowObjects中的雪花对象融合放到图像img中,融合时y坐标随机下移一定高度,x坐标左右随机小范围内移动
"""
global snowShapesList,snowObjects
horizontalMaxDistance,verticalMaxDistance = 5,10 #水平方向左右漂移最大值和竖直方向下落最大值
rows,cols = img.shape[:2]
maxObjsPerRow = int(cols/100)
snowObjects += generateOneRowSnows(cols, random.randint(0, maxObjsPerRow))
snowObjectCount = len(snowObjects)
rows,cols = img.shape[0:2]
imgResult = np.array(img)
for index in range(snowObjectCount-1,-1,-1):
imgObj = snowObjects[index] #每个元素为(imgId,x,y)
if imgObj[2]>rows: #如果雪花的起始纵坐标已经超出背景图像的高度(即到达背景图像底部),则该雪花对象需进行失效处理
del(snowObjects[index])
else:
imgSnow = snowShapesList[imgObj[0]]
x,y = imgObj[1:] #取该雪花上次的位置
x = x+random.randint(-1*horizontalMaxDistance,horizontalMaxDistance) #横坐标随机左右移动一定范围
y = y+random.randint(1,verticalMaxDistance) #纵坐标随机下落一定范围
snowObjects[index] = (imgObj[0],x,y) #更新雪花对象信息
imgResult = addImgToLargeImg(imgSnow,imgResult,(x,y),180) #将所有雪花对象图像按照其位置融合到背景图像中
return imgResult #返回融合图像
复制代码
3.3.4、实现视频雪花飘落特效视频合成
下面的代码调用 addSnowEffectToImg 实现对《粉丝记事本》视频的雪花飘落特效:
from moviepy.editor import *
def addVideoSnowEffect(videoFileName,resultFileName):
clip = VideoFileClip(videoFileName)
newclip = clip.fl_image(addSnowEffectToImg, apply_to=['mask'])
newclip.write_videofile(resultFileName)
if __name__ == '__main__':
addVideoSnowEffect(r'f:\video\fansNote.mp4',r'f:\video\fansNote_snow.mp4')
复制代码
3.4、雪花飘落效果
四、小结
本文介绍了制作视频雪花飘落特效的原理、实现的思想以及流程,并利用 Python+OpenCV+Moviepy 提供了关键的实现代码,是一个供大家理解图像融合、Moviepy 视频变换的完整案例。
评论