3×2のPNGが、Pillowで向きを補正すると2×3になった。左上の画素も10から40へ変わる。
開いた画像を画面どおりの座標で crop するとずれる。自分は切り出し位置を確認してて、左上の値の変化に引っかかった。
LLMへ送る画像と、切り出しに使う画像の向きはそろえたい。
PillowでEXIFの向きを再現する
CPython 3.14.8、Pillow 12.3.0で確認。次を orientation.py に保存し、python3 orientation.py で動かす。PNGならJPEGの圧縮で画素値が変わるのを避けられる。
from io import BytesIO
from PIL import Image, ImageOps
image = Image.new("L", (3, 2))
image.putdata([10, 20, 30, 40, 50, 60])
exif = Image.Exif()
exif[274] = 6
buf = BytesIO()
image.save(buf, format="PNG", exif=exif)
buf.seek(0)
with Image.open(buf) as raw:
fixed = ImageOps.exif_transpose(raw)
print(raw.size, fixed.size)
print(raw.getpixel((0, 0)), fixed.getpixel((0, 0)))
print(raw.getexif().get(274), fixed.getexif().get(274))
again = ImageOps.exif_transpose(fixed)
assert again.size == fixed.size
assert again.tobytes() == fixed.tobytes()
出力はこうなる。assertも通った。
(3, 2) (2, 3)
10 40
6 None
cropに渡す座標はどちらの画像のものか
EXIFの274は向きのタグ。値6は表示時に時計回りへ90度回す指定で、保存された画素はそのまま残る。このPNGの raw では、左上は10だった。
ImageOps.pyの実装も確認した。exif_transpose は値6を ROTATE_270 に対応させ、画素を回してから向きのタグを削除する。Pillowの回転指定は反時計回りなので、この270度は時計回り90度と同じ。タグが消えるため、再適用しても回らない。
幅と高さだけ入れ替えても、左上の10は40にならない。
LLMへ送る画像は、向きの補正を済ませてからリサイズする。切り出しにもその画像を使う。元の解像度で切り出すなら、補正後の幅と高さを使って座標を戻す。返ってきた座標がピクセル値か正規化値かも、呼び出す側の契約として決めておく。
