はじめに
@NemesisさんのJavaで湯婆婆を実装してみる - Qiitaを発端に、様々な言語やフレームワーク、果てはサービスを題材に実装されている湯婆婆。
令和のHello World!
なんて呼ばれるぐらいプログラミングの基礎問題として優れているかと思います。
「基礎的」なお題であるせいか、どの言語でも 「コマンドラインでの対話式」 で実装されていることが多いです。
記事に独自性を出すためにも、ここは原作を忠実に再現して 「入力は手書き」 そして 「入力内容をCloud Vision API
で読み取らせて例の処理を実行」 することにしました。
どうせなら指でスラスラ入力させたかったのでReactNative
で実装しています。
コード
import React, {useRef, useState} from 'react';
import {
StatusBar,
SafeAreaView,
StyleSheet,
Text,
View,
TouchableOpacity,
} from 'react-native';
import RNDrawOnScreen from 'react-native-draw-on-screen';
import ViewShot, {captureRef} from 'react-native-view-shot';
// GCPで取得したAPIキー
const GCP_API_KEY = 'xxxxxxxxxxxx';
const Yubaba = () => {
const RNDraw = useRef(null);
const RNViewShot = useRef(null);
const [name, setName] = useState('');
const newName = name.substr(Math.floor(Math.random() * name.length), 1);
// クリアボタン押下イベント
const onPressClear = () => {
RNDraw?.current?.clear();
setName('');
};
// OKボタン押下イベント
const onPressOk = () => {
// react-native-view-shotで描画部分のキャプチャを取得
captureRef(RNViewShot, {result: 'base64'}).then(async (data) => {
// リクエストボディ作成
const body = JSON.stringify({
requests: [
{
features: [
{
// テキスト認識
type: 'TEXT_DETECTION',
// 取得したい結果数
maxResults: 1,
},
],
image: {
// base64エンコードした画像をそのままセット
content: data,
},
},
],
});
// CloudVisionAPI実行
const response = await fetch(
'https://vision.googleapis.com/v1/images:annotate?key=' + GCP_API_KEY,
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
body: body,
},
);
// レスポンス解析
const resJson = await response.json();
const description =
resJson.responses[0]?.textAnnotations[0]?.description || '';
// 正常にテキストが読み取れていたらsetName実行
if (description) {
// 改行コードが含まれていることがあるため除外
setName(description.replace(/\r?\n/g, ''));
}
});
};
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.contentWrap}>
<Text>{`契約書だよ。そこに名前を書きな。`}</Text>
</View>
<ViewShot style={styles.canves} ref={RNViewShot}>
<RNDrawOnScreen penColor={'black'} strokeWidth={10} ref={RNDraw} />
</ViewShot>
<View style={styles.buttonWrap}>
<TouchableOpacity
style={[styles.button, {borderColor: '#007AFF'}]}
onPress={onPressOk}>
<Text style={[styles.buttonText, {color: '#007AFF'}]}>{`OK`}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, {borderColor: '#FF7A00'}]}
onPress={onPressClear}>
<Text
style={[styles.buttonText, {color: '#FF7A00'}]}>{`クリア`}</Text>
</TouchableOpacity>
</View>
{!!name && (
<View style={styles.contentWrap}>
<Text>{`フン。${name}というのかい。贅沢な名だねぇ。`}</Text>
<Text>{`今からお前の名前は${newName}だ。いいかい、${newName}だよ。\n分かったら返事をするんだ、${newName}!!`}</Text>
</View>
)}
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
contentWrap: {
width: '100%',
margin: '4%',
marginTop: '8%',
},
canves: {
height: '30%',
margin: '2%',
borderWidth: 2,
borderColor: '#ccc',
backgroundColor: '#FFF',
},
buttonWrap: {
margin: '2%',
width: '100%',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
button: {
borderWidth: 1,
margin: '2%',
padding: 10,
borderRadius: 4,
},
buttonText: {
fontSize: 14,
},
});
export default Yubaba;
コードの解説
基本的な湯婆婆は@dryttさんのReact (JavaScript) で湯婆婆を実装してみる - Qiitaのロジックを踏襲しているので、
ここではこの湯婆婆の特色であるCloudVisionAPI
を使った手書き文字の読み取り、について掘り下げます。
手書きUI
手書きのUI
にはreact-native-draw-on-screenを使用しています。
文字認識を行いやすくするため、文字色は黒で線の太さも固定としています。
さらに手書きした内容をいったん画像として保持したいので、react-native-view-shotを使って画像化したい要素をラップしています。
import RNDrawOnScreen from 'react-native-draw-on-screen';
import ViewShot, {captureRef} from 'react-native-view-shot';
// ...略
<ViewShot style={styles.canves} ref={RNViewShot}>
<RNDrawOnScreen penColor={'black'} strokeWidth={10} ref={RNDraw} />
</ViewShot>
キャプチャ
手書きが完了したらreact-native-view-shot
でキャプチャを撮ります。
CloudVisionAPI
にはbase64
で送りつけたいのでオプションで指定しています。
※指定しなかった場合は端末ストレージ内に配置されたキャプチャのパスがdata
に入ります。
captureRef(RNViewShot, {result: 'base64'}).then(async (data) => {/** dataにbase64エンコードした内容がそのまま入る */ }
CloudVisionAPIへ送信
あとはCloudVisionAPI
の仕様に従ってリクエストを投げるだけです。
先にGCP
プロジェクトを作成し、課金設定をしておく必要があります。
※1000リクエスト/月までは無料です。
// リクエストボディ作成
const body = JSON.stringify({
requests: [
{
features: [
{
// テキスト認識
type: 'TEXT_DETECTION',
// 取得したい結果数
maxResults: 1,
},
],
image: {
// base64エンコードした画像をそのままセット
content: data,
},
},
],
});
// CloudVisionAPI実行
const response = await fetch(
'https://vision.googleapis.com/v1/images:annotate?key=' + GCP_API_KEY,
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
body: body,
},
);
実行結果
そこは「千」じゃないんかーい!
おわりに
ここまで読んでいただきありがとうございます。
入門的とは言い難いので、湯婆婆本来の趣旨からは外れてしまったかもしれません。
Cloud Vision API
を使ったのは初めてですが、思ったよりも遥かに簡単に導入できました。
精度が落ちるかもしれませんが、カメラを起動して撮影した文字から名前を読み取ることもできます。