ハードコピー・画像ファイル内を指定のキーワードで検索して機械的にマスクする、ぞ！　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](https://qiita-image-store.s3.amazonaws.com/0/194769/631d9b25-f753-6a73-20c5-c81b0ea3da70.png)

■ 使用後　[raspberrypiをキーワードに消しこみ]
![masked.png](https://qiita-image-store.s3.amazonaws.com/0/194769/40cf6b88-ed9f-394c-8ada-a6a3dc0a85da.png)

■ 使用前
![65.png](https://qiita-image-store.s3.amazonaws.com/0/194769/7429c961-4280-2f68-b856-a64602a6c968.png)

■ 使用後　[Qtをキーワードに消しこみ]
![masked.png](https://qiita-image-store.s3.amazonaws.com/0/194769/e47f567e-7c66-1dc7-f1e3-c36eaa25abca.png)

■ 使用前
![24.png](https://qiita-image-store.s3.amazonaws.com/0/194769/47727453-4fb1-b72b-f524-000a2aa371ba.png)

■ 使用後　[Arduinoをキーワードに消しこみ]
![masked.png](https://qiita-image-store.s3.amazonaws.com/0/194769/a5fecd19-f574-a55d-82cc-71fead7f8f46.png)

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

#◆ ツールの仕様

<details><summary>**AutomaticImageMask.py**</summary><div>

```python: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()
```

</div></details>

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

python3 AutomaticImageMask.py xxx sample.png raspberrypi
```

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