Google Cloud Functions に ファイルをアップロードして Vision API で OCR

Google Cloud Functions へファイルをアップロードしGoogle Cloud Storage へ保存できたので、アップロードしたので、そのファイルを Cloud Vision APIにかけてOCRを行う。

Firebase Admin SDK を利用しているので、Google Cloud Platform 周りの認証が隠蔽されるので非常に便利。

以下の点にはまったが、なんとか夏季休暇2日目に疎通できた。

  • Functions呼び出しURLを間違えていることに気づかず、Vision APIは、Firebase Admin SDK が利用できないと勘違いし空回り。結局利用できた。
  • TypeScript と import を使用したかったのだが、tsc のエラーが解決できなかったので、コーディングでは、import を使用して、型定義の恩恵をえて、デプロイ時にはtscエラーを回避するために、require に切り替えるなど。

1.Functions 実装

functions/index.ts

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import * as Busboy from 'busboy';

// コーディング時にTypeScriptの型定義を import して利用したいが、
// デプロイではエラーになるので、require を使う
//import  { v1p1beta1 as vision }  from '@google-cloud/vision';
const vision = require("@google-cloud/vision").v1p1beta1;

export const helloVisions = functions.https.onRequest((request, response) => {
    if (request.method !== 'POST') {
        response.status(405).end();
        return;
    }
    const visionClient = new vision.ImageAnnotatorClient();
    const busboy = new Busboy({headers: request.headers});
    const tmpdir = os.tmpdir();

    const uploads: {[key:string]:string} = {};
    const fileWrites: Array<Promise<any>> = [];

    busboy.on('file', (fieldname, file, filename) => {
        console.log(`Processed file ${filename}`);
        const filepath = path.join(tmpdir, filename);

        console.log(`Filepath ${filepath}`);
        uploads[fieldname] = filepath;

        const writeStream = fs.createWriteStream(filepath);
        file.pipe(writeStream);

        const promise = new Promise((resolve, reject) => {
            file.on('end', () => {
                writeStream.end();
            });
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
        });
        fileWrites.push(promise);
    });

    busboy.on('finish', async () => {
        await Promise.all(fileWrites);

        for (const file in uploads) {
            const filepath = uploads[file];
            console.log(`Finish filename:${filepath}`);

            const [textDetections] = await visionClient.textDetection(filepath);
            const [annotation]:any = textDetections.textAnnotations;
            
            const text = annotation ? annotation.description : '';
            console.log("## OCR ## " + text);
            response.write(text);

            try {
                console.log(`UN LINK FILE : ${filepath}`);
                fs.unlinkSync(filepath);
            } catch(e) {
            }
        }
        response.send();
    });

    busboy.on('error', async () => {
        console.log("busboy on error");
    });
    busboy.end(request.rawBody);
});

2.呼び出しページ

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="uft-8">
</head>
<body>
<h1>Cloud Vision</h1>
	<form action="https://{Firebase Cloud Functions のURL}/helloVisions" method="POST" enctype="multipart/form-data">
		<p><input type="file" name="upload_file1"></p>
		<p><input type="submit" value="Submit"></p>
	</form>
<h1>Log Console</h1>
	<a href="https://console.cloud.google.com/logs" target="_blnak">GCP Log Console</a>
</body>
</html>

3.実行

OCRにかけるサンプル画像

ソースコードを適当にキャプチャ。

upload_sample_image

アップロードページ

上記アップロードページを起動

スクリーンショット 2020-08-09 12.43.02

上記アップロードの、Cloud Vision にサンプルファイルを指定し、submit

結果が返ってきた!OK! ながかった。

スクリーンショット 2020-08-09 12.43.14

GCPのログ

ログにも、想定した結果がはきだされている。

log

4.tscエラー対応

最終的には、以下の対応を行う必要はなかった(冒頭の記述参照)のだが、メモ。

(1) Cannot find name ‘AsyncIterator’

https://github.com/apollographql/graphql-subscriptions/issues/83

tsconfig.json

  "compilerOptions": {
     "lib": [
       "esnext.asynciterable"
     ],

(2) require(…) is not a function

https://stackoverflow.com/questions/33007878/nodejs-typeerror-require-is-not-a-function

5.参考

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です