5.32. Сохранение и сжатие¶
До сих пор все разделы работали с изображениями на камере: захваченными в буфер кадра или размещёнными в куче MicroPython, обработанными методами модуля image и либо отображёнными в предпросмотре IDE, либо переданными на следующий этап обработки в том же скрипте. Большинству приложений в какой-то момент нужно выполнить обратное действие: взять изображение, находящееся в данный момент в ОЗУ, и поместить его куда-то на постоянное хранение – на SD-карту, на USB-хост, по сети – туда, откуда его сможет прочитать что-то иное, кроме камеры.
Модуль image предоставляет два пути для этой работы. Путь save записывает изображение в файл на файловой системе, при этом формат файла выбирается по расширению, а детали кодирования обрабатываются методом. Путь to-format возвращает объект Image, содержащий закодированный поток байтов, пригодный для передачи в потоковый или сетевой вызов без обращения к файловой системе. Каждый подходит для своего типа приложений; оба построены на одном и том же движке сжатия в основе.
5.32.1. Сохранение в файл¶
save() записывает изображение в файловую систему по указанному пути:
img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)
Формат выбирается по расширению файла. Распознаются пять расширений: .bmp записывает Windows bitmap (без потерь, без сжатия, побайтово захваченные пиксели); .pgm записывает portable graymap (без потерь, только оттенки серого); .ppm записывает portable pixmap (без потерь, RGB); .jpg и .jpeg оба записывают JPEG (с потерями, со сжатием). Изображение-получатель уже должно быть в правильном цветовом формате для выбранного контейнера – цветное изображение, сохранённое как .pgm, является ошибкой.
roi ограничивает сохранение поддиапазоном-прямоугольником изображения, так же как это делает ключевое слово roi в любом другом методе модуля image. По умолчанию используется полное изображение. Это ключевое слово игнорируется при сохранении изображения, сжатого в JPEG, поскольку форма на диске уже охватывает полный кадр, а повторное кодирование через обрезку свело бы на нет смысл сохранения уже сжатых байтов.
quality – это качество сжатия JPEG от 0 до 100, и оно имеет смысл только тогда, когда выходной формат – JPEG (для форматов без потерь это ключевое слово игнорируется). Значение по умолчанию 50 – правильный баланс для большинства приложений; диапазон от 70 до 85 – для более высокого визуального качества, диапазон от 30 до 50 – для маленьких миниатюр и передачи в условиях ограниченной полосы пропускания, а 90 и выше зарезервирован для случаев, когда изображение будет проверяться вручную или обрабатываться последующим алгоритмом, чувствительным к артефактам сжатия.
Изображение-получатель возвращается, чтобы вызовы можно было сцеплять: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Возвращаемый объект – это то же самое изображение в памяти; сохранение является побочным эффектом.
Типичный случай использования – паттерн захват-и-журналирование. Срабатывает триггер (обнаружен блоб, нажата кнопка, истёк таймер); скрипт захватывает кадр; добавляет временную метку к имени файла; и вызывает save(), чтобы отправить изображение на SD-карту. Предпросмотр IDE продолжает работать, срабатывает следующий триггер, и сохранённые файлы накапливаются.
5.32.2. Кодирование в память¶
Когда назначением является не файловая система, а сетевое соединение, последовательный порт или вход другого модуля, приложению нужен закодированный поток байтов в памяти, а не на диске. to_jpeg() и to_png() производят именно это:
encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)
Поведение по умолчанию – преобразование на месте: получатель преобразуется в изображение JPEG (или PNG), и возвращается тот же самый объект. При copy=True преобразование записывается во вновь выделенный объект в куче; при copy_to_fb=True результат помещается в буфер кадра. Этот выбор такой же, как и в любом другом методе преобразования – на месте по умолчанию, копия, когда оригинал нужен впоследствии.
quality и subsampling – это те же самые регуляторы настройки JPEG, что и в пути сохранения. subsampling выбирает схему субдискретизации цветности: image.JPEG_SUBSAMPLING_AUTO подбирает наилучшую для выбранного качества, image.JPEG_SUBSAMPLING_444 сохраняет цветность в полном разрешении (самый большой файл, наилучшая точность цвета), image.JPEG_SUBSAMPLING_422 и image.JPEG_SUBSAMPLING_420 уменьшают разрешение цветности вдвое по одной или обеим осям (файлы меньше, лёгкое размытие цвета, незаметное на типичных дистанциях просмотра). Значение по умолчанию AUTO – правильный выбор, если у приложения нет особой потребности.
PNG через to_png() работает без потерь, но кодируется медленнее и для фотографического содержимого даёт файлы крупнее, чем JPEG (фотографическое содержимое плохо сжимается схемой предсказания PNG). Используйте PNG, когда изображение представляет собой штриховую графику, скриншот или содержит графику с резкими краями, нарисованную поверх захваченного кадра – кодирование без потерь сохраняет резкие края, которые JPEG размыл бы. В остальных случаях правильный выбор по умолчанию – JPEG.
И to_jpeg(), и to_png() принимают те же позиционные и масштабирующие ключевые слова в стиле рисования, что и другие методы преобразования – x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint – так что один и тот же вызов может за один шаг закодировать масштабированную, обрезанную или преобразованную по палитре версию источника. compress() – это устаревшее написание to_jpeg(); оба принимают одинаковые аргументы и дают одинаковый результат.
5.32.3. Что даёт сжатие¶
Цифры, стоящие за компромиссом между JPEG и необработанными данными, стоит разобрать один раз.
Кадр RGB565 размером 320 на 240 занимает 153 600 байт (один захваченный кадр в QVGA). Кадр 640 на 480 занимает 614 400 байт; кадр 1280 на 960 – 2 457 600 байт. Ни один из них не велик по сравнению с экраном настольного компьютера или телефона, но они существенны в контексте камеры, у которой всего несколько МБ ОЗУ, SD-карты с конечной пропускной способностью записи и канала связи с хостом, который обычно работает через USB CDC, UART или беспроводной модуль на умеренных скоростях.
JPEG при quality=50 обычно сжимает фотографический захваченный кадр в 10-20 раз: тот кадр 640 на 480 размером 614 КБ становится закодированным потоком байтов от 30 до 60 КБ. При quality=85 сжатие падает до 5-10 раз (от 60 до 120 КБ для того же кадра). При quality=10 – с обилием артефактов, но всё ещё распознаваемый – сжатие достигает 30-50 раз (от 12 до 20 КБ).
Эти цифры определяют, что практически можно сделать с сохранёнными кадрами. Путь к SD-карте, выдерживающий 10 МБ/с, справляется с 30 кадрами в секунду содержимого VGA, закодированного в JPEG при quality=50, с запасом (около 1-2 МБ/с); сохранение того же содержимого без сжатия требует более 18 МБ/с, что превышает то, что путь файловой системы камеры выдерживает при записи на карту. USB-хост, забирающий закодированные в JPEG кадры через CDC со скоростью 1 МБ/с, получает кадры по 30-60 КБ примерно с частотой от 15 до 30 кадров в секунду; забирая необработанные кадры с той же скоростью, он получает один-два кадра в секунду.
Коротко: методы сжатия – это не просто удобство для сохранения. Именно они делают захваченный кадр пригодным к использованию за пределами камеры с той частотой кадров, которая важна для приложения. Выбор правильного сжатия – JPEG с качеством 50 для общего журналирования, 80 для качественной работы, PNG для захвата штриховой графики – является частью повседневной работы любого нетривиального приложения для камеры.