memo : 文字画像を透過して貼り付ける

Object Detection として記入箇所を推定する場合、データの準備が大きな問題となります。たまたま電子タブレットでサインする機会があって気が付いたのですが、記入文字を背景は透明にして帳票画像に貼り付ければ学習データがいくらでも作れます。当然Overfittingには注意が必要ですが、少ないデータでAugmentationするよりよほど効果的な気がします。
やっとgithubに乗せるほどでもない良い感じの量のソースを乗せる時が来ました。(リファクタリングもテストもしていませんし、電車で書いたので汚いかと(どこで書いても。。。))



透過して貼り付け

基本的に白地の帳票に黒字で記入される前提なのでグレースケール前提のソースですが、カラーでやりたい場合は一部変更すれば使えると思います。先に実行結果を張るとこんな感じです。

左上、左下、真ん中やや上に同じ文字が張り付けられていて、尚且つ背景を消していないことがわかると思います。ソースは下記です。

def overlapping_img(base_img, overlap_img, x=0, y=0):
    """
    画像を透過して貼り付ける.

    Parameters
    ----------
    base_img : numpy.ndarray
        貼り付け先となる画像.
    overlap_img : numpy.ndarray
        貼り付けたい画像.
    x : int
        貼り付けるx座標(左上).
    y : int
        貼り付けるy座標(左上).

    Returns
    -------
    dst : numpy.ndarray
        overlap_imgを張り付けた画像.(float64で返る)

    """
    dst = base_img.copy()
    height, width = overlap_img.shape[0:2]
    base_height, base_width = base_img.shape[0:2]
    assert y + height < base_height, 'y方向にはみ出る'
    assert x + width < base_width, 'x方向にはみ出る'

    # 白黒反転してマスクとする
    mask = 255 - cv2.cvtColor(overlap_img, cv2.COLOR_RGB2GRAY)
    # 3色分に増やす。
    mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    mask[mask > 200] = 255
    mask[mask <= 200] = 0

    # アルファチャンネルがあれば削除
    overlap_img = overlap_img[:,:,:3]
    # float64で0~1へ
    mask = mask / 255
    overlap_img = overlap_img / 255
    dst = dst / 255

    # 透過率に応じて元の画像を暗くする。
    dst[y:y+height, x:x+width] *= 1 - mask
    # 貼り付ける方の画像に透過率をかけて加算。
    dst[y:y+height, x:x+width] += overlap_img * mask
    dst = dst * 255
    return dst

印字されているところには貼り付けしないようにする

既に文字が印字されているところに手書き文字を張り付けたところで実際に読み取る帳票とかけ離れてしまいます。(ただ、記入文字が枠線と被っている帳票はよく見ます。)
なので貼り付け予定の領域で画素値の平均をとって、一定より明るければあんまり文字が書かれていないということにします。

def check_target_area(clipped_img, threshold=240):
    """
    画像が大体白かどうか判定する.

    Parameters
    ----------
    clipped_img : numpy.ndarray
        画像がほぼ空白になっているか確認したい画像.
    threshold : int
        大体白だからOKとする閾値.

    Returns
    -------
    bool : bool
        画素の平均が指定した閾値より高ければ(大体白だったら)True.

    """
    if len(clipped_img.shape) > 2:
        clipped_img = cv2.cvtColor(clipped_img, cv2.COLOR_RGB2GRAY)
    if float(clipped_img.mean()) > threshold:
        return True
    return False

先ほど張り付けた画像でいうと、真ん中よりやや上のほとんど印字と被っている領域ではFalseが返ってきます。
あとは記入文字画像の種類を増やしてランダムに座標指定して貼り付けていけば学習データセットが自動的に作成できそうです。

余談 SynthTextについて

ちなみにですが、画像に文字を貼り付けて情景文字検出・認識の学習データとする試みは数年前からあって、SynthTextという手法が提案されています。ソースも公開されています。情景文字検出・認識に取り組んでいると必ず見るものですが、私が知る限り日本語対応はイマイチです。(なくはないけど、不思議な日本語が表示される)。また、あちらは文字を張り付けられる領域かどうかをセグメンテーションで判断するので、枠線と被っちゃう文字とかちょっとはみ出た文字とかが作りにくいのが難点かなと思っています。
SynthTextやICDARのデータセットで学習させて、FineTuneとしてこの記事手法で作成したデータセットで学習させるのが良いかもしれません。
久しぶりにDeepLearningさせられそうで楽しみです。


カテゴリー:DeepLearning,Object Detection,OpenCV,画像処理

Output不足なエンジニア

統計が好きになれず、機械学習やったら必然的に統計が必要になるだろうと思ったら想像以上に機械学習にハマる。数学は芸術なので商売にするつもりはないけど、DeepLearningは数学じゃないし商売にしたいと思っているところ。画像処理がメイン。