zoukankan      html  css  js  c++  java
  • [Python] 将视频转成ASCII符号形式、生成GIF图片

    一、简要说明
    • 简述:本文主要展示将视频转成ASCII符号形式展示出来,带音频。
    • 运行环境:Win10/Python3.5。
    • 主要模块: PILnumpyshutil
    二、简单分析

      在网上看到转成字符形式的视频,感觉挺有趣的,于是查阅相关资料,开始实现一下。基本思路:主要使用 ffmpeg 对进行视频操作,然后使用 PIL 对图片进行缩小、灰度和转码的处理。流程如下:

    1. 创建临时路径。
    2. 将视频按帧分割成图片存入临时目录。
    3. 遍历将图片缩放、转成灰度,再转成ASCII形式的图片。
    4. 将ASCII形式的图片合成视频。
    5. 获取源文件的音频文件。
    6. 合并视频和音频文件。

      再来看看效果图:

    在这里插入图片描述   在这里插入图片描述

    在这里插入图片描述在这里插入图片描述

    三、开发流程

      3.1、创建目录,存储图片的临时路径

        # [1]、创建存储临时图片的路径
        def createpath(self):
            print("-" * 30)
            print("[1/6]正在创建临时路径...")
            print("-" * 30 + '
    ')
    
            # 源视频文件的图片路径
            if not os.path.exists(self.pic_path):
                os.makedirs(self.pic_path)
            else:
                # 清空在创建
                shutil.rmtree(self.pic_path)
                os.makedirs(self.pic_path)
    
            # 转换之后的图片路径
            if not os.path.exists(self.ascii_path):
                os.makedirs(self.ascii_path)
            else:
                # 清空再创建
                shutil.rmtree(self.ascii_path)
                os.makedirs(self.ascii_path)
            
            # 存储输出文件的目录
            if not os.path.exists(self.outpath):
                os.makedirs(self.outpath)
    

    以上代码主要创建源视频切割图片存储路径、转码后图片存储路径和输出文件的存储路径,图片的存储路径为 临时路径 ,每次执行前会先清空之前的文件,请注意。

      3.2、将视频分割成图片

    	# [2]、将视频分割成图片
        def video2pic(self):
            print("-" * 30)
            print("[2/6]正在切割原始视频为图片...")
            print("-" * 30 + '
    ')
            # 使用ffmpeg切割图片,命令行如下
            cmd = 'ffmpeg -i {0} -r 24 {1}/%06d.jpeg'.format(self.filename, self.pic_path)
    
            # 执行命令
            os.system(cmd)
    
    cmd:ffmpeg -i [输入文件名] -r [fps,帧率] [分割图存储路径]
    

    这里就比较简单,使用 ffmpeg 将视频分割成图片并按照相应个数存储在临时路径即可。查阅ffmpeg命令行说明

      3.3、将视频分割成图片

    	# [3]、将图片缩放、转成ascii形式
        def pic2ascii(self):
            print("-" * 30)
            print("[3/6]正在处理分析图片,转成ascii形式...")
            print("-" * 30 + '
    ')
            # 读取原始图片目录
            pic_list = sorted(os.listdir(self.pic_path))
    
            total_len = len(pic_list)
            count = 1
    
            # 遍历每张图片
            for pic in pic_list:
                # 图片完整路径
                imgpath = os.path.join(self.pic_path, pic)
    
                # 1、缩小图片,转成灰度模式,存入数组
                origin_img = Image.open(imgpath)
    
                # 缩小之后宽高
                resize_width = int(origin_img.size[0] / self.resize_times)
                resize_height = int(origin_img.size[1] / self.resize_times)
    
                resize_img = origin_img.resize((resize_width, resize_height), Image.ANTIALIAS).convert("L")
    
                img_arr = np.array(resize_img)
    
                # 2、新建空白图片(灰度模式、与原始图片等宽高)
                new_img = Image.new("L", origin_img.size, 255)
                draw_obj = ImageDraw.Draw(new_img)
                font = ImageFont.truetype("arial.ttf", 8)
    
                # 3、将每个字符绘制在一定的区域内
                for i in range(resize_height):
                    for j in range(resize_width):
                        x, y = j*self.resize_times, i*self.resize_times
                        index = int(img_arr[i][j]/4)
                        draw_obj.text((x, y), self.ascii_char[index], font=font, fill=0)
    
                # 4、保存字符图片
                new_img.save(os.path.join('temp_ascii', pic), "JPEG")
                print("已生成ascii图(%d/%d)" % (count, total_len))
                count += 1
    

    这一步是重点,在遍历获取源图片目录列表之后,就可以分步进行操作了:

    1. 缩小图片、转成灰度模式,存入数组。
    2. 新建空白图片(灰度模式、与原始图片等宽高)。
    3. 将每个字符绘制在一定的区域内。
    4. 保存字符图片。

    下面就是替换的字符:

    self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/|()1[]?-_+~<>i!......... ")
    

      3.4、将ascii形式的图片合成视频

    	# [4]、合成视频
        def ascii2video(self):
            print("-" * 30)
            print("[4/6]正在合成视频...")
            print("-" * 30 + '
    ')
            # 输出视频保存路径
            savepath = os.path.join(self.outpath, self.outname)
    
            cmd = 'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}'.format(self.ascii_path, savepath)
    
            os.system(cmd)
    

    遍历转码的图片,合成视频。

    cmd:ffmpeg -threads 2 -start_number [开始图片编号] -r [帧率,fps] -i [图片路径] -vcodec [指定解码器] [输出文件名]
    

      3.5、获取音频mp3文件

     	# [5]、获取原始视频的mp3文件
        def video2mp3(self):
            print("-" * 30)
            print("[5/6]正在分离音频文件...")
            print("-" * 30 + '
    ')
    
            # mp3名字和保存路径
            name = self.filename.split('.')[0] + '.mp3'
            savepath = os.path.join(self.outpath, name)
            cmd = 'ffmpeg -i {0} -f mp3 {1}'.format(self.filename, savepath)
    
            os.system(cmd)
    
    cmd:ffmpeg -i [输入视频文件名] -f mp3 [输出的mp3文件名]
    

      3.5、合并视频和音频文件

    	# [6]、将视频和音频合并
        def mp4andmp3(self):
            print("-"*30)
            print("[6/6]正在合并视频和音频...")
            print("-" * 30 + '
    ')
    
            cmd = 'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}'.format(self.mp4filename, self.mp3ilename,  self.mergefilename)
    
            os.system(cmd)
    

    上面代码就是将视频和音频进行合并,转成全符号的视频也不会丢失音频。

    cmd :ffmpeg -i [视频文件名] -i [音频文件名] -strict -2 -f mp4 [合并后的文件名]
    
    四、生成GIF动图
    # -*- coding:utf-8 -*-
    import imageio
    import os
    
    # 图片路径
    pic_path = "temp_pic"
    
    # 输出文件名
    outname = "jljt.gif"
    
    # 越过的图片数
    skip_num = 10
    
    pic_list = sorted(os.listdir(pic_path))
    
    frames = []
    total_len = len(pic_list)
    
    # 遍历、读取图片,这里的
    for i in range(0, total_len, skip_num):
        path = os.path.join(pic_path, pic_list[i])
        frames.append(imageio.imread(path))
    
    # 生成GIF图片
    imageio.mimsave(outname, frames, "GIF", duration=0.1)
    print("生成完成")
    
    

    上面主要实现:将分割出来的图片,合成一张GIF动图,通过设置越过的图片数,可以减小容量,但是会加速动画效果,上面的效果图,就是通过这里生成的。

    五、附录
    *转发需注明出处
    
    # -*- coding:utf-8 -*-
    
    from PIL import Image, ImageDraw, ImageFont
    import numpy as np
    import os
    import sys
    import shutil
    
    
    class Video2Ascii:
    
        def __init__(self, filename):
            # 执行前的一些判断
            if not os.path.isfile(filename):
                print("源文件找不到,或者不存在!")
                exit()
    
            temp_arr = filename.split('.')
    
            # 字符列表,从左至右逐渐变得稀疏,对应着颜色由深到浅
            self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/|()1[]?-_+~<>i!......... ")
    
            # 传入视频文件名
            self.filename = filename
            # 输出视频文件名
            self.outname = temp_arr[0] + "_out." + temp_arr[1]
    
            # 存储图片的临时路径、输出路径
            self.pic_path = 'temp_pic'
            self.ascii_path = 'temp_ascii'
            self.outpath = 'temp_out'
    
            # 设置图片缩小的倍数
            self.resize_times = 6
    
            # 设置输出文件的名字,声音文件以及带声音的输出文件
            self.mp3ilename = os.path.join(self.outpath, temp_arr[0] + '.mp3')
            self.mp4filename = os.path.join(self.outpath, self.outname)
    
            # 合并输出的视频文件
            self.mergefilename = os.path.join(self.outpath, temp_arr[0] + '_voice.' + temp_arr[1])
    
        # [1]、创建存储临时图片的路径
        def createpath(self):
            print("-" * 30)
            print("[1/6]正在创建临时路径...")
            print("-" * 30 + '
    ')
    
            # 源视频文件的图片路径
            if not os.path.exists(self.pic_path):
                os.makedirs(self.pic_path)
            else:
                # 清空在创建
                shutil.rmtree(self.pic_path)
                os.makedirs(self.pic_path)
    
            # 转换之后的图片路径
            if not os.path.exists(self.ascii_path):
                os.makedirs(self.ascii_path)
            else:
                # 清空再创建
                shutil.rmtree(self.ascii_path)
                os.makedirs(self.ascii_path)
    
            # 存储输出文件的目录
            if not os.path.exists(self.outpath):
                os.makedirs(self.outpath)
    
        # [2]、将视频分割成图片
        def video2pic(self):
            print("-" * 30)
            print("[2/6]正在切割原始视频为图片...")
            print("-" * 30 + '
    ')
            # 使用ffmpeg切割图片,命令行如下
            cmd = 'ffmpeg -i {0} -r 24 {1}/%06d.jpeg'.format(self.filename, self.pic_path)
    
            # 执行命令
            os.system(cmd)
    
        # [3]、将图片缩放、转成ascii形式
        def pic2ascii(self):
            print("-" * 30)
            print("[3/6]正在处理分析图片,转成ascii形式...")
            print("-" * 30 + '
    ')
            # 读取原始图片目录
            pic_list = sorted(os.listdir(self.pic_path))
    
            total_len = len(pic_list)
            count = 1
    
            # 遍历每张图片
            for pic in pic_list:
                # 图片完整路径
                imgpath = os.path.join(self.pic_path, pic)
    
                # 1、缩小图片,转成灰度模式,存入数组
                origin_img = Image.open(imgpath)
    
                # 缩小之后宽高
                resize_width = int(origin_img.size[0] / self.resize_times)
                resize_height = int(origin_img.size[1] / self.resize_times)
    
                resize_img = origin_img.resize((resize_width, resize_height), Image.ANTIALIAS).convert("L")
    
                img_arr = np.array(resize_img)
    
                # 2、新建空白图片(灰度模式、与原始图片等宽高)
                new_img = Image.new("L", origin_img.size, 255)
                draw_obj = ImageDraw.Draw(new_img)
                font = ImageFont.truetype("arial.ttf", 8)
    
                # 3、将每个字符绘制在 8*8 的区域内
                for i in range(resize_height):
                    for j in range(resize_width):
                        x, y = j*self.resize_times, i*self.resize_times
                        index = int(img_arr[i][j]/4)
                        draw_obj.text((x, y), self.ascii_char[index], font=font, fill=0)
    
                # 4、保存字符图片
                new_img.save(os.path.join('temp_ascii', pic), "JPEG")
                print("已生成ascii图(%d/%d)" % (count, total_len))
                count += 1
    
                # exit()
    
        # [4]、合成视频
        def ascii2video(self):
            print("-" * 30)
            print("[4/6]正在合成视频...")
            print("-" * 30 + '
    ')
            # 输出视频保存路径
            savepath = os.path.join(self.outpath, self.outname)
    
            cmd = 'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}'.format(self.ascii_path, savepath)
    
            os.system(cmd)
    
        # [5]、获取原始视频的mp3文件
        def video2mp3(self):
            print("-" * 30)
            print("[5/6]正在分离音频文件...")
            print("-" * 30 + '
    ')
    
            # mp3名字和保存路径
            name = self.filename.split('.')[0] + '.mp3'
            savepath = os.path.join(self.outpath, name)
            cmd = 'ffmpeg -i {0} -f mp3 {1}'.format(self.filename, savepath)
    
            os.system(cmd)
    
        # [6]、将视频和音频合并
        def mp4andmp3(self):
            print("-"*30)
            print("[6/6]正在合并视频和音频...")
            print("-" * 30 + '
    ')
    
            cmd = 'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}'.format(self.mp4filename, self.mp3ilename,  self.mergefilename)
    
            os.system(cmd)
    
        # [0]、启动
        def start(self):
            """
                > 程序流程:
                    1、创建路径
                    2、将原始视频分割成图片
                    3、将图片缩放、转成ascii形式
                    4、将ascii形式的图片合成视频
                    5、获取音频mp3文件
                    6、合并视频和音频文件
            :return:
            """
            self.createpath()
            self.video2pic()
            self.pic2ascii()
            self.ascii2video()
    
            self.video2mp3()
            self.mp4andmp3()
    
            print("程序执行完成")
    
    
    if __name__ == "__main__":
        if len(sys.argv) != 2:
            print("参数不匹配,请参考(脚本名 原始视频):xxx.py test.mp4 ")
            exit()
    
        demo = Video2Ascii(sys.argv[1])
        demo.start()
    
  • 相关阅读:
    VS2008编写MFC程序--使用opencv2.4()
    November 02nd, 2017 Week 44th Thursday
    November 01st, 2017 Week 44th Wednesday
    October 31st, 2017 Week 44th Tuesday
    October 30th, 2017 Week 44th Monday
    October 29th, 2017 Week 44th Sunday
    October 28th, 2017 Week 43rd Saturday
    October 27th, 2017 Week 43rd Friday
    October 26th, 2017 Week 43rd Thursday
    October 25th, 2017 Week 43rd Wednesday
  • 原文地址:https://www.cnblogs.com/reader/p/10172919.html
Copyright © 2011-2022 走看看