2025, Oct 05 13:16

Аффинный сдвиг в Pillow: как правильно сместить изображение

Разбираемся, почему аффинный transform в Pillow сдвигает контент «не туда», и как сместить изображение вправо правильно: знак параметра c и рабочий пример.

Смещение изображения аффинным преобразованием в Pillow кажется простым, пока перенос не начинает идти «не туда». Положительное горизонтальное смещение вроде бы должно двигать пиксели вправо, но результат уезжает влево. Причина в том, как Pillow задаёт аффинное отображение для transform: выходные пиксели берутся из входного изображения по обратному преобразованию.

Демонстрация проблемы

Следующий фрагмент кода пытается сдвинуть изображение на 85 пикселей вправо. Вместо этого содержимое визуально уходит влево.

from PIL import Image

img_src = Image.open('Test image.png')
params_wrong = [1, 0, 85, 0, 1, 0]
img_shifted = img_src.transform(
    img_src.size,
    Image.AFFINE,
    params_wrong,
    Image.BILINEAR,
    fillcolor=(150, 150, 150)
)
img_shifted.save('Transformed image.png')

Почему это происходит

В аффинном преобразовании Pillow кортеж параметров (a, b, c, d, e, f) задаёт первые две строки обратного преобразования. Отображение идёт от выхода ко входу. Для каждого выходного пикселя с координатами (x, y) значение берётся из входа в точке (a x + b y + c, d x + e y + f).

Эта функция принимает 6-элементный кортеж (a, b, c, d, e, f), содержащий первые две строки матрицы, обратной аффинному преобразованию. Для каждого пикселя (x, y) на выходном изображении новое значение берётся из позиции (a x + b y + c, d x + e y + f) во входном изображении, с округлением до ближайшего пикселя.

Это означает, что положительное c заставляет выход выбирать значения во входных точках, смещённых вправо, поэтому картинка кажется сдвинутой влево. Чтобы содержимое на холсте поехало вправо, нужно брать выборку из входа, смещённого влево на ту же величину.

Исправление и корректный пример

Чтобы сдвинуть изображение на 85 пикселей вправо, используйте отрицательное c. Каждый выходной пиксель (x, y) возьмёт значение из входной координаты (x − 85, y), и сдвиг получится вправо.

from PIL import Image

source_img = Image.open('Test image.png')
params_fix = [1, 0, -85, 0, 1, 0]
result_img = source_img.transform(
    source_img.size,
    Image.AFFINE,
    params_fix,
    Image.BILINEAR,
    fillcolor=(150, 150, 150)
)
result_img.save('Transformed image.png')

Почему это важно

Сбивающее с толку поведение геометрических преобразований тратит время и порождает скрытые ошибки в пайплайнах препроцессинга, генерации синтетических данных и аугментации изображений. Понимание того, что transform в Pillow выбирает значения по обратному отображению, объясняет, почему знаки у сдвига выглядят «перевёрнутыми», и помогает не сдвинуть контент не в ту сторону.

Вывод

Если аффинный сдвиг в Pillow идёт не туда, проверьте знак. Кортеж (a, b, c, d, e, f) относится к обратному отображению, поэтому для горизонтального сдвига вправо параметр c должен быть отрицательным. Думайте о том, откуда выход берёт значения во входном изображении — и направление сразу станет очевидным.

Статья основана на вопросе на StackOverflow от Christopher Pratt и ответе от J Earls.