ReactNativeでOCR実装

##  はじめに
業務で扱ったOCRを実装してみた。
題材は千と千尋の神隠しの湯婆婆が千尋の名前を取るシーン

以下記事がおもしろくてやってみたくなった
[Javaで湯婆婆を実装してみる](https://qiita.com/Nemesis/items/c7192a7c510788d2cba2)

「入力は手書き」 そして 「入力内容をCloud Vision APIで読み取らせて例の処理を実行」 することにした


## コード
```react
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 = '';

const Yubaba = () => {
  const RNDraw = useRef(null);
  const RNViewShot = useRef(null);
  const [name, setName] = useState('');
  const { length } = name;
  const newName = name.substr(Math.floor(Math.random() * length), 1);

  // Cloud Vision API call
  const callCloudVisionAPI = async (data) => {
    const body = JSON.stringify({
      requests: [
        {
          features: [
            {
              // テキスト認識
              type: 'TEXT_DETECTION',
              // 取得したい結果数
              maxResults: 1,
            },
          ],
          image: {
            // base64エンコードした画像をそのままセット
            content: data,
          },
        },
      ],
    });

    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 || '';

    if (description) {
      setName(description.replace(/\r?\n/g, ''));
    }
  };

  // クリアボタン押下イベント
  const onPressClear = () => {
    RNDraw?.current?.clear();
    setName('');
  };

  // OKボタン押下イベント
  const onPressOk = () => {
    captureRef(RNViewShot, { result: 'base64' }).then(callCloudVisionAPI);
  };

  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,
  },
});
```

## 手書きUI
手書きのUIには、react-native-draw-on-screenを使用しています。文字認識を容易にするため、文字色は黒で線の太さも固定しています。さらに、手書きした内容を一時的に画像として保存したいため、react-native-view-shotを使用して画像化したい要素をラップしています。
　
```react
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"に格納されます。

★プロジェクトで学んだことが生きた。。。。

```react
// react-native-view-shotで描画部分のキャプチャを取得
 captureRef(RNViewShot, {result: 'base64'}).then(async (data)
```


## CloudVisionAPIへ送信
[Cloud Vision](https://cloud.google.com/vision/docs?hl=ja)(doc)
1000リクエスト/月までは無料。

```react
 // リクエストボディ作成
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,
  },
);
```
## 実行結果
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3433353432392f65656662666264302d653762382d626437632d666361322d3938316330393561626534642e676966.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3520671/9d8aca26-0411-020c-05cc-f3d87c990c27.gif)



## まとめ。ざっとコードの要点


1. **import文**: 必要なReact Nativeコンポーネントやライブラリをインポートしています。

2. **GCP_API_KEY**: Google Cloud Vision APIにアクセスするためのAPIキーが定義されています。APIキーはセキュリティのため、実際の値が示されていないことに注意してください。

3. **Yubabaコンポーネント**: アプリケーションのメインコンポーネントが定義されています。

4. **useRefフック**: RNDrawとRNViewShotの2つのリファレンスが作成されています。これらは、描画部分を手書きするためのコンポーネント（RNDrawOnScreen）と、描画部分をキャプチャするためのコンポーネント（ViewShot）への参照を保持します。

5. **useStateフック**: `name`という状態変数が定義されています。手書きの名前が入力された後、ここに名前がセットされて表示されます。

6. **onPressClear関数**: クリアボタンが押されたときに、手書きの名前をクリアして`name`を空にします。

7. **onPressOk関数**: OKボタンが押されたときに、手書きの名前をテキストとして認識し、`name`にセットします。

8. **captureRef関数**: ViewShotを使用して描画部分をキャプチャします。キャプチャした画像は、Google Cloud Vision APIに送信されます。

9. `fetch`を使用して、Cloud Vision APIに対してPOSTリクエストを送信し、手書きの名前をテキストとして認識します。

10. **stylesオブジェクト**: アプリケーションのスタイルが定義されています。

11. **return文**: アプリケーションのUIがJSXで記述されています。View、Text、TouchableOpacityなどのコンポーネントが組み合わさっています。

12. ViewShotの中にRNDrawOnScreenコンポーネントがあり、ユーザーが手書き入力を行えるようになっています。

13. 「OK」ボタンと「クリア」ボタンがあり、それぞれ`onPressOk`と`onPressClear`関数が紐づいています。

14. `name`が空でなければ、手書きされた名前に対して湯婆婆のセリフが表示されます。