はじめに
今回は、VScode上で名刺画像ファイルをHTTPリクエスト経由で送信して、VS code上で名刺画像ファイルの情報を取得する機会があったので、自分用の忘備録の代わりに、その手順をご紹介致します。
環境
Laravel6.2
Mac OS
VS code
REST Clientの簡単なご説明
まず、REST ClientとはVS Code上でレスポンスを確認できるというVS codeの拡張ツールです。
例えば今回でいうと画像ファイルを送信しているので、その画像ファイルの具体的な情報はデバッグ等を実行してブラウザでで確認するのが通常のケースだと思いますが、REST Clientを使用するとその手間が省け、HTTPリクエストで送信される画像ファイルの情報がVS code上で確認できるというものです。
この文章だけだと理解し辛いかもしれませんので、REST Clientを分かりやすくご紹介している記事が既にあるので、具体的な挙動やインストールの方法はこちらの記事でご確認して頂ければと思います。
REST Clientで画像ファイルを送信
HTTPリクエストの送信元である、httpファイルを作成致します。
ファイル名は apitest_local.httpとしてLaravelプロジェクト直下に配置するものとします。
現在のディレクトリ構造はこんな感じです。
var/www/html/laravelプロジェクト/apitest_local.http
画像ファイルを送信したい時のhttpファイルは以下です。
POST https://api.example.com/test/get_business_card
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="image"; filename="TEST.jpg"
Content-Type: image/jpeg
< ./TEST.jpg
------WebKitFormBoundary7MA4YWxkTrZu0gW--
殆ど英語の羅列で意味が分かりませんが、ドキュメント通りに書くと上記のようになります。
大事なポイントは以下です。
1.POST https://api.example.com/test/get_business_card
こちらはHTTPリクエストを受け取りたい箇所のURLで、今回はLaravelのコントローラー内にあるget_business_cardというメソッドで取得したかったので、上記のように記述しております。
具体的なルーティングの定義方法は後ほど説明致しますが、Laravelのapi.phpというファイルに記載します。
2.Content-Type: multipart/form-data
画像ファイルを送信する場合は、上記の記載が必要です。HTMLでファイル送信の処理を書く際にも以下のように記述する必要がありますが、それと同義です。
<form method="POST" action="/upload" enctype="multipart/form-data">
3.Content-Disposition: form-data; name="image"; filename="TEST.jpg"
name="image"は、コントローラーの受け取り側から、送信されたファイル(httpリクエスト)を取得する際のファイル名です。
filename="TEST.jpg"は、インターネットからダウンロードした際、使用PCのローカルに保存する際の画像名です。今回は、TEST.jpgという名前で画像保存したので、上記のfilenameとしております。
4.< ./TEST.jpg
こちらはhttpファイルから見た時の画像ファイルを配置している位置です。先程作成したvar/www/html/laravelプロジェクト/apitest_local.http
と同じ位置に保存しているので
上記のように記述しております。<
の次に半角スペースが一つ分必要な点に注意しましょう。
先程、1.の部分で少し触れましたが、画像ファイルの送り先はlaravelプロジェクト/routes/api.php
で以下のように定義します。
Route::post('test/get_business_card', [App\Http\Controllers\BusinessCardController::class, 'get_business_card'])->name('get_business_card);
これで、REST Client(VS code)で画像ファイルを、コントローラーのメソッド(API)に送る準備が出来たので、次にBusinessCardController側の処理をご説明致します。
Google vision APIで、名刺画像を取得する
LaravelでGoogle vision APIを使用する方法は下記の記事が分かりやすかったので、大体は記事通りに進めていけば問題はないのですが、少し躓いたので、その部分を重点的にご説明していきます。
Google vision APIにアクセスする際のキー(jsonファイル名)はvision_api_key.json
として
laravelプロジェクト/storage/json
ディレクトリの直下に置きます。
あとは参考記事にも書かれていますが、Laravelのどのファイルからもアクセス出来るように以下のように環境変数を定義します。
GOOGLE_CLOUD_PROJECT=*******************
GOOGLE_APPLICATION_CREDENTIALS=/var/www/html/storage/json/vision_api_key.json
GOOGLE_CLOUD_PROJECT
は、vision_api_key.json
の「project_id」
に書かれている値です。
しかし、記事通りに進めていくと Google vision API
にアクセスしようとする際に、次のようなエラーが出ます。
fatal error: Uncaught DomainException: Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information in /path/vendor/google/auth/src/ApplicationDefaultCredentials.php:168
調べていくと、これは認証系のエラーでGoogle vision API
にアクセスする際に先程
配置した vision_api_key.json
が上手く読み込めていないようです。ここで結構躓いたのですが、下記の記事が参考になりました。
以下のコマンドを使うと、.envファイルが読み込めなくなる
php artisan config:cacheconfigを経由して読み込むと、エラーがなくなります。
という事ですので、下記のようにlaravelプロジェクト/config/vision_cloud_api.phpを作成します。
<?php
return [
'fruits' => env('GOOGLE_APPLICATION_CREDENTIALS', '/var/www/html/storage/json/vision_api_key.json'),
]
//第一引数は、.envで定義した値を読み込み、第二引数は第一引数がNULLの場合に読み込む値です。
?>
続いてコントローラーの中身をご説明致します。
今、コントローラーの中身の全体は下記のようになっています。
<?php
namespace App\Http\Controllers\test;
use App\Http\Controller;
use Illuminate\Http\Request;
use Google\Cloud\Vision\V1\ImageAnnotatorClient;//Google Vision APIを使用する為に、この部分を追加
use App\Models\PostalCode;
class BusinessCardController extends Controller
{
public function get_business_card(Request $request)
{
$vision_path = config('vision_cloud_api.vision_path');
putenv("GOOGLE_APPLICATION_CREDENTIALS={$vision_path}");
$send_image = $request->file('image');
$client = new ImageAnnotatorClient();
$image = $client->createImageObject(file_get_contents($send_image));
$response = $client->textDetection($image);
if (!is_null($response->getError())) {
return ['result' => false];
}
$send_image->store('public/images/');
$annotations = $response->getTextAnnotations();
$description = str_replace('""', ''< $annotations[0]->getDescription());
$arr = array();
$return_data = '';
$delimited_description = explode('', $description);
foreach ($delimited_description as $str) {
$kaigyo = explode("\n", $str);
foreach ($kaigyo as $data) {
array_push($arr, $data);
}
}
foreach ($arr as $search) {
if (str_contains($search, '〒')) {
$return_data = $search;
}
}
if ($return_data) {
$return_data = str_replace('〒', '', $return_data);
$zip_code = str_replace("-", "", $return_data);
$first_code = substr($zip_code, 0, 3);
$last_code = substr($zip_code, 3);
$return_address = PostalCode::WhereSearch($first_code, $last_code)->first();
}
}
}
?>
重要なポイントは下記です。
先程作成したconfig/vision_cloud_api.phpを読み込み、putenv
というPHPの関数で
Google Vision APIを使用する際、APIにアクセスする際に、どのアクセスキーを読み込むのかというパスを設定しています。
$vision_path = config('vision_cloud_api.vision_path');
putenv("GOOGLE_APPLICATION_CREDENTIALS={$vision_path}");
私の場合、上記の箇所を記述しないと後に記述しているこの部分を実行しようとする際に
$image = $client->createImageObject(file_get_contents($send_image));
fatal error: Uncaught DomainException: Could not load the default credentials.
というエラーが発生してしまっていました。
次にこちらの部分を簡単にコメントアウトでご説明致します。
$arr = array();//配列として格納する為の変数を用意。
$return_data = '';//取得した郵便番号を取得する為の変数を用意。
$space = explode('', $description);//$descriptionの中に取得した名刺画像に記載されている文字列が全て格納されているので、空白でそれらの文字列を区切る。
//foreach文で先程区切った文字列を一つずつ改行しながら配列形式に格納していく。
foreach ($delimited_description as $str) {
$kaigyo = explode("\n", $str);
foreach ($kaigyo as $data) {
array_push($arr, $data);
}
}
//今回は郵便番号だけ取得したかったので、先程名刺から取得した文字列の中から、郵便番号だけを抜き取る。
foreach ($arr as $search) {
if (str_contains($search, '〒')) {
$return_data = $search;
}
}
以上で、名刺画像から文字列を取得し、郵便番号を取得することが出来ました。
おわりに
名刺画像から取得した文字列は、名前の最後の1文字と役職名、会社所在の郵便番号が何故か混在して連想配列形式で取得されてしまうので、結果的にforeach文を2回周す必要があり、少し複雑なロジックになってしまいましたが、最終的にはGoogle Vision API
について理解を深掘り出来たので良かったです。
この部分もう少し知りたい、この部分もっとこうした方が良いのではないかというアドバイスがあればコメント欄にて反応を頂けますと嬉しいです。長文になりましたが、ここまで読んで頂きありがとうございます。