5.21. Масштабирование, отражение и обрезка

Все предыдущие подразделы работали с пикселями в тех же позициях, где они изначально находились. Семейство преобразований меняет это. Масштабирование отправляет каждый входной пиксель в другую выходную позицию, возможно сразу в несколько выходных позиций (при увеличении) или в позицию, общую с несколькими другими входными пикселями (при уменьшении). Отражение и поворот делают то же самое через другое отображение. Обрезка сохраняет прямоугольное подмножество входных пикселей и отбрасывает остальные.

Модуль image предоставляет это семейство через три метода, которые разделяют большинство своих аргументов и большую часть своего поведения:

  • copy() – создаёт копию изображения, возможно масштабированную, обрезанную или переориентированную.

  • crop() – та же операция, что и copy, но с расчётом на то, что приложение собирается выбрать подпрямоугольник из источника.

  • scale() – то же самое, но с расчётом на то, что приложение собирается изменить размер результата.

Все три используют одни и те же аргументы и один и тот же механизм преобразования; различие в том, куда по умолчанию попадает результат. copy() создаёт новое изображение, тогда как crop() и scale() изменяют источник на месте.

5.21.1. Общие аргументы

Один вызов объединяет любую комбинацию масштабирования, обрезки, ориентации и извлечения каналов, какую запросит приложение:

x_scale и y_scale масштабируют вход вдоль горизонтальной и вертикальной осей независимо. Оба по умолчанию равны 1.0 (без масштабирования). Разные значения для каждой дают неравномерное масштабирование – например, кадр, растянутый вдвое шире, чем он высок.

roi ограничивает вход прямоугольником исходного изображения, пропуская через остальную часть преобразования только эти пиксели. Это и есть часть операции «обрезка»: передайте roi, чтобы извлечь подобласть.

hint – это битовое поле флагов, которое выбирает метод интерполяции и любые отражения ориентации. Несколько флагов объединяются побитовым ИЛИ (hint=image.BILINEAR | image.HMIRROR). Флаги разбиваются на две группы – семейство интерполяции и семейство ориентации – которые не имеют отношения друг к другу, но используют одно и то же битовое поле.

rgb_channel выбирает один канал источника RGB565. 0 означает красный, 1 означает зелёный, 2 означает синий; результат выходит как изображение в оттенках серого, содержащее только этот канал. Полезно, например, для пороговой обработки только по красному каналу.

color_palette и alpha_palette переотображают значения пикселей через таблицу поиска на выходе, так же как это делают методы преобразования to_rainbow() и to_ironbow().

copy=True и copy_to_fb=True следуют тому же соглашению, что и любой другой метод, создающий результат – на месте по умолчанию, copy=True выделяет отдельный результат, copy_to_fb=True помещает результат в буфер кадра для предпросмотра в IDE.

5.21.2. Интерполяция: AREA, BILINEAR, BICUBIC

Когда масштабирование отправляет каждый выходной пиксель в позицию, которая не совпадает ни с одним отдельным входным пикселем, метод должен выбрать, какое значение записать. Этим управляют три флага:

image.BILINEAR интерполирует между четырьмя ближайшими входными пикселями с весами по их расстоянию от выходной позиции. Результат более гладкий, чем у метода ближайшего соседа, без заметных ступенчатостей на диагональных линиях, но дополнительная арифметика обходится примерно вчетверо дороже прохода методом ближайшего соседа. Правильный выбор для большинства задач увеличения и для любого нецелого коэффициента масштабирования.

image.BICUBIC интерполирует между шестнадцатью ближайшими входными пикселями с использованием кубической кривой, что даёт ещё более гладкие результаты ценой ещё большего объёма арифметики. Лучшее качество для требовательных к нему приложений, чувствительных к стоимости; редко стоит дополнительных вычислений для текущих кадров, которые IDE будет лишь отображать.

image.AREA усредняет каждый входной пиксель, попадающий внутрь области выходного пикселя – правильный алгоритм для уменьшения. Билинейная и бикубическая – это интерполяторы: они оценивают значение между пикселями источника, что нужно при увеличении, но при уменьшении каждый выходной пиксель покрывает множество пикселей источника, а интерполятор считывает лишь несколько ближайших – детали, которые он пропускает, возвращаются в виде наложения спектров. image.AREA вместо этого включает каждый покрытый пиксель в усреднение.

Алгоритм масштабирования по умолчанию без какого-либо hint – это метод ближайшего соседа, который является самым дешёвым и правильным ответом, когда источник уже находится в пиксельном разрешении назначения.

5.21.3. Ориентация: отражения и повороты

Флаги ориентации – это небольшой набор булевых преобразований, которые свободно сочетаются друг с другом и с флагами интерполяции:

  • image.VFLIP отражает изображение по вертикали (верх становится низом).

  • image.HMIRROR отражает его по горизонтали (лево становится правом).

  • image.TRANSPOSE меняет местами оси x и y (строки становятся столбцами).

Большинство поворотов получается путём комбинирования этих трёх. Модуль также предоставляет именованные сокращения:

  • image.ROTATE_90 (= VFLIP | TRANSPOSE)

  • image.ROTATE_180 (= HMIRROR | VFLIP)

  • image.ROTATE_270 (= HMIRROR | TRANSPOSE)

В коде:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. Обработка соотношения сторон

Когда соотношение сторон источника не совпадает с прямоугольником, в который он вписывается, три флага решают, что делать с этим несоответствием:

image.SCALE_ASPECT_KEEP сохраняет соотношение сторон источника и добавляет чёрные полосы к результату – источник масштабируется, пока не впишется внутрь назначения, а остаток назначения заполняется пустыми (нулевыми) пикселями. Правильный выбор, когда сохранение источника без искажений важнее, чем заполнение всего вывода.

image.SCALE_ASPECT_EXPAND сохраняет соотношение сторон источника и обрезает его – источник масштабируется, пока не заполнит назначение, а части, выходящие за пределы назначения, отсекаются. Правильный выбор, когда заполнение всего вывода важнее, чем видимость каждой части источника.

image.SCALE_ASPECT_IGNORE игнорирует соотношение сторон и растягивает источник, чтобы заполнить назначение, принимая любые вносимые при этом искажения. Правильный выбор, когда приложение уже учло искажение – например, когда размеры назначения на самом деле не являются прямоугольником той же сцены.

По умолчанию (без установленного флага соотношения сторон) ведёт себя так же, как SCALE_ASPECT_IGNORE: растягивает до заполнения. Приложения, для которых важно соотношение сторон, явно указывают один из трёх.

5.21.5. Когда какой выбирать

Большинство изменений размера используют scale() с парой x_scale / y_scale и подсказкой интерполяции:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

Большинство поворотов используют тот же вызов с hint=image.ROTATE_90 или аналогичным.

Обрезка использует crop() с нестандартным roi:

img.crop(roi=(40, 30, 200, 150))

Когда источник должен пережить операцию – захват опорного кадра, получение миниатюры кадра, который вот-вот будет обработан с разрушением – copy() создаёт результат как новое изображение и оставляет источник нетронутым:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

Именно это поведение по умолчанию и есть настоящее различие за тремя именами: scale и crop преобразуют на месте, copy выделяет память. Ключевые аргументы размещения результата перекрывают разрыв: copy=True у scale или crop выделяет результат как отдельный буфер в куче вместо перезаписи источника, а copy_to_fb=True у любого из трёх помещает его в буфер кадра для предпросмотра в IDE.