【ハンズオン】ReactとGoogle Cloud Vision APIで画像認識アプリを作ろう【TypeScript】

## はじめに
Google Cloud Vision APIは、Googleが提供する機械学習を活用した画像解析サービスです。

このAPIを使用することで、開発者は画像に含まれる情報を自動的に抽出し、様々な解析を行うことができます。

今回、Reactを使ってGoogle Cloud Vision APIを使って簡単な画像認識アプリを作ってみました。

## 1. 環境構築

Node環境があることを前提にして行います。

templateはtypescriptを指定して、Google Cloud Vision APIをコールするためにaxiosをインストールしておきます。

npm startしてReactの初期画面が表示されれば成功です。

```$ npx create-react-app google-vision-app --template typescript
$ cd google-vision-app
$ npm i axios
$ npm start
```

## 2. Cloud Vision APIの設定

![スクリーンショット 2024-05-25 17.03.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242816/44538361-a83e-ea4a-fd2c-1faa7f4d4737.png)

Cloud Vision APIはGCPの管理画面の検索窓から「Cloud Vision API」を実行し、有効化されていない場合は「有効化」するを押します。

「このAPIを使用するには、認証情報が必要になる可能性があります」と表示された場合は、「認証情報」から「認証情報を作成」します。

作成後、APIキーが表示されるのでコピーしておきます。

## 3. 環境変数の設定

作成したAPIキーを.envファイルを作成し、環境変数を設定します。

creare-react-appで作ったプロジェクトはprocess.env.REACT_APP_から始まる変数を読み込みができるようになっています。

```.env
REACT_APP_GOOGLE_CLOUD_VISION_API_KEY=xxxxxxxxxxxx
```

## 4. Cloud Vision APIを呼び出す

Cloud Vision APIのキーを設定できたら、実際にCloud Vision APIを呼び出して結果を表示するようにします。

流れとしては、画像をアップロードしたら、Cloud Vision APIを呼び出して、レスポンスを整形して画面に表示するといった感じです。

```App.tsx

import React, { useState } from 'react';
import axios from 'axios';

interface LabelAnnotation {
  description: string;
  score: number;
}

const App: React.FC = () => {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [labels, setLabels] = useState<LabelAnnotation[]>([]);

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files && event.target.files.length > 0) {
      setSelectedFile(event.target.files[0]);
    }
  };

  const handleFileUpload = async () => {
    if (!selectedFile) return;

    const reader = new FileReader();
    reader.readAsDataURL(selectedFile);
    reader.onloadend = async () => {
      const base64String = reader.result?.toString().replace(/^data:image\/[a-z]+;base64,/, "");

      try {
        const apiKey = process.env.REACT_APP_GOOGLE_CLOUD_VISION_API_KEY;
        const response = await axios.post(
          `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`,
          {
            requests: [
              {
                image: {
                  content: base64String,
                },
                features: [
                  {
                    type: 'LABEL_DETECTION',
                    maxResults: 10,
                  },
                ],
              },
            ],
          }
        );

        const labelAnnotations = response.data.responses[0].labelAnnotations;
        const labels: LabelAnnotation[] = labelAnnotations.map((annotation: {description: string, score: string}) => ({
          description: annotation.description,
          score: annotation.score,
        }));

        setLabels(labels);
      } catch (error) {
        console.error('Error uploading file:', error);
      }
    };
  };

  return (
    <div className="App">
      <h1>Google Cloud Vision API Demo</h1>
      <input type="file" onChange={handleFileChange} />
      <button onClick={handleFileUpload}>アップロードして解析</button>
      {labels.length > 0 && (
        <div>
          <h2>解析結果:</h2>
          <table>
            <thead>
              <tr>
                <th>ラベル</th>
                <th>スコア</th>
              </tr>
            </thead>
            <tbody>
              {labels.map((label, index) => (
                <tr key={index}>
                  <td>{label.description}</td>
                  <td>{label.score.toFixed(2)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
};

export default App;
```

## 5. 画像認識アプリを動かす

![takenoko.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242816/aaab6a61-73d4-0d1d-b705-98fd764f69f5.jpeg)

では実際に動かしてみます。適当に画像を用意して、実行してみます。

今回はたけのこの画像を用意して、アップロードして解析してみました。結果は以下の通りです。

![スクリーンショット 2024-05-25 17.07.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242816/56bb210b-e597-6c00-9364-1bff9f4a701f.png)

食べ物や植物に対して高いスコアが出ています。他も材料や自然な食べ物、主食などわりと的確にラベリングできていると思います。


## 6. スタイルを整える

最後にスタイルを整えます。特にこだわりがないようであれば、そのままコピペして使用して大丈夫です。

```App.css
.App {
  text-align: center;
  font-family: 'Arial', sans-serif;
  margin: 20px;
}

h1 {
  color: #333;
  font-size: 2em;
  margin-bottom: 20px;
}

input[type="file"] {
  display: block;
  margin: 20px auto;
  padding: 10px;
  font-size: 1em;
}

button {
  background-color: #4CAF50;
  color: white;
  border: none;
  padding: 10px 20px;
  font-size: 1em;
  cursor: pointer;
  border-radius: 5px;
}

button:hover {
  background-color: #45a049;
}

table {
  margin: 20px auto;
  border-collapse: collapse;
  width: 80%;
  max-width: 600px;
}

table th, table td {
  border: 1px solid #ddd;
  padding: 8px;
}

table th {
  background-color: #f2f2f2;
  color: #333;
  text-align: left;
}

table tr:nth-child(even) {
  background-color: #f9f9f9;
}

table tr:hover {
  background-color: #ddd;
}

table th, table td {
  text-align: left;
}
```

CSSを書いたら、App.tsxに以下のimportを追加して完了です。
```App.tsx
import './App.css';
```
今度はラーメンの画像で実行してみます。

![ramen.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242816/43b97a54-b118-840f-62f3-3835bbb75271.jpeg)

![スクリーンショット 2024-05-25 22.06.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242816/b5ff40c7-bcc3-3c00-740c-acc97502650e.png)

ローカルの画面を見ると、良い感じのデザインになりました。あとは画像のプレビューやラベルの日本語化などしても良いかもしれません。


## おわりに

今回はCloud Vision APIを利用して簡単な画像認識アプリを実装しました。

画像認識してラベリングした結果をDBに保存するなどするとより実用的なアプリになるかと思います。

## JISOUのメンバー募集中！
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
実践的なカリキュラムで、あなたのエンジニアとしてのキャリアを最短で飛躍させましょう！
実践重視：即戦力を育てるアウトプット中心のプログラム。
モダンなスキル : Reactを中心としたモダンな技術を学べる。
キャリアアップ：スキルアップだけでなく、キャリアパスのサポートも充実。
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください！
▼▼▼
https://projisou.jp/