🍫

RustでGCP VisionAIを使ってチョコっと物体検出してみる.APIキーで.

2023/10/31に公開

ナマステ!

neruneruna7です.
あいさつです.

はじめに

RustでGCPのVisionAPIをチョコっと使ってみたので,そのメモです. ハロウィンなのでチョコをみなさんにあげます.絵文字で.👻🍫

APIキーを使ってアクセスしています

やりたいことは,画像を物体検出して,ラベルと確度が欲しいだけです.

2023/10/31現在,GCP公式からRust向けのクレートはありません.
非公式のクレートはあるかもしれませんが,ちょびっと使いたいだけなのにそれは面倒なので,そのほかメジャーなクレートだけでやってみます.

環境

Windows 11
Rust 1.72.1

使用したクレート

serde = "1.0.148"
serde_json = "1.0.93"

reqwest = { version = "0.11", features = ["json"] }
dotenvy = "0.15.7"
anyhow = "1.0.70"

ドキュメント

何にしろとりあえず公式ドキュメントをさらっと眺めましょう.
https://cloud.google.com/vision/docs?hl=ja

APIを使って画像のラベル検出するクイックスタートガイドです
https://cloud.google.com/vision/docs/detect-labels-image-api?hl=ja

流れはわかったでしょうか.

GCPの準備,APIキーの発行

この記事を参考にしました
https://zenn.dev/tmitsuoka0423/articles/get-gcp-api-key

Vision APIにアクセスする

いよいよアクセスしてみましょう.
とは言っても,ざっくり言うとhttps://vision.googleapis.com/v1/images:annotateにPOSTするだけです.

...おや,APIキーはどうするのでしょうか?
今回はURLのクエリパラメータにくっつけることにします.

https://vision.googleapis.com/v1/images:annotate?key={あなたの発行したAPIキー}

リクエストの確認

どのようにPOSTし,どのようなデータが返ってくるのか確認しましょう

ここに,次の形のjsonをPOSTします.

GCPのオブジェクトストレージ上に画像がある場合 / 公開URLでアクセスできる画像を使う場合

{
  "requests":[
    {
      "image":{
"source": {
          "imageUri": "gs://cloud-samples-data/vision/demo-img.jpg" // 画像のURI
        }
      },
      "features":[
        {
          "type":"LABEL_DETECTION", //どのような処理をするか
          "maxResults":2 //検出するラベルの数
        }
      ]
    }
  ]
}

Base64エンコードした画像を送る場合

{
  "requests":[
    {
      "image":{
        "content":"/9j/7QBEUGhvdG9zaG9...image contents...fXNWzvDEeYxxxzj/Coa6Bax//Z" //base64エンコードした文字列
      },
      "features":[
        {
          "type":"LABEL_DETECTION", //どのような処理をするか
          "maxResults":2 //検出するラベルの数
        }
      ]
    }
  ]
}

リプライの確認

すると次のようなものが返ってきます

descriptionが検出されたラベル名で,scoreが確度のようですね.

{
  "responses": [
    {
      "labelAnnotations": [
        {
          "mid": "/m/0199g",
          "description": "Bicycle",
          "score": 0.98430496,
          "topicality": 0.98430496
        },
        {
          "mid": "/m/083wq",
          "description": "Wheel",
          "score": 0.976651,
          "topicality": 0.976651
        }
      ]
    }
  ]
}

Rustからアクセスする

あとはこれをRustでやるだけです.

改めて,使用するクレートを載せておきます

serde = "1.0.148"
serde_json = "1.0.93"

reqwest = { version = "0.11", features = ["json"] }
dotenvy = "0.15.7"
anyhow = "1.0.70"

1つ注意点を.Rustで.envファイルを扱う際,dotenvクレートは現在メンテされていません.dotenvyクレートを使いましょう.

全体

URLとbodyを組み立てて,リクエストを送って,返ってきたjsonから必要なデータを取り出しているだけです.

/// visionAPIにリクエストを投げて,物体検出する 結果はラベルとスコアの組で返す
pub async fn object_detect(image_url: &str, api_key: &str) -> anyhow::Result<(String, String)> {
    let access_url = format!(
        "https://vision.googleapis.com/v1/images:annotate?key={}",
        api_key
    );

    let body = json!({
        "requests": [
            {
                "image": {
                    "source": {
                        "imageUri": image_url
                    }
                },
                "features": [
                    {
                        "type":"LABEL_DETECTION",
                        "maxResults":1
                    }
                ]
            }
        ]
    });

    let client = reqwest::Client::new();
    let res = client
        .post(&access_url)
        .header("Content-Type", "application/json")
        .json(&body)
        .send()
        .await?;

    // 構造を定義するのが大変なので,Value型を使う
    let value: Value = res.json().await?;

    let label = value[0]["labelAnnotations"][0]["description"].to_string();
    let score = value[0]["labelAnnotations"][0]["score"]
        .to_string()
        .parse()?;

    Ok((label, score))
}

特に詳しく解説する必要がないくらいコードそのままの処理です.

1つ,Value型を使用している理由ですが,型の定義が面倒だったからです.

必要なのはdescriptionscoreだけですから,そのためにいちいち型定義するのは面倒です.便利なことにserd_jsonValue型は柔軟にjsonを扱えるので,今回はそれに甘えることにしました.

真面目にやるなら,ちゃんと型定義した方がいいと思います.

おわりに

これだけです.簡単ですね.

間違いやおかしな点があれば,是非指摘していただけると幸いです.

Discussion