🌄

Gemini Pro Vision による画像タグ付けを Cloud Run ジョブで並列処理する

2023/12/18に公開

2023年は「Cloud Run を触って覚える」をテーマとした ひとりアドベントカレンダー を開催しており、Cloud Run のさまざまな機能や Cloud Run でよく使う構成などをご紹介しています。

18日目は Cloud Run ジョブで Gemini Pro Vision の API を使って画像にタグを付ける方法についてご紹介します。Gemini Pro および Gemini Pro Vision は現在、プレビューで提供しています。

https://cloud.google.com/blog/ja/products/ai-machine-learning/gemini-support-on-vertex-ai?hl=ja

Cloud Run の概要は「gihyo.jp」で解説していますので、こちらもぜひご覧ください。

https://gihyo.jp/article/2023/10/modern-app-development-on-google-cloud-03

API を有効化する

まずは使用する API を有効化しましょう。コマンドの実行は Cloud Shell で行います。

gcloud services enable \
  bigquery.googleapis.com \
  storage-api.googleapis.com \
  aiplatform.googleapis.com

数秒~数分後、API の有効化が完了します。

画像データを用意する

次に画像データを用意し、GCS バケットにアップロードしておきます。画像データは商品画像データなど、お好きな画像を用意してください。ここでは5枚の画像を用意した前提で進めます。

ここでは Google Merchandise Store の商品画像をいくつか使用しました。

https://shop.googlemerchandisestore.com/

ローカルに images フォルダを作り、その中に入れておきます。

images フォルダ
images フォルダ

次のコマンドでバケットを作成します。

gcloud storage buckets create gs://<BUCKET_NAME> \
  --location asia-northeast1

Cloud Shell に画像をアップロードするには、オプションメニューから [アップロード] を選びます。

アップロード
アップロード

[フォルダ] をクリックし images フォルダを選びます。

フォルダの指定
フォルダの指定

Cloud Shell へのアップロードが完了したら、次のコマンドで GCS バケットに画像ファイルをアップロードします。

gcloud storage cp images/* gs://<BUCKET_NAME>/

以上で GCS の準備は完了です。

BigQuery を用意する

次に BigQuery のデータセット、およびテーブルを用意します。

次のコマンドで BigQuery のバケットを作成します。

bq --location asia-northeast1 \
  mk \
  --dataset \
  <PROJECT_ID>:products

BigQuery テーブルに読み込ませる CSV ファイルを用意します。ここでは GCS バケットにアップロードした画像ファイルをリストし、CSV ファイルに加工します。

gcloud storage ls gs://<BUCKET_NAME>/ > tags.csv
awk '{print $0","}' tags.csv > import.csv

import.csv は次のようなファイルになります。

import.csv
gs://<BUCKET_NAME>/GGOEGHPB211899.jpg,
gs://<BUCKET_NAME>/GGOEGHPT221410.jpg,
gs://<BUCKET_NAME>/GGOEGXXX2037.jpg,
gs://<BUCKET_NAME>/GGOEGXXX2079.jpg,
gs://<BUCKET_NAME>/GGOEGXXX2217.jpg,

BigQuery のスキーマ定義ファイルを作成します。

schema.json
[
  {
    "name": "uri",
    "type": "STRING"
  },
  {
    "name": "tags",
    "type": "STRING",
    "mode": "NULLABLE"
  }
]

bq load を実行し、テーブルの作成とインポートを実行します。

bq load \
  --source_format=CSV \
  <PROJECT_ID>:products.tags \
  ./import.csv \
  ./schema.json

BigQuery コンソールを開き、次のクエリを実行しデータが入っていることを確認します。

SELECT uri, tags FROM `<PROJECT_ID>.products.tags`

BigQuery テーブルのクエリ結果
BigQuery テーブルのクエリ結果

Cloud Run ジョブをデプロイする

次に Cloud Run ジョブをデプロイしましょう。Git リポジトリからジョブに使用するサンプルコードをクローンします。

git clone https://github.com/suwa-yuki/gemini-jobs-sample.git
cd gemini-jobs-sample

main.js では BigQuery のレコードごとに Gemini Pro Vision の API を呼び出すような実装をしています。

main.js
'use strict'
const {BigQuery} = require('@google-cloud/bigquery')
const {VertexAI} = require('@google-cloud/vertexai')
const {Storage} = require("@google-cloud/storage")
const {CLOUD_RUN_TASK_INDEX = 0} = process.env

const projectId = process.env.PROJECT_ID
const bq = new BigQuery()
const gcs = new Storage()
const vertexAI = new VertexAI({project: projectId, location: 'asia-northeast1'});
const model = vertexAI.preview.getGenerativeModel({
    model: "gemini-pro-vision",
    generation_config: {
        "max_output_tokens": 2048,
        "temperature": 0.4,
        "top_p": 1,
        "top_k": 32
    }
})
const prompt = "あなたはECサイトのコンテンツ管理者です。添付した商品画像に相応しいタグを生成してください。カンマ区切りで出力してください。"

const main = async () => {
    const query = `SELECT uri FROM \`${projectId}.products.tags\` LIMIT 1 OFFSET ${CLOUD_RUN_TASK_INDEX}`
    const data = await bq.query(query)
    const row = data[0][0]
    const uri = row.uri
    console.log(`execute : ${uri}`)
    const [bucket, object] = uri.replace('gs://', '').split('/')

    const blob = await gcs.bucket(bucket).file(object).download()
    const base64 = Buffer.from(blob[0]).toString('base64')

    const request = {
        "contents": [{
            "role": "user",
            "parts": [
              {
                "text": prompt
              },
              {
                "inlineData": {
                  "mimeType": "image/jpeg",
                  "data": base64
                }
              }
            ]
          }
        ]
    }

    const stream = await model.generateContentStream(request)
    const response = await stream.response
    const tags = JSON.stringify(response.candidates[0].content.parts[0].text)

    const insert = `UPDATE \`${projectId}.products.tags\` SET tags = ${tags} WHERE uri = "${uri}"`
    await bq.query(insert)
}
main().catch(err => {
    console.error(err)
    process.exit(1)
})

Cloud Run ジョブをデプロイします。ここではコマンドラインからビルド・デプロイします。

gcloud run jobs deploy gemini-jobs-sample \
  --region asia-northeast1 \
  --source . \
  --tasks 5 \
  --set-env-vars "PROJECT_ID=<PROJECT_ID>" \
  --execute-now

ここでは画像が5つなので --tasks5 を指定していますが、BigQuery テーブルのレコード数によって可変させたい場合は Workflows と組み合わせてタスクを動的に実行する形が良いと思います。詳しくは次の記事で解説しています。

https://zenn.dev/google_cloud_jp/articles/cloudrun-workflows

デプロイ時にジョブを実行するオプションを設定したので、デプロイ後実行されています。Cloud Run ジョブのコンソールでは、すべてのタスクの実行が成功していることが確認できました。

ジョブの実行結果の確認
ジョブの実行結果の確認

BigQuery テーブルをクエリしてみます。

SELECT uri, tags FROM `<PROJECT_ID>.products.tags`

Gemini Pro Vision で生成したタグが、各画像単位でセットされていることが確認できました。

BigQuery テーブルのクエリ結果
BigQuery テーブルのクエリ結果

まとめ

Gemini Pro Vision のマルチモーダル機能はとても面白いので、まずはコンソールからでも良いのでぜひ試してみてください。今回は一例として紹介しましたが、Cloud Run ジョブと組み合わせると Gemini Pro Vision の呼び出しをスケールさせながら並列処理することができます。今後様々なユースケースで活用できると思います。

Gemini Pro Vision については下記のドキュメントが参考になりますので、あわせてご確認ください!

Google Cloud Japan

Discussion