4.14. Основы CSI

Модуль csi – это способ, которым код на Python управляет датчиком камеры. Каждый скрипт, который захватывает кадр, следует одной и той же трёхчастной структуре: импорты вверху, однократная настройка в середине и цикл while True внизу, который извлекает кадры с камеры по одному за раз.

4.14.1. Типичный цикл

import csi, image, time

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    # process img here
    print(clock.fps())

4.14.2. Что делает каждый вызов

import csi, image, time

Подключает три модуля. csi управляет датчиком, image определяет класс Image, который возвращает snapshot(), а time предоставляет помощник time.clock(), используемый для измерения числа кадров в секунду.

csi.CSI()

Создаёт экземпляр CSI, который обёртывает один физический датчик камеры. Конструктор захватывает периферийное устройство камеры и записывает конфигурацию для конкретного датчика. Камеры с одним датчиком имеют один экземпляр CSI; камеры с двумя датчиками (цветной плюс тепловой, цветной плюс событийный) имеют два, каждый из которых выбирается аргументом cid конструктора.

csi0.reset()

Включает питание и конфигурирует датчик. По умолчанию он подаёт импульс на вывод сброса датчика, затем записывает регистры I2C датчика в известное начальное состояние. Последующие вызовы конфигурации – pixformat, framesize, ручки автоматического управления – отправляют дополнительные записи в регистры по той же управляющей шине I2C.

csi0.pixformat(csi.RGB565)

Записывает регистры датчика, которые выбирают выходной формат пикселей. Доступные варианты – это форматы, представленные на странице форматы пикселей: RGB565, GRAYSCALE, BAYER, YUV422 и JPEG на датчиках, которые его поддерживают.

csi0.framesize(csi.QVGA)

Записывает регистры, которые выбирают выходное разрешение. QVGA – это 320 × 240; именованные размеры доходят до WQXGA2 (2592 × 1944, около 5 МП) на датчиках, которые их поддерживают. Пользовательский кортеж (width, height) тоже работает, если он согласуется с выходными возможностями датчика.

clock = time.clock()

Создаёт вспомогательный объект часов. Каждый вызов clock.tick() внутри цикла записывает время начала итерации; time.clock.fps() сообщает недавнюю частоту цикла в кадрах в секунду.

img = csi0.snapshot()

Захватывает один кадр с датчика и возвращает его как Image. Механика того, как этот кадр оказывается в памяти, заслуживает более пристального рассмотрения.

4.14.3. Как snapshot заполняет память

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

Вместо этого MCU перекладывает передачу на прямой доступ к памяти (DMA) – аппаратный механизм, отдельный от CPU, который копирует байты из одного места в другое внутри MCU, вообще не задействуя CPU. Входное периферийное устройство камеры захватывает каждый входящий байт пикселя в небольшой FIFO на кристалле; те этапы ISP, которые выполняются на стороне MCU, обрабатывают данные по пути; а механизм DMA записывает готовые пиксели в буфер кадра в RAM по соответствующему пиксельному смещению. Ничто в этой цепочке не нуждается в CPU после того, как канал DMA запрограммирован.

Когда вызывается snapshot():

  1. Драйвер CSI программирует механизм DMA адресом буфера кадра, длиной передачи (объёмом пикселей одного кадра) и функцией обратного вызова для прерывания завершения DMA.

  2. Драйвер включает входное периферийное устройство камеры и ждёт, пока датчик не сигнализирует о начале следующего кадра.

  3. По мере того как датчик передаёт кадр, периферийное устройство пропускает каждый байт пикселя через ISP и далее в механизм DMA, который записывает результат в RAM по следующему смещению буфера кадра. CPU свободен для выполнения другого кода во время передачи.

  4. Когда приходит последний пиксель кадра, DMA генерирует прерывание завершения, драйвер обёртывает буфер кадра в Image, и snapshot() возвращает его пользовательскому коду.

Возвращаемый Image не владеет копией пиксельных данных – он указывает на один из буферов кадра камеры в RAM. Сколько буферов кадра хранит камера и как они передаются между DMA и пользовательским кодом при каждом вызове snapshot(), зависит от режима буферизации, выбранного приложением через framebuffers().