EXIF Orientation: почему рамки съезжают после поворота изображения
Разметчик открывает фотографию товара и рисует рамку вокруг упаковки. В обучающем пайплайне файл читается иначе: одни просмотрщики применяют EXIF Orientation, другие работают с исходной сеткой пикселей. Изображение имеет другую ширину и высоту, а координаты рамки остались прежними. Модель получает правильный класс с ошибочным расположением объекта; на проверке это выглядит как плохая локализация.
Сначала выберите систему координат
EXIF Orientation — метаданные, которые сообщают, как показывать записанные пиксели. ImageOps.exif_transpose применяет эту ориентацию к пикселям. После поворота на 90 градусов прежние координаты рамки нельзя просто перенести без изменения. Нужно либо размечать и обучать в исходной системе пикселей, либо повернуть изображение и рамку вместе и сохранить результат в одной системе.
В примере изображение записано как 4×2 пикселя с ориентацией 6. Рамка в исходных координатах имеет формат xyxy и полуоткрытые границы. Код читает файл из памяти, применяет ориентацию и переводит рамку. Для этого поворота точка границы переходит по правилу (x, y) → (H − y, x), где H — исходная высота.
from io import BytesIO
from PIL import Image, ImageOps
raw = Image.new("RGB", (4, 2), "white")
exif = raw.getexif()
exif[274] = 6 # поворот на 90° по часовой стрелке при показе
buffer = BytesIO()
raw.save(buffer, format="JPEG", exif=exif)
buffer.seek(0)
loaded = Image.open(buffer)
oriented = ImageOps.exif_transpose(loaded)
box = (1, 0, 3, 2) # xyxy, правая и нижняя границы не включены
height = loaded.height
x1, y1, x2, y2 = box
new_box = (height - y2, x1, height - y1, x2)
print("до:", loaded.size, box)
print("после:", oriented.size, new_box)
assert oriented.size == (2, 4)
assert new_box == (0, 1, 2, 3)После нормализации размер становится 2×4, рамка — (0, 1, 2, 3). Эти значения рассчитаны и проверены в коде. Для других значений Orientation формула другая: возможны отражения и повороты. В рабочем импорте пройдите все реально встречающиеся значения EXIF и проверьте рамки на нескольких изображениях каждого типа.
Как не сломать датасет при исправлении
Не вызывайте нормализацию повторно на уже нормализованной картинке вместе с повторным переносом координат. После exif_transpose сохраните новую версию пикселей и связанной разметки, а исходный файл оставьте доступным для аудита. Запишите размер до и после, значение Orientation и версию преобразования. Обязательно проверьте, что все рамки находятся внутри новых границ, имеют положительную ширину и высоту и визуально покрывают объект.
Эта проблема отличается от масштаба letterbox. Если рамка съезжает только после подготовки входа модели, посмотрите разбор координат после letterbox. Здесь ошибка возникает раньше — при чтении и ориентации самого файла. Исправлять её обратным масштабированием в конце инференса поздно: модель уже обучалась на неверных примерах.
При массовой проверке сгруппируйте файлы по исходному значению Orientation и посчитайте, сколько рамок вышло за новые границы после переноса. Нулевая доля выходов необходима, но недостаточна: формула с перепутанной осью иногда оставляет рамку внутри картинки. Поэтому сохраните несколько контрольных изображений с рамками для каждого встреченного поворота. Если загрузчик удаляет EXIF без поворота пикселей, тот же файл может выглядеть иначе в браузере и в обучении; сверяйте уже декодированные пиксели, поступающие модели.
В курсе «Computer Vision Engineer: от детекции и Doc-AI до продакшна» вы готовите и проверяете датасеты, обучаете детекторы и собираете сервис с контролем качества. Этот путь помогает довести исправление координат от файла разметки до проверяемого инференса. Откройте программу курса.
Продолжить на практике
- Computer Vision Engineer: от детекции и Doc-AI до продакшна ↗
Практический курс по Computer Vision. Соберёте продакшн-сервис: датасет → обучение (YOLO/RT-DETR, SAM), трекинг, OCR/Doc-AI, веб-приложение (UI+API) и мониторинг. Экспорт в ONNX/TensorRT/OpenVINO, батчинг/квантование под CPU/GPU. Плюс профессиональные блоки: mAP/IoU/Dice и калибровка доверия, QA разметки и аугментации, p95/стоимость, дрейф данных и алерты, guardrails (NMS/правила), A/B-тесты, Triton/gRPC/FastAPI.
