Dolice で運営している壁紙アプリの自動タグ付けを、2026 年 7 月に一般提供となった Opus 4.7 の高解像度ビジョンへ切り替えたとき、和柄のグラデーションや浮世絵の細い線といった「微細な差」を拾えるようになった手応えがありました。
うれしくなって、パイプラインに流れる画像を全部そのまま原寸で送りました。翌週、入力トークンの請求を見て手が止まりました。分類精度は確かに上がっていたのに、画像入力のトークンだけで前週の約 2.4 倍に膨らんでいたのです。
縮小すればいい、という話ではありませんでした。風景か人物かを分けるだけなら 512px で十分ですが、近接した重複壁紙を見分けたり、UI サンプル画像の中の小さな文字を読んだりする場面では、縮小した瞬間に精度が崩れます。必要なのは「全部を縮小する」でも「全部を原寸で送る」でもなく、1 枚ごとに、その画像がどれだけの解像度を要求するかを決めるルールでした。
個人開発で複数の壁紙アプリを回している身として、精度を守りながらコストを削る線引きは切実でした。その decision rule をどう組んだか、そして週次でどれだけコストが下がったかを、実測とともに残します。
画像トークンは面積で決まる — 縮小は「コスト」ではなく「情報量」の調整
まず把握しておきたいのは、Claude が画像に課金するトークンは、ほぼ画素の面積で決まるという点です。ドキュメント上の概算式は次の通りです。
入力トークン ≈ (幅px × 高さpx) ÷ 750
そして長辺が 1568px を超える画像、または約 115 万画素を超える画像は、API 側で自動的に縮小されてから処理されます。つまり原寸で送っても、ある大きさから先はトークンが頭打ちになり、送信帯域だけを無駄に使うことになります。
私の壁紙パイプラインで count_tokens を使って実測した、解像度ティア別のおおよその入力トークンが下の表です。式はあくまで概算なので、本番運用では必ず count_tokens エンドポイントで実測することを強く推奨します。数十トークン単位でずれます。
| 送信時の長辺 | 概算画素 | 入力トークン(実測) | 1万枚あたり入力コスト(Sonnet 5 導入価格 $2/M 換算) |
| 384px | 約 15 万 | 約 200 | 約 $4.0 |
| 512px | 約 26 万 | 約 350 | 約 $7.0 |
| 768px | 約 59 万 | 約 790 | 約 $15.8 |
| 1024px | 約 105 万 | 約 1,400 | 約 $28.0 |
| 上限付近(約 115 万画素) | 約 115 万 | 約 1,540 | 約 $30.8 |
表を作って初めて腑に落ちたのですが、384px と 1024px では 1 万枚あたり 7 倍のコスト差があります。全部を原寸で送っていた私は、風景の分類にまで一番高い列を払っていたわけです。縮小は画質を落とす後ろ向きな操作ではなく、その判断に必要な情報量まで解像度を落とすという前向きな設計だと捉え直しました。
なお、base64 文字列としてうっかりテキスト欄に画像を詰めると、この面積ベースの課金が効かず桁違いのトークンを食います。image ブロックで渡す前提の話です。この落とし穴はツール結果の画像ブロックでトークンを節約するで詳しく検証しています。
どこで縮小が効き、どこで壊れるか
解像度を落とせるかどうかは、「そのタスクが画像のどの粒度を見ているか」で決まります。私の 30 カテゴリの壁紙分類で、縮小に耐えるものと耐えないものを仕分けると、はっきり二層に分かれました。
縮小してよい(512px で精度が落ちない)判定は、画像全体の構図やテーマを見るものでした。風景・人物・抽象・季節といった大分類は、384〜512px でも Opus 4.7 は安定して当てます。むしろ縮小したほうがノイズが減って安定する場面すらありました。
原寸が要る判定は、細部の差が結論を分けるものでした。具体的には次の三つです。
| 判定の種類 | 縮小すると起きること | 必要な長辺の目安 |
| 微細テクスチャ(和柄・グラデーション・ノイズ加工) | 模様が潰れて「無地」に誤分類 | 1024px 以上 |
| 近接重複の判定(ほぼ同じ 2 枚の差分) | 差分が消えて別物を「同一」と判定 | 1024px 以上 |
| 画像内の小さな文字(サンプルUI・ロゴ) | 文字が読めず内容を取り違える | 上限付近 |
Opus 4.7 の高解像度ビジョン強化が効くのは、まさにこの下の層です。前のモデルでは原寸で送っても微細テクスチャの読み取りが不安定でしたが、4.7 では原寸を渡せば安定して拾うようになりました。逆に言えば、高解像度ビジョンの恩恵を受けたい画像にだけ原寸を払えばよく、それ以外は縮小で十分ということです。この線引きが decision rule の骨格になります。
1枚ごとに解像度を決める preflight
設計の核は、「まず縮小ティアで安く判定し、その結果を見て、必要なときだけ原寸へ格上げする」という二段構えです。最初から原寸で送らないのがコスト面の肝で、confidence が低いか、対象が細部依存クラスのときだけ格上げします。
縮小と送信を担う preflight を Python で書くとこうなります。Pillow で長辺を指定ティアに収め、image ブロックとして Claude に渡します。
import base64, io, json
from PIL import Image
from anthropic import Anthropic
client = Anthropic()
# 細部依存クラス(縮小に耐えないカテゴリ)を明示しておく
FINE_DETAIL_CLASSES = {"和柄", "グラデーション", "浮世絵", "ノイズ加工"}
def encode_at(path: str, long_edge: int) -> dict:
"""画像を指定した長辺まで縮小し、image ブロック用の dict を返す。"""
img = Image.open(path).convert("RGB")
w, h = img.size
scale = long_edge / max(w, h)
if scale < 1.0: # 拡大はしない。縮小のみ
img = img.resize((round(w * scale), round(h * scale)), Image.LANCZOS)
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=90)
b64 = base64.standard_b64encode(buf.getvalue()).decode()
return {
"type": "image",
"source": {"type": "base64", "media_type": "image/jpeg", "data": b64},
}
def classify(path: str, long_edge: int, model: str) -> dict:
"""指定解像度・指定モデルで 1 枚を分類し、ラベルと自己申告 confidence を得る。"""
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{
"role": "user",
"content": [
encode_at(path, long_edge),
{"type": "text", "text":
"この壁紙を 30 カテゴリのいずれかに分類し、"
'JSON で {"label": ..., "confidence": 0.0-1.0} だけ返してください。'},
],
}],
)
return json.loads(msg.content[0].text)
ここで大事なのは scale < 1.0 のガードです。元画像より大きくアップスケールしても情報は増えず、トークンだけが増えます。縮小は情報量を減らす操作、拡大は無駄なコスト、という非対称をコードに落としておけば、うっかりの拡大で本番のコストが膨らむ事態を回避できます。
二段のエスカレーション梯子と棄却
preflight の上に、判定を格上げしていく梯子を載せます。段は三つです。まず安い縮小+Sonnet 5 で当て、迷ったら原寸+Opus 4.7 に上げ、それでも決められなければ人手へ棄却します。
DOWNSCALE, FULLSIZE = 512, 1568
SONNET, OPUS = "claude-sonnet-5", "claude-opus-4-7"
def decide(path: str) -> dict:
# 段1: 縮小 + Sonnet 5 で安く当てる
r = classify(path, DOWNSCALE, SONNET)
# 細部依存クラスに触れた、または自信が薄いときだけ格上げ
need_detail = r["label"] in FINE_DETAIL_CLASSES or r["confidence"] < 0.75
if not need_detail:
return {"label": r["label"], "route": "downscale/sonnet", "conf": r["confidence"]}
# 段2: 原寸 + Opus 4.7 の高解像度ビジョンで見直す
r2 = classify(path, FULLSIZE, OPUS)
if r2["confidence"] >= 0.85:
return {"label": r2["label"], "route": "fullsize/opus", "conf": r2["confidence"]}
# 段3: それでも決まらなければ人手キューへ棄却
return {"label": None, "route": "abstain", "conf": r2["confidence"]}
段1 で終われば 1 枚 350 トークン、段2 まで行っても大半の枚数は段1 で止まるので、原寸コストを払うのは本当に細部が要る画像だけになります。0.75 と 0.85 の二つのしきい値は、クラスごとに目標 precision から逆算して決めるのが本筋です。全クラス一律のしきい値が少数クラスを静かに壊す罠は、画像分類の信頼度を鵜呑みにしないで検証したので、そちらの較正手順と組み合わせてください。
棄却(段3)を捨て札にしないことも大切です。私は棄却された画像を優先度つきの人手キューに流し、そこで付けた正解ラベルを翌週のしきい値の再調整に回しています。棄却は失敗ではなく、ルールを育てる入力です。
Before/After — 一律原寸から解像度ルールへ
切り替えの前後を、同じ 1 週間ぶんの処理量(約 9,400 枚)でそろえて比較しました。
| 指標 | Before(一律 原寸+Opus) | After(解像度ルール) |
| 原寸で送った枚数 | 9,400 枚(100%) | 1,860 枚(約 20%) |
| 画像入力トークン合計 | 約 1,447 万 | 約 553 万 |
| 週の画像入力コスト | 約 $60 | 約 $22 |
| 細部依存クラスの precision | 0.94 | 0.94 |
| 人手キューへの棄却 | 約 40 枚/週 | 約 52 枚/週 |
画像入力コストは週 $60 から $22 へ、約 63% 下がりました。狙いどおり、精度を決める細部依存クラスの precision は 0.94 のまま動いていません。原寸を払うべき 2 割の画像にだけ払い、残り 8 割を縮小に回した結果です。
棄却が週 40 枚から 52 枚へ少し増えたのは、縮小で自信が持てなかった画像が正直に手を挙げている状態で、むしろ健全だと受け止めています。精度を落として安くしたのではなく、払う場所を選び直したというのが、この設計のいちばん伝えたいところです。壁紙の自動分類そのものの土台については、壁紙 30 カテゴリのバッチ分類を実運用した記録にまとめてあります。
次の一歩
まず手元の分類タスクで、対象画像を 512px に縮小したときと原寸のときで、同じ 50 枚の判定がどれだけ変わるかを測ってみてください。差がほとんど出ないなら、あなたのタスクは今日から縮小に回せます。差が出るクラスだけを FINE_DETAIL_CLASSES に入れれば、この decision rule の骨格はそのまま使えます。
私自身、壁紙という細部が命の題材だからこそ「全部原寸」に逃げていましたが、測ってみると原寸が本当に要るのは 2 割でした。解像度を一段落とす勇気は、実測が与えてくれます。お読みいただきありがとうございました。