GCPのCloud Vision APIを使ってみた

# 前提
下記クイックスタートの「Vision APIの設定」は完了しているものとする。
[クイックスタート: Vision API を設定する  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/docs/setup?hl=ja)

# やること
Vision APIの呼び出し方は大きく分けて2つあり、（本当は3つ）
1つは、クライアントライブラリと呼ばれるGCP専用のライブラリを介して呼び出す方法（GCP推奨の方法）であり、
もう1つは、既に用意されているエンドポイントからREST APIを呼び出す方法である。
今回は、クライアントライブラリからのVisionAPIの実行と、REST API経由でのVisionAPIの実行をやってみる。
開発はNodeJSで行う。

# クライアントライブラリを使ってVision APIを実行する
公式の説明によると、クライアントライブラリは以下のようなものである。
>[クライアント ライブラリの説明  |  Cloud API  |  Google Cloud](https://cloud.google.com/apis/docs/client-libraries-explained?hl=ja)  
>
* Cloud API を簡単かつ直感的に使用できるように、各言語で慣用的な生成済みコードや手書きコードを提供します。
* Google での認証など、サーバーとの通信に関する下位レベルのすべての詳細を処理します。
* npm や pip など、使い慣れたパッケージ管理ツールを使用してインストールできます。
* 場合によっては、gRPC を使用してパフォーマンスを高めることもできます  

## プロジェクトの準備
適当なディレクトリを作り、その中で`npm init`を実行。
以降はこのディレクトリ配下で作業を行う。

```shell
mkdir my-app
cd my-app
npm init
```

### 注意
環境変数`GOOGLE_APPLICATION_CREDENTIALS`で、クイックスタートで作ったサービスアカウントキーファイルへのパスを設定しておく必要がある。

```
export GOOGLE_APPLICATION_CREDENTIALS=PATH_TO_KEY_FILE
```

## クライアントライブラリのインストール
```shell
npm install --save @google-cloud/vision
```

## 実装
index.jsを作って、下記のように実装。（公式ドキュメントのサンプルをほぼコピペ）
[クイックスタート: クライアント ライブラリの使用  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/docs/quickstart-client-libraries?hl=ja#using_the_client_library)

```js
async function quickstart() {
  // Imports the Google Cloud client library
  const vision = require("@google-cloud/vision");

  // Creates a client
  const client = new vision.ImageAnnotatorClient();

  // Performs label detection on the image file
  const [result] = await client.labelDetection("./sports.jpg");
  const labels = result.labelAnnotations;
  console.log("Labels:");
  labels.forEach((label) => console.log(label.description));
}
// これだけだとasync-awaitする必要はあまりない（なくても動く）
(async function () {
  await quickstart();
})();
```
上記で使用している`labelDetection`APIは、入力となる画像に含まれる物体、場所、活動、動物の種類、商品などを検出して、ラベルとしてアウトプットしてくれるもの。
例として、サッカー選手が真ん中に写っている画像を入力したときのアウトプットは以下のようになる。すごい。

```shell
node index.js 
Labels:
Player
Sports
Sports equipment
Football player
Team sport
Ball game
Soccer player
Soccer
Football
Tournament
```

# REST API経由でのVision APIの呼び出し
[Vision API リクエストを作成する  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/docs/request?hl=ja)

## エンドポイント
Vision APIのエンドポイントは以下のとおり。

```
POST https://vision.googleapis.com/v1/images:annotate
```

## 認証の設定
REST APIの実行には、「APIキー」または「サービスアカウント」による認証が必要であるが、今回は「APIキー」による認証を採用する。
APIキーの作成は、こちらを参照。 → [API キーの使用  |  認証  |  Google Cloud](https://cloud.google.com/docs/authentication/api-keys?hl=ja&visit_id=637246713360916796-291330020&rd=1)
APIキーを使う場合は、クエリパラメータとして渡す。

```
POST https://vision.googleapis.com/v1/images:annotate?key=YOUR_API_KEY
```

### APIキーの保護
上記で作成したAPIキーは、デフォルトだと何も制限されていないため、APIキーを知っている人なら誰でもAPIが実行できる状態になっている。
いくつかの制限方法があるが、今回は自分だけ使えれば良いので、IPアドレスでの制限を設定。
https://cloud.google.com/docs/authentication/api-keys?hl=ja&visit_id=637246713360916796-291330020&rd=1#api_key_restrictions
下記のようなサイトで、自分のグローバルIPアドレスを調べて、GCPコンソール上で設定する。
念のためIPv4とIPv6のアドレス両方を許可した。(NodeJSから直接REST APIを実行する場合は、IPv4の設定だけでOKだったが、自分の環境だとブラウザを介してAPIを実行させる場合はIPv6の設定が必要だった。)
https://test-ipv6.com/index.html.ja_JP

IPアドレス制限に引っかかった場合は、以下のようなエラーレスポンスが帰ってくる。
`XXX.XXX.XXX.XXX`の部分に自分のIPアドレスが入っているので、もし設定がうまくいっておらず下記のようなエラーが出た場合は、`XXX.XXX.XXX.XXX`を許可するように設定すればOK。（`XXX.XXX.XXX.XXX`はIPv6の場合もある。）

```json
{
    code: 403,
    message: 'The provided API key has an IP address restriction. The originating IP address of the call (XXX.XXX.XXX.XXX) violates this restriction.',
    status: 'PERMISSION_DENIED',
    details: [ [Object] ]
}
```

## リクエストの形式
```json
{
  "requests":[
    {
      "image":{
        "content":"/9j/7QBEUGhvdG9...image contents...eYxxxzj/Coa6Bax//Z"
      },
      "features":[
        {
          "type":"LABEL_DETECTION",
          "maxResults":1
        }
      ]
    }
  ]
}
```
### image
画像の指定方法は3種類ある。 

* base64形式で`image.content`に直接指定する
* gcpのストレージサービス Cloud Storage にあるデータのURIを`image.source.imageUri`に指定する
* HTTP または HTTPS でアクセス可能な公開URLを`image.source.imageUri`に指定する（※ このパターンはDOSなどの不正利用防止のため失敗することがあるので、本番での利用は非推奨）

今回は、base64形式のパターンを試す。
Base64形式へのエンコード方法は、下記に記載がある。
[Base64 エンコード  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/docs/base64?hl=ja)

### features
[Feature  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/docs/reference/rest/v1/Feature?hl=ja#Type)
`type`には、`FACE_DETECTION `や`LABEL_DETECTION`など、機能に応じた文字列を指定する。
`maxResults`には、上記で指定した機能で、最大何件検出を行うかを数値で指定する。

## レスポンス例（ラベル検出の場合）
```json
{
  "responses": [
    {
      "labelAnnotations": [
        {
          "mid": "/m/0bt9lr",
          "description": "dog",
          "score": 0.97346616
        },
        {
          "mid": "/m/09686",
          "description": "vertebrate",
          "score": 0.85700572
        }
      ]
    }
  ]
}
```
## 準備
### APIキーの設定
環境変数にAPIキーを設定する。 `export VISION_API_KEY={API_KEY}`
NodeJSでは、環境変数で設定した値を、`process.env["環境変数名"]`で取得できる。

### パッケージのインストール
リクエストを送るために、`axios`をインストールする。`npm i axios`

## 実装
今回は今後あまり使わなそうなランドマーク検出を使う。

```js
function toBase64(imagePath) {
  // Read the file into memory.
  const fs = require("fs");
  const imageFile = fs.readFileSync(imagePath);

  // Convert the image data to a Buffer and base64 encode it.
  return Buffer.from(imageFile).toString("base64");
}

(async function () {
  const axios = require("axios");

  const apiKey = process.env["VISION_API_KEY"];
  if (!apiKey) {
    console.log("Env 'VISION_API_KEY' must be set.");
    process.exit(1);
  }

  const visionApiUrl = `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`;
  const imagePath = "./liberty.jpeg";
  const options = {
    requests: [
      {
        image: {
          content: toBase64(imagePath),
        },
        features: [
          {
            type: "LANDMARK_DETECTION",
            maxResults: 3,
          },
        ],
      },
    ],
  };
  try {
    const result = await axios.post(visionApiUrl, options);
    console.log("Request success!");

    if (result.data && result.data.responses) {
      const responses = result.data.responses;
      
      responses.forEach((response) => {
        console.log("Landmarks:");
        response.landmarkAnnotations.forEach((annotation) =>
          console.log(annotation)
        );
      });
    }
  } catch (error) {
    console.error(error.response || error);
  }
})();
```
## レスポンス
自由の女神像の写真をインプットとした場合の結果

```shell
node index.js 
Request success!
Landmarks:
{
  mid: '/m/072p8',
  description: 'Statue of Liberty National Monument',
  score: 0.790907,
  boundingPoly: { vertices: [ [Object], [Object], [Object], [Object] ] },
  locations: [ { latLng: [Object] } ]
}
{
  mid: '/m/072p8',
  description: 'Statue of Liberty',
  score: 0.77413857,
  boundingPoly: { vertices: [ [Object], [Object], [Object], [Object] ] },
  locations: [ { latLng: [Object] } ]
}
{
  mid: '/m/02nd_',
  description: 'New York City',
  score: 0.6340594,
  boundingPoly: { vertices: [ [Object], [Object], [Object], [Object] ] },
  locations: [ { latLng: [Object] } ]
}
```

# (おまけ)Vision APIの実行回数の確認
Vision APIには、ラベル検出、テキスト検出、顔検出などの様々な種類の機能があるが、
それぞれについて、月1000回までが無料利用枠となっている。(機能によって違うが、1001回目からはだいたい$1.50[/1000回]かかる)
個人で、しかもお試しで使ってる分には1000回を超えることはないと思うが、心配だったので確認方法を探した。
[料金  |  Cloud Vision API  |  Google Cloud](https://cloud.google.com/vision/pricing?hl=ja)

GCPコンソールのホームにて、
デフォルトだと画面右にある「お支払い」カードの
「→請求の詳細を表示」を押すと、
機能ごとに何回使ったかがわかる。