RustでGCP VisionAIを使ってチョコっと物体検出してみる.APIキーで.
ナマステ!
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"
ドキュメント
何にしろとりあえず公式ドキュメントをさらっと眺めましょう.
APIを使って画像のラベル検出するクイックスタートガイドです
流れはわかったでしょうか.
GCPの準備,APIキーの発行
この記事を参考にしました
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型を使用している理由ですが,型の定義が面倒だったからです.
必要なのはdescription
とscore
だけですから,そのためにいちいち型定義するのは面倒です.便利なことにserd_json
のValue
型は柔軟にjsonを扱えるので,今回はそれに甘えることにしました.
真面目にやるなら,ちゃんと型定義した方がいいと思います.
おわりに
これだけです.簡単ですね.
間違いやおかしな点があれば,是非指摘していただけると幸いです.
Discussion