Claude Media
Claudeで座標・バウンディングボックスを正確に取得する方法

Claudeで座標・バウンディングボックスを正確に取得する方法

Claudeに正確な座標を返させるコツは、0〜1000正規化ではなく絶対ピクセル座標を明示的に指示することです。structured outputsでJSON化する手順と、座標がリサイズ後の画像基準になる注意点を実装例付きでまとめます。

Claudeに正確な座標やバウンディングボックスを返させる最大のコツは、0〜1000のような正規化座標ではなく絶対ピクセル座標をプロンプトで明示的に指定することです。Claudeは正規化座標を求められると精度が落ちます。加えてstructured outputsでJSONスキーマを固定すれば、座標を散文ではなく機械可読なオブジェクトとして取得できます。

返ってくる座標はどの画像の座標か

Claudeは画像やPDFに含まれる表・フォーム項目・チャート要素・UI部品などの領域を、バウンディングボックスや点として返せます。座標系は画像の標準的な向きで、原点(0, 0)は画像の左上、xは右方向、yは下方向に増えます。

返ってくる座標は、アップロードした元画像の座標ではありません。Claudeがリサイズしたあとに実際に見ている画像の座標です。見た目には合っているようで実際にはズレた位置を指す、という不具合の出どころがここにあります。基本的な画像・PDFの扱い方はClaudeのPDF読み込み・画像解析の使い方、得意・苦手な作業の見分け方はClaude画像解析でできることにまとめています。

ステップ1: 絶対ピクセル座標で明示的にプロンプトする

プロンプトでは、絶対ピクセル座標であることを書き込みます。たとえば「各テーブルのバウンディングボックスを、ピクセル座標で[x1, y1, x2, y2](左上と右下の座標)として返してください」のように指定します。

「0から1000の範囲でバウンディングボックス座標を返してください」といった正規化座標の指示は避けます。Claudeは正規化座標のリクエストにうまく対応できません。正規化した値が最終的に必要でも、まずピクセル座標で受け取り、自分のコードで割り算する方が精度は安定します。

Claudeの空間認識には限界があります。座標・位置の特定は近似値にとどまるので、期待するフォーマットをプロンプトで明示し、大量処理に載せる前に結果を目視で数件スポットチェックします。

OCRパイプライン・フォーム抽出・チャート解析・UI要素の位置特定のように、特定領域に対して後続処理を走らせるタスクでは、座標のズレがそのまま処理ミスになります。

ステップ2: structured outputsでJSON形式に固定する

座標を「テーブルはだいたい画像の中央あたりにあります」のような散文で受け取ると、後続処理でパースが必要になり不安定です。structured outputsでoutput_config.formatにJSON Schemaを渡すと、指定した形のJSONが返ります。以下は検出した要素ごとにlabelとbbox(x1・y1・x2・y2)を返すスキーマ例です。

import anthropic
 
client = anthropic.Anthropic()
 
response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": "<base64エンコードした画像>",
                    },
                },
                {
                    "type": "text",
                    "text": "画像内のすべてのテーブルを検出し、絶対ピクセル座標でバウンディングボックスを返してください。",
                },
            ],
        }
    ],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "elements": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "label": {"type": "string"},
                                "bbox": {
                                    "type": "object",
                                    "properties": {
                                        "x1": {"type": "number"},
                                        "y1": {"type": "number"},
                                        "x2": {"type": "number"},
                                        "y2": {"type": "number"},
                                    },
                                    "required": ["x1", "y1", "x2", "y2"],
                                    "additionalProperties": False,
                                },
                            },
                            "required": ["label", "bbox"],
                            "additionalProperties": False,
                        },
                    }
                },
                "required": ["elements"],
                "additionalProperties": False,
            },
        }
    },
)
print(next(block.text for block in response.content if block.type == "text"))

output_config.formatが現行のパラメータ名です。以前のベータ版にあったoutput_formatは非推奨で、使うにはベータヘッダーstructured-outputs-2025-11-13が必要です(なしだと400エラー)。Python SDK 1.0以降ではclient.beta.messages.create()に渡すとTypeErrorにもなるため、新しいコードでは使いません。

Bedrock経由の場合は、structured outputsが使えるのは旧来のAmazon Bedrock連携(Opus 4.6以前向け)のOpus 4.6・Sonnet 4.6・Sonnet 4.5・Opus 4.5・Haiku 4.5に限られ、Claude in Amazon Bedrockでは使えません。

bboxをオブジェクトにしているのには理由があります。配列の長さを4に固定するminItemsやmaxItemsはスキーマで使えず、書くと400エラーになります(minItemsが受け付けられるのは0と1だけです)。[x1, y1, x2, y2]の配列でも受け取れますが、要素数がちょうど4であることはスキーマでは保証できません。

オブジェクトなら4つのキーをすべてrequiredに入れるだけで欠けを防げ、配列が必要なら自分のコードで変換できます。プロパティの並び順はスキーマに書いた順ではなく必須プロパティが先、任意プロパティが後という規則で決まるため、全部をrequiredにしておくと出力順序も安定します。

範囲外の座標はスキーマでは防げない

minimumやmaximumのような数値の制約は、structured outputsのJSON Schemaでは使えません。x2が画像の幅を超える値や、x1より小さい値が返ることを、スキーマ側では止められないということです。SDKのヘルパー(parse()やzodOutputFormatなど)を使うと、minimumなどの制約は説明文に移され、応答を受け取った側のコードで検証されます。

スキーマが保証するのは「4つの数値キーがそろったJSON」までです。座標の妥当性は受け取った側で検査します。範囲の基準は元画像ではなく、Claudeが見ているリサイズ後のサイズです。

def is_valid_bbox(bbox, width: int, height: int) -> bool:
    """width/height はリサイズ後(Claudeが見ている)画像のサイズ"""
    return (
        0 <= bbox.x1 < bbox.x2 <= width
        and 0 <= bbox.y1 < bbox.y2 <= height
    )

弾いた要素は捨てるか、同じ画像を再度投げる対象に回します。ここで落ちる件数が多いなら、リサイズ後のサイズの前提が違っている可能性があります(次のステップ3で扱います)。

Pydantic・Zodで型安全に取得する

生のJSON Schemaを自分で書く代わりに、PydanticやZodのモデル定義からスキーマを自動生成する方法もあります。Python SDKのclient.messages.parse()にPydanticモデルを渡すと、パース済みのオブジェクトがそのまま返り、辞書からのキー取り出しが不要になります。

from pydantic import BaseModel
from anthropic import Anthropic
 
 
class BoundingBox(BaseModel):
    x1: float
    y1: float
    x2: float
    y2: float
 
 
class DetectedElement(BaseModel):
    label: str
    bbox: BoundingBox
 
 
class DetectionResult(BaseModel):
    elements: list[DetectedElement]
 
 
client = Anthropic()
 
response = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[...],  # 画像ブロックとプロンプトはステップ2と同じ
    output_format=DetectionResult,
)
print(response.parsed_output.elements)

TypeScriptならZodスキーマをzodOutputFormat()に渡す形が同じ役割を果たします。output_format=はPython SDKのclient.messages.parse()専用の簡易記法で、内部ではoutput_config.formatに変換されます。他のSDKやCLI経由で呼ぶ場合はoutput_configを直接指定します。

バウンディングボックスを大量の画像に適用するときの注意

structured outputsは、内部でスキーマから制約付きサンプリング用の文法を生成してから使う仕組みです。同じスキーマを初めて使うリクエストでは、この文法をコンパイルする分だけレイテンシーが上乗せされます。コンパイル結果は最後に使ってから24時間キャッシュされるので、同じbboxスキーマを何百枚もの画像に繰り返し適用するバッチ処理では、2回目以降のリクエストは速くなります。

スキーマの構造やツールの構成を変えるとキャッシュは無効化されます。nameやdescriptionだけの変更なら無効化されません。

もう一点、structured outputsを使うとClaude側で出力形式を説明する追加のシステムプロンプトが自動的に挿入されるため、入力トークンがわずかに増えます。output_config.formatを変更すると、その会話スレッドのプロンプトキャッシュも無効化されます。大量の画像を1つの会話で連続処理する設計なら、スキーマを途中で変えない方がキャッシュヒット率を保てます。

ステップ3: 座標がズレるときはリサイズを疑う

ステップ1・2で正確な座標を取得しても、どの画像に対して描画・クリック・切り出しをするかを間違えると意味がありません。長辺かビジュアルトークン(28×28ピクセルの1ブロックが1トークン)のどちらかの上限を超える画像は自動的に縮小されます。縮小後の画像が座標の基準になります。

ティア対象モデル長辺上限ビジュアルトークン上限
高解像度対象モデルClaude 4.7以降のモデル長辺上限2576pxビジュアルトークン上限4784
標準対象モデルそれ以外のモデル長辺上限1568pxビジュアルトークン上限1568

長辺が上限以内でも、トークン数が上限を超えれば縮小されます。ほとんどの写真やスクリーンショットでは、最終サイズを決めるのはトークン上限です。長辺の上限が効くのは、パノラマや縦長のスマホ画面のような細長い画像に限られます。

標準ティアで1920×1080のスクリーンショットを送ると、1568×882ではなく1456×819に縮みます。長辺の上限だけで暗算すると、すべての座標が目標からずれます。

長辺が1568px未満でも縮むことがあります。A4を130dpiでスキャンした1075×1520の画像で見てみます。

数字

A4スキャン(1075×1520)を標準ティアに送ったとき

  • 元画像のトークン数

    39×55=2145

    どちらの辺も1568px未満

  • 上限

    1568

    2145が超えるので縮小される

  • Claudeが見る画像

    924×1307

    座標の基準はこのサイズ

  • 高解像度ティア

    1075×1520

    上限4784以内で縮小なし

トークン数 = ⌈幅÷28⌉ × ⌈高さ÷28⌉

標準ティアのモデルから高解像度ティアのモデルに切り替えるだけで、座標のズレが消えることがあります。縮小の計算方法と実際のズレ方は画像リサイズで座標がズレる原因と直し方でも扱っています。

縮小後の座標を元画像に戻す

ズレを避ける方法は2つあります。

くらべる

座標を合わせる2つの方法

換算なし

送る前にリサイズする

手元の画像がそのままClaudeの見る画像になるので、返った座標をそのまま使えます。画像の入力元を自分で制御できる場合に向きます。

後から補正

返った座標を換算する

上流から届く画像を変えられないときの方法です。リサイズ後のサイズを計算し、座標をその幅・高さで割って元画像の幅・高さを掛けます。

リサイズ後のサイズは、公式ドキュメントが載せている参照実装(resized_size)で求まります。

def to_original(x, y, original, resized):
    """original/resized はそれぞれ (幅, 高さ)"""
    return (x / resized[0] * original[0], y / resized[1] * original[1])
 
# A4スキャン: 元画像 1075×1520、リサイズ後 924×1307
print(to_original(462, 653.5, (1075, 1520), (924, 1307)))
# (537.5, 760.0)

Claudeは縮小後の画像を、右端と下端が28の倍数になるまで余白で埋めて処理します。924×1307なら、924×1316になります。割る数は余白込みの1316ではなく、リサイズ後の1307です。1316で割ると、上の例のyは760ではなく約755になり、元画像で5px近くずれます。

ズレを例外に変えるoversized_image

事前にリサイズしておいても、入力元が変わったりモデルのティアが変わったりすると、気づかないうちにサーバー側の縮小が復活します。画像ブロックにtransformationsを付けると、この静かなズレをエラーにできます。

{
  "type": "image",
  "source": { "type": "base64", "media_type": "image/png", "data": "..." },
  "transformations": { "oversized_image": "error" }
}

縮小されるはずの画像は、400のinvalid_request_errorで拒否されます。メッセージには画像のサイズと、収まる最大サイズが入ります。1920×1080を標準ティアのモデルに送った場合は次のとおりです。

messages.0.content.0: image dimensions 1920x1080 exceed the maximum image size of a model named on this request and would be downsized to 1456x819; scale the image to at most 1456x819 or set the image's oversized_image setting to "downsize"

指定は画像ブロックごとです。クリックに使うスクリーンショットだけをerrorにして、縮小されても困らないロゴは既定のまま(downsize)にする、といった混在ができます。PDFを渡すdocumentブロックにはこのフィールドがありません。トークン数を数えるエンドポイントも同じ判定をするので、推論の前にサイズが収まるかを試せます。

stop_reasonを確認してから使う

structured outputsはほとんどの場合スキーマどおりのJSONを返しますが、2つの例外があります。1つはstop_reason: "max_tokens"で、max_tokensに達して応答が途中で打ち切られたケースです。検出した要素が多い画像では、配列の途中でJSONが切れたまま返ることがあります。

バウンディングボックスの検出では、要素数を事前に見積もりにくいものです。レスポンスを使う前に必ずstop_reasonを確認し、max_tokensならより大きな値で再試行します。

もう1つはstop_reason: "refusal"で、安全性の理由でClaudeが拒否した場合です。ステータスは200で、トークンは課金されます。スキーマよりも拒否メッセージが優先されるため、stop_reasonを見ずにパースするとエラーハンドリングが漏れます。

よくあるつまずき

  • 小さい要素は精度が落ちる: 小さい要素は画像が縮小される過程で情報が失われやすく、座標の精度も下がります。対象領域を先にクロップして送り、返ってきた座標にクロップ原点の座標を足し戻すか、高解像度ティアのモデルを使います。
  • PDFの座標はページに正確に戻せない: PDFのページはサーバー側で寸法をコントロールできない形にラスタライズされるため、返ってきた座標を元のPDFページへ正確にマッピングできません。PDFで座標を扱うなら、自分でページを画像化してから送ります。
  • Computer Useツールでのクリック座標は、この記事の一般的な画像座標とは別の設計です。tool_resultで返すスクリーンショットは自動リサイズされず、上限を超えるとエラーで拒否されます。専用のスケーリング計算はComputer Useの座標ズレを直すスケーリング計算式にまとめています。
  • 取得した座標をそのまま実運用のトリガーにすると、精度低下がサイレントに本番処理へ流れ込みます。座標のズレは例外を投げないため、監視をエラーログだけに頼ると気づくのが遅れます。範囲チェックとサンプリングでの目視確認を挟みます。

まとめ

座標が合わないと感じたら、プロンプトを疑う前に、画像が縮小されていないか(標準ティアか高解像度ティアか)を調べます。ズレを見逃したくないパイプラインでは、oversized_image: "error"で縮小を拒否する形が向きます。

JSON出力の基本的な使い方はClaude JSONモードの使い方でも扱っています。

この記事を共有:XはてブLinkedIn