Angular から Base64エンコードした画像ファイルを Cloud Functions にアップロードして Cloud Vision API でOCR
チュートリアルにずばり「光学式文字認識(OCR)のチュートリアル」があるんだけど、下図のように、かなりいろいろな機能を網羅的に紹介したいがためなのか、複雑すぎるように見える(試すのが面倒)。
このチュートリアルが元ネタのためか、ググって出てくる OCRのサンプルもいったんCloud Storageに保存してから、トリガーでFunctionsを動かして Vision APIを呼び出すサンプルはそこそこ見つかるのだけれど、シンプルに Cloud Functions を利用して、OCRの結果だけを取得するようなのは見つからない(探し方がわるい?)。
料金(os.tmpdirにファイルを一時的に書き込むよりCloud Storageのほうが安い?)や、整合性維持的なメリットがあるのかしら?
と思ったら、理由書いてあった。
https://cloud.google.com/functions/docs/writing/http?hl=ja
注: Cloud Functions では HTTP リクエストの本文のサイズが 10 MB に制限されるため、この上限を超えるリクエストは、関数の実行前に拒否されます。1 つのリクエストのサイズ上限を超えて永続するファイルは、Cloud Storage に直接アップロードすることをおすすめします。
Base64にエンコードすると、サイズは133%程度になるので、上限が10MBだと、実質は7.5MB程度か、、
今回の要件にはサイズは十分(かつ永続化させる必要もない)なので、以下検証を経てそろえた素材で、単純にファイルをFunctionsに送ってOCRかけて結果をJSONで返す実装を試す。
1.Angular Page
ファイル選択のボタンと、アップロードボタン、結果を表示するテキストエリアを配置。
<textarea cols="60" rows="5" [(ngModel)]="ocrText"></textarea> <input type="file" (change)="onFileChanged($event)"> <button (click)="onUpload()">Upload!</button>
2.コンポーネント
- FileReader#readAsDataURL() で、選択したファイルを、Base64にエンコーディングする。
- ファイルアップロード では、マルチパートで送信していたが、Functions アプリ呼び出しでは、パラメーターをJSONで渡す必要がありそうなので、Base64エンコードした文字列として引き渡す。
- AngularFireFunctions#httpsCallable() で、呼び出し可能Functionsを生成。
import { Component, OnInit } from '@angular/core'; import { AngularFireFunctions } from '@angular/fire/functions'; import { Observable, from } from 'rxjs'; @Component({ selector: 'app-management', templateUrl: './management.component.html', styleUrls: ['./management.component.scss'] }) export class ManagementComponent implements OnInit { visionsCallable: any; selectedFile: File; ocrText: string = ''; constructor(private fns: AngularFireFunctions) { this.visionsCallable = fns.httpsCallable('sampleOnCallVisions'); } ngOnInit(): void { } onFileChanged(event) { this.selectedFile = event.target.files[0]; } async onUpload() { const reader = new FileReader(); const promise = new Promise(function(resolve, reject){ reader.onload = (function(){ return function(e){ // data:text/plain;base64,xxxxx var fileBase64 = e.target.result.split(',')[1]; resolve(fileBase64); }; })(); }); reader.readAsDataURL(this.selectedFile); const fileBase64 = await promise.then(); console.log(fileBase64); const observer = this.visionsCallable( { filename: this.selectedFile.name, base64encodedFile: fileBase64 }) as Observable<any>; try { const res = await observer.toPromise(); this.ocrText = res.result; } catch(e) { console.log(e); } } }
3.Functions
-
- この辺で試したことを組み合わせて実装
- この辺で試したことを組み合わせて実装
index.ts
export const sampleOnCallVisions = functions.https.onCall( async (data, context) => { const filename: string = data?.filename as string; const base64encodedFile: string = data?.base64encodedFile as string; if (filename == null || typeof filename !== 'string' || base64encodedFile == null || typeof base64encodedFile !== 'string') { throw new functions.https.HttpsError( 'invalid-argument','filename and base64encodedFile are required.'); } if (!context.auth) { throw new functions.https.HttpsError( 'failed-precondition', 'not authenticated.'); } const tmpdir = os.tmpdir(); const filepath = path.join(tmpdir, filename); console.log(`Filepath ${filepath}`); const visionClient = new vision.ImageAnnotatorClient(); fs.writeFileSync(filepath, base64encodedFile, {encoding: 'base64'}); const [textDetections] = await visionClient.textDetection(filepath); console.log(textDetections); const [annotation]:any = textDetections.textAnnotations; console.log(annotation); const text = annotation ? annotation.description : ''; console.log("## OCR ## " + text); return {result: text}; });
4.実行
4.1 サンプルイメージ
いざ実行。以下のサンプルイメージをOCRにかける。
4.2 実行結果
選択したファイルが、OCRかかって、結果が表示されたOKOK!!