美文网首页有点意思二次元我的Python自学之路
python 将视频转化为字符画之badapple

python 将视频转化为字符画之badapple

作者: treelake | 来源:发表于2016-10-13 14:25 被阅读1251次

    看了用PYTHON制作字符动画演示科技bilibili哔哩哔哩弹幕视频网后觉得挺好玩,复现了一下,整体思路很简单,将视频分解为图片,然后将图片逐一转换为字符画,然后利用浏览器进行逐帧播放。

    浏览器播放效果
    • 首先安装FFmpeg,一款开源的视频软件,有丰富的视频处理功能。如何在Windows上安装FFmpeg程序

    • 然后使用window下的批处理batch对视频抓帧,在工作目录下新建run.bat

    mkdir images
    set /p input="input file:"
    set /p rate="set frame rate(Hz value, fraction or abbreviation):"
    set /p output="output file:"
    ffmpeg -i %input% -r %rate% %output%
    

    然后双击该bat运行,在第一句后输入被转化视频名称,在第二句后指定抓帧频率,为33.333(与后文代码中的播放间隔相对应),在第三句后输入images/%d.bmp,将所有转化图片放于工作目录下的images文件夹中。

    抓帧
    • 测试单张转换。在这里,仅仅先将图片转换为灰阶,然后对每个像素点做判断,根据黑白分别转化为'@'' '(空格),最后输出文本到html中。
     import os
    os.chdir(r'F:\badapple!!\images') # 转移到工作目录
    from PIL import Image, ImageFont, ImageDraw
    originalImage = Image.open('6382.bmp')# 图片尺寸为(480, 360)
    grayImage = originalImage.resize((120, int(90/2))).convert('L')
    # 将图片尺寸缩小,即减少像素点,并转换为灰阶
    # int(90/2) 因为字符的高约是宽的两倍,由于之后是一个像素点替换为一个字符,所以提前将高缩小一倍
    resulttext = ''
    for row in range(grayImage.size[1]): # 先行后列
        for col in range(grayImage.size[0]):
            pixel = grayImage.getpixel((col, row))
            char = '@' if pixel < 127 else ' ' # 像素点值为0是黑色
            resulttext += char
        resulttext += '\n'
    #print(resulttext)
    head = '''
    <html>
    <head>
    </head>
    <style>
    pre {font-size:14px; line-height:14px}
    </style>
    <body>
    <pre>
    '''
    foot = '''
    </pre>
    </body>
    </html>
    '''
    with open('1.html','w') as f:
        f.write(head)
        f.write(resulttext)
        f.write(foot)
    
    单张转换效果
    • 由于上述对图像的转化只有黑白两个层次,太过简单,表现力不够丰富,所以我们将一系列字符按一定规则排序,然后匹配到相应的灰度上。我们先从网上下载一份simsun的字体文件来提供画图中的字体,然后利用PIL中的画图功能,先创建一个最大字符尺寸的矩形白板,然后在上面画上字符,然后计算它的平均像素(每个像素点*该点像素值/总像素点数),根据平均像素来排序。然后再做下单张测试。此外,由于文本输出到html文件,所以需要html.escape()函数进行转义。
    补充:chr函数将数字转换为ACII码对应的字符 chr
    ### 然后对像素对字符的转换方式进行改进
    font= ImageFont.truetype('../simsun.ttf', 14)
    chars = list(chr(i) for i in range(32, 126))
    sizeList = list(font.getsize(char) for char in chars)
    import functools
    maxSize = functools.reduce(lambda x,y:(max(x[0],y[0]), max(x[1],y[1])), sizeList)
    #(8, 15)
    tempCharImage = Image.new('L', maxSize, 'white')
    tempCharDraw = ImageDraw.Draw(tempCharImage)
    charDegreeDict = {}
    for char in chars:
        tempCharDraw.rectangle([(0,0), maxSize], fill='white')#在(0,0)位置处以白色填充一个canvasSize的矩形。
        tempCharDraw.text((0,0), char, font=font)
        pixelColor = tempCharImage.getcolors()#返回当前图片上的所有色彩及其像素点数的列表,[(个数,色彩),(个数,色彩),...]
        grayDegree = sum(pixelnum*color for pixelnum,color in pixelColor)/(maxSize[0]*maxSize[1])
        charDegreeDict[char] = grayDegree
    sortedCharDegreeList = sorted(charDegreeDict.items(), key=lambda d:d[1])
    sortedCharDegreeList = list(i[0] for i in sortedCharDegreeList)
    charsIndexMax = len(sortedCharDegreeList) -1 
    # ['M', 'W', '@', 'N', 'H', '%', 'X', '$', 'B', 'K', 'R', '#', 'E', 'U', 'Q', 'D', '&', 'F', 'w', '8', 'k', '9', '6', 'h', 'm', 'A', 'P', 'p', 'd', '*', 'V', 'g', 'Y', '0', '5', 'b', 'q', 'G', 'O', 'n', 'x', 'f', 'S', 'J', 'T', 'Z', '3', 'y', 'u', 'I', 'C', 'L', '2', 'a', ']', '[', '1', '?', 'l', 's', 'e', 'r', '|', '}', '{', 't', 'z', 'o', 'v', '4', '+', '/', 'i', 'j', '=', 'c', '7', '(', ')', '!', '\\', '_', '>', '<', ':', '-', '"', "'", ',', ';', '.', '^', '`', ' ']
    ### 按灰度替换字符重新测试
    import os
    os.chdir(r'F:\badapple!!\images')
    from PIL import Image, ImageFont, ImageDraw
    originalImage = Image.open('6382.bmp')# 图片尺寸为(480, 360)
    grayImage = originalImage.resize((120, int(90/2))).convert('L')
    # 将图片尺寸缩小,即减少像素点,并转换为灰阶
    # int(90/2) 因为字符的高是长的两倍,由于之后是一个像素点替换为一个字符,所以提前将高缩小一倍
    resulttext = ''
    for row in range(grayImage.size[1]):
        for col in range(grayImage.size[0]):
            pixel = grayImage.getpixel((col, row))
            char = sortedCharDegreeList[int(pixel/255*charsIndexMax)] # 像素点值为0是黑色,255为白色
            resulttext += char
        resulttext += '\n'
    #print(resulttext)
    head = '''
    <html>
    <head>
    </head>
    <style>
    pre {font-family:simsun;font-size:14px; line-height:14px}
    </style>
    <body>
    <pre>
    '''
    foot = '''
    </pre>
    </body>
    </html>
    '''
    with open('1.html','w') as f:
        f.write(head)
        import html
        f.write(html.escape(resulttext))
        f.write(foot)
    
    多层次字符替换
    • 然后就是对所有图片进行转换了,利用glob找出工作目录images文件夹下的所有bmp图片,依次处理。所有字符图片存于html文件中的<pre></prev>标签中,一个标签对应一个图,然后在js代码中每隔30秒进行下一张图的显示和前一张的隐藏,这样就实现了播放。

    补充:glob的用法

    import glob  
    #获取指定目录下的所有图片  
    print glob.glob(r"E:\Picture\*\*.jpg")  
    #获取上级目录的所有.py文件  
    print glob.glob(r'../*.py') #相对路径  
    
    ### ffmpeg -i "Touhou - Bad Apple!!  PV.webm" -f mp3 -vn apple.mp3 可以输出音频,暂时没有用到
    workdir = r'F:\badapple!!\images'
    ### 按灰度替换的字符列表
    sortedCharDegreeList = ['M', 'W', '@', 'N', 'H', '%', 'X', '$', 'B', 'K', 'R', '#', 'E', 'U', 'Q', 'D', '&', 'F', 'w', '8', 'k', '9', '6', 'h', 'm', 'A', 'P', 'p', 'd', '*', 'V', 'g', 'Y', '0', '5', 'b', 'q', 'G', 'O', 'n', 'x', 'f', 'S', 'J', 'T', 'Z', '3', 'y', 'u', 'I', 'C', 'L', '2', 'a', ']', '[', '1', '?', 'l', 's', 'e', 'r', '|', '}', '{', 't', 'z', 'o', 'v', '4', '+', '/', 'i', 'j', '=', 'c', '7', '(', ')', '!', '\\', '_', '>', '<', ':', '-', '"', "'", ',', ';', '.', '^', '`', ' ']
    charsIndexMax = len(sortedCharDegreeList) -1 
    import os, glob, html
    os.chdir(workdir)
    from PIL import Image, ImageFont, ImageDraw
    result = []
    imgs = glob.glob('*.bmp')
    imgs = sorted(imgs, key=lambda x: int(x.split('.')[0])) # 这里对图片路径进行了处理,取后缀前的数字值进行排序
    #
    for img in imgs:
        originalImage = Image.open(img)# 图片尺寸为(480, 360)
        grayImage = originalImage.resize((120, int(90/2))).convert('L')
        # 将图片尺寸缩小,即减少像素点,并转换为灰阶
        # int(90/2) 因为字符的高是长的两倍,由于之后是一个像素点替换为一个字符,所以提前将高缩小一倍
        resulttext = ''
        for row in range(grayImage.size[1]):
            for col in range(grayImage.size[0]):
                pixel = grayImage.getpixel((col, row))
                char = sortedCharDegreeList[int(pixel/255*charsIndexMax)] # 像素点值为0是黑色,255为白色
                resulttext += char
            resulttext += '\n'
        result.append(resulttext)
        print(img,'is done!')
    #
    head = '''
    <html>
    <head>
    </head>
    <style>
    pre {display:none;font-family:simsun;font-size:14px; line-height:14px}
    </style>
    <script>
    window.onload = function(){
        var pres = document.getElementsByTagName('pre');
        var i = 0;
        var play = function(){
            if(i > 0){
                pres[i-1].style.display = 'none';
            }
            pres[i].style.display = 'inline-block';
            i++;
            if(i == pres.length){
                clearInterval(run)
            }
        }
        run = setInterval(play, 30)
    }
    </script>
    <body>
    '''
    foot = '''
    <video width="480" height="360" controls="controls" autoplay="autoplay">
      <source src="../Touhou - Bad Apple!!  PV.webm" type="video/webm" />
    </video>
    </body>
    </html>
    '''
    with open('2.html','w') as f:
        f.write(head)
        for resulttext in result:
            f.write("<pre>")
            f.write(html.escape(resulttext))
            f.write("</pre>")
        f.write(foot)
    
    最终效果
    • 最后你可以优化一下字符的替换方式,去掉某些显示效果不好的字符。

    相关文章

      网友评论

      本文标题:python 将视频转化为字符画之badapple

      本文链接:https://www.haomeiwen.com/subject/ghibyttx.html