ClaudeでPDFの座標がずれる原因と直し方
PDFはサーバー側で制御できない寸法にラスタライズされるため、返ってきた座標を元ページへそのまま当てはめるとズレます。ズレの起きる仕組みと、自前でラスタライズしてから送る回避策をコード付きで解説します。
Claudeにバウンディングボックス(画像内の要素を囲む矩形の座標)を返させると、PDFではその座標が元のページ上の位置とズレることがあります。原因は、PDFの各ページがサーバー側で制御できない寸法にラスタライズされるためです。通常の画像とは対処法が違うので、ズレの起きる仕組みと回避策を実装レベルで確認します。
座標が合っているかどうかは、OCRパイプライン・フォーム抽出・チャート解析・UI要素の位置特定のように、画像の特定領域に対して後続処理を実行するタスクで直接効いてきます。座標が数十ピクセルでもズレると、切り出した領域が対象からはみ出したり、クリック座標が別の要素を指したりします。見た目のズレは小さくても、後続の自動処理では致命的です。
PDFの座標はなぜズレるのか
バウンディングボックスとは、画像内の要素を四角形で囲んだときの左上と右下の座標([x1, y1, x2, y2])のことです。Claudeは表・フォーム項目・グラフの要素・UI部品などの領域を、この形式のピクセル座標で返せます。
ここでつまずきやすいのは、Claudeが返す座標が「送った画像」ではなく「Claudeが処理した後の画像」上の座標だという点です。Claudeは画像をモデルの解像度上限に合わせてリサイズしてから処理します。通常の画像なら、送信前に自分でリサイズしておけば座標はそのまま使えます。
PDFは事情が違います。PDFの各ページは、Anthropic側がサーバー内部で自動的にラスタライズ(画像化)します。生成される画像の寸法は指定できません。だから、返ってきた座標を元のPDFページへそのまま当てはめると位置がズレます。
Claudeの画像リサイズとパディングの仕組み
座標のズレを理解するには、画像処理の仕組みを先に押さえておく必要があります。Claudeは次の2つの上限を両方満たす最大サイズへ、縦横比を保ったまま画像を縮小します。
- 辺の上限: 標準解像度層で1568px、高解像度層で2576px
- 視覚トークンの上限:
⌈幅/28⌉ × ⌈高さ/28⌉で計算されるトークン数が、標準層で1568、高解像度層で4784を超えないこと
見落としやすいのは視覚トークンの上限です。130DPIでスキャンしたA4用紙は1075×1520pxで、どちらの辺も1568px未満です。しかし視覚トークンは39×55=2,145トークンかかり、標準層の上限1,568を超えます。結果、Claudeは924×1307へ縮小してから処理します。辺の上限だけを見ていると、この縮小に気づけません。
さらにClaudeは、リサイズ後の画像を28pxの倍数まで右端と下端だけパディングします(924×1307は924×1316になります)。パディング部分に画像の内容は含まれません。正規化やリスケールの計算は、パディング後ではなくリサイズ後の寸法で行います。パディング後の寸法で割ると、座標全体がわずかにズレます。
PDFで「事前リサイズ」が使えない理由
通常の画像なら、送信前に自分でリサイズしておけば座標のズレを防げます。リサイズ後の寸法さえ分かっていれば、Claudeが返す座標をそのまま使えるからです。
PDFではこの手が使えません。PDFの各ページはClaude側がサーバー内部でラスタライズし、生成される画像の寸法はこちらから見えず制御もできません。事前リサイズの前提である「送った画像の寸法を自分が把握している」が、PDFでは成立しないのです。
回避策は1つです。PDFのページを自分でラスタライズしてから、画像として送信します。こうすれば「送った画像=Claudeが処理した画像」という前提が戻り、通常の画像と同じ事前リサイズの手法が使えます。ピクセル単位で座標を正確に扱いたいOCRパイプラインやフォーム抽出では、この一手間が要ります。
ラスタライズ自体は、PDFレンダリングライブラリでページごとにDPI(解像度)を指定してPNG化するだけです。DPIを固定しておけば、同じPDFを何度処理しても寸法が一定になり、座標の再現性も保てます。逆にDPIを都度変えると、同じ文書でもリサイズの有無や縮小率が実行ごとに変わり、座標のズレ方も変わります。バッチ処理では、DPIをコードの外(設定値)で固定しておくと事故が減ります。
リサイズ後の寸法を計算する参考実装(Python)
import math
def count_image_tokens(width: int, height: int) -> int:
return math.ceil(width / 28) * math.ceil(height / 28)
def resized_size(width, height, max_edge=1568, max_tokens=1568):
def fits(w, h):
return (
math.ceil(w / 28) * 28 <= max_edge
and math.ceil(h / 28) * 28 <= max_edge
and count_image_tokens(w, h) <= max_tokens
)
if fits(width, height):
return (width, height)
if height > width:
h, w = resized_size(height, width, max_edge, max_tokens)
return (w, h)
aspect = width / height
lo, hi = 1, width
while lo + 1 < hi:
mid = (lo + hi) // 2
if fits(mid, max(round(mid / aspect), 1)):
lo = mid
else:
hi = mid
return (lo, max(round(lo / aspect), 1))
# A4スキャンの例
print(resized_size(1075, 1520)) # (924, 1307)自前でラスタライズしたページ画像をこの関数に通しておけば、送信前に「Claudeが実際に処理する寸法」を先に把握できます。高解像度層のモデルを使う場合はmax_edge=2576, max_tokens=4784に置き換えます。
座標のズレを防ぐ3つの方法 — 使い分け早見表
座標のズレへの対処は、状況に応じて3つの選択肢に分かれます。
| 状況 | 対処法 | やること |
|---|---|---|
| 画像を自分で用意できる | 対処法事前リサイズ | やること送信前にリサイズ計算を行い、Claudeが処理する寸法と一致させる |
| PDFのページを扱う | 対処法自前でラスタライズしてから事前リサイズ | やることページを画像化し、通常画像と同じ手順に落とし込む |
| リサイズの発生自体を検知したい | 対処法transformationsで明示的にエラー化 | やること画像ブロックに"oversized_image": "error"を指定する |
3つ目のtransformationsは、リサイズの発生そのものを検知する仕組みです。画像コンテンツブロックに"oversized_image": "error"を指定すると、その画像がリサイズされるようなリクエストは処理前に400エラーで弾かれます。エラーメッセージには元の寸法と、リサイズせず処理できる最大寸法が含まれます。画像ソースの変更やモデルの解像度層の切り替えで座標がひそかにズレ始めても、サイレントな劣化ではなく明示的なエラーとして気づけます。ただしPDFのdocumentブロックはこの設定を受け付けません(document内にネストしたimageブロックには使えます)。
構造化されたJSONで座標を取得したい場合は、プロンプトで散文を求めず、structured outputsでJSON出力を固定する方法と組み合わせるのが確実です。[x1, y1, x2, y2]の配列を持つスキーマを定義しておけば、パース失敗のリスクも減らせます。
座標処理でよくあるつまずき
辺の上限だけで見積もってしまう
辺の上限(1568px / 2576px)だけを見て、視覚トークンの上限を見落とすケースが一番多いつまずきです。1920×1080のスクリーンショットは、辺の上限だけを見ると1568×882に縮むと思いがちですが、実際は1456×819まで縮みます。辺の計算だけで見積もると、座標が体系的にズレます。
パディング後の寸法で正規化する
リサイズ後の寸法とパディング後の寸法を混同すると、座標全体がわずかにズレます。正規化・リスケールの分母には、必ずリサイズ後(パディング前)の寸法を使います。
解像度層を取り違える
標準解像度層と高解像度層では、辺とトークンの上限が2倍以上違います。高解像度層のモデルに標準層の上限を当てはめてリスケールすると、縮小されていない画像まで縮小されたものとして扱ってしまい、座標が別方向にズレます。
token countingが通ってもリクエストが通るとは限らない
Token countingエンドポイントは、画像を完全処理せずに寸法からトークン数を見積もります。カウントが成功しても、Messages APIのリクエスト上限を満たす保証にはなりません。事前チェックとしては便利ですが、リクエスト時の拒否を防ぐものではありません。
スキャンのDPIを変えたら過去の座標データが合わなくなった
同じPDFでも、スキャンや変換時のDPIが変われば元画像の寸法が変わり、縮小の有無や縮小率も変わります。過去に取得した座標を新しいスキャンへ使い回すと、DPIの違いだけでズレが生じます。座標データはDPIとセットで保存しておくと、後から原因を切り分けやすくなります。
リサイズを強制エラー化する設計はなぜ座標バグの再発を防ぐか
事前リサイズも自前ラスタライズも、正しく実装できていれば座標はズレません。問題は、正しく実装できている状態がいつまで続くかです。画像ソースを切り替えた、モデルを高解像度層に変えた、上流のシステムが仕様変更した。こうした変化はリサイズの発生条件を静かに動かし、座標はズレたまま気づかれずに残ります。
transformationsの"oversized_image": "error"は、この種の劣化を「動くが精度が落ちる」から「動かなくなる」に変える設計です。座標がズレる余地を残したまま黙って通すのではなく、リサイズが起きた時点でリクエストごと落とします。座標の精度が業務に直結するパイプラインでは、遅くエラーになる方が、早く間違った位置を返すより扱いやすいと言えます。
とはいえ、この設定は精度を上げる仕組みではありません。劣化を検知する仕組みです。座標そのものを正確にする作業は、事前リサイズか自前ラスタライズのどちらかを正しく実装することでしか進みません。
PDFをFiles APIで扱う実装手順はClaude Files APIでPDFを処理する方法にまとめています。Files APIとAPI全体の位置付けを先に押さえたい場合はAnthropic API完全ガイドを参照してください。
まとめ
PDFの座標がズレる原因は、ページのラスタライズ寸法をサーバー側が決めていて送信側から制御できないことにあります。通常の画像は事前リサイズで防げますが、PDFは自分でページをラスタライズしてから画像として送るまでが対処の一手間です。リサイズの計算は辺の上限とトークンの上限の両方で行い、正規化はパディング前の寸法を分母にします。座標精度が重要なパイプラインにはtransformationsのoversized_image: "error"を仕込んでおきます。リサイズによる劣化を、サイレントな不具合ではなく明示的なエラーに変えられます。