5.3. Форматы пикселей¶
Алгоритм, обнаруживающий границы, ожидает, что каждый пиксель хранит значение яркости. Алгоритм, отслеживающий цветной объект, ожидает, что каждый пиксель несёт цвет. Алгоритм, выполняющий морфологическое замыкание, ожидает, что каждый пиксель либо включён, либо выключен. Формат пикселей, который несёт Image – один из перечисленных в каталоге Vision Sensors – это то, что делает эти ожидания проверяемыми заранее: формат заранее сообщает, в каком виде находятся пиксели и какие алгоритмы, следовательно, могут работать с ними без этапа преобразования.
Эта страница о том, как это ограничение проявляется на практике. Какой формат окажется правильным выбором, зависит от того, что собирается делать конвейер, а методы преобразования между форматами – это то, как конвейер, которому нужно несколько из них, связывает этапы воедино.
Пять несжатых форматов пикселей и то, как упаковываются их байты. JPEG и PNG здесь не нарисованы, потому что они представляют собой сжатые потоки переменной длины, а не сетки пикселей фиксированного размера.¶
5.3.1. Рабочая лошадка – оттенки серого¶
Большая часть классического машинного зрения сводится к работе со значениями яркости. Обнаружение границ, сопоставление шаблонов, декодирование AprilTag, оценка оптического потока, морфологические операторы, анализ блобов – все они на том уровне, на котором работают алгоритмы, смотрят на то, насколько ярок каждый пиксель и как эта яркость соотносится с яркостью соседних пикселей. Цвет сцены часто полезен для приложения, которое их вызывает, но самим алгоритмам он не нужен.
Формат оттенков серого даёт алгоритмам именно это, без накладных расходов. Один байт на пиксель хранит значение яркости от 0 (чёрный) до 255 (белый). Этот формат вдвое меньше RGB565 и YUV422 и втрое меньше RGB888, поэтому каждая операция обрабатывает меньше данных – и быстрее, и с меньшей нагрузкой на буфер. На меньших камерах, где буфер кадра конкурирует за оперативную память с остальной частью скрипта, эта разница в размере может оказаться тем, что определяет, поместится ли конвейер вообще. Если цвет – не та подсказка, которая нужна алгоритму, оттенки серого – правильный ответ.
5.3.2. Цвет через RGB565¶
Когда цвет является подсказкой – отслеживание цветного маркера, различение красных яблок и зелёных, выделение элемента интерфейса по его оттенку – два байта на пиксель дают достаточно цвета для тех видов классификации, которые выполняют алгоритмы. RGB565 – это цветовой формат по умолчанию на камере и тот, который ожидают учитывающие цвет методы.
Отрисовка аннотированного кадра – рисование рамок обнаружения, вывод диагностического текста, вывод кадра на экран или на удалённый просмотрщик – также естественным образом требует RGB565. Предпросмотр в IDE, встроенные контроллеры дисплея и большинство сетевых получателей либо используют этот формат напрямую, либо дёшево преобразуют из него.
5.3.3. Bayer как формат хранения¶
Изображение Bayer – это сырой выход датчика, до того как ISP выполнил дебайеризацию в готовое цветовое представление. Каждый пиксель – это один байт, хранящий единственный цветовой канал – тот, который пропустил цветовой фильтр в этой позиции мозаики. Это делает изображение Bayer таким же по размеру, как изображение в оттенках серого, и втрое меньше RGB888, что соответствует тому, для чего Bayer действительно полезен: хранению множества кадров одновременно, когда оперативная память является связывающим ограничением.
Загвоздка в том, что алгоритмы в модуле image не работают с изображениями Bayer напрямую. Без дебайеризации ни один пиксель не несёт достаточно информации, чтобы самостоятельно вынести суждение о цвете, а паттерны, которые ищут алгоритмы – границы, углы, блобы – были бы искажены мозаикой. Единственные способы прочитать или изменить изображение Bayer – это get_pixel() и set_pixel(); всё остальное ожидает готовое представление.
Складывающийся из этого паттерн – хранить кадры как Bayer столько, сколько им нужно находиться в очереди, и преобразовывать каждый из них либо в оттенки серого, либо в RGB565 в момент, когда фактически начинается его обработка. Преобразование стоит тактов процессора, но экономит оперативную память, которая иначе была бы занята хранением готовых кадров на протяжении всего времени жизни приложения.
Примечание
Единственные операции модуля image над пикселями Bayer напрямую – это get_pixel(), set_pixel() и путь JPEG-кодирования, который питает предпросмотр в IDE или удалённый просмотрщик. Рисование, анализ и фильтрация – всё это требует сначала преобразования в оттенки серого, RGB565 или бинарный формат.
5.3.4. YUV422 для конвейеров, которым нужно и то, и другое¶
YUV422 разделяет информацию каждого пикселя на канал яркости (Y) и два канала цветности (U и V) и субдискретизирует цветность так, что соседние пары пикселей разделяют один U и один V. Среднее число байтов на пиксель составляет два – столько же, сколько у RGB565 – но они скомпонованы так, что канал Y уже представляет собой непрерывное 8-битное изображение в оттенках серого, расположенное по известным смещениям в буфере.
Такая компоновка – именно то, что нужно конвейеру, когда часть его этапов работает с оттенками серого, а части нужен цвет. Чтение значений Y напрямую для этапов в оттенках серого избавляет от затрат на явное преобразование; каналы U и V присутствуют на случай, когда более позднему этапу действительно понадобится цвет. Вне этого конкретного паттерна RGB565 обычно является более простым выбором для цвета, а оттенки серого – более простым выбором для работы только с яркостью; ценность YUV422 заключается в том, что он хорош в обоих сразу.
Примечание
Модуль image работает с YUV422 более ограниченно, чем с оттенками серого, RGB565 или бинарным форматом – прямое чтение канала Y для работы в оттенках серого и путь JPEG-кодирования, который питает предпросмотр в IDE или удалённый просмотрщик. Учитывающие цвет методы ожидают RGB565; кадры YUV422 требуют явного преобразования перед анализом цвета или рисованием.
5.3.5. Бинарный формат, маски и пороговый вывод¶
Бинарное изображение – это один бит на пиксель: каждый пиксель равен либо 0, либо 1. Этот формат редко встречается как захват с датчика; вместо этого он появляется как естественный выход порогования (где проверка цвета или яркости классифицирует каждый пиксель в «да, совпадает» или «нет, не совпадает») и как естественный вход для морфологических операций и для аргумента mask, который принимают многие методы.
Практическое преимущество этого формата – его размер. Бинарное изображение занимает одну восьмую от объёма изображения в оттенках серого, поэтому переносить большую маску – попиксельный выбор того, какие позиции должна затронуть некоторая последующая операция – дёшево. То, что многие операции принимают бинарное изображение как именованный аргумент mask=, – это обратная сторона той же мысли: формат мал, и связывание бинарного вывода одного этапа со входом-маской другого – распространённый паттерн конвейера.
5.3.6. JPEG и PNG на границе¶
Объекты Image в формате JPEG и PNG отличаются от остальных в каталоге. Это не сетки пикселей; это сжатые байтовые потоки, которые кодируют данные пикселей в форме, которую операции на уровне пикселей не могут прочитать. Вызов get_pixel() для JPEG не возвращает пиксель в позиции; пиксель не лежит распакованным где-либо в буфере, чтобы метод мог его извлечь.
JPEG и PNG появляются на границе обработки изображений, где данные пикселей покидают камеру или входят в неё в сжатой форме. Сохранение кадра на диск как JPEG сохраняет файл маленьким; отправка кадра по сети как JPEG делает передачу дешёвой; загрузка эталонного кадра из файла JPEG позволяет ему лежать на диске в гораздо более компактной форме, чем заняли бы сырые пиксели. Для любого из этих сценариев сжатое представление – правильный ответ. Чтобы выполнить какую-либо реальную обработку JPEG, однако, приложение сначала преобразует его в пригодный для работы формат – и именно это преобразование является тем местом, где сжатые байты разворачиваются в пиксели и где фактически происходит разбухание буфера (JPEG размером 30 КБ может стать 600 КБ RGB565).
5.3.7. Преобразование между форматами¶
Путь преобразования – это то, что сшивает разные форматы в единый конвейер. Пять методов класса Image берут существующее изображение и возвращают новое в другом формате:
to_grayscale()создаёт изображение с одним байтом на пиксель – формат, который нужен классическим алгоритмам.to_rgb565()создаёт двухбайтовый на пиксель цветовой формат, на котором говорят и учитывающие цвет методы, и предпросмотр в IDE.to_bitmap()создаёт однобитовое бинарное изображение – формат, который принимают морфология и аргументыmask.to_jpeg()создаёт JPEG-сжатое изображение, пригодное для сохранения или передачи.to_png()создаёт PNG-сжатое изображение, когда предпочтительнее кодирование без потерь, чем меньшие по размеру файлы JPEG.
Каждое преобразование по умолчанию выполняется на месте: буфер исходного изображения перезаписывается преобразованным результатом, и исходные пиксели источника исчезают после возврата из вызова. Это самый дешёвый вариант как по процессору, так и по памяти, и это правильный ответ, когда исходный кадр больше ни для чего не понадобится.
Когда источник всё ещё нужен – когда более поздний этап конвейера должен видеть исходный кадр – два именованных аргумента переопределяют поведение на месте по умолчанию. copy=True выделяет отдельный буфер для преобразованного изображения в куче Python и оставляет источник нетронутым. copy_to_fb=True выполняет то же выделение, но размещает его в буфере кадра, а не в куче – к чему приложение прибегает, когда преобразованное изображение должно попасть в предпросмотр IDE, поскольку IDE читает из буфера кадра.
Ещё два метода создают изображения RGB565, окрашенные через палитру, а не прямым преобразованием. to_rainbow() отображает каждое одноканальное входное значение в цвет вдоль плавного градиента, проходящего через видимый спектр. to_ironbow() отображает каждое входное значение в нелинейную палитру тепловизора, идущую от чёрного через тёмно-красные и оранжевые к белому. Оба являются инструментами визуализации, а не измерения; смысл в том, чтобы сделать одноканальное изображение, чьи сырые значения иначе были бы невидимы для глаза, читаемым с первого взгляда.
5.3.8. Размер буфера¶
Ещё одна деталь о форматах, которую стоит проговорить явно. size() всегда сообщает размер байтового буфера, а не количество пикселей. Для несжатых форматов это напрямую следует из размеров и числа байтов на пиксель: width * height * bytes_per_pixel. Для JPEG и PNG это размер сжатого потока, который меняется от кадра к кадру в зависимости от того, что содержит сцена. Код, который выделяет буферы из бюджетов байтов, использует size() для первого случая; код, который выводит сжатые кадры с камеры, читает его после каждого сжатия, чтобы узнать, сколько байтов на самом деле содержит поток.