pythonで画面上の指定した範囲の文字を取得する

## 概要
ちょっと故あって画像から文章を取得する処理が作れないかといろいろ調べてみました。ひとまず形になったのでまとめてみました。
流れとしては以下のようになっています。
1. メインディスプレイのスクリーンショットを取得。
1. 取得したスクリーンショットから取り出したい部分を選択する。
1. 文字列をクリップボードに保存。またテキストファイルとして保存も同時に実施。

それぞれ以下のページを参考にして作成しました。
1. [OCR部分](https://qiita.com/ku_a_i/items/93fdbd75edacb34ec610)
1. [画像を範囲指定で取得する部分](https://qiita.com/8_hisakichi_8/items/47f6d37e6f425f29c8a8)

## Tesseractの導入
[こちら](https://hituji-ws.com/code/python/tesseract-ocr/)を参考にTesseractを導入します。また[OCR部分](https://qiita.com/ku_a_i/items/93fdbd75edacb34ec610)を参考にエンジンの変更を行います。この時インストール先のディレクトリを後続の処理で使うためメモしておきます。

## ライブラリの導入
以下のライブラリをインストールします。
* pyocr
* pyautogui
* Pillow

## 処理の流れ
処理としては以下のようになっています。
1. メインディスプレイのスクリーンショットを取得しtkinterのウィンドウ上に出力。
1. tkinterのウィンドウに描画されたところから必要なところを指定する。
1. 指定した範囲の画像を切り出すする。
1. 切り出した画像を保存する
1. 保存した画像をから文字列を取得する。
1. 半角スペースを削除した文字列をクリップボードに保存する。
1. 元の文字列と半角スペースを削除した文字列両方ともテキストファイルとして保存する。


ソースコード全体は以下になります。
プロパティファイル使ってTesseractのパスを読み込む形にしています。実際に手元で使う際は直接指定するなどしてください。


<details><summary>ソースはこちら</summary>

```python
import os
import tkinter
import pyocr
import pyautogui 
import configparser
from datetime import datetime
from PIL import Image, ImageEnhance, ImageTk


iniFile=configparser.ConfigParser()
iniFile.read('resources/appConfig.ini','UTF-8')
#Pah設定
TESSERACT_PATH = iniFile.get('env','TESSERACT_PATH')
TESSDATA_PATH = iniFile.get('env','TESSDATA_PATH')

os.environ['PATH'] += os.pathsep + TESSERACT_PATH
os.environ['TESSDATA_PREFIX'] = TESSDATA_PATH

#OCRエンジン取得
tools = pyocr.get_available_tools()
tool = tools[0]

#OCRの設定 ※tesseract_layout=6が精度には重要。デフォルトは3
builder = pyocr.builders.TextBuilder(tesseract_layout=6)

RESIZE_RETIO = 2 # 縮小倍率の規定

# ドラッグ開始した時のイベント
def start_point_get(event):
    global start_x, start_y # グローバル変数に書き込みを行なうため宣言

    canvas1.delete('rect1')  # すでに'rect1'タグの図形があれば削除

    # canvas1上に四角形を描画（rectangleは矩形の意味）
    canvas1.create_rectangle(event.x,
                             event.y,
                             event.x + 1,
                             event.y + 1,
                             outline='red',
                             tag='rect1')
    # グローバル変数に座標を格納
    start_x, start_y = event.x, event.y

# ドラッグ中のイベント
def rect_drawing(event):

    # ドラッグ中のマウスポインタが領域外に出た時の処理
    if event.x < 0:
        end_x = 0
    else:
        end_x = min(img_resized.width, event.x)
    if event.y < 0:
        end_y = 0
    else:
        end_y = min(img_resized.height, event.y)

    # 'rect1'タグの画像を再描画
    canvas1.coords('rect1', start_x, start_y, end_x, end_y)

# ドラッグを離したときのイベント
def release_action(event):

    # 'rect1'タグの画像の座標を元の縮尺に戻して取得
    start_x, start_y, end_x, end_y = [
        round(n * RESIZE_RETIO) for n in canvas1.coords('rect1')
    ]

    cropImag =img.crop(box=(start_x,start_y,end_x,end_y))

    nowDate=datetime.now().strftime('%Y%m%d')
    fileName=datetime.now().strftime('%Y%m%d%H%M%S')
    currentDirectory=os.getcwd()
    workDir=os.path.join(currentDirectory,nowDate)
    tempDir=os.path.join(currentDirectory,nowDate,'tmp')
    os.makedirs(tempDir, exist_ok=True)
    # 画像のファイル名
    pngFileName=os.path.join(tempDir,nowDate+'.png')

    # 画像をいったん保存
    cropImag.save(pngFileName)
    # 保存した画像を開く
    readimg = Image.open(pngFileName)

    #適当に画像処理 このあたりはオリジナルのままです
    img_g = readimg.convert('L') #Gray変換
    enhancer= ImageEnhance.Contrast(img_g) #コントラストを上げる
    img_con = enhancer.enhance(2.0) #コントラストを上げる

    #画像からOCRで日本語を読んで、文字列として取り出す
    readText = tool.image_to_string(img_con , lang='jpn', builder=builder)

    #半角スペースを消す ※読みやすくするため 英語の場合は単語がつぶれてしまうので日本語用のみ
    readTextJp = readText.replace(' ', '')

    print(readTextJp)
    # クリップボードにコピー
    root.clipboard_append(readTextJp)

    # テキストファイルとしても出力しておく 日本語用と英語用で分ける
    textFileJp=os.path.join(workDir,fileName+'jp.txt')
    textFileEn=os.path.join(workDir,fileName+'.txt')
    with open(textFileJp, mode='w') as f:
        f.write(readTextJp)

    with open(textFileEn, mode='w') as f:
        f.write(readText)

    # ダイアログにも表示させる
    pyautogui.alert(readTextJp)


# メイン処理
if __name__ == '__main__':

    # 表示する画像の取得（スクリーンショット） メインディスプレイのみが対象です
    img = pyautogui.screenshot()
    # スクリーンショットした画像は表示しきれないので画像リサイズ
    img_resized = img.resize(size=(int(img.width / RESIZE_RETIO),
                                   int(img.height / RESIZE_RETIO)),
                             resample=Image.BILINEAR)

    root = tkinter.Tk()
    root.attributes('-topmost', True) # tkinterウィンドウを常に最前面に表示

    # tkinterで表示できるように画像変換
    img_tk = ImageTk.PhotoImage(img_resized)

    # Canvasウィジェットの描画
    canvas1 = tkinter.Canvas(root,
                             bg='black',
                             width=img_resized.width,
                             height=img_resized.height)
    # Canvasウィジェットに取得した画像を描画
    canvas1.create_image(0, 0, image=img_tk, anchor=tkinter.NW)

    # Canvasウィジェットを配置し、各種イベントを設定
    canvas1.pack()
    canvas1.bind('<ButtonPress-1>', start_point_get)
    canvas1.bind('<Button1-Motion>', rect_drawing)
    canvas1.bind('<ButtonRelease-1>', release_action)

    root.mainloop()

```
</details>

## 処理の説明
* 画像の取得部分などは[参考にしたソース](https://qiita.com/8_hisakichi_8/items/47f6d37e6f425f29c8a8)を流用しています。 `release_action` の部分についていろいろと手を加えているのでそこについての補足を。
* 以下の処理で指定した範囲の画像を取得しています。

```python
cropImag =img.crop(box=(start_x,start_y,end_x,end_y))
```

* 以下の処理で画像を保存します。また後続の処理のため画像を開きなおします。

```python
    # 画像をいったん保存
    cropImag.save(pngFileName)
    # 保存した画像を開く
    readimg = Image.open(pngFileName)
```

* 以下の処理で文字列を取得します。おそらく単語単位でスペースが入ってしまうため日本語を読み取った場合はスペースを除去しています。英語の場合はそのままとしています。

```python
    #画像からOCRで日本語を読んで、文字列として取り出す
    readText = tool.image_to_string(img_con , lang='jpn', builder=builder)

    #半角スペースを消す ※読みやすくするため 英語の場合は単語がつぶれてしまうので日本語用のみ
    readTextJp = readText.replace(' ', '')
```

* 以下の処理でクリップボードに保存しています。

```python
root.clipboard_append(readTextJp)
```

* 以下の処理でテキストファイルを保存し、最後にダイアログを表示します。

```python
    # テキストファイルとしても出力しておく 日本語用と英語用で分ける
    textFileJp=os.path.join(workDir,fileName+'jp.txt')
    textFileEn=os.path.join(workDir,fileName+'.txt')
    with open(textFileJp, mode='w') as f:
        f.write(readTextJp)

    with open(textFileEn, mode='w') as f:
        f.write(readText)

    # ダイアログにも表示させる
    pyautogui.alert(readTextJp)
```

## 動作例
### 英語の場合
* [AWSのドキュメント](https://docs.aws.amazon.com/whitepapers/latest/building-data-lakes/building-data-lakes.pdf#building-data-lake-aws)で試した例です。英語の方はクリップボードには保存されていないため出力したファイルでの確認のみとなります。

![英語.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657697/8ea5b878-7834-3431-97f0-ca1687907836.gif)

* 結果

![その1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657697/918cd0e6-3960-37b6-824f-f4885e07c751.png)

### 日本語の場合
* [AWSのドキュメント](https://docs.aws.amazon.com/ja_jp/wellarchitected/latest/framework/wellarchitected-framework.pdf#welcome)で試した例です。クリップボードにも保存されているのでそのまま貼り付けも可能です。

![日本語.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657697/8821eb29-6b10-a274-2c9d-979aa535528b.gif)

* 結果

![その2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657697/89feefa2-7ae8-80d7-fc14-83dcfa469831.png)

## 最後に

精度としてはちょっと悪いところもありますが、無料でここまでできるので十分な気も・・・
現在は直接起動する方式でまたエラー処理等もない状態なのでいろいろと手直しをしていこうかと思います。
また英語のほうはクリップボードに保存できない状態なのでそのあたりも制御できるようにやっていこうかなと

最後に参考にしたページを以下に
https://qiita.com/8_hisakichi_8/items/47f6d37e6f425f29c8a8
https://qiita.com/ku_a_i/items/93fdbd75edacb34ec610
https://hituji-ws.com/code/python/tesseract-ocr/