MAGAZINE

ルーターマガジン

データ/フォーマット

Gemini APIで類似画像をクラスタリング!枚数増加時の精度劣化を回避する方法

2025.06.13
Pocket

はじめに

Googleが提供するGemini APIは、単なるテキスト処理にとどまらず、画像の内容を理解することが可能です。この機能を活用することで、画像データをこれまで以上に柔軟に扱えるようになります。 Gemini APIでは、Files APIを使用することで最大3600枚もの大量の画像を扱うことができます。 そこで本記事では、gemini apiで多数の画像をまとめて添付することによって画像のクラスタリングができないか検証してみました。

Gemini APIで画像を複数入力

本検証では、Gemini 2.0 Flashというモデルを使用します。
以下に、Gemini APIで複数枚の画像を入力する手順を説明します。

アップロード先の指定

まず、Geminiにリクエストを送信する前に、入力したい画像をFiles APIを使用してアップロードする必要があります。 入力したいファイルのアップロード先を、以下のコマンドで取得します。curlの-iオプションを指定して、レスポンスヘッダを出力します。このレスポンスヘッダのx-goog-upload-urlの値が次に使用するアップロード先のurlになります。$GEMINI_API_KEYこちらで取得したご自身のGemini APIキーを環境変数に設定してください。

コマンド:
curl -si "https://generativelanguage.googleapis.com/upload/v1beta/files?key=$GEMINI_API_KEY" \
-H "X-Goog-Upload-Protocol: resumable" \
-H "X-Goog-Upload-Command: start" \
-H "X-Goog-Upload-Header-Content-Type: image/png" \
-H "Content-Type: application/json" \
-d "{'file': {'display_name': '#{アップロードしたいファイルパス}'}}"
レスポンスヘッダ:
HTTP/2 200 
content-type: text/plain; charset=utf-8
x-guploader-uploadid: AAO2Vworb67ZKGSDl4sZyB50NNPcjFS9bK-H6wpncEiAXhWiKXdTmzR7mn-_0vsfQz4S-GMHrEaQRKdLMKpaf0jq8CkqFgDDV9JuF0_YLX65lg
x-goog-upload-status: active
x-goog-upload-url: https://generativelanguage.googleapis.com/upload/v1beta/files?key=XXXXX&upload_id=YYYYY&upload_protocol=resumable
x-goog-upload-control-url: https://generativelanguage.googleapis.com/upload/v1beta/files?key=XXXXX&upload_id=YYYYY&upload_protocol=resumable
x-goog-upload-chunk-granularity: 8388608
x-goog-upload-header-x-google-esf-cloud-client-params: backend_service_name: "generativelanguage.googleapis.com" backend_fully_qualified_method: "google.ai.generativelanguage.v1beta.FileService.CreateFile"
x-goog-upload-header-x-google-session-info: GgQYECgLIAE6IxIhZ2VuZXJhdGl2ZWxhbmd1YWdlLmdvb2dsZWFwaXMuY29t
x-goog-upload-header-x-google-backends: unix:/tmp/esfbackend.1746054081.848135.1625565
x-goog-upload-header-content-type: application/json; charset=UTF-8
x-goog-upload-header-x-google-security-signals: FRAMEWORK=ONE_PLATFORM,ENV=borg,ENV_DEBUG=borg_user:genai-api;borg_job:prod.genai-api
x-goog-upload-header-vary: Origin
x-goog-upload-header-vary: X-Origin
x-goog-upload-header-vary: Referer
x-goog-upload-header-x-google-gfe-backend-request-cost: 37.9626
content-length: 0
date: Thu, 01 May 2025 01:53:19 GMT
server: UploadServer
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

ファイルのアップロード

次に、先ほど取得したアップロード先のurlであるx-goog-upload-urlを以下のように指定して、Geminiに入力したい画像ファイルをアップロードします。このレスポンスのuriの値(以下、file_uri)を次のGeminiへのリクエストで使用します。

コマンド:
curl -s "#{x-goog-upload-url}" \
-H "X-Goog-Upload-Offset: 0" \
-H "X-Goog-Upload-Command: upload, finalize" \
--data-binary "@#{アップロードしたいファイルパス}"
レスポンス:
{
  "file": {
    "name": "files/xxxxx",
    "displayName": "image.png",
    "Type": "image/png",
    "sizeBytes": "2318361",
    "createTime": "2025-05-01T02:15:42.854224Z",
    "updateTime": "2025-05-01T02:15:42.854224Z",
    "expirationTime": "2025-05-03T02:15:42.811718110Z",
    "sha256Hash": "MTVhNTdiNDg4MmU0YmUzYTkxY2JhOWQ2ZWY3YTAzNGJjODM1MTZiZTBjMzE5OGE0ZDQ4NmZlNDRmOGMyOTk3Ng==",
    "uri": "https://generativelanguage.googleapis.com/v1beta/files/xxxxx",
    "state": "ACTIVE",
    "source": "UPLOADED"
  }
}

複数の画像を入力する場合は、入力したい画像すべてに対して、上記「アップロード先の指定」と「ファイルのアップロード」の手順を繰り返します。

リクエストの送信

先ほど取得したfile_uriを指定することで、Geminiへのリクエストに画像を組み込むことができます。以下は、2つの画像を入力する場合の例です。

curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
  -H 'Content-Type: application/json' \
  -X POST \
  -d '{
    "contents": [
      {
        "parts": [
          {
            "text": "#{プロンプト}"
          },
          {
            "file_data": {
              "mime_type": "image/png",
              "file_uri": "#{file_uri_1}"
            }
          },
          {
            "file_data": {
              "mime_type": "image/png",
              "file_uri": "#{file_uri_2}"
             }
          }
        ]
      }
    ]
  }'

複数画像を入力して、クラスタリング

クラスタリング対象

今回の検証では、広告画像をクラスタリングの対象とします。広告画像には、同じ広告内容でありながら、サイズやレイアウト、解像度が異なるものが存在します。 そのような、サイズ違いだが内容は同じである広告画像を同一画像として判定することを目指します。 まず、ChatGPTを使用して架空ブランドの広告画像を5種類作成しました。 次に、これらの5種類の広告画像それぞれに対して、「縦長(1024x1536)」「正方形(1024x1024)」「横長(1536x1024)」の3つの異なるサイズの画像を生成しました。 これにより、合計15枚(5種類 x 3パターン)の広告画像を用意し、これらが内容に基づいて正しく5つのグループにクラスタリングされるかを検証します。

クラスタリング方法

Gemini APIに送信するプロンプトは以下の通りです。
先ほどアップロードしたfile_uriと画像を対応付けるように指示しています。

これらの画像15枚すべてについて、「画像内のテキストや人物、背景の色やイラストの形状」が同じもの同士でグルーピングしてください。
テキストや背景、登場人物などが一部でも異なるものは別グループとして判別してください。
入力されたfile_uriに対応する画像のグループidを出力してください

クラスタリング結果

5種類の広告画像、計15枚についてクラスタリングを実行した結果は以下の通りです。各グループに、内容が同一の画像が出力されていることが確認できます。 15枚程度であれば、100%の精度で判定できていることが分かります。

レスポンス(一部省略): 
[
  {
    "group_id": 1,
    "file_uri": [
      "file_uri_2.png",
      "file_uri_5.png",
      "file_uri_10.png"
    ],
    "short_theme": "Beauty",
    "background_color": "white",
    "color_pattern": "white and gray"
  },
  {
    "group_id": 2,
    "file_uri": [
      "file_uri_8.png",
      "file_uri_12.png",
      "file_uri_14.png"
    ],
    "short_theme": "BabyWater",
    "background_color": "pink",
    "color_pattern": "pink and white"
  },
  {
    "group_id": 3,
    "file_uri": [
      "file_uri_1.png",
      "file_uri_4.png",
      "file_uri_13.png"
    ],
    "short_theme": "Kiwamizu",
    "background_color": "dark blue",
    "color_pattern": "blue and silver"
  },
  {
    "group_id": 4,
    "file_uri": [
      "file_uri_3.png",
      "file_uri_6.png",
      "file_uri_7.png"
    ],
    "short_theme": "Uruskin",
    "background_color": "beige",
    "color_pattern": "beige and brown"
  },
  {
    "group_id": 5,
    "file_uri": [
      "file_uri_0.png",
      "file_uri_9.png",
      "file_uri_11.png"
    ],
    "short_theme": "MasterLotion",
    "background_color": "sky blue",
    "color_pattern": "blue and white"
  }
]
(省略)
"usageMetadata": {
    "promptTokenCount": 3738,
    "candidatesTokenCount": 506,
    "totalTokenCount": 4244,
    "promptTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 124
      },
      {
        "modality": "IMAGE",
        "tokenCount": 3614
      }
    ],
    "candidatesTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 506
      }
    ]
  }

分類された画像

画像枚数が増えると精度が急激に下がる

クラスタリング対象

次に、入力する画像枚数を増やした場合にクラスタリングの精度がどう変化するかを検証します。 広告の種類数を7種類に増やし、さらに「縦長」「正方形」「横長」のそれぞれのサイズごとの枚数を2枚ずつに増やします。この合計42枚 (7種類 x 3パターン x 2枚) の画像について、同様のクラスタリングを試みます。

クラスタリング結果

画像枚数を増やしてクラスタリングした結果、正しく分類できたのは42枚中19枚のみで、精度は約45%まで大幅に低下してしまいました。 さらに、42枚の画像を入力したにもかかわらず、33枚分の分類結果しか出力されませんでした。

この時のAPI利用状況は、入力トークン量が10,960、出力トークン量が587でした。なお、Gemini 2.0 Flashの無料枠における1分あたりのトークン上限は1,000,000なので、トークン制限の問題ではないと考えられます。

入力したfile_uriに基づいて画像を分類させるようにしましたが、実際に入力した画像の内容とGeminiのレスポンスの画像の内容が一致しませんでした。
画像枚数が多くなると、入力した画像とfile_uriの対応づけがうまくいかなくなり、精度が大幅に低下する傾向があるようです。 Geminiに入力する時点で、入力した複数の画像を識別するための識別子がないことが問題の一因と考えられます。 この課題を踏まえ、入力した複数の画像をGeminiにも人間にも明確に識別できるような別の入力形式がないか検討しました。 そこで、複数の画像を結合して1つの動画にし、動画のフレーム番号や経過時間を各画像の識別子として活用できるのではないかという仮説に至りました。

複数画像を結合した動画を入力してクラスタリング

入力画像を結合して動画化

まず、クラスタリング対象の42枚の画像を、1枚あたり1秒間表示されるように結合し、42秒の動画ファイルを作成します。 今回使用したFFmpegのコマンドは以下のとおりです。入力画像それぞれのアスペクト比を維持しつつ、余白は白で埋めて結合しています。

ffmpeg -r 1 -i "image%02d.png" \
-vf "scale=720:480:force_original_aspect_ratio=decrease,pad=720:480:-1:-1:color=white" \
output.mp4

クラスタリング方法

画像の場合と同様に、Files APIを用いて動画ファイルをアップロードし、そのfile_uriを用いてGeminiにリクエストを送信します。今回は動画を入力するため、mime_typevideo/mp4と指定する必要があることに注意してください。

curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
  -H 'Content-Type: application/json' \
  -X POST \
  -d '{
    "contents": [
      {
        "parts": [
          {
            "text": "#{プロンプト}"
          },
          {
            "file_data": {
              "mime_type": "video/mp4",
              "file_uri": "#{file_uri}"
            }
          }
        ]
      }
    ]
  }'

プロンプトでは、入力した動画の秒数ごとに画像をグループ分けするように指示します。

この動画の0秒目から1秒おきの画面42枚すべてについて、
「画像内のテキストや人物、背景の色やイラストの形状」が同じもの同士でグルーピングしてください。
テキストや背景、登場人物などが一部でも異なるものは別グループとして判別してください。

クラスタリング結果

画像を結合した動画の秒数に基づいてクラスタリングを行った結果、42件すべてが期待通りにグループ分けされ、精度が100%に改善しました。

レスポンス(一部省略):
[
    {
        "group_id": 1,
        "sec": ["00:00", "00:15", "00:18", "00:28", "00:34", "00:38"],
        "short_theme": "Woman and white bottle",
        "background_color": "white",
        "color_pattern": "monochrome"
    },
    {
        "group_id": 2,
        "sec": ["00:01", "00:03", "00:10", "00:17", "00:21", "00:22"],
        "short_theme": "BabyWater on pink background",
        "background_color": "pink",
        "color_pattern": "monochrome"
    },
    {
        "group_id": 3,
        "sec": ["00:02", "00:07", "00:13", "00:30", "00:35", "00:37"],
        "short_theme": "Kiwamizu on blue background",
        "background_color": "blue",
        "color_pattern": "monochrome"
    },
    {
        "group_id": 4,
        "sec": ["00:04", "00:11", "00:27", "00:32", "00:33", "00:39"],
        "short_theme": "URUSKIN on beige background",
        "background_color": "beige",
        "color_pattern": "monochrome"
    },
    {
        "group_id": 5,
        "sec": ["00:05", "00:09", "00:16", "00:19", "00:25", "00:26"],
        "short_theme": "SnowSkin on snowy background",
        "background_color": "light blue",
        "color_pattern": "monochrome"
    },
    {
        "group_id": 6,
        "sec": ["00:06", "00:08", "00:12", "00:20", "00:29", "00:40"],
        "short_theme": "MasterLotion on sea background",
        "background_color": "light blue",
        "color_pattern": "colorful"
    },
    {
        "group_id": 7,
        "sec": ["00:14", "00:23", "00:24", "00:31", "00:36", "00:41"],
        "short_theme": "All-in-one gel on white background",
        "background_color": "white",
        "color_pattern": "monochrome"
    }
]
(省略)
"usageMetadata": {
    "promptTokenCount": 10767,
    "candidatesTokenCount": 808,
    "totalTokenCount": 11575,
    "promptTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 107
      },
      {
        "modality": "VIDEO",
        "tokenCount": 10660
      }
    ],
    "candidatesTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 808
      }
    ]
  }

分類された画像

クラスタリング対象の母数が増えても、動画内の「秒数」という明確な識別子が各画像に対応付けられるため、Geminiは個々の画像を混同することなく処理できたと考えられます。結果として、入力画像と出力結果の対応が崩れず、非常に高い精度が得られました。

この時のAPI利用状況は、入力トークン量が10,767、出力トークン量が808であり、入力形式を多数の個別画像から単一の結合動画に変更しても、総入力トークン数はほとんど変化していません。つまり、動画入力が精度向上に寄与する一方で、コスト面でのデメリットが少ないと言えます。

まとめ

今回は、Gemini APIを利用した画像のクラスタリング方法についてご紹介しました。
Gemini APIで多数の画像を入力する際には、個別に画像を送信するよりも、画像を結合して1つの動画として入力する方が、画像の順序を認識する精度が高くなることが分かりました。 また、入力形式が複数画像の場合と結合動画の場合で入力トークンは変わらないため、より精度の高い動画入力の方がコストパフォーマンスに優れていると言えます。
今後、さらに多くの画像枚数での検証や、より複雑なクラスタリング条件での挙動についても調査していく価値があるでしょう。

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから