PythonでOCR

Netflixの字幕がダウンロードできると知ったので実行してみたが、 日本語字幕が画像だった※ため、PythonOCRにかけて文字列に変換した。結果は微妙。

※ 後でエピソード単位だと日本語字幕を文字列で取得できることに気付いた。。。作品単位だと画像になる模様。

Install

OSはWindows10。ORCには以下の2つが必要。

  1. pyorc-0.5.3
  2. tesseract-ocr-setup-3.05.02-20180621

1はpip。 2は、Tesseract at UB Mannheimから、 tesseract-ocr-setup-3.05.02-20180621.exeをダウンロードして実行。 最初3.02を使っていたが、精度が低かったため、3.05を選択。 4.00が出てるので、そちらの方がいいかも。 exe実行時、OCRにかける言語を選択するオプションでは日本語を選択すること。

Code

以下のコードを実行すると、字幕画像を文字列にしてファイルに出力してくれる。 OCRで解析したい画像字幕のフォルダはimg_dirに指定する。 文字列にした字幕ファイルもここに保存される。 なお、コードはgithubにも上げてます。

# coding: utf-8
from PIL import Image
import sys
import pyocr
import pyocr.builders
import os
from tqdm import trange

# Set Netflix subtitle image directory.
img_dir = '../data/planetes/PLANETES.S01E02.WEBRip.Netflix/'

# Get a tool.
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("No OCR tool found")
    sys.exit(1)
tool = tools[0]
print("Will use tool '%s'" % (tool.get_name()))

langs = tool.get_available_languages()
print("Available languages: %s" % ", ".join(langs))

# Image to string.
txts = []
img_names = [f for f in os.listdir(img_dir) if f.split('.')[-1].lower() in ('png')]
img_names = sorted(img_names)
print(img_names)
for i in trange(len(img_names), desc='img 2 str'):
    txt = tool.image_to_string(
        Image.open(img_dir+img_names[i]),
        lang='jpn',
        builder=pyocr.builders.TextBuilder()
    )
    print(txt)
    txts.append(txt)

# Save the subtitle.
subs = open(img_dir+'subs.txt', 'w')
subs.write('\n'.join(txts))
subs.close()

Result

複雑な漢字が苦手らしい。縁取りがあるからな気もする。 また、他の画像でも試したが、画像の大きさとも精度が相関している模様。

  • 画像
    f:id:Shoto:20190106181405p:plain

  • 文字列
    ステ一 ンヨノ建L ロ時に出た廃某 武物

  • 画像
    f:id:Shoto:20190106181419p:plain

  • 文字列
    これなんか
    かなり当たるようですよ

References

Inverse Trasnsformation of multihot matrix with DataFrame

When dealing with attributes in classification problem etc, there are some data saved in onehot or multihot matrix despite attributes are too many. Such data is too large to handle, I want to get only the target attributes and hold the data with smaller size. It is inverse transformation of multihot matrix and I processed it with DataFrame.

First prepare the data. Attributes (a - d) are 1 in each row.

lis = [
    ['row_0',1,0,0,0],
    ['row_1',1,1,0,0],
    ['row_2',0,0,0,1]
]
df = pd.DataFrame(lis, columns=['name', 'a', 'b', 'c', 'd'])
print(df)
    name  a  b  c  d
0  row_0  1  0  0  0
1  row_1  1  1  0  0
2  row_2  0  0  0  1

Get attributes in each row and insert them in one column. I am using a for statement, so I want to improve it.

lis = []
for i in range(len(df)):
    active_columns = df[df==1].ix[i].dropna().index.tolist()
    lis.append('_'.join(active_columns))
df['attr'] = lis
df = df.drop(['a', 'b', 'c', 'd'], axis=1)
print(df)
    name attr
0  row_0    a
1  row_1  a_b
2  row_2    d

Saving a image has no axis and no margin

When saving an image processed with matplotlib, the image axes and margins are not necessary. So I surveyed how to remove them and summarized them.

Code

from scipy.misc import imread
import matplotlib.pyplot as plt

img = imread('IMG_1382.JPG')
plt.imshow(img / 255.)
plt.axis('off')  # 1
plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)  # 2
plt.savefig('IMG_1382_ex.JPG', bbox_inches='tight', pad_inches=0)  # 3

Points are the following.

  1. remove axes
  2. remove labels
  3. remove margin

Executable code is here. When $ test.py is input at terminal, the next image has no axis and no mergin is saved at the same directory.

f:id:Shoto:20180916010309j:plain

P.S. It works well on Windows, but remains small frame on Ubuntu. I don't know why.

References

GitHubリポジトリーの一部のフォルダーをダウンロードする

DeepLabV3+がTensorFlowで利用できるようになった。 とりあえず、DeepLabV3+関連のファイルだけ欲しいだが、GitHubにはそうゆう機能がないので、 Chrome拡張機能GitZipを使って、DeepLabV3+のフォルダーだけをダウンロードしてみた。

手順

  1. ChromeブラウザからGitZipをダウンロードしてインストール
  2. Chromeブラウザを再起動
  3. ダウンロードしたいリポジトリーのフォルダーの1つ上の階層を開く
  4. 対象フォルダーの余白部分をダブルクリック
  5. 左下の下向きの矢印をクリックしてフォルダーをダウンロード

実例

DeepLabV3+の場合、 research/deeplabフォルダーに入っているので、まずresearchフォルダーに移動する。 次にdeeplabフォルダーのコメントの右側の余白あたりをダブルクリックすると、 deeplabのフォルダーアイコンの左側にチェックが付く。 すると右下に下向きの矢印が現れるので、それをクリックするとダウンロードが開始する。

f:id:Shoto:20180505024253p:plain

参考文献

Download a single folder or directory from a GitHub repo

動画からの音声抽出と動画への音声結合

testpy.hatenablog.com

上記の記事で、動画を左右反転させて、新たな動画を生成した。 生成した動画には音声がないため、元の動画から音声を抽出して、結合してみた。

コード

# coding: utf-8
import sys
import cv2
import moviepy.editor as mp

class Test:
    def __init__(self):
        # Set video names.
        self.input_video = sys.argv[1]
        self.output_video = sys.argv[2]


    def main(self):
        self.set_audio()


    def set_audio(self):
        # Extract audio from input video.
        clip_input = mp.VideoFileClip(self.input_video).subclip()
        clip_input.audio.write_audiofile('audio.mp3')

        # Add audio to output video.
        clip_output = mp.VideoFileClip(self.output_video).subclip()
        clip_output.write_videofile(self.output_video.replace('.avi', '.mp4'), audio='audio.mp3')


if __name__ == "__main__":
    Test().main()

set_audio()が本体。前半2行で音声の抽出と保存、後半の2行で音声の結合を行っている。 結合する際、動画の拡張子がmp4じゃないとコーデックエラーが出る。 でも、mp4の方が容量が少ないので、それでいいかなと。

使い方

上記のコードをtest.pyで保存して、第一引数に入力動画名、第二引数に出力動画名(aviファイル)を指定する。

> python test.py input.MOV ouput.avi
[MoviePy] Writing audio in audio.mp3
100%|###################################################################################################################| 623/623 [00:00<00:00, 733.71it/s]
[MoviePy] Done.
[MoviePy] >>>> Building video output.mp4
[MoviePy] Writing video ouput.mp4
100%|####################################################################################################################| 846/846 [00:25<00:00, 33.65it/s]
[MoviePy] Done.
[MoviePy] >>>> Video ready: output.mp4

>

動画の生成と音声の結合を同時に実行

前回行った動画の生成と、今回行った音声の結合を同時に行ってみた。 音声を結合した動画がmp4なので、生成する動画もmp4(コーデックはMP4S)にしたのだが、警告が出て、どうも上手く音声付きの動画が生成されない。 この辺は後ほど対応したい。

OpenCV: FFMPEG: tag 0x5334504d/'MP4S' is not supported with codec id 13 and format 'mp4 / MP4 (MPEG-4 Part 14)'
OpenCV: FFMPEG: fallback to use tag 0x00000020/' ???'

同時に実行したコードを載せておく。 使い方は上に示したのと同じ。

# coding: utf-8
import sys
import cv2
import moviepy.editor as mp

class Test:
    def __init__(self):
        # Set video names.
        self.input_video = sys.argv[1]
        self.output_video = sys.argv[2]


    def main(self):
        self.make_video()
        self.set_audio()


    def make_video(self):
        # Get input video information.
        cap = cv2.VideoCapture(self.input_video)
        fps = int(cap.get(cv2.CAP_PROP_FPS))
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        # Set output video infomation.
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        #fourcc = cv2.VideoWriter_fourcc(*'MP4S')
        vw = cv2.VideoWriter(self.output_video, fourcc, fps, (width, height))  # Set the above information.

        # Make the output video.
        print('Making a video...')
        while(True):
            ret, img = cap.read()
            if ret == True:
                img_flip = cv2.flip(img, 1)  # Flip horizontal
                vw.write(img_flip)  # Add frame
            else:
                break

        # Post processing.
        cap.release()
        cv2.destroyAllWindows()


    def set_audio(self):
        # Extract audio from input video.
        clip_input = mp.VideoFileClip(self.input_video).subclip()
        clip_input.audio.write_audiofile('audio.mp3')

        # Add audio to output video.
        clip_output = mp.VideoFileClip(self.output_video).subclip()
        clip_output.write_videofile(self.output_video.replace('.avi', '.mp4'), audio='audio.mp3')



if __name__ == "__main__":
    Test().main()

参考文献

OpenCVで動画を作成する

OpenCVで動画を作成できる。 動画を左右反転させて、新たな動画を作成してみた。

コード

# coding: utf-8
import sys
import cv2

class VideoMaker:
    def __init__(self):
        pass


    def main(self):
        # Set video names.
        input_video = sys.argv[1]
        output_video = input_video.replace('.MOV', '_flip.avi')

        # Get input video information.
        cap = cv2.VideoCapture(input_video)
        fps = int(cap.get(cv2.CAP_PROP_FPS))
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        # Set output video infomation.
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        vw = cv2.VideoWriter(output_video, fourcc, fps, (width, height))  # Set the above information.

        # Make the output video.
        print('Making a video...')
        while(True):
            ret, img = cap.read()
            if ret == True:
                img_flip = cv2.flip(img, 1)  # Flip horizontal
                vw.write(img_flip)  # Add frame
            else:
                break

        # Post processing.
        cap.release()
        cv2.destroyAllWindows()


if __name__ == "__main__":
    VideoMaker().main()

解説

処理の大まかな流れは以下の通り。

  1. 入力動画の情報をゲット
  2. 出力動画の情報をセット
  3. 出力動画の作成・保存

FPSや画素数などを入出力で一致させている。 参考文献によると、コーデックはXVIDがおすすめとのこと。 MJPGX264も指定できるが、サイズが大きすぎたり小さすぎたりするらしい。 試してないけど。 vw.write(img_flip)で処理した画像を追加すれば、動画の作成と保存を同時に行ってくれる。 特に保存の必要はない。

使い方

video_maker.pyにコードを記載している。 コードと同じ場所に保存している動画(MOVファイル)を引数に指定すると、左右反転させた動画が作成される。

> python video_maker.py 2018_spring.MOV
Making a video...
>

参考文献

膨張差分法とキャニー法による線画の比較

アニメや漫画を線画にする際、白を膨張させてグレースケールとの差分を取る方法(以下、膨張差分法と呼ぶ)が多く用いられている。 しかし、実写に膨張差分法を適用したところ、実写の描写の細かさが影響してノイズが残りやすいことが分かった。 そこで膨張差分法とは別に、キャニー法という一般的なエッジ検出を適用して線画を生成し、両者の比較を行った。

処理コード

膨張差分法による線画生成をimage_2_linedraw_4_anime()、キャニー法による線画生成をimage_2_linedraw_4_photo()に実装。 前者は前述や参考文献でも述べられている通り、白を膨張させてグレースケールとの差分を取っている。 後者はブラーをかけたあとキャニー法を適用している。 main()を実行すると、画像ディレクトリーに入っているすべての画像に対して、2種類の手法で線画を生成する。

# coding: utf-8
import sys
import os
import random
import numpy as np
import cv2
import matplotlib
import numpy as np
import matplotlib.pyplot as plt


class Main:
    def __init__(self):
        self.blur_size = (7,7)
        self.dilate_size = (3,3)


    def main(self):
        image_dir = '../data/images/'
        image_names = [f for f in os.listdir(image_dir) if f[-4:].lower()=='.jpg' or f[-4:].lower()=='.png']
        image_names = [f for f in image_names if f.find('_linedraw.jpg')==-1]

        for image_name in image_names:
            self.image_2_linedraw_4_anime(image_dir, image_name)
            self.image_2_linedraw_4_photo(image_dir, image_name)


    def image_2_linedraw_4_anime(self, image_dir, image_name):
        img = cv2.imread(image_dir+image_name, cv2.IMREAD_GRAYSCALE) # Gray
        img_dilate = cv2.dilate(img, np.ones(self.dilate_size), iterations=1)  # Dilation
        img = cv2.absdiff(img_dilate, img)  # diff
        img = cv2.bitwise_not(img)  # Black and white inversion

        cv2.imwrite(image_dir+image_name.replace('.jpg', '_anime_linedraw.jpg'), img)  # Save


    def image_2_linedraw_4_photo(self, image_dir, image_name):
        img = cv2.imread(image_dir+image_name)
        img = cv2.GaussianBlur(img, self.blur_size, 0)  # Blur
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # BGR to Gray

        img = cv2.Canny(img, threshold1=90, threshold2=110)  # Canny method
        img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)  # Gray to BGR
        img = cv2.bitwise_not(img)  # Black and white inversion

        cv2.imwrite(image_dir+image_name.replace('.jpg', '_photo_linedraw.jpg'), img)


if __name__ == "__main__":
    Main().main()

膨張差分法とキャニー法による線画作成の比較

左から元画像、膨張差分法を用いたimage_2_linedraw_4_anime()の結果、キャニー法を用いたimage_2_linedraw_4_photo()の結果。 画像が小さいのでクリックして拡大して見てください。

アニメから線画を生成

膨張差分法の最初の4枚は素晴らしいほど、上手く線画になっている。 PaintsChainerが上手く機能するのも納得の訓練データ生成処理と言える。 最後だけ漫画と実写の共存だが、膨張差分法はTシャツの漫画が上手くできてて、キャニー法は女性の上半身が上手くできてる。 両者を融合すると良い感じになると思う。

f:id:Shoto:20180318035809j:plainf:id:Shoto:20180318035811j:plainf:id:Shoto:20180318035814j:plain
f:id:Shoto:20180318035820j:plainf:id:Shoto:20180318035825j:plainf:id:Shoto:20180318035830j:plain
f:id:Shoto:20180318035741j:plainf:id:Shoto:20180318035744j:plainf:id:Shoto:20180318035748j:plain
f:id:Shoto:20180318035757j:plainf:id:Shoto:20180318035801j:plainf:id:Shoto:20180318035804j:plain
f:id:Shoto:20180318035706j:plainf:id:Shoto:20180318035713j:plainf:id:Shoto:20180318035719j:plain

写真から線画を生成

やはり膨張差分法だと若干ノイズが大きい感じ。 かと言ってキャニー法がさらに良いかと言うと微妙だけど。 一応、最初の4枚は実写から線画の生成が個人的に上手くいったと思ったものを載せている。 ただ、最後だけは膨張差分法の方が良い感じだと思う。ノーマン・ロックウェルっぽささえある。 背景が白くて、対象物の足と靴がシンプルだからかも知れない。 というか、実写でも前処理でシンプルにすると、膨張差分法の方が上手くいくのかも。 この辺は暇ができたらリサーチしたい。

f:id:Shoto:20180318035631j:plainf:id:Shoto:20180318035635j:plainf:id:Shoto:20180318035640j:plain
f:id:Shoto:20180318035648j:plainf:id:Shoto:20180318035652j:plainf:id:Shoto:20180318035656j:plain
f:id:Shoto:20180318035839j:plainf:id:Shoto:20180318035842j:plainf:id:Shoto:20180318035846j:plain
f:id:Shoto:20180318035850j:plainf:id:Shoto:20180318035854j:plainf:id:Shoto:20180318035858j:plain
f:id:Shoto:20180318035905j:plainf:id:Shoto:20180318035909j:plainf:id:Shoto:20180318035914j:plain

まとめ

実写を線画にする際は、膨張差分法だとノイズが多くて、キャニー法だとノイズが少なすぎる傾向にある。 なので、その中間地点の線画が出せると良い感じになると思う。

参考文献