4.16. Предпросмотр изображения

Пул буферов кадров – это место, откуда приложение читает свои кадры. Пока приложение работает над этими кадрами, тому, что подключено к камере для их предпросмотра, тоже нужна копия каждого кадра. У камеры есть второй, выделенный для этой цели буфер, и единое правило, когда он заполняется: каждый раз, когда приложение вызывает snapshot(), предыдущий захваченный кадр копируется в буфер предпросмотра перед тем, как новый кадр будет возвращён.

Приложение и средство предпросмотра никогда не конкурируют за одну и ту же память. Приложение читает свой кадр из пула; средство предпросмотра читает свой кадр из буфера предпросмотра. Оба происходят параллельно.

4.16.1. Потоковый буфер кадра

Буфер предпросмотра – потоковый буфер кадра – это единая область RAM фиксированного размера, отдельная от пула буферов кадров. Её размер задаётся на этапе сборки прошивки и не меняется в зависимости от framesize() или pixformat(). Около мегабайта – типичное значение на современных OpenMV Cam – достаточно большое, чтобы вместить предпросмотр умеренного разрешения, и значительно меньшее, чем полноразмерный кадр при самых больших размерах датчика.

Код приложения не читает и не записывает этот буфер напрямую; драйвер камеры заполняет его как побочный эффект вызова snapshot().

4.16.2. Что snapshot делает для предпросмотра

При каждом вызове snapshot(), прежде чем драйвер вернёт предыдущий буфер кадра приложения обратно в пул и выдаст новый, он копирует предыдущий кадр в буфер предпросмотра – вместе со всем, что приложение нарисовало поверх него во время обработки, оставшимся на изображении. Возможны две ветви. Какая из них выполняется, выбирается средством предпросмотра, а не камерой: потребитель, открывший предпросмотр, сообщает драйверу, хочет ли он необработанное изображение или JPEG, и какой размер окна для необработанного изображения он может принять.

  • Необработанная уменьшенная копия. Когда средство предпросмотра запросило необработанные кадры, драйвер копирует предыдущий кадр в его собственном формате пикселей (RGB565, оттенки серого и т. д.). Если кадр больше окна необработанного изображения, которое запросило средство предпросмотра, драйвер уменьшает его масштаб с билинейной фильтрацией, пока он не поместится; в противном случае пиксели проходят без изменений. Никаких артефактов сжатия; средство предпросмотра видит те же пиксели, над которыми работало приложение.

  • Сжатие JPEG. Когда средство предпросмотра запросило JPEG – или когда необработанная копия вообще не поместилась бы в потоковый буфер – драйвер сжимает предыдущий кадр в JPEG в полном разрешении в потоковый буфер. Качество регулируется адаптивно для каждого кадра, чтобы сжатый вывод оставался в пределах ёмкости потокового буфера. Когда кадр помещается, драйвер постепенно повышает качество на один шаг к потолку, который зависит от размера пикселей захваченного кадра (меньшим кадрам разрешено более высокое качество; большие кадры ограничены ниже, чтобы они не могли переполниться при небольшом изменении содержимого). Когда кадр не помещается, драйвер уменьшает текущее качество вдвое, удерживает его на сниженном уровне в течение следующих нескольких десятков кадров, чтобы новая настройка успела установиться, и отбрасывает переполнившийся кадр из предпросмотра. Цикл приложения продолжает работать без влияния; только средство предпросмотра пропускает отброшенный кадр.

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

Средство предпросмотра опрашивает по собственному расписанию, как правило, гораздо медленнее, чем камера захватывает, поэтому оно подвыборочно прореживает частоту необработанного захвата: показываются только те снимки, которые оно успевает прочитать вовремя. Если свежий snapshot() приходит в буфер предпросмотра до того, как средство предпросмотра прочитало предыдущий кадр, буфер всё ещё заблокирован средством предпросмотра, и новое обновление предпросмотра пропускается – этот захват теряется из потока предпросмотра. Собственный пул буферов кадров приложения не затрагивается; захваченный кадр всё равно попадает в приложение в обычном порядке.

4.16.3. Ручная отправка последнего кадра

Поскольку предпросмотр обновляется как побочный эффект snapshot(), скрипт, который завершается, так и не вызвав snapshot снова, оставляет то, что он последним отправил в предпросмотр, висеть на средстве предпросмотра неопределённо долго – что для скрипта, выполняющего свою работу до первого снимка и затем завершающегося, является пустым предпросмотром. image.Image.flush() (или эквивалентный flush() для объекта CSI) копирует текущее содержимое буфера кадра приложения в потоковый буфер по требованию, без захвата нового кадра:

img = csi0.snapshot()
# process the image and draw on it
img.flush()                               # previewer sees the annotated frame

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

Примечание

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