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-done.

  2. Драйвер вмикає периферійний пристрій введення камери та очікує, поки датчик просигналізує про початок наступного кадру.

  3. Коли датчик передає кадр потоково, периферійний пристрій передає кожен байт пікселя через ISP і далі до рушія DMA, який записує результат у RAM за наступним зсувом кадрового буфера. CPU вільний виконувати інший код під час передачі.

  4. Коли надходить останній піксель кадру, DMA запускає своє переривання done, драйвер обгортає кадровий буфер у Image, і snapshot() повертає його до користувацького коду.

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