画像内個人情報を黒消ししてみた(名前編)

## はじめに

画像内の個人情報を黒消ししてみます(Pythonで実装)。
画像内の人名や地名を見つけ、黒塗りするシンプルなものです。

※ 画像内個人情報を黒消ししてみた(電話番号編)は[こちら](https://qiita.com/mingchun_zhao/items/3bbcafbd1026380b47eb)

## 黒消し結果から

|処理前の画像|処理後の画像|
|:--:|:--:|
|![input.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/914bfe6f-26ea-b0aa-85c5-ec4387f98f6d.png)|![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/be13c7e5-3138-3e7b-dc59-813cc869bfc2.png)|

## 使用したツール

- Tesseract
    - さまざまなオペレーティングシステム上で動作する光学式文字認識(OCR)エンジン
    - Apache License下でリリースされたフリーソフトウェア
- OpenCV
    - 画像処理・画像解析および機械学習等の機能を持つライブラリ
    - C/C++、Java、Python、MATLABで使用可能
    - POSIX準拠のUnix系OS、Linux、Windows、Android、iOS等をサポート
- GiNZA
    - オープンソースの日本語自然言語処理ライブラリ
    - 高速かつ高精度な日本語の解析処理/依存構造(係り受け)解析/固有表現抽出
    - 高度なNLP技術を国際化されたフレームワーク上で利用可能
- spaCy
    - 高度な自然言語処理を行うため、PythonとCythonで書かれたOSSライブラリ
    - 多言語解析処理と固有表現抽出のため、統計的ニューラルネットワークモデルや字句解析モデルを提供

## 開発環境

- CPU: Intel(R) Core(TM) 3.30GHz(4コア8スレッド)
- メモリ: 16 GB
- OS: Windows 10
- Python 3.10

## 事前準備

### Tesserractをインストール

- Tesseract OCR 64bit版をダウンロード
[tesseract-ocr-w64-setup-v5.1.0.20220510.exe(64 bit)](https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-w64-setup-v5.1.0.20220510.exe)

- インストール時に、以下日本語オプションにチェックを入れる
    - Additional script data (download)
        - Japanese script
        - Japanese vertical script
    - Additional language data (download)
        - Japanese
        - Japanese (vertical)

### Tesseract OCRのPythonラッパー`pytesseract`をインストール

```
pip3 install pytesseract
```

### OpenCVのPythonラッパー`opencv-python`をインストール

```
pip3 install opencv-python
```

### GiNZAの日本語モデル`ja_ginza`をインストール

```
pip3 install ja-ginza
```

###

## サンプルコード

- 実行方法
    - `python redact.py`
- 実行結果
    - 入力画像: カレントディレクトリのinput.png
    - 標準出力: 抽出されたテキストと固有名詞の判定結果
    - 出力画像: カレントディレクトリにoutput.pngが生成される
- ※ tesseract実行ファイルパスを通す必要あり

```python:redact.py
import pytesseract as pt
import cv2
import re
import spacy

# tessdataディレクトリを指定
tessdata_dir_config = '--tessdata-dir "C:/Program Files/Tesseract-OCR/tessdata"'
# tesseract実行ファイルを指定
pt.pytesseract.tesseract_cmd = 'C:/Program Files/Tesseract-OCR/tesseract.exe'

# spaCyからGiNZAのja_ginzaモデルをロード
nlp = spacy.load('ja_ginza')

# 固有名詞の判定
def is_named_entity(text: str):
    # テキストをDocクラスに変換
    doc = nlp(text)
    # DocクラスはTokenクラスのイテレーター
    for token in doc:
        # 品詞タグから固有名詞の単語を抽出
        if token.pos_ in ['PROPN']:
            # token.textは日本語形態素の単位
            print(token.text, token.tag_, type(token))
            return True
    return False

# 黒消し処理
def redact(img: cv2.Mat, details: dict, idx: int):
    # 黒消し範囲
    (x, y, w, h) = (details['left'][idx], details['top'][idx],
                    details['width'][idx], details['height'][idx])
    start = (x, y)
    end = (x + w, y + h)
    color = (0, 0, 0)
    thickness = -1
    # 黒消し実施
    return cv2.rectangle(img, start, end, color, thickness)

def redact_image(infile: str, keys: list = None):
    # opencvで画像を読み込む
    img = cv2.imread(infile)
    # 画像をバイナリに変換
    bin_img = cv2.bitwise_not(img)
    # 画像からテキスト抽出(日本語と英語を指定、psm=6: 単一テキストブロックとみなす)
    details = pt.image_to_data(bin_img, output_type=pt.Output.DICT,
                               lang='jpn+eng', config=r'--psm 6')
    # マッチした回数
    matches = 0
    for idx in range(len(details['text'])):
        # 抽出テキストの信頼度スコアが30%以上のみ使用
        if float(details['conf'][idx]) > 30.0:
            print(details['text'][idx])
            if is_named_entity(details['text'][idx]):
                # 固有名詞を黒消し
                matches += 1
                img = redact(img, details, idx)
            elif keys:
                # 黒消しキーワードで検索
                for key in keys:
                    results = re.findall(key, details['text'][idx], re.IGNORECASE)
                    for result in results:
                        matches += 1
                        img = redact(img, details, idx)
    if matches > 0:
        redacted_img = img.copy()
        # 画像を保存
        cv2.imwrite("output.png", redacted_img)  

if __name__ == '__main__':
    # 画像内の固有名詞を黒消し
    redact_image(infile='input.png')
```

- 実行結果
```
>python redact.py
T
123-4567
東京
東京 名詞-固有名詞-地名-一般 <class 'spacy.tokens.token.Token'>
都
東京
東京 名詞-固有名詞-地名-一般 <class 'spacy.tokens.token.Token'>
市
東京
東京 名詞-固有名詞-地名-一般 <class 'spacy.tokens.token.Token'>
町
1-2-3
桃太郎
桃太郎 名詞-固有名詞-人名-名 <class 'spacy.tokens.token.Token'>
様
電話
番号
000-1111-2222
```
```
>dir output.png
```

## おわりに

画像内の名前など固有名詞を、黒消ししてみました。
いろいろ改善は必要ですが、ひとまず。