利用Opencv将视频转换为字符视频

2019-11-17   98 次阅读


摘要:在Python课上学习了将图片转换为字符画的代码,想看看可不可以把视频转换为字符画视频,便有了此次为期一周陆陆续续写的视频画代码。

整体思路

  1. 分离出构成视频的图片
  2. 对图片进行ASCII码的转换
  3. 将转换好的图片进行合成为视频
  4. 本次为了方便测试,未添加删除生成文件的代码,需要手动删除或者自己添加代码。

将像素转换为ASCII码

将像素转换为ASCII码是为了在后期上色以及转换为字符时加以区别。

def get_char(r,g,b,alpha = 256):
    ascii_char = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
    #ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. ")
    if alpha == 0:
        return ''
    length = len(ascii_char)
    gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
    unit = (256.0+1)/len(ascii_char)
    return ascii_char[int(gray/unit)]

​ 此函数的输入的图片的三基色加上透明度,分别为R,G,B,ALPHA。此处会在后面用pixel = im.getpixel((j, i))来进行传递值。

​ 其中ascii_char就是字符列表,用来将不同灰度的像素进行不同字符体替换的参照,大家可以参试用不同的字符来观察出最好的组合。ps:听说个数必须为32的倍数,我也没有进行尝试,有兴趣的小伙伴们可以探究下其中的原因。

​ alpha在为0的时候便是完全透明的图片,所以返回空,当他不为零时则将图片转换为灰度图像,此处运用

​ gray = 0.2126 * r + 0.7152 * g + 0.0722 * b

来将图片转换为灰度图像进行下面的ASCII输出。

将视频分隔成图片

#将视频转换为图片 并进行计数,返回总共生成了多少张图片!
def video_to_pic(vp):
    #vp = cv2.VideoCapture(video_path)
    number = 0
    if vp.isOpened():
        r,frame = vp.read()
        if not os.path.exists('cache_pic'):
            os.mkdir('cache_pic')
        os.chdir('cache_pic')
    else:
        r = False
    while r:
        number += 1
        cv2.imwrite(str(number)+'.jpg',frame)
        r,frame = vp.read()
    print('\n由视频一共生成了{}张图片!'.format(number))
    os.chdir("..")
    return number

​ 可以看出来函数的传入值是vp = cv2.VideoCapture(video_path) 我将其放在主函数来进行输入,怕某些小伙伴没看见所以下其下写了备注。

1.isOpened()的用来检测资源是否在 VideoCapture()函数中打开。

2.os.chdir在PYTHON中的方法是用于改变当前工作目录到指定的路径。此处直接进入新创建的文件夹将生成的图片直接存入文件夹中。

3.返回number方便于后面的使用。

将图片转换为字符图片

此处分为两个函数进行(明明放在一起可以省事,课当初太懒了,懒得改了555)

def star_to_char(number,save_pic_path):
    if not os.path.exists('cache_char'):
        os.mkdir('cache_char')
    img_path_list = [save_pic_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标图片文件的路径列表
    task = 0
    for image_path in img_path_list:
        img_width , img_height = Image.open(image_path).size   #获取图片的分辨率
        task += 1
        img_to_char(image_path, img_width , img_height, task)
        print('{}/{} is finished.'.format(task,number))
    print('=======================')
    print('All image was finished!')
    print('=======================')
    return 0

​ 第一个函数相当于开始准备函数,其中传入的是上一个传出的总共生成多少个图片的统计number,还有字符图片存储的路径。

​ img_path_list 是用来存储图片相对路径的列表,采用内置for函数进行存储。

​ 利用Image.open().size来获取图片大小,传入下面的转换函数。

def img_to_char(image_path,raw_width,raw_height,task):
    width = int(raw_width/ 6)
    height = int(raw_height / 15)
    im = Image.open(image_path).convert('RGB')#必须以RGB模式打开
    im = im.resize((width,height),Image.NEAREST)
    
    txt = ''
    color = []
    for i in range(height):
        for j in range(width):
            pixel = im.getpixel((j, i))
            color.append((pixel[0],pixel[1],pixel[2])) #将颜色加入进行索引
            if len(pixel)==4 :
                txt +=get_char(pixel[0],pixel[1],pixel[2],pixel[3])
            else:
                txt +=get_char(pixel[0],pixel[1],pixel[2])
        txt += '\n'
        color.append((255,255,255))

    im_txt = Image.new("RGB",(raw_width,raw_height),(255,255,255))
    dr = ImageDraw.Draw(im_txt)
    #font = ImageFont.truetype('consola.ttf', 10, encoding='unic') #改为这个字体会让图片比例改变
    font = ImageFont.load_default().font
    x,y = 0,0
    font_w,font_h=font.getsize(txt[1])
    font_h *= 1.37 #调整字体大小
    for i in range(len(txt)):
        if(txt[i]=='\n'):
            x += font_h
            y = -font_w
        dr.text((y,x),txt[i] ,fill = color[i])
        y+=font_w
    os.chdir('cache_char')
    im_txt.save(str(task)+'.jpg')
    os.chdir("..")
    return 0

​ 要提醒的是,看下面代码:

width = int(raw_width/ 6)
height = int(raw_height / 15)

​ 如果要将图片进行转换必须把图片大小进行转换,否则转换出的代码必定会出现乱码。

​ dr = ImageDraw.Draw(im_txt)是opencv中创建画板的函数。

​ font_h是将字体进行调整,可以让生成的字符画视频更加的好康。

​ 下面 for i in range(len(txt)):就是遍历txt按行按列进行写入图片,当遇到\n时推出并进行上色,最后将图片进行输出。

将图片合成为视频

def jpg_to_video(char_image_path,FPS):
    video_fourcc=VideoWriter_fourcc(*"MP42")  # 设置视频编码器,这里使用使用MP42编码器,可以生成更小的视频文件
    char_img_path_list = [char_image_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标字符图片文件的路径列表
    char_img_test = Image.open(char_img_path_list[1]).size   #获取图片的分辨率
    if not os.path.exists('video'):
            os.mkdir('video')
    video_writter= VideoWriter('video/new_char_video.avi' , video_fourcc , FPS , char_img_test)
    sum  = len(char_img_path_list)
    count = 0
    for image_path in char_img_path_list:
        img = cv2.imread(image_path)
        video_writter.write(img)
        end_str = '100%'
        count= count + 1
        process_bar(count/sum, start_str='', end_str=end_str, total_length=15)
    
    video_writter.release()
    print('\n')
    print('=======================')
    print('The video is finished!')
    print('=======================')

​ 此处最需要注意的是第二行的视频编码器,我进行了大量的测试,发现MP42是可以生成较小并且较小的视频文件的编码方式,具体详细参数可以参考

[]: https://blog.csdn.net/Archger/article/details/102868923

完整代码

import cv2 
from PIL import Image,ImageFont,ImageDraw
import os
from cv2 import VideoWriter, VideoWriter_fourcc, imread, resize
#=========================
#coding:UTF-8
# 视频转字符画含音频version-1
#参考1:https://blog.csdn.net/mp624183768/article/details/81161260
#参考2:https://blog.csdn.net/qq_42820064/article/details/90958577
#参考3:https://blog.csdn.net/zj360202/article/details/79026891
#=========================
def get_char(r,g,b,alpha = 256):
    ascii_char = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
    #ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. ")
    if alpha == 0:
        return ''
    length = len(ascii_char)
    gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
    unit = (256.0+1)/len(ascii_char)
    return ascii_char[int(gray/unit)]

#将视频转换为图片 并进行计数,返回总共生成了多少张图片!
def video_to_pic(vp):
    #vp = cv2.VideoCapture(video_path)
    number = 0
    if vp.isOpened():
        r,frame = vp.read()
        if not os.path.exists('cache_pic'):
            os.mkdir('cache_pic')
        os.chdir('cache_pic')
    else:
        r = False
    while r:
        number += 1
        cv2.imwrite(str(number)+'.jpg',frame)
        r,frame = vp.read()
    print('\n由视频一共生成了{}张图片!'.format(number))
    os.chdir("..")
    return number


def img_to_char(image_path,raw_width,raw_height,task):
    width = int(raw_width/ 6)
    height = int(raw_height / 15)
    im = Image.open(image_path).convert('RGB')#必须以RGB模式打开
    im = im.resize((width,height),Image.NEAREST)
    
    txt = ''
    color = []
    for i in range(height):
        for j in range(width):
            pixel = im.getpixel((j, i))
            color.append((pixel[0],pixel[1],pixel[2])) #将颜色加入进行索引
            if len(pixel)==4 :
                txt +=get_char(pixel[0],pixel[1],pixel[2],pixel[3])
            else:
                txt +=get_char(pixel[0],pixel[1],pixel[2])
        txt += '\n'
        color.append((255,255,255))

    im_txt = Image.new("RGB",(raw_width,raw_height),(255,255,255))
    dr = ImageDraw.Draw(im_txt)
    #font = ImageFont.truetype('consola.ttf', 10, encoding='unic') #改为这个字体会让图片比例改变
    font = ImageFont.load_default().font
    x,y = 0,0
    font_w,font_h=font.getsize(txt[1])
    font_h *= 1.37 #调整字体大小
    for i in range(len(txt)):
        if(txt[i]=='\n'):
            x += font_h
            y = -font_w
        dr.text((y,x),txt[i] ,fill = color[i])
        y+=font_w
    os.chdir('cache_char')
    im_txt.save(str(task)+'.jpg')
    os.chdir("..")
    return 0


def star_to_char(number,save_pic_path):
    if not os.path.exists('cache_char'):
        os.mkdir('cache_char')
    img_path_list = [save_pic_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标图片文件的路径列表
    task = 0
    for image_path in img_path_list:
        img_width , img_height = Image.open(image_path).size   #获取图片的分辨率
        task += 1
        img_to_char(image_path, img_width , img_height, task)
        print('{}/{} is finished.'.format(task,number))
    print('=======================')
    print('All image was finished!')
    print('=======================')
    return 0

def process_bar(percent, start_str='', end_str='', total_length=0):
    #进度条
    bar = ''.join("■ " * int(percent * total_length)) + ''
    bar = '\r' + start_str + bar.ljust(total_length) + ' {:0>4.1f}%|'.format(percent*100) + end_str
    print(bar, end='', flush=True)

def jpg_to_video(char_image_path,FPS):
    video_fourcc=VideoWriter_fourcc(*"MP42")  # 设置视频编码器,这里使用使用MP42编码器,可以生成更小的视频文件
    char_img_path_list = [char_image_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标字符图片文件的路径列表
    char_img_test = Image.open(char_img_path_list[1]).size   #获取图片的分辨率
    if not os.path.exists('video'):
            os.mkdir('video')
    video_writter= VideoWriter('video/new_char_video.avi' , video_fourcc , FPS , char_img_test)
    sum  = len(char_img_path_list)
    count = 0
    for image_path in char_img_path_list:
        img = cv2.imread(image_path)
        video_writter.write(img)
        end_str = '100%'
        count= count + 1
        process_bar(count/sum, start_str='', end_str=end_str, total_length=15)
    
    video_writter.release()
    print('\n')
    print('=======================')
    print('The video is finished!')
    print('=======================')

if __name__ == '__main__':
    
    video_path = 'test.mp4'
    save_pic_path = 'cache_pic'
    save_charpic_path = 'cache_char'

    vp = cv2.VideoCapture(video_path)
    number = video_to_pic(vp)
    FPS = vp.get(cv2.CAP_PROP_FPS)
    star_to_char(number , save_pic_path)
    vp.release()
    jpg_to_video(save_charpic_path,FPS)
    

注:转载请注明出处

本文由 hongCYu 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
原文链接:https://hongcyu.cn/posts/opencv-pictovideo.html
最后更新于:2020-12-03 10:44:21

Coffee