Bootstrap

给视频添加雪花飘落特效

一、背景和需求

老猿在某平台开了个Moviepy音视频剪辑的专栏,最近有博友咨询能否实现给视频加雪花飘落特效,答案是肯定能,但要达到比较好的效果则需要花点时间。为此老猿利用周末的时间,理了下实现的思路,利用Python+Moviepy+OpenCV实现了一个给任意视频加雪花飘落特效的Python视频处理应用。

二、实现原理

要给视频加雪花特效,是基于以下原理来实现的:

三、具体实现

3.1、雪花图片

本次实现的案例对应的雪花图片为:

文件名为:。上述图片的雪花是标准的雪花图像,但图像比较大,在视频中直接展示这么大的雪花就很假,老猿经过测试,发现将其缩小到原图像的五分之一以内比较象真正的雪花。

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视频变换的完整案例。