5.2. Координаты и области¶
Обработка изображений действует на пиксели, и чтобы воздействовать на пиксель, алгоритм должен адресовать его по координате. Чтобы воздействовать на прямоугольник пикселей – то же самое: прямоугольник нужно описать способом, относительно которого алгоритм и код приложения согласуются между собой. Соглашение, которое модуль image использует для координат и прямоугольников, простое, но содержит одну деталь, сбивающую с толку читателей, привыкших к математическому соглашению, а не к соглашению компьютерной графики, и об этой детали стоит сказать прямо с самого начала.
5.2.1. Сетка пикселей¶
Пиксель (0, 0) – это верхний левый угол изображения. Ось x идёт вправо, поэтому большее значение x означает положение правее. Ось y идёт вниз, поэтому большее значение y означает положение ниже по изображению. Изображение размером ширина на высоту содержит пиксели с целочисленными координатами от (0, 0) до (width - 1, height - 1); пикселя в точке (width, 0) или (0, height) не существует – эти позиции являются правой и нижней границами, на один шаг дальше последнего реального пикселя в каждом направлении.
Направленная вниз ось y – это та самая деталь, упомянутая выше. Читатель, привыкший к геометрии на миллиметровой бумаге, ожидает, что большее значение y означает положение выше; здесь эта интуиция инвертирована в точности наоборот. Причина инверсии в том, что и цифровые датчики, и цифровые дисплеи работают, начиная с верхнего левого угла, и проходят вправо по каждой строке, сверху вниз; размещение пикселей в памяти в том же порядке делает соотношение между «позицией i в буфере» и «строкой r, столбцом c изображения» максимально простым арифметическим выражением – позиция i пикселя (x, y) есть просто y * width + x. Каждая библиотека обработки изображений согласилась на это устройство десятилетия назад по той же причине, и цена этому – одна небольшая мысленная поправка при первой работе с изображениями.
Система координат изображения: начало в верхнем левом углу, x идёт вправо, y идёт вниз. Прямоугольная область внутри изображения задаётся своим верхним левым углом (x, y) и своими размерами (w, h).¶
5.2.2. Прямоугольники¶
Большинство операций над изображением интересует не столько отдельный пиксель, сколько прямоугольник пикселей – область для просмотра, область для копирования наружу, кадр внутри кадра для вычисления статистики. Форма для задания прямоугольника выбирает простейшее возможное расширение соглашения для одиночного пикселя: задаётся координата верхнего левого угла, за которой следуют размеры прямоугольника, упакованные в кортеж из четырёх элементов (x, y, w, h). Пиксели внутри прямоугольника находятся в столбцах с x по x + w - 1 и в строках с y по y + h - 1.
Деталь, о которой здесь стоит сказать прямо, заключается в том, что w и h – это размеры, а не координаты нижнего правого угла. Прямоугольник (10, 20, 4, 3) покрывает столбцы 10, 11, 12, 13 и строки 20, 21, 22 – всего двенадцать пикселей – а не область, идущую от (10, 20) до (4, 3). Соглашение единообразно по всему модулю, поэтому, как только оно усвоено, ошибки прекращаются, но в первый раз оно действительно сбивает людей с толку.
Форма (x, y, w, h) встречается в трёх местах, которые выглядят различными, но разделяют одно соглашение. Первое – когда изображение описывает свою собственную область: прямоугольник, покрывающий всё изображение, есть (0, 0, width, height). Второе – когда метод обнаружения возвращает результат с ограничивающей рамкой – blob, rect, apriltag – и рамка сообщается обратно как (x, y, w, h). Третье – когда методу нужно указать работать с подобластью изображения, а не со всем кадром; именованный аргумент roi, ограничивающий область операции, принимает тот же кортеж из четырёх элементов.
Взять ограничивающую рамку у одного метода и передать её в аргумент roi следующего метода – один из самых распространённых паттернов в обработке изображений. Ограничивающая рамка грубого первого обнаружения сужает область поиска для более точного второго, и именно единообразный словарь для результатов обнаружения и аргументов методов делает этот паттерн настолько простым, насколько он есть – одна форма кортежа, используемая одинаково с обеих сторон передачи.
5.2.3. Целочисленные адреса, дробные центроиды¶
Сами адреса пикселей являются целыми числами. Пиксель либо находится, либо не находится в заданном целочисленном столбце и строке, и вопрос о том, что находится в координате (40.5, 30.7), поставлен некорректно – нет пикселя, сидящего точно в этой позиции. Однако некоторые величины, которые модуль image выводит из позиций пикселей, являются дробными, и стоит понять почему, чтобы это различие не подвело приложение позже.
Самый распространённый случай – это центроид – центр масс области. Для связной области пикселей центроид в форме с плавающей точкой – это среднее позиций входящих в неё пикселей, взвешенное по их плотности. Область, пиксели которой расположены на границе двух столбцов, будет иметь центроид x, равный, скажем, 41.6 – реальную позицию, которую глаз описал бы как «середину этой области», даже если ни один настоящий пиксель не сидит точно на этом x. Объекты результатов обнаружения несут обе формы как свойства только для чтения: целочисленную пару (cx / cy, полезную, когда позиция возвращается во что-то, что ожидает целочисленные координаты пикселей) и пару с плавающей точкой (cxf / cyf, полезную, когда позиция идёт в контур управления, который выигрывает от субпиксельного разрешения).
Другой случай – это смещение между двумя кадрами, измеренное в частотной области. Методы, анализирующие спектральное содержимое изображения, а не его пиксели напрямую, могут разрешать сдвиги тоньше одного пикселя и сообщают эти сдвиги как значения (dx, dy) с плавающей точкой.
Правило большого пальца: адреса пикселей – целые числа; позиции и сдвиги, выходящие из алгоритма, могут быть числами с плавающей точкой. Методы рисования принимают любую форму и округляют числа с плавающей точкой вниз до ближайшего целочисленного пикселя, когда результат должен попасть на сетку.
5.2.4. Декартовы и полярные координаты¶
Описанная до сих пор система является декартовой: каждый пиксель задаётся своим горизонтальным и вертикальным смещением от начала координат. Это та система, в которой хранятся байты – пиксель i в буфере соответствует пикселю в столбце i % width и строке i // width, при проходе строк сверху – и это та система, в которой каждый метод работает по умолчанию.
О втором представлении стоит знать, потому что некоторые алгоритмы работают в нём гораздо лучше. Полярные координаты задают каждый пиксель его расстоянием от выбранной центральной точки и углом между ним и опорным направлением. Пиксели изображения не сдвинулись – байты по-прежнему находятся в том же буфере с построчным размещением – но схема адресации переключилась с «насколько вправо и насколько вниз» на «насколько далеко от центра и под каким углом вокруг него».
Одна и та же точка P, заданная двумя способами: декартовы координаты (x, y) от верхнего левого начала, полярные координаты (r, theta) от выбранного центра.¶
Зачем вообще переключаться? Из-за двух тождеств, превращающих трудные поиски в лёгкие.
В полярных координатах поворот изображения вокруг выбранного центра – это та же операция, что и перенос его пикселей вдоль оси угла – направления x в перепроецированном изображении. Повёрнутая копия – это оригинал, сдвинутый влево или вправо в полярной форме.
В варианте логарифмически-полярных координат – ось расстояния использует логарифмический масштаб, ось угла остаётся линейной – масштабирование изображения вокруг выбранного центра – это та же операция, что и перенос его пикселей вдоль оси расстояния – направления y. Масштабированная копия – это оригинал, сдвинутый вверх или вниз в логарифмически-полярной форме.
Поэтому алгоритм, которому нужно распознать известный паттерн при повороте или масштабе, может вести свой поиск в полярном пространстве, где оба преобразования превращаются в обычные переносы. Переносы искать гораздо дешевле, чем повороты и масштабы, и именно полярное перепроецирование делает эту замену доступной.
Полярные координаты не заменяют декартовы для хранения пикселей; байты всегда живут на декартовой сетке. Модуль предоставляет пару методов, которые перепроецируют изображение из декартовой формы в полярную по требованию, алгоритм, которому нужны полярные координаты, делает свою работу, и либо результат проецируется обратно наружу, либо измерение в полярном пространстве используется напрямую. Этот механизм – единственная причина, по которой полярные координаты вообще появляются где-либо на поверхности модуля.
С декартовыми координатами для задания отдельных пикселей, кортежем из четырёх элементов (x, y, w, h) для задания их прямоугольников и полярными координатами, доступными, когда алгоритм выигрывает от них, приложение имеет полный словарь для задания того, где в изображении что-то находится. Что именно хранится в любой из этих позиций – это следующий слой фундамента.