Python
OpenCV
DeepLearning
GoogleCloudVisionAPI

ハードコピー・画像ファイル内を指定のキーワードで検索して機械的にマスクする、ぞ! by Google Cloud Vision API

◆ はじめに

Qiitaに記事を投稿する際にイメージを貼る機会が増えたが、公開したくないキーワードが大量に含まれている場合、イメージの貼り付け自体を尻込みしてしまっていた。
記事を読みやすく綺麗に整形したいのに、イメージの中に見せたくないキーワードが大量にちりばめられていると、手作業でマスク加工するのがめちゃくちゃ億劫だったから。

ということで、高価な加工ソフトを買うのもシャクだし、すでにごまんと世に有るとは思うが、Google検索が下手クソで見つけられなかったので、ハードコピーを任意のキーワードで機械的にマスクするツールをPython+Google Cloud Vision API+OpenCVで作成した。
誰の役に立つかわからないけど、一応Gitへコミットしておく。
https://github.com/PINTO0309/AutomaticImageMask.git

余談だが、こちらのソフト 「Tegaki」 がすごい。
手書き認識率 99.2% とか、まじかよ?
社に在籍するエンジニアもすごい人がゴロゴロいるっぽいし。
使いたい・・・金払うから、早く個人向けにAPI提供開始してくれないかな・・・
Tegaki https://www.tegaki.ai/

◆ 環境

  • Google Cloud Vision API ← Googleアカウントの取得とAPIキーの取得が必要
  • Python3
  • OpenCV3.x

◆ 実行イメージ

raspberrypi というキーワード周辺をザックリとマスクしたサンプル。
該当ワードの前後が余分に大きくマスクされちゃってるけど、自分は許容範囲内。 2018.05.01改善
ユーザIDやら何やらが漏れるよりはマシ。
取りこぼしが一切ないのは衝撃的。
そこらのOCRソフトより精度が高いのではなかろうか。
ベタ塗りなのでディープラーニングでも復元できませんぜ。たぶん。

えっと・・・できない・・・よね?
挑戦者求む。

Google Cloud Vision APIは、1,000APIコール/月 まで叩き放題だから、よほどカオスな使い方しない限りは実質完全無料。
画像内文字列のアノテーション結果(文字解析でヒットした単語と座標情報)は全てJSONで綺麗に返ってくるため、コレに限らず色々と活用はできそう。

いやぁ、Google神っすわ、まじで。

■ 使用前
sample.png

■ 使用後 [raspberrypiをキーワードに消しこみ]
masked.png

■ 使用前
65.png

■ 使用後 [Qtをキーワードに消しこみ]
masked.png

■ 使用前
24.png

■ 使用後 [Arduinoをキーワードに消しこみ]
masked.png

惜しむらくは顔写真にモザイク処理ができないこと、ぐらい。

◆ ツールの仕様

AutomaticImageMask.py
AutomaticImageMask.py
from base64 import b64encode
import json
import requests
import argparse
import cv2
import sys

ENDPOINT_URL = 'https://vision.googleapis.com/v1/images:annotate'

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Google Cloud Vision Api')
    parser.add_argument('api_key', help='api path')
    parser.add_argument('image', help='image path')
    parser.add_argument('mask_word', help='Character to be masked')
    args = parser.parse_args()

    img_requests = []
    with open(args.image, 'rb') as f:
        ctxt = b64encode(f.read()).decode()
        img_requests.append({
                'image': {'content': ctxt},
                'features': [{
                    'type': 'TEXT_DETECTION',
                    'maxResults': 10
                }]
        })

    response = requests.post(ENDPOINT_URL,
                             data=json.dumps({"requests": img_requests}).encode(),
                             params={'key': args.api_key},
                             headers={'Content-Type': 'application/json'})

    chars = [bytes(char, 'utf-8') for char in list(args.mask_word)]
    charslen = [1 if len(char) == 1 else 2 for char in chars]
    masklength = sum(charslen)

    out = cv2.imread(args.image)
    height, width = out.shape[:2]
    json_dict = response.json()['responses']

    for x in json_dict:
        for y in x:
            if y == 'textAnnotations':
                for z in x[y]:

                    x1 = 0
                    y1 = 0
                    x2 = 0
                    y2 = 0

                    try:
                        x1 = z['boundingPoly']['vertices'][0]['x']
                    except:
                        pass
                    try:
                        y1 = z['boundingPoly']['vertices'][0]['y']
                    except:
                        pass
                    try:
                        x2 = z['boundingPoly']['vertices'][2]['x']
                    except:
                        pass
                    try:
                        y2 = z['boundingPoly']['vertices'][2]['y']
                    except:
                        pass

                    description = z['description'].replace('\n','')

                    if args.mask_word in description:

                        descchars = [bytes(char, 'utf-8') for char in list(description)]
                        desccharslen = [1 if len(char) == 1 else 2 for char in descchars]
                        desccharslength = sum(desccharslen)
                        celwidth = round((x2 + 1 - x1) / desccharslength)

                        if celwidth > 0 and desccharslength <= 50:
                            s = description.find(args.mask_word)
                            x1 = x1 + sum(desccharslen[:s]) * celwidth
                            x2 = x1 + masklength * celwidth + 1
                            rectposition = ((x1, y1), (x2+1, y2+1))
                            #print(description, '\t\t', rectposition)

                            if ((x2 - x1) * (y2 - y1)) <= ((width * height) * 0.1):
                                cv2.rectangle(out, rectposition[0], rectposition[1], (105, 105, 105), -1)

    cv2.imshow('masked', out)
    cv2.imwrite('masked.png', out)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

マスク処理の実行方法
python3 AutomaticImageMask.py [Cloud Vision APIのキー] [マスク対象の画像ファイル名] [マスクを掛けたいキーワード]

python3 AutomaticImageMask.py xxx sample.png raspberrypi

上記を実行すると、masked.png というマスク加工済みの画像ファイルが生成される。
複数画像、複数キーワードをまとめてマスクしたい人は、引数のキーワードをマルチに設定できるように適当に加工してください。
ロジックは書きっぱなしで見なおしてもいないので、気に食わない箇所があったら、よろしく修正してやってください。