app开发导入网站模板,中学生网站制作,php网站培训机构企业做网站,在哪个公司建设网站好作者 | ZackSock来源 | ZackSock(ID:ZackSock)Python牛已经不是一天两天的事了#xff0c;但是我开始也没想到#xff0c;Python能这么牛。前段时间接触了一个批量抠图的模型库#xff0c;而后在一些视频中找到灵感#xff0c;觉得应该可以通过抠图的方式#xff0c;给视频…作者 | ZackSock来源 | ZackSock(ID:ZackSock)Python牛已经不是一天两天的事了但是我开始也没想到Python能这么牛。前段时间接触了一个批量抠图的模型库而后在一些视频中找到灵感觉得应该可以通过抠图的方式给视频换一个不同的场景于是就有了今天的文章。我们先看看能实现什么效果先来个正常版的先看看原场景下面是我们切换场景后的样子看起来效果还是不错的有了这个我们就可以随意切换场景坟头蹦迪不是梦。另外我们再来看看另外一种效果相比之下要狂放许多实现步骤我们都知道视频是由一帧一帧的画面组成的每一帧都是一张图片我们要实现对视频的修改就需要对视频中每一帧画面进行修改。所以在最开始我们需要获取视频每一帧画面。在我们获取帧之后需要抠取画面中的人物。抠取人物之后就需要读取我们的场景图片了在上面的例子中背景都是静态的所以我们只需要读取一次场景。在读取场景之后我们切换每一帧画面的场景并写入新的视频。这时候我们只是生成了一个视频我们还需要添加音频。而音频就是我们的原视频中的音频我们读取音频并给新视频设置音频就好了。具体步骤如下读取视频获取每一帧画面批量抠图读取场景图片对每一帧画面进行场景切换写入视频读取原视频的音频给新视频设置音频因为上面的步骤还是比较耗时的所以在视频完成后通过邮箱发送通知告诉我视频制作完成。模块安装我们需要使用到的模块主要有如下几个pillowopencvmoviepypaddlehub我们都可以直接用pip安装pip install pillowpip install opencv-pythonpip install moviepy其中OpenCV有一些适配问题建议选取3.0以上版本。在我们使用paddlehub之前我们需要安装paddlepaddle具体安装步骤可以参见官网。用paddlehub抠图参考别再自己抠图了Python用5行代码实现批量抠图。我们这里直接用pip安装cpu版本的# 安装paddlepaddlepython -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple# 安装paddlehubpip install -i https://mirror.baidu.com/pypi/simple paddlehub有了这些准备工作就可以开始我们功能的实现了。具体实现我们导入如下包import cv2 # opencvimport mail # 自定义包用于发邮件import mathimport numpy as npfrom PIL import Image # pillowimport paddlehub as hubfrom moviepy.editor import *其中Pillow和opencv导入的名称不太一样还有就是我自定义的mail模块。另外我们还要先准备一些路径# 当前项目根目录系统自动获取当前目录BASE_DIR os.path.abspath(os.path.join(os.path.dirname(__file__), .))# 每一帧画面保存的地址frame_path BASE_DIR frames# 抠好的图片位置humanseg_path BASE_DIR humanseg_output# 最终视频的保存路径output_video BASE_DIR esult.mp4接下来我们按照上面说的步骤一个一个实现。(1)读取视频获取每一帧画面在OpenCV中提供了读取帧的函数我们只需要使用VideoCapture类读取视频然后调用read函数读取帧read方法返回两个参数ret为是否有下一帧frame为当前帧的ndarray对象。完整代码如下def getFrame(video_name, save_path):读取视频将视频逐帧保存为图片并返回视频的分辨率size和帧率fps:param video_name: 视频的名称:param save_path: 保存的路径:return: fps帧率size分辨率# 读取视频video cv2.VideoCapture(video_name)# 获取视频帧率fps video.get(cv2.CAP_PROP_FPS)# 获取画面大小width int(video.get(cv2.CAP_PROP_FRAME_WIDTH))height int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))size (width, height)# ?获取帧数用于给图片命名frame_num str(video.get(7))name int(math.pow(10, len(frame_num)))# 读取帧ret为是否还有下一帧frame为当前帧的ndarray对象ret, frame video.readwhile ret:cv2.imwrite(save_path str(name) .jpg, frame)ret, frame video.readname 1video.releasereturn fps, size在标?处我获取了帧的总数然后通过如下公式获取比帧数大的整十整百的数frame_name math.pow(10, len(frame_num))这样做是为了让画面逐帧排序这样读取的时候就不会乱。另外我们获取了视频的帧率和分辨率这两个参数在我们创建视频时需要用到。这里需要注意的是opencv3.0以下版本获取帧率和画面大小的写法有些许差别。(2)批量抠图批量抠图需要用到paddlehub中的模型库代码很简单这里就不多说了def getHumanseg(frames):对帧图片进行批量抠图:param frames: 帧的路径:return:# 加载模型库humanseg hub.Module(namedeeplabv3p_xception65_humanseg)# 准备文件列表files [frames i for i in os.listdir(frames)]# 抠图humanseg.segmentation(data{image: files})我们执行上面函数后会在项目下生成一个humanseg_output目录抠好的图片就在里面。(3)读取场景图片这也是简单的图片读取我们使用pillow中的Image对象def readBg(bgname, size):读取背景图片并修改尺寸:param bgname: 背景图片名称:param size: 视频分辨率:return: Image对象im Image.open(bgname)return im.resize(size)这里的返回的对象并非ndarray对象而是Pillow中定义的类对象。(4)对每一帧画面进行场景切换简单来说就是将抠好的图片和背景图片合并我们知道抠好的图片都在humanseg_output目录这也就是为什么最开始要准备相应的变量存储该目录的原因def setImageBg(humanseg, bg_im):将抠好的图和背景图片合并:param humanseg: 抠好的图:param bg_im: 背景图片这里和readBg函数返回的类型一样:return: 合成图的ndarray对象# 读取透明图片im Image.open(humanseg)# 分离色道r, g, b, a im.split# ?复制背景以免源背景被修改bg_im bg_im.copy# 合并图片bg_im.paste(im, (0, 0), maska)return np.array(bg_im.convert(RGB))[:, :, ::-1]在标?处我们复制了背景如果少了这一步的话生成的就是我们上面的“千手观音效果”了。其它步骤都很好理解只有返回值比较长我们来详细看一下# 将合成图转换成RGB这样A通道就没了bg_im bg_im.convert(RGB)# 将Image对象转换成ndarray对象方便opencv读取im_array np.array(bg_im)# 此时im_array为rgb模式而OpenCV为bgr模式我们通过下面语句将rgb转换成bgrbgr_im_array im_array[:, :, ::-1]最后bgr_im_array就是我们最终的返回结果。(5)写入视频为了节约空间我并非等将写入图片放在合并场景后面而是边合并场景边写入视频def writeVideo(humanseg, bg_im, fps, size)::param humanseg: png图片的路径:param bgname: 背景图片:param fps: 帧率:param size: 分辨率:return:# 写入视频fourcc cv2.VideoWriter_fourcc(*mp4v)out cv2.VideoWriter(green.mp4, fourcc, fps, size)# 将每一帧设置背景files [humanseg i for i in os.listdir(humanseg)]for file in files:# 循环合并图片im_array setImageBg(file, bg_im)# 逐帧写入视频out.write(im_array)out.release上面的代码也非常简单执行完成后项目下会生成一个green.mp4这是一个没有音频的视频后面就需要我们获取音频然后混流了。(6)读取原视频的音频因为在opencv中没找到音频相关的处理所以选用moviepy使用起来也非常方便def getMusic(video_name):获取指定视频的音频:param video_name: 视频名称:return: 音频对象# 读取视频文件video VideoFileClip(video_name)# 返回音频return video.audio然后就是混流了。(7)给新视频设置音频这里同样使用moviepy传入视频名称和音频对象进行混流def addMusic(video_name, audio):实现混流给video_name添加音频# 读取视频video VideoFileClip(video_name)# 设置视频的音频video video.set_audio(audio)# 保存新的视频文件video.write_videofile(output_video)其中output_video是我们在最开始定义的变量。(8)删除过渡文件在我们生产视频时会产生许多过渡文件在视频合成后我们将它们删除def deleteTransitionalFiles:删除过渡文件frames [frame_path i for i in os.listdir(frame_path)]humansegs [humanseg_path i for i in os.listdir(humanseg_path)]for frame in frames:os.remove(frame)for humanseg in humansegs:os.remove(humanseg)最后就是将整个流程整合一下。(8)整合我们将上面完整的流程合并成一个函数def changeVideoScene(video_name, bgname)::param video_name: 视频的文件:param bgname: 背景图片:return:# 读取视频中每一帧画面fps, size getFrame(video_name, frame_path)# 批量抠图getHumanseg(frame_path)# 读取背景图片bg_im readBg(bgname, size)# 将画面一帧帧写入视频writeVideo(humanseg_path, bg_im, fps, size)# 混流addMusic(green.mp4, getMusic(video_name))# 删除过渡文件deleteTransitionalFiles(9)在main中调用我们可以把前面定义的路径也放进了if __name__ __main__:# 当前项目根目录BASE_DIR os.path.abspath(os.path.join(os.path.dirname(__file__), .))# 每一帧画面保存的地址frame_path BASE_DIR frames# 抠好的图片位置humanseg_path BASE_DIR humanseg_output# 最终视频的保存路径output_video BASE_DIR esult.mp4if not os.path.exists(frame_path):os.makedirs(frame_path)try:# 调用函数制作视频changeVideoScene(jljt.mp4, bg.jpg)# 当制作完成发送邮箱mail.sendMail(你的视频已经制作完成)except Exception as e:# 当发生错误发送错误信息mail.sendMail(在制作过程中遇到了问题 e.__str__)这样我们就完成了完整的流程。发送邮件邮件的发送又是属于另外的内容了我定义了一个mail.py文件具体代码如下import smtplibfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipart # 一封邮件def sendMail(msg):#sender 发件人to_list [收件人]subject 视频制作情况# 创建邮箱em MIMEMultipartem[subject] subjectem[From] senderem[To]