視頻轉換字符畫視頻

目錄

文本信息

項目所用主要庫

視頻轉換的原理

完整代碼

代碼中值得注意的事項

不足之處


文本信息

       視頻有一幀一幀的畫面構成,利用字符替代畫面中的像素信息,就可以將視頻轉換爲字符畫的視頻,本文將利用python庫,對一段視頻進行轉換,將其轉換爲字符串展示的視頻,本文在文章——Python將視頻轉換爲全字符視頻(含音頻)的參考下完成。

項目所用主要庫

opencv ——參考文檔

pillow——參考文檔

re

視頻轉換的原理

        將視頻轉換爲字符畫,原理是利用像素點信息將其映射到字符上,對於彩色視頻,需要對其進行處理,將其轉換爲灰度視頻,其轉換原理爲利用灰度值公式將彩色的RGB像素表示爲灰度值。最後再利用ascii碼,將灰度值映射爲字符。

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

 ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "

完整代碼

import cv2
import time
import os
from PIL import Image,ImageFont,ImageDraw
from cv2 import VideoWriter,VideoWriter_fourcc,imread,resize
import re

#轉換爲字符畫
class CharFrame:
    ascii_char = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "

    # 像素映射到字符
    def pixelToChar(self, luminance):
        return self.ascii_char[int(luminance / 256 * len(self.ascii_char))]

    # 將普通幀轉爲 ASCII 字符幀
    def convert(self, img, limitSize=(),fill=False,wrap=False):
        if limitSize != () and (img.shape[0] > limitSize[1] or img.shape[1] > limitSize[0]):
            #對圖像縮放到適合終端的大小
            img = cv2.resize(img, limitSize, interpolation=cv2.INTER_NEAREST)
        ascii_frame = ''
        blank = ''
        if fill:
            blank += ' ' * (limitSize[0] - img.shape[1])
        if wrap:
            blank += '\n'
        for i in range(img.shape[0]):
            for j in range(img.shape[1]):
                ascii_frame += self.pixelToChar(img[i, j])
            ascii_frame += blank
        return ascii_frame


class V2Char(CharFrame):
    charVideo = []

    def __init__(self, path,size=(120,30)):
        """
        :param path:視頻文件路徑
        :param size: 所要縮放的大小,(width,height)
        """
        self.path=path
        self.size=size
        self.genCharVideo(path)

    def genCharVideo(self, filepath):
        self.charVideo = []
        # 用opencv讀取視頻,設置相機
        cap = cv2.VideoCapture(filepath)
        #get(5)爲視頻的幀速率
        self.FPS=cap.get(cv2.CAP_PROP_FPS)#或者用get(5)
        #get(7)爲視頻的幀數
        nf = int(cap.get(7))
        print('Generate char video, please wait...')
        for i in range(nf):
            # 轉換顏色空間,第二個參數是轉換類型,cv2.COLOR_BGR2GRAY表示從BGR↔Gray
            #read()讀取視頻畫面,第一個參數表示讀取是否成功,第二個即爲畫面,所以索引用1
            #每read一次讀取一個畫面
            rawFrame = cv2.cvtColor(cap.read()[1], cv2.COLOR_BGR2GRAY)
            frame = self.convert(rawFrame,self.size,fill=True)#注意這裏的參數輸入
            self.charVideo.append(frame)
        #釋放相機
        cap.release()
        print('Finished.')


    #設置最大的單字符長度
    def max_width(self):
        font = ImageFont.truetype(os.path.join("fonts", "arial.ttf"), 18)
        font_size=[]
        for i in self.ascii_char:
            font_size.append(font.getsize(i)[0])
        return max(font_size)


    #對一行切分爲數組的形式,即表示爲一個畫面
    def str_slice(self,char,row,col):
        slice_char=[]
        for i in range(row):
            slice_char.append(char[i*col:(i+1)*col])
        return slice_char


    def str2fig(self):
        #很多字符的寬度不一樣,需要設置字符最大的寬度
        font = ImageFont.truetype('arial.ttf', 18)
        le_width=self.max_width()
        line_height=font.getsize(self.charVideo[0])[1]
        #上面每3600個字符爲一個畫面,在列表中爲一行,30*120
        col,row=self.size
        #畫布參數設置
        img_height=line_height*(row+1)
        img_width=le_width*(col+1)
        #處理過的圖片保存位置,以視頻的名字命名文件夾
        catalog=self.path.split('.')[0]
        self.mkdir(catalog)
        #繪製圖片
        for p_id,char in enumerate(self.charVideo):
            div_char=self.str_slice(char,row,col)
            im = Image.new("RGB", (img_width, img_height), (255, 255, 255))
            dr = ImageDraw.Draw(im)
            for i,str in enumerate(div_char):
                for j in range(len(str)):
                    dr.text((5+j*le_width,5+i*line_height), str[j],
                            font=font, fill="#000000")
            im.save(catalog+r'\pic_{}.jpg'.format(p_id))


    def mkdir(self,catalog):
        isExists = os.path.exists(catalog)
        if not isExists:
            os.makedirs(catalog)
            return True
        else:
            return False

    def jpg2video(self):
        catalog=self.path.split('.')[0]
        #利用os.listdir()讀取文件有亂序的問題,需要對其進行排序
        images=os.listdir(catalog)
        images.sort(key=lambda x: int(re.findall(r'\d+', x)[0]))
        im=Image.open(catalog+'\\'+images[0])
        fourcc = VideoWriter_fourcc(*"MJPG")
        vw = VideoWriter('stringvideo' + '.avi', fourcc,self.FPS, im.size)
        for image in images:
            frame=cv2.imread(catalog+'\\'+image)
            vw.write(frame)
        vw.release()

    #遞歸刪除目錄
    def remove_dir(self,path):
        if os.path.exists(path):
            if os.path.isdir(path):
                dirs = os.listdir(path)
                for d in dirs:
                    if os.path.isdir(path+'/'+d):
                        self.remove_dir(path+'/'+d)
                    elif os.path.isfile(path+'/'+d):
                        os.remove(path+'/'+d)
                os.rmdir(path)
                return
            elif os.path.isfile(path):
                os.remove(path)
            return


    def gen_video(self):
        self.str2fig()
        print('str2fig Finished')
        print('start generate video.wait...')
        self.jpg2video()
        print('generate video Finished')
        print('start delete catalog.wait...')
        self.remove_dir(self.path.split('.')[0])
        print('delete cache catalog finished')

if __name__ == '__main__':
    #修改你索要輸入的文件的路徑
    v2char = V2Char(r'D:\pycharm_work\img_relad\video.mp4')
    v2char.gen_video()

代碼中值得注意的事項

1)、cv2.resize():第一個參數表示畫面信息,第二個參數limitSize表示要對畫面進行的縮放大小,其格式應該爲(width,height),即像素的列和行

2)、視頻幀率的獲取:利用以下代碼得到,cap.get()的參見參數參考文章——OpenCV VideoCapture.get()參數詳解

cap = cv2.VideoCapture(filepath)
self.FPS=cap.get(cv2.CAP_PROP_FPS)

3)、在將視頻處理爲字符串的表示之後,要再將字符串寫爲圖片保存,由於所用的字符有不同的長度,因此直接整段寫入的話會有圖片混亂的問題,因此用max_width()函數統一字符在圖片上的表示長度,在寫入圖片時,按照像素點進行處理。即代碼的str2fig()的三個for循環片段。因此在生成畫面時比較慢。

4)、os.listdir()函數用來讀取保存的圖片,由於其返回的可能是亂序的,因此要對其中的內容進行排序。比如,本次處理的視頻共有1400餘幀,用os.listdir()讀取後返回的文件名列表大概如下:

['pic_0.jpg', 'pic_1.jpg', 'pic_10.jpg', 'pic_100.jpg', 'pic_1000.jpg', 'pic_1001.jpg', 'pic_1002.jpg', 'pic_1003.jpg', 'pic_1004.jpg', 'pic_1005.jpg', 'pic_1006.jpg', 'pic_1007.jpg', 'pic_1008.jpg', ....'pic_999.jpg']

直接讀取圖片生成視頻會有明顯的視頻時間線不同步問題,利用re模塊處理所讀到的文件名,提取其中的數字,利用sort函數進行排序,得到有序的文件名列表,其中re.findall()函數返回值爲一個列表,需要提取其中的內容,並用int()轉爲數字。

5)、cv2.imread()函數要求輸入文件的完整路徑,否則無法讀取圖片,遇到視頻生成失敗時,檢查frame是否爲None,是的話可能是文件名輸入出錯等情況導致

不足之處

在函數str2fig()中,將畫布和畫筆的打開都放在了for循環中,這導致每次打開處理一個畫面就要打開一次畫筆,因此運行速度比較慢,本來的計劃是將畫筆畫布放在循環之外,這樣就可以不用每次都打開了。但在實際運行中發現,這樣的運行方式導致,後來生成的畫面中有前一幀的信息沒有擦除,需要在保存畫面後,將畫布清空,但在網上沒有找到清除畫布內容的方法,因此將其放到了循環內。

im = Image.new("RGB", (img_width, img_height), (255, 255, 255))
dr = ImageDraw.Draw(im)
for p_id, char in enumerate(self.charVideo):
    div_char = self.str_slice(char, row, col)
    for i, str in enumerate(div_char):
        for j in range(len(str)):
            dr.text((5 + j * le_width, 5 + i * line_height), str[j],
                    font=font, fill="#000000")
    im.save(catalog + r'\pic_{}.jpg'.format(p_id))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章