「AWS認定取得を目指す、英語が苦手なエンジニアに贈る！」英語サイトをスクリーンショット撮って、画像ファイルからテキストデータを抽出後、翻訳をしてみる 〜AWSとGoogle Cloud APIの差〜

# 背景
### AWS認定の対策はどのようにされていますか？
AWS 認定クラウドプラクティショナーやソリューションアーキテクト – アソシエイトは対策本が出ており、模擬試験もついているため、対策勉強がしやすい環境になってます。しかし、デベロッパー – アソシエイト以降になると、日本語の対策本が少ないため、模擬試験をやろうと思うと、[Udemy](https://www.udemy.com/)や[Whizlabs](https://www.whizlabs.com/)を頼ることになります。しかし、これらサイトの模擬試験は英語がほとんどです。(2019年12月03日現在、Udemyに日本語の模擬試験が1つあります）

### UdemyとWhizlabsの違い
模擬試験は沢山やっておいたほうがよいと思ったので、UdemyとWhizlabsで英語の模擬試験をやってみました。自分の英語力はかなりあやしいので、英語が読み取れない時は、問題文をコピーして、[Google翻訳](https://translate.google.co.jp/?hl=ja#view=home&op=translate&sl=en&tl=ja)で翻訳しながら解答してました。

Udemyは、Google翻訳を利用することでなんとかなったのですが、Whizlabsは問題と解答の選択肢がコピーできないようになってます。よくある右クリックをOFFにしているサイトと同じです。そこで、右クリックとコピー禁止を回避できるChromeの拡張機能を試してみたのですが、どうもうまくいかない。そこで、Whizlabsのサポートに問い合わせしてみたのですが、コピーできるようにする予定は無いとの回答をいただきました:sob:

### [Amazon Rekognition](https://aws.amazon.com/jp/rekognition/)
さあ困りました。HTMLソースを表示して、そこから問題文をコピーして、Google翻訳するか！:muscle:も考えましたけど、問題数もかなり多いし、選択肢もあわせてコピーするのはかなり大変だったので、問題文のスクリーンショット画像を撮り、その画像ファイルからテキストを抽出できることを考えました。

AWSの**Amazon Rekognition**を使えば、画像ファイルからテキスト抽出できることを知っていたため、Rekognitionを使ってみることにしました。

# 事前準備
問題英文のショットが無いと始まらないので、https://www.whizlabs.com/aws-developer-associate/ にある、Free Testの15問のスクリーンショットを取ります。ブラウザの表示を拡大設定にして、なるべく大きめな画像にしました。

*補足：これは後でわかったことですが、解答を選択している状態ですと、その選択肢の文字が抽出できないことが多いようです。スクリーンショットを撮る時には、未回答の状態で撮りましょう。*

環境はMacOSを使っています。スクリーンショットを取ると、ファイル名が日本語になってしまうため、ファイル名の変換をします。en-q000.pngとしたかったので、以下のコードでファイル名の変換をしました。

実行例

```
python filename_change.py /Users/hogehoge/screenshot/test
```

<details><summary>filename_change.py（折りたたんでいます）</summary><div>

```python
import sys
import os
import glob

def main(dir):
    # 画像が置かれているディレクトリを引数にする。おしりのスラッシュは不要
    pathpattern = dir + "/*"
    # 問題1からファイルのタイムスタンプが並んでいることを前提とする
    files= sorted( glob.glob(pathpattern) ,key=os.path.getmtime )
    cnt=1
    for file in files:
       newfilename = dir+"/en-q"+str(cnt).zfill(3)+".png" 
       os.rename(file,newfilename)
       print("ファイル名を{0}から{1}へ変更しました".format(file,newfilename))
       cnt+=1

if __name__ == '__main__':
    main(sys.argv[1])
```
</div></details>

これで画像ファイル名が、en-q001.pngからen-q015.pngまで15個のファイルを作成できました。この画像ファイルをS3へアップロードします。
アップロード後は、このようになります。

``` shell
% aws s3 ls s3://mybucket/sample/
2019-12-03 17:43:39          0 
2019-12-03 17:43:52     132268 en-q001.png
2019-12-03 17:43:52     135713 en-q002.png
2019-12-03 17:43:52     130853 en-q003.png
2019-12-03 17:43:52     112020 en-q004.png
2019-12-03 17:43:52     125841 en-q005.png
(以下省略）
```

# Amazon Rekognitionを使った文字認識

ここに[サンプルコード](https://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/text-detecting-text-procedure.html)が掲載されているため、利用はいたって簡単でした。画像をS3にアップロードして、レスポンスを加工するだけです。AWS CLIでは、以下だけで結果が取得できます。ここからTypeがLINEというテキストを連結すればよいだとうと思い、Python boto3でコーディングしてみました。

```shell

aws rekognition detect-text \
--image "S3Object={Bucket=yourbucket,Name=shot.png}" \
--output json
```
実際のコードは、S3上のオブジェクト名を取得→Rekognition API→結果の文字列を連結→ファイルに出力 ということをしています。

実行例

```
python aws_gettext_fromimage.py yourbucketname sample ./test-1205
```


<details><summary>aws_gettext_fromimage.py（折りたたんでいます）</summary><div>

```python
import boto3
import sys
import os

rekognition_client = boto3.client('rekognition')
s3_client = boto3.client('s3')

def detext_text(bucketname, object):
    response=rekognition_client.detect_text(Image={'S3Object':{'Bucket':bucketname,'Name':object}})
    jsonDetextText=response['TextDetections']
    sentence=''
    for jsondata in jsonDetextText:
        if jsondata['Type'] == 'LINE':
            sentence = sentence + ' ' + jsondata['DetectedText'] + '\n'
    return ( sentence )

def main(bucketname, prefix, outputdir):
    # s3のオブジェクト一覧を取得する
    response=s3_client.list_objects(
        Bucket=bucketname,
        Prefix=prefix
    )
    if 'Contents' in response:
        keys = [content['Key'] for content in response['Contents']]
    else:
        print("S3のオブジェクト名が取得できません")
        return ("NG")

    # リストの最初はprefixなので削除
    keys.pop(0)
    # ディレクトリを作成する
    if not os.path.exists(outputdir):
        os.makedirs(outputdir)

    for key in keys:
        # オブジェクト名だけを取得して拡張子を.txtにする
        outputfilename = outputdir + "/" + (key.rsplit("/",1)[1][:-4]) + ".txt"
        # ファイルへ出力する
        with open(outputfilename,'w') as f:
            f.write( detext_text(bucketname, key) )

if __name__ == '__main__':
    # 引数からバケット名,prefix,出力先ディレクトリ名を取得する
    bucketname = sys.argv[1]
    prefix = sys.argv[2]
    outputdir = sys.argv[3]

    main(bucketname, prefix, outputdir)
```
</div></details>

これで出力先ディレクトリに15個のテキストファイルが生成されました。しかし、以下が認識できていませんでした。

* 解答の選択肢はまったく読み取れていない
* 問題文の文末が読みれていないことが多い
* 問題文上では同じ行なのに、違う行として認識している
    * 例えば、`You're a developer for a company`を`You're for（改行)a developer a company`と認識。

最初の２つは、まあ仕方ないかなーと思っていましたけど、３つ目は補正が大変です。改行は連結してしまえば解決しますが、単語の位置が変わってしまいます。

どうしようかなと考えていたところ、AWSにあるサービスなら、Google Cloudにもあるだろうと思い、調べた見たところ、ありました！**Cloud Vision API**です。

# Google CloudのCloud Vision APIの結果に驚き！！！
調べてみたところ、AWSのRekognition+S3と同じように、Cloud Vison+Storageで利用できたので、先程のソースコードを少し改修しました。

``` python
from google.cloud import vision
from google.cloud import storage
import sys
import os

vision_client = vision.ImageAnnotatorClient()
storage_client = storage.Client()

def detext_text(bucketname,object):
    uri = 'gs://'+bucketname+'/'+object
    response = vision_client.annotate_image({
        'image': {'source':{'image_uri': uri}},
        'features': [{'type':vision.enums.Feature.Type.TEXT_DETECTION}]
    })
    return ( response.text_annotations[0].description )

def main(bucketname, prefix, outputdir):

    storage_client = storage.Client()
    blobs = storage_client.list_blobs(
        bucketname,
        prefix=prefix
    )
    if not os.path.exists(outputdir):
        os.makedirs(outputdir)

    for blob in blobs:
        objectname = blob.name
        if objectname.endswith("/"):
            continue
        # オブジェクト名だけを取得して拡張子を.txtに変換する
        outputfilename = outputdir + "/" + (objectname.rsplit("/",1)[1][:-4]) + ".txt"
        with open(outputfilename,'w') as f:
            f.write( detext_text(bucketname, blob.name) )

if __name__ == '__main__':
    
    bucketname = sys.argv[1]
    prefix = sys.argv[2]
    outputdir = sys.argv[3]

    main(bucketname, prefix, outputdir)
```
AWSのSDKと比較すると、APIをコールした後のレスポンスの取り扱いがちょっと異なる印象がありましたが、サンプルなどを参考にして動作させることができました:raised_hands:
実行方法は、上の`aws_gettext_fromimage.py`と同じです。

**結果のテキストファイルを見て、びっくりです!!** :scream_cat:

Rekognitionの時とは異なり、**解答の選択肢は読み取れているし、一部の単語が改行されてしまっていることもありません。**一部の解答の選択肢の(A./B./C.) が欠落していたり *(選択肢のA.B.C.が欠落する時は、解答が2行になっている時が多かったです）* 、`B. Security`が`B.（改行）Security`となっているだけです。


**AWSとGoogle Cloudでこんなに差が出るものなのかと。おそらく、画像によって向き不向きがあるんだろうとは思います**が、今回はそこまで検証している時間がないため、Cloud Vision APIで生成したテキストファイルを採用しました！

ここで、問題文は、画像ファイルで見たまま、改行されてしまっていますので、今回はVSCodeの置換（正規表現）で改行をスペースに変換しました。

# Google CloudのCloud Translationを使って翻訳
テキストデータ化できているので、Google翻訳にコピペすればいいのだが、せっかくなので、翻訳ができるCloud Translationを使ってみることにした。
## Google CloudのClient Library
参考にしたドキュメントは、以下の２つです。必要なライブラリーは`pip`を使ってインストールします。

- [テキストの翻訳（v3beta1）](https://cloud.google.com/translate/docs/translating-text-v3?hl=ja#translate_translate_text_beta-python)
- [Client for Cloud Translation API](https://googleapis.dev/python/translation/latest/gapic/v3beta1/api.html)


ここで困ったことが。。。AWSと違って、検索しても情報量が少ない。Pythonの`type()`で型を調べながらというように、かなり思考錯誤しました。まあ、今回が、初めてGoogle Cloudのライブラリを使ったため、慣れていないことが原因だと思います。

ドキュメントとソースコードのパラメータの渡し方が異なりますが、まだベータ版のため、変わっているところだと思います。



### 実行方法
英文のテキストデータファイルが格納されているディレクトリを引数にして実行してください。

```
python translate.py ./work
```
- translate.py（**プロジェクトIDは書き換えてから実行してください**）

``` python
from google.cloud import translate_v3beta1 as translate
import sys
import glob
import os

translate_client = translate.TranslationServiceClient()

def translate_txt(text):
    project_id = 'Your Project ID'
    location = 'global'
    src_lang = 'en'
    dst_lang = 'ja'

    parent = translate_client.location_path(project_id,location)
    response = translate_client.translate_text(
                    text, 
                    dst_lang,
                    parent,
                    mime_type='text/plain',
                    source_language_code=src_lang,
                    model="projects/{}/locations/global/models/general/nmt".format(project_id)
                )
    translated_text=[]
    for translation in response.translations:
        translated_text.append(translation.translated_text)

    return( translated_text )

def orgtext_fromfile(filename):
    f = open( filename )
    orgtext = f.read()
    f.close()

    return(orgtext)

def main(orgdatadir):
    files= sorted(glob.glob("{}/*.txt".format(orgdatadir)), key=os.path.getmtime)

    for file in files:
        orgdata = []
        orgtext = orgtext_fromfile(file)
        orgdata.append(orgtext)

        with open(file, 'a') as f:
            print("-------",file=f)
            for outtext in translate_txt(orgdata):
                print(outtext, file=f)

if __name__ == '__main__':
    orgdatadir = sys.argv[1]
    main(orgdatadir)

```

このように実行すると、英文テキストに翻訳結果を追加書き込みします。15問なら一気に処理しても、すぐに終わります。また翻訳結果も問題ないですね。

これで、翻訳された問題・解答の選択肢を見ながら、Whizlabsの解答を選択していくことができました〜！

# Whizlabs以外でもCloud Vision APIを試してみた
## Youtube その１
Yourubeの再生を停止させて、テキスト部分をスクリーンショット撮って、Vision APIで抽出してみました。テストしてみた動画はこれです。

https://youtu.be/8j_hxBw5B4E?t=10

結果は、正解でした！

## Youtube その2
今度は背景が白ではない動画からスクリーンショットを撮って試してみました。

https://youtu.be/kg_63wV7bL8?t=68

結果は、タイトル枠の線をIとして認識しまっただけで、他はすべて読み取れてました。
すばらしい:raised_hands:

# かかる料金
さあ、実際にかかる料金はいくらなのでしょうか？

## [Cloud Vision APIの料金表](https://cloud.google.com/vision/pricing?hl=ja)
- 最初の1000ユニットは無料とある。1001回以上は$1.50
- 今回は1000ユニットもコールしないので、無料と判断した

## [Cloud Translationの料金表](https://cloud.google.com/translate/pricing?hl=ja)
- 100万文字ので$20
    - 100万文字に満たない場合は、以下ように課金されるようです

> 料金は「比例」計算で課金されます。料金は実際にサービスに送信された文字数に応じて請求されます。たとえば、1 か月以内に 75,000 文字を処理するように送信した場合、$1.50 が請求されます。

さすがに、100万文字にはならないだろうと思うますので、ほとんど課金されないかなと思います。

# まとめ
作業の手順をまとめます。

1. 英文のスクリーンショットを撮る
2. スクリーンショットのファイル名は作業しやすいように変更する
3. Google Cloud Storageに画像ファイルをアップロードする
4. Cloud Vision APIを使って画像ファイルからテキストデータを抽出する
5. テキストデータに修正が必要な場合は修正する
6. Google Cloud Translationを使って翻訳する。5. でテキストファイルを開くなら、ここはGoogle翻訳を使ってもよいかと思う。

これだけの作業を行えば、Whizlabsの問題文を簡単（？）に翻訳することができます。**Whizlabs以外にも、Youtube内の英語も翻訳できたりします。**

## 作業の自動化
上記1.から６．の作業手順が面倒なので、Storageに画像をアップロードしたら、Cloud Functionsを使って、翻訳までやってしまうこともできるんじゃないかと考えました。しかし、なにせ、Google Cloudをいじったことがあまりなかったので、そこまでは検討できませんでした。きっとできるはず！

## AWS と Google Cloud
これまでは、私の中では、AWS一択でしたが、今回はGoogle Cloudのほうがよい結果が出ました。まだ他にもAzureもあります。ケースに応じたサービスが選択できるよう視野を広げていかないとなと反省しました:bow_tone1:

## 環境について
この記事は以下の環境を使っております。

- Python 3.7.2
- boto3                         1.9.172  
- botocore                      1.12.221 
- google-api-core               1.14.3 
- google-cloud-core             1.0.3    
- google-cloud-storage          1.23.0
- google-cloud-vision           0.40.0 
- google-cloud-translate        2.0.0

## おことわり
- ソースコードは、エラー処理等は入れてないところが多々あります。もし利用される場合は、そのあたりを考慮してください。
- 今回は、AWS CLI,SDK/Google Cloud APIをコールするための設定等は割愛しました。そのあたりは検索すれば出てきますので、そちらを参照ください。
- この記事を書き終わった後に気づきましたが、Whizlabsに限ったことで、もっと簡単にGoogle翻訳できる方法がありました。しかし、ここには書かないでおきます。:skull_crossbones:

**以上で終わりますが、そもそも英語の勉強しろよっていうツッコミは無しでお願いします** :sunglasses:



---
:fork_and_knife: [FORK Advent Calendar 2019](https://qiita.com/advent-calendar/2019/fork)
:point_left: 6日目 [ImageMagickでHEICをJPEGに変換する](https://qiita.com/kinoleaf/items/df93150874bf0a6c9b9d) @kinoleaf さんお疲れさまでした！
:point_right: 8日目 @AsaToBan さんよろしくお願いします。