python自動製作gif並新增文字

lyrichu發表於2018-05-29

引言

    最近租的房子快到期了,哎,因為去年是第一次找房子租,結果遇到了一個東北黑中介,押一付三,房子有啥問題,燈壞了,下水道堵了,原來籤合同的時候說的客氣,說是馬上就會上門解決,結果實際上我每次找他,都是各種推脫,最後不了了之。全部是我自己想辦法解決的。現在又打電話催著我要搬走,押金也不打算退給我了。真的是rlgl。我反對地域黑,但是以後東北的中介一生黑。(再次宣告,不是地域黑,我也有認識的人很好的東北的朋友。)今天我又看到了有錢可以為所欲為的經典gif,所以我心血來潮想做一個自己的東北黑中介版的為所欲為gif。本來我想找一個沒有文字的gif,然後找一個軟體新增文字,然後合成gif的。結果在網上搜尋了一下,沒有找到合適的原版gif,只找到了一個有文字的gif。到是有軟體可以手動新增文字合成gif的,但是如果要首先把原來的文字去除,然後再一點點新增,手動要做大量的工作,很是麻煩。我再嘗試了半個小時還沒弄好就放棄了(但是我想應該有比較簡便的方法,只是我懶得再自己去找了)。最後我想既然作為一個程式設計師,能不能通過程式碼去實現這個功能呢?當然可以啦。我最熟悉的是python,自然優先考慮上python了。之前接觸過PIL這個庫,我記得它有給圖片新增文字的功能,而且最近不是才剛剛看了moviepy這個庫,它可以很容易合成gif,把這兩個結合到一起不就很容易實現我想要的目標了嗎?一不做二不休,開搞!

1. 處理gif以及基本思路

    我找到的原始的gif如下圖1所示:

o_1cem1dj0ii4s16a22q12hn287a.gif-w.jpg

圖1 原始”為所欲為”gif(來自微博)

如果你的電腦已經安裝了ffmpeg的話,那麼將gif分割成單幀的序列圖片是非常容易的,執行下面的命令:

ffmpeg -i 為所欲為.gif %d.jpg

就可以將為所欲為.gif分割成1.jpg,2.jpg,…。當然你也可以直接使用python的PIL庫來處理gif,使用如下的程式碼即可:

from PIL import Image,ImageSequence
gif = Image.open("為所欲為.gif")
for i,frame in enumerate(ImageSequence.Iterator(gif),1):
    frame.save("%d.jpg" % i)

或者你也可以使用之前介紹過的moviepy庫(具體可以參考我之前的一篇部落格),如果你對opencv比較熟悉的話,自然也可以使用opencv來處理。這裡就不詳細說了。
    得到gif的幀序列影像之後,簡單來說一下後面處理的思路。因為原來的gif有些幀是有文字的,我們要想新增自己的文字,就必須要把原來的文字去除掉,然後換成自己的。所以後面基本的思路就是:

  • 找出那些有文字出現的幀,把文字去除;
  • 原來的文字去除之後,替換成自己的合適的文字,形成新的幀影像;
  • 將新的幀影像合成為新的gif(這裡使用moviepy合成)。

2. 實現程式碼以及簡要解析

具體實現程式碼如下:

# -*- coding:utf-8 -*-
import matplotlib.pyplot as plt 
from PIL import Image,ImageDraw,ImageFont
from scipy.misc import imread,imsave,imresize
import numpy as np 
import os
from glob import glob
from moviepy.editor import ImageSequenceClip
# 消除文字的圖片序號
modify_img_index = list(range(11,14))+list(range(27,37)) + list(range(44,61)) + list(range(62,81)) 
                    + list(range(82,94)) + list(range(97,106)) + list(range(112,132)) + list(range(146,168))
text_info = [(`好啊`,(120,140))]*(14-11) + 
            [(`就算你今天找人來抗議`,(40,140))]*(37-27)  + 
            [(`就算你的理由再完美`,(50,140))] *(61-44)  + 
            [(`我想不退錢就不退錢`,(50,140))] *(81-62) + 
            [(`畢竟我是東北黑中介`,(50,140))] *(94-82) + 
            [(`東北黑中介了不起啊`,(50,140))] *(106-97) + 
            [(`sorry,東北黑中介真的可以為所欲為`,(5,140))]*(132-112) + 
            [(`以後讓他天天鬧`,(60,140))]*(158-146) + 
            [(`天天鬧`,(110,140))]*(169-158)
index_to_text = dict(zip(modify_img_index,text_info))
# 儲存圖片資料夾
save_dir = "output"
if not os.path.exists(save_dir):
    os.mkdir(save_dir)
new_color = np.array([50,50,50]) # 消除文字之後的顏色
fontsize = 18
fontcolor = (0,255,255)
fps = 10
resize_scale = 1.5 # 新圖的寬和高是原來的多少倍
# 消除文字的x,y邊界座標
y_begin = 10
y_end = 280
x_begin = 140
x_end = 160
# 全部檔案列表(無序)
img_names = glob("*.jpg")
#print(img_names)
def modify():
    # 遍歷每一個影像
    for img_name in img_names:
        img = imread(img_name)
        h,w = img.shape[0],img.shape[1]
        #plt.imshow(img)
        img_new = np.copy(img)
        index = int(img_name.split(".")[0])
        # 如果圖片有文字,需要修改
        if index in modify_img_index:
            # 消除文字
            img_new[x_begin:x_end,y_begin:y_end] = new_color
            text,pos = index_to_text[index]
            img_new = drawtext(Image.fromarray(img_new),text,pos,fontsize,fontcolor)
            #儲存新的圖片
            # imsave(os.path.join(save_dir,img_name),img_new)
            if resize_scale != 1:
                # resize
                img_new = img_new.resize((int(w*resize_scale),int(h*resize_scale)))
                img_new.save(os.path.join(save_dir,img_name))
            print("Modify and save %s!" % img_name)
        else:
            # 沒有文字,不需要修改,直接儲存原來的圖片
            if resize_scale != 1:
                img = imresize(img,resize_scale)
                imsave(os.path.join(save_dir,img_name),img)
            print("Save %s!" % img_name)
def drawtext(img,text,pos,fontsize,fontcolor):
    ```
    draw text for img
    @param img:image to draw text
    @param text:text to draw
    @param pos:where to draw the text((x,y))
    @param fontsize: font size
    @param fontcolor: font color
    ```
    font = ImageFont.truetype("simsun.ttc",fontsize)
    draw = ImageDraw.Draw(img)
    draw.text(pos,text,fontcolor,font = font)
    # plt.imshow(img)
    # plt.show()
    return img 
def test_drawtext():
    img_name = "4.jpg"
    img = Image.open(img_name)
    #text = "好啊"
    text = "東北黑中介了不起啊"
    pos = (40,140)
    fontsize = 20
    fontcolor = (0,255,255)
    drawtext(img,text,pos,fontsize,fontcolor)
# 利用影像序列生成gif
def generate_gif(img_names,save_path,fps = 10):
    # img_names:list of image names
    clip = ImageSequenceClip(img_names,fps = fps)
    clip.write_gif(save_path)
    print("Write %s successfully!" % save_path)

# 主函式
def main():
    modify()
    save_gif_path = os.path.join(save_dir,"東北黑中介.gif")
    img_names = sorted(glob(os.path.join(save_dir,"*.jpg")),key = lambda x: int(x.split(".")[0].split("/")[1]))
    generate_gif(img_names,save_gif_path,fps)

#test_drawtext()
if __name__ == `__main__`:
    main()

上面的程式碼思路其實還是比較清楚的。主要麻煩的地方就在於,我們沒法自動確認哪些幀出現了文字,所以在這裡我採用手動觀察影像(有167張圖)的方式,確定了出現文字的幀序列區間;然後對於這些幀序列區間,其文字替換成我自己的文字。還有一個問題就是,我要消除文字,就要知道文字的位置,其實這個可以歸結為一個目標檢測的問題,或者說是一個OCR問題(OCR的第一步,定位文字),但是OCR做起來還是很麻煩的,這裡暫時不說,後面有空我會專門討論OCR問題。這裡比較好的地方在於,所有的文字出現的高度位置幾乎是一樣的,只是寬度方向的位置不一樣(有的字多,有的字少),這裡我還是採用手動觀察標註的辦法,可以藉助於matplotlib顯示影像,用滑鼠點選影像的某一點,它會顯示該點的(x,y)座標和畫素值,這樣我們就可以手動確定要消除的文字的位置,以及消除文字要填充的顏色了。按照這個思路,我們可以得到一些消除原來文字,新增新文字之後的影像如下圖2所示:

o_1cem488ad1tkk3pe16pb7jn10lva.jpg-w.jpg

圖2 原始幀和消除文字後新增新文字的幀(上是原始幀,下是處理之後的新幀)

    最終合成的新的gif如下圖3所示:

o_1cem4hohk1nkc1r7f1rkt37h1pu3a.gif-w.jp

圖3 最終合成的gif

3. 小結

    本文主要利用python的PIL庫(用於處理影像,新增文字等)和moviepy(用於生成gif)自動生成了gif並新增了文字。但是還有一些問題,比如文字自動定位並識別沒有解決。這些問題留到後面介紹OCR技術的時候再和大家介紹。如果只是簡單地嘗試合成gif,大家不妨自己動手試一試。

全文完,感謝閱讀!

熱愛程式設計,熱愛機器學習!
github:http://www.github.com/Lyrichu
github blog:http://Lyrichu.github.io
個人部落格站點:http://www.movieb2b.com(不再維護)


相關文章