Google Cloud Vision APIを使って画像から文字を読み取る

ほぼ、「やってみた」レベルではありますが、自分のメモとしても書いておこうと思います。

やりたかったことは、画像から文字を読み取りたい、ということです。そのために、Google Cloud Visionを使います。

最初に言うと、プログラミング的なことはほぼGoogleさんが提供するサンプルコードでできるので、ここで独創的なことは何もやっていないのですが、わかりづらかったのはGoogle CloudのAPIを有効にすることなので、そこを絡めてメモしておきたいと思いました(笑)。
なので、その辺を含め紹介していきます。

①Google Cloudにアカウントがなければ作りましょう。
Google Cloud Console

②Vision APIを設定にします。
下記を読んで、手順通りにやります。
https://cloud.google.com/vision/docs/setup

途中、秘密鍵を設定する、環境変数でそれを設定する、など面倒なくだりがありますが、やります。

Cloud SDKのインストールもします。これ、結構時間がかかります。

③②まで終わったら、次は下記のURLを見ましょう。

https://cloud.google.com/vision/docs/quickstart-client-libraries

クライアント ライブラリをインストールする

のあたりから始めます。私はPythonで行きます。(`・ω・´)

pip install --upgrade google-cloud-vision

とやります。

で、ついにサンプルを動作させることができます!

下記の猫ちゃんの写真を読み込むサンプルを、Googleさんが用意してくれています。ありがたや~

https://raw.githubusercontent.com/googleapis/python-vision/master/samples/snippets/quickstart/resources/wakeupcat.jpg

import io
import os

# Imports the Google Cloud client library
from google.cloud import vision

# Instantiates a client
client = vision.ImageAnnotatorClient()

# The name of the image file to annotate
file_name = os.path.abspath('resources/wakeupcat.jpg')

# Loads the image into memory
with io.open(file_name, 'rb') as image_file:
    content = image_file.read()

image = vision.Image(content=content)

# Performs label detection on the image file
response = client.label_detection(image=image)
labels = response.label_annotations

print('Labels:')
for label in labels:
    print(label.description)

上記を実行してみるも…

 
Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see... 

というエラーが出ます。

セイセイセイ~ ちょっと待ってくださいよ~ 

と言いたくなりますが、落ち着いてググると、下記に情報があります

https://stackoverflow.com/questions/45501082/set-google-application-credentials-in-python-project-to-use-google-api

下記の一文を、コードに追加します。

os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"
#上記はただのサンプルなので、D:/以下はご自分のキーファイルの場所を指定してください。 

すると、次は課金が有効ではない、というエラーが出ます。

Google Cloudで課金を有効にします。下記のリンクを見てやりましょう。
https://cloud.google.com/billing/docs/how-to/modify-project?hl=ja&visit_id=637623682365584203-1106306741&rd=1#confirm_billing_is_enabled_on_a_project

はぁ、はぁ、ついに実行できましたかね?

コードは全文でこのようになります。

import io
import os

# Imports the Google Cloud client library
from google.cloud import vision

os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"

# Instantiates a client
client = vision.ImageAnnotatorClient()

# The name of the image file to annotate
file_name = os.path.abspath('wakeupcat.jpg')

# Loads the image into memory
with io.open(file_name, 'rb') as image_file:
    content = image_file.read()

image = vision.Image(content=content)

# Performs label detection on the image file
response = client.label_detection(image=image)
labels = response.label_annotations

print('Labels:')
for label in labels:
    print(label.description)

実行すると

Labels:
Cat
Window
Felidae
Carnivore
Jaw
Ear
Small to medium-sized cats
Window blind
Gesture
Whiskers

と表示され、おお~ 画像が読み込まれた!というのがわかります。

えー、しかし、上記のは画像にラベルをつけるというサンプルです。

我々の目標は、書いてある字を読み取ることでした!

今度はこちらのサンプルを使います。

https://cloud.google.com/vision/docs/ocr?hl=ja

今回は、下記の画像を使います。とある日の私のコンビニの領収書です。

なので、ちょっと上記を変更します。text_detection()というメソッドを使います。


import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"

def detect_text(path):
    """Detects text in the file."""
    from google.cloud import vision
    import io
    client = vision.ImageAnnotatorClient()

    with io.open(path, 'rb') as image_file:
        content = image_file.read()

    image = vision.Image(content=content)

    response = client.text_detection(image=image)
    texts = response.text_annotations
     
    return texts

texts = detect_text("receipt.jpg")
print('\n"{}"'.format(texts[0].description))

結果は下記のようになります。

"横浜北幸2丁
神奈川県横浜市西区北幸 2丁目9-
目店
電話: 045-324-5611
レジ#4
領 収書
7PLメンスヒ*オレSイ化粧水ソープ
スターハックスアイスチャイティーラテ
7Pセフンフレット3枚入
009
00乙米
*88
小 計 (税抜8%)
消費税等( 8%)
小計 (税抜10%)
消費税等(10%)
合 計
(税率 8%対象
(税率10%対象
(内消費税等8%
(内消費税等10%
nanaco支払
お買上明細は上記のとおりです。
[]マークは軽滅税率対象です。 nanaco番号 nanaco残高 今回ポイント ポ”イント残高 伝票番号 半288 半23 009夫 09夫 ギ971 半311) (099
半23)
(09夫
工L6夫

****4717
ギ3, 357
LTLヤ
4 P
240P
210-621-454-5134
Thet x帆術海戦
プレゼントキャンペーン
セブンネットでお買い物&セブンイレブン受取りで
応募者から抽選で711名様に
呪術迴戰ォリジナルQUOカード
をプレゼント
thopping
*判>11
vG-MーC4ト
O芥見下々/菜社,呪術圈戦割作载同会
"

ちょっとね~ うーん、残念!

という感じですね。ただ、定型文みたいなものはほぼ読み込めていると思われます。

Amazon Aurora 読み込み整合性とかエンドポイントとか取り留めのないメモを

AWSの優れたDBエンジンの一つである、あの「Amazon Aurora」について、読み取り時のデータ整合性のことが少し気になったらしく、ちょっと調べてみたりしたんですって。

内容には誤りがあるかもしれません。お気付きの場合にはコメント等でご指摘いただけますと幸いです。

読み込み整合性

いきなりAuroraではないのですが、読み込み整合性に関して「Amazon DynamoDB」というDBのお話を少し。

DynamoDBはAWSにて提供されている、NoSQLのDBサービスです。DynamoDBでは、データの読み込みオペレーションとして、2種類が用意されています。

結果整合性のある読み込み(Eventually Consistent Reads)

DynamoDB テーブルからの読み込みオペレーションの応答には、最近の書き込みオペレーションの結果が反映されていないことがあります。応答には古いデータが含まれる場合があります。少し時間がたってから読み込みリクエストを繰り返すと、応答で最新のデータが返されます。


読み込み整合性 – Amazon DynamoDB

読み込みリクエストの直前に書き込みが行われていた場合などに、直前の書き込みが反映されていないデータをレスポンスとして返す可能性があります。

複数ストレージでレプリケーションが行われるような状況では、実際に全てのストレージへの書き込み完了を待つことなく、特定の基準を満たせば書き込みオペレーションが完了したと見なして処理を進めることがあります。
DynamoDBでも同様の仕様ということでしょうか。レスポンス速そう。

強力な整合性のある読み込み(Strongly Consistent Reads)

強力な整合性のある読み込みをリクエストすると、DynamoDB は成功した以前のすべての書き込みオペレーションからの更新が反映された最新データの応答を返します 。ただし、この整合性には以下のような欠点があります。

・強力な整合性のある読み込みは、ネットワークの遅延または停止があった場合には利用できなくなる可能性があります。この場合、DynamoDB はサーバーエラー (HTTP 500) を返す場合があります。

・強力な整合性のある読み込みでは、結果整合性のある読み込みよりもレイテンシーが高くなる場合があります。

・グローバルセカンダリインデックス (GSI) では、強力な整合性のある読み込みはサポートされていません。

・強力な整合性のある読み込みでは、結果整合性のある読み込みよりも多くのスループット容量が使用されます。詳細については、読み込み/書き込みキャパシティーモード を参照してください。

読み込み整合性 – Amazon DynamoDB

「結果整合性のある読み込み」とは異なり、直前に行われた書き込みの内容が反映された、時間軸で見て整合性のあるレスポンスを返す方式です。堅い感じ。

「結果整合性のある読み込み」と「強力な整合性のある読み込み」はユースケースに応じて使い分けるのが良さそう、という定型文を置いておきます。

AuroraDBクラスターのエンドポイント

やっとAuroraのエンドポイントの話になります。
そも、Auroraは下記のようなエンドポイントを持ちます。[4]

クラスターエンドポイント

AuroraDBクラスターの「書き込みエンドポイント」を指します。

例)mydbcluster.cluster-123456789012.us-east-1.rds.amazonaws.com:3306

リーダーエンドポイント

AuroraDBクラスターの「読み取りエンドポイント」を指します。
クラスターに1つ以上のAuroraレプリカが含まれる場合、ラウンドロビンDNSによって自動で負荷分散が行われます。

例)mydbcluster.cluster-ro-123456789012.us-east-1.rds.amazonaws.com:3306

カスタムエンドポイント

ユーザーが作成可能なエンドポイントです。AuroraDBクラスター内のいくつかのDBインスタンスのみを選択して、負荷分散を行うといったことができるようです。

例)myendpoint.cluster-custom-123456789012.us-east-1.rds.amazonaws.com:3306

インスタンスエンドポイント

AuroraDBクラスター内のそれぞれのインスタンスが個別に持つエンドポイントです。通常の負荷分散以上にきめ細かいロードバランシングを行いたいような場合や、特に個別のインスタンスに接続したいような場合の利用が想定されます。

例)mydbinstance.123456789012.us-east-1.rds.amazonaws.com:3306

Q. Auroraの読み込み整合性ってどうなっているの?

  • クラスターエンドポイントを利用する場合、書き込みと読み込みを1つのエンドポイントで行う事になります。
    • この場合、レプリカラグを考慮する必要がなく、Auroraの書き込み/読み取り成功の判定条件(Quorum原理)によれば、強い整合性が保たれると考えられます。
    • 読み書きの成否判定について、書き込み成功の基準は「過半数」、読み取り成功の基準は「半数以上」が設定されているため、理論上は古いデータが返ることはなさそうに見えます。
    • また、Auroraは分散したストレージを単一の論理ボリュームとして認識する構成であるため、やはり構築上古いデータが返ることはない(ようにできている)と考えて良さそうです。[6] [7]
  • リーダーエンドポイント含むその他のエンドポイントを利用する場合には、レプリカラグが影響してきます。
    • 書き込みから読み取りの間隔を超えるラグが生じていた場合、書き込み完了前のデータを読み取ってしまう可能性があると考えられます。
    • DBの変更頻度にもよりますが、レプリカラグは通常100ミリ秒未満です。[6]
  • マルチマスタークラスター構成の場合には、設定で強い整合性を要求することができるようですが、パフォーマンスとトレードオフになるようです。[5]
    • 詳細は割愛。

読み込み整合性について明記した資料が見当たらないなと思ったのですが、読み書きのアーキテクチャ面で理論的に強い整合性が保たれているため、整合性という文脈での説明が不要なのかもしれません。すごい。

Q. AuroraDBクラスターにプライマリしか置いてない場合、エンドポイントはどうなるの?

  • Auroraクラスターにプライマリインスタンスしか含まれていない場合でも、リーダーエンドポイントは作成されます。
    • AuroraDBクラスターに含まれるインスタンスがプライマリインスタンス1台のみである(=レプリカが含まれていない)場合、リーダーエンドポイントに接続した場合でも、プライマリインスタンスに接続されます。
      • この場合、リーダーエンドポイントを介して書き込みオペレーションが実行できます。[4]

書き込み権限は接続したエンドポイントではなく、接続先のDBインスタンスによって判断されるという仕様のようです。
まあでも権限の割り当てはインスタンスごとだし、プライマリに接続される以上はそりゃそうか?なるほど。

参考

モデルを作ろう

弊社では、プログラマー向けに月に一度ぐらいの頻度で勉強会を行っています。

この度、私もやることになって💦 話した内容になります。

プログラミング中級者以上向けに話をしました。

複雑なことをプログラムして、動かす。それをメンテナンスしていく。

とても難しいことです。

私が思う、一つのカギは、よいモデルを作ることです。

でも、

「モデルって何なの?」

という話があまりネットにありません。MVCという文脈で語られるモデルについてはいっぱいあると思うんですが。

なので、まずはモデルの説明と、モデルをどう作っていくべきかという話、そしてまずはモデルを作っていってほしいをチームの皆さんに話したほうがいいと思い、社内勉強会でその話をしました。

スライドシェアにその内容を、多少加工したものをアップしました。

スライド中のサンプルはPHPで書いてますが、オブジェクト指向ができる言語なら、言語はなんでもいいと思います。

ご参考になれば幸いですし、ご意見があればお寄せください。

DateTimeImmutable()のmodifyでの時刻変更がうまくいかなかった

DateTimeImmutable()を使って、下記の処理をやろうとしました。
①… 「秒」の値を 60で割って 「分」へ変換
②… ①で計算した「分」の値をmodifyで時刻($time_to_check)から引く
③… ②で変換した時刻を$time_beforeに代入

しかし、どうやら小数点の計算をさせてしまっていたようでおかしくなりました…汗
($time_beforeが2084年になってしまいました。back to the futureなため正常に動きません…)

「秒」から「分」の換算でfloor()をしたら直りました(;’∀’)
意外と小数点の処理を忘れちゃうので皆さんお気を付けください。


というか、そもそもの話ですが、 最初から時間の単位は統一しておいたほうがいいですね。(この場合なら秒に合わせてmodifyではsecondを使う)
こういう変換はミスにもつながりますし極力回避したほうがいいということがよくわかりました。

二つの時刻を調べて結果を秒数で返すのに、DateTimeImmutable と strtotime のどちらが早いか調べてみた

PHP で日付や時刻を取得するには、date()関数やstrtotime()関数、DateTimeクラスやDateTimeImmutableクラスを用いると思います。
今回は、strtotime()関数とDateTimeImmutableクラスの処理速度を比較していきます。

DateTimeImmutableとは

DateTimeImmutableは日付や時刻を表すことができるクラスです。
DateTimeImmutableクラスで日付や時刻を取得するには、オブジェクトを作成します。
DateTimeImmutableクラスはDateTimeクラスとほぼ同じですが、 DateTimeImmutableクラスでは、作成したオブジェクトをメソッドで扱う際に、元のオブジェクトは変更せずに新しいオブジェクトを返します。
詳細はこちらをご覧ください。

使用例

下記を2021年6月28日の15時30分に実行します。

<?php
$datetime = new DateTimeImmutable('now', new DateTimeZone('Asia/Tokyo'));
echo $datetime->format('Y-m-d H:i:s'); //2021-06-28 15:30:24

strtotimeとは

strtotimeは指定日時をUnix タイムスタンプに変換するメソッドです。
Unix タイムスタンプの詳細に関してはこちらをご覧ください。

使用例

<?php
$unix_time = strtotime("2021-06-28 15:30:24");
echo $unix_time; //1624894224

実際にやってみる

今回は2021年4月18日21時15分42秒のUnix タイムスタンプと2021年4月18日21時25分32秒のUnix タイムスタンプの差を出す処理を10万回ループさせて実行速度を比較します。

<?php

//DateTimeImmutableの場合
$time_start = microtime(true);

$count = 0;
while($count <= 100000) {
    $datetime01 = new DateTimeImmutable("2021-04-18 21:15:42");
    $datetime02 = new DateTimeImmutable("2021-04-18 21:25:32");
    $diff = $datetime01->getTimestamp() - $datetime02->getTimestamp();
    $count++;
}

$time = microtime(true) - $time_start;
echo "{$time} 秒";


//strtotimeの場合
$time_start2 = microtime(true);

$count = 0;
while($count <= 100000) {
    $unix_time01 = strtotime("2021-04-18 21:15:42");
    $unix_time02 = strtotime("2021-04-18 21:25:32");
    $diff2 = $unix_time01 - $unix_time02;
    $count++;
}

$time2 = microtime(true) - $time_start2;
echo "{$time2} 秒";

結果

0.51721596717834 秒
0.29946088790894 秒

結果としては、strtotimeの方が実行速度が早く、10万回繰り返すと約0.2秒の差が生じることがわかりました。

ちなみに、DateTimeImmutableクラスにはふたつのDateTimeオブジェクトの差を返すdiff()関数というものがあるのでそちらの処理速度も出してみます。

<?php

$time_start1 = microtime(true);

$count = 0;
while($count <= 100000) {
    $datetime1 = new DateTimeImmutable("2021-04-18 21:15:42");
    $datetime2 = new DateTimeImmutable("2021-04-18 21:25:32");
    $diff1 = $datetime1->diff($datetime2);
    $durationInSeconds = ($diff1->s)
        + ($diff1->i * 60)
        + ($diff1->h * 60 * 60)
        + ($diff1->d * 60 * 60 * 24)
        + ($diff1->m * 60 * 60 * 24 * 30)
        + ($diff1->y * 60 * 60 * 24 * 365);
    $count++;
}

$time1 = microtime(true) - $time_start1;
echo "{$time1} 秒";

結果

0.52968621253967 秒

DateTimeImuutableのgetTimestamp()メソッドを使った処理とそこまで差がないことがわかりました。

参考サイト: