Python 视频画面字符化

  • Demo:bilibili
  • Using:Python 3.5,ffmpeg-bin
  • Dependence:PIL,string,numpy,math

务必注意这只是Prototype,性能可以说是完全没有的
首先截取画面,命令行如下

ffmpeg -ss TIME -t DURING -i INPUT -frames:v TOTAL OUTPUT
  • TIME:开始截取的时间
  • DURING:截取时长
  • INPUT:视频文件名
  • TOTAL:截取帧数
  • OUTPUT:输出的图片名(例如pic%d.png,将生成pic1.png,pic2.png,…)

接着Python

from PIL import Image,ImageDraw,ImageFont
import string
import numpy
import math

FontSize=24

font = ImageFont.truetype('consola.ttf',FontSize)
FontPxSize=font.getsize('A')
LetterLuma={}
LetterUsed="qwertyuiopasdfghjklzxcvbnm[];',.{}:\"<>?~!@#$%^&*()_+-=123456789/"   
#允许使用的字符
for letter in LetterUsed:
    imgl=Image.new('RGB',FontPxSize,(255,255,255))
    drawl=ImageDraw.Draw(imgl)
    drawl.text((0,0),letter,(0,0,0),font=font)
    dat=list(imgl.getdata())
    ave=numpy.average(dat)
    LetterLuma[letter]=ave
LetterLuma[' ']=255

LowID=1     #图片开始的序号
HighID=2    #图片结束的序号+1
IDafx='k'   #图片编号前缀
fload=''    #图片位置
QuickGuess={}

for id in range(LowID,HighID):
    filename=fload+IDafx+str(id)+'.png'   #假设图片为png格式
    img=Image.open(filename)
    img.load()
    img=img.convert("L")
    imgsize=img.size
    maxrow=math.floor(imgsize[1]/FontPxSize[1])
    maxcol=math.floor(imgsize[0]/FontPxSize[0])
    linesize=imgsize[1]
    pxwidth=FontPxSize[0]
    pxheight=FontPxSize[1]
    outputimg=Image.new('RGB',(maxcol*FontPxSize[0],maxrow*FontPxSize[1]),(255,255,255))
    outputdraw=ImageDraw.Draw(outputimg)
    for h in range(0,maxrow):
        RanderText=''
        TextCache=[]
        for w in range(0,maxcol):
            '''
            开始计算平均灰度
            '''
            l=0.0
            l=numpy.average(list((img.getpixel((x,y)) for x in range(w*pxwidth,(w+1)*pxwidth) for y in range(h*pxheight,(h+1)*pxheight))))   
            '''l/=FontPxSize[0]*FontPxSize[1]'''
            bestguess=' '
            if (QuickGuess.get(int(l))==None):
                for key in LetterLuma:
                    if (abs(LetterLuma[bestguess]-l)>abs(LetterLuma[key]-l)):
                        bestguess=key
                        QuickGuess[int(l)]=bestguess
            else:
                bestguess=QuickGuess[int(l)]
            TextCache.append(bestguess)
        RanderText=RanderText.join(TextCache)
        outputdraw.text((0,h*pxheight), RanderText,(0,0,0),font=font)
    outputimg.save(fload+IDafx+'MOD'+str(id)+'.png')
    img.close()

最后再用ffmpeg合成,命令行如下

ffmpeg -framerate FRAMERATE -i PICINPUT -codec copy FILMOUTPUT
  • FRAMERATE:帧率
  • PICINPUT:图片
  • copy:指定的视频压缩格式为仅复制(可换成h264)
  • FILMOUTPUT:输出视频

为了快速开发所以没有用getdata方法把数据导出list再计算平均灰度考虑对齐问题好烦啊这是性能瓶颈之一,因为getpixel方法比average方法的CPU耗时多
另外一个可能瓶颈在于硬盘IO,数据从硬盘流到内存(ffmpeg解码)再流回硬盘(保存为png)再流到内存(解码png)再流到硬盘(保存为png)再流到内存(ffmpeg编码)再流到硬盘(保存为视频),鉴于不知道有没有Python上包装ffmpeg的库,可以用C写两个函数,然后黏到Python上。

Demo在8核3.2GHz的处理器上,ffmpeg截图约50 fps,Python处理(多进程)约1 fps,ffmpeg合成约300 fps