はじめに
画像ファイルの日本語文字認識のデモアプリを制作しました。
日本語のOCRをするにあたって、精度やスピードはVision APIがダントツらしいので実装してみました。
アプリケーション制作
フロントエンドは、個人的に馴染みのあるReactを選びました。
バックエンドは、拡張性も想定してLambdaを使うことにしました。
ただAmplifyを使ったり、API Gatewayを設置したりすると手間がかかりすぎると思った+後から修正できるはずなので、とりあえず関数URLを設定したLambdaのみを作って、動くところまで実装しています。
フロントエンドの準備
$ npm create vite@latest
Need to install the following packages:
create-vite@5.0.0
Ok to proceed? (y)
✔ Project name: … vision-api-demo
✔ Select a framework: › React
✔ Select a variant: › TypeScript
$ cd vision-api-demo
$ npm install
$ npm run dev
npm run dev
でローカルにアプリケーションが立ち上がれば大丈夫です。
Vision APIの準備
-
セットアップの手順|公式docsの1~3まで行う
- GCPからプロジェクトの設定を行なう
- VisionAPIを有効にする
サービスアカウントを作成
サービスアカウントのロールは「オーナー」にします。
サービスアカウントに紐づくJsonキーを作成してダウンロードしておきます。
Lambdaの準備
今回はマネージコンソールからLambdaだけを作成しました。
実行環境はPython3.10で構築しています。
GCPにアクセスするための設定
Lambdaレイヤーの準備
PythonからGCPにアクセスするライブラリをインストールしたレイヤーを作成します。
Lambdaと同じランタイム環境でインストールを行った方が良いので、Cloud9で以下のコマンドを使ってzipファイルを作成します。
mkdir google-cloud-vision
cd google-cloud-vision
mkdir python
pip3 install -t ./python urlib3<2
pip3 install -t ./python google-cloud-vision
zip -r google-cloud-vision.zip python
補足:urllib3のバージョンが2.xだとLambdaでエラーが発生するので、バージョン指定してます。
バックエンドとフロントエンドの連携
Lambdaは、画像を受け取ってVision APIにリクエストを送り結果を返す処理を書いています。
フロントは画像をアップロードしてLambdaにリクエストを送って結果を表示するようにしておきました。
import json
import base64
import boto3
from google.cloud import vision
def lambda_handler(event, context):
content = event['body']
img_bytes = base64.b64decode(content)
img_binary = base64.b64decode(img_bytes)
# Cloud Vision API呼び出し
client = vision.ImageAnnotatorClient()
image = vision.Image(content=img_binary)
response = client.document_text_detection(
image=image,
image_context={'language_hints': ['ja']}
)
# レスポンスからテキストデータを抽出
output_text = ''
for page in response.full_text_annotation.pages:
for block in page.blocks:
for paragraph in block.paragraphs:
for word in paragraph.words:
output_text += ''.join([
symbol.text for symbol in word.symbols
])
output_text += '\n'
return {
'statusCode': 200,
'result':"SUCCESS",
'output_text':output_text
}
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Container from '@mui/material/Container';
import CssBaseline from '@mui/material/CssBaseline';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import axios from "axios";
import { useState } from 'react';
import './App.css';
function App() {
const defaultTheme = createTheme();
const [imageData, setImageData] = useState(null)
const [base64Image, setBase64Image] = useState(null)
// ここを書き換える
const functionURL = "https://xxxxxxx.lambda-url.ap-northeast-1.on.aws/"
// ファイル選択時のイベント
function onFileChange(input) {
const files = input.target.files
if (files.length > 0) {
var file = files[0]
var reader = new FileReader()
reader.onload = (event) => {
setImageData(event.target.result)
setBase64Image(event.target.result.split(',')[1])
};
reader.readAsDataURL(file)
} else {
setImageData(null)
}
}
// 画像プレビューの定義
let preview
if (imageData != null) {
preview = (
<div>
<img src={imageData} width="100%"/>
</div>
)
}
// OCRリクエスト
const invoke = async() => {
const response = await axios.post(functionURL, base64Image)
console.log(response)
}
return (
<>
<ThemeProvider theme={defaultTheme}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
}}
>
<CssBaseline />
<Container component="main" sx={{ mt: 8, mb: 2 }} maxWidth="sm">
<Typography variant="h2" component="h1" gutterBottom>
文字認識アプリ
</Typography>
<Typography variant="h5" component="h2" gutterBottom>
<input type="file" accept="image/*" onChange={(e) => {onFileChange(e)}} />
<Button variant="contained" onClick={invoke}>
Submit
</Button>
</Typography>
<Typography variant="body1">
{preview}
</Typography>
</Container>
<Box
component="footer"
sx={{
py: 3,px: 2,mt: 'auto',
backgroundColor: (theme) =>
theme.palette.mode === 'light'
? theme.palette.grey[200]
: theme.palette.grey[800],
}}
>
<Container maxWidth="sm">
<Typography variant="body1">
My custom footer
</Typography>
</Container>
</Box>
</Box>
</ThemeProvider>
</>
)
}
export default App
おわりに
無事にバックエンドで画像を受け取って文字認識するところまでできました。
Vision APIの精度には驚かされましたね。
参考文献等
画像ファイル送信
Lambda × Vision API