4.14. Podstawy CSI

Moduł csi to sposób, w jaki kod Python steruje sensorem kamery. Każdy skrypt, który przechwytuje ramkę, ma ten sam trzyczęściowy kształt: importy na górze, jednorazowa konfiguracja pośrodku i pętla while True na dole, która pobiera ramki z kamery jedna po drugiej.

4.14.1. Typowa pętla

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. Co robi każde wywołanie

import csi, image, time

Wprowadza trzy moduły. csi steruje sensorem, image definiuje klasę Image, którą zwraca snapshot(), a time udostępnia pomocnika time.clock() używanego do pomiaru liczby klatek na sekundę.

csi.CSI()

Tworzy instancję CSI, która opakowuje jeden fizyczny sensor kamery. Konstruktor przejmuje urządzenie peryferyjne kamery i zapisuje konfigurację specyficzną dla danego sensora. Kamery z pojedynczym sensorem mają jedną instancję CSI; kamery z dwoma sensorami (kolor plus termowizja, kolor plus zdarzeniowy) mają dwie, każda wybierana argumentem cid w konstruktorze.

csi0.reset()

Zasila i konfiguruje sensor. Domyślnie generuje impuls na pinie resetu sensora, a następnie zapisuje rejestry I2C sensora do znanego stanu początkowego. Kolejne wywołania konfiguracyjne – pixformat, framesize, pokrętła automatycznego sterowania – wysyłają więcej zapisów do rejestrów przez tę samą magistralę sterującą I2C.

csi0.pixformat(csi.RGB565)

Zapisuje rejestry sensora, które wybierają wyjściowy format pikseli. Dostępne opcje to formaty przedstawione na stronie formaty pikseli: RGB565, GRAYSCALE, BAYER, YUV422 oraz JPEG na sensorach, które go obsługują.

csi0.framesize(csi.QVGA)

Zapisuje rejestry, które wybierają wyjściową rozdzielczość. QVGA to 320 × 240; nazwane rozmiary sięgają aż do WQXGA2 (2592 × 1944, około 5 MP) na sensorach, które je obsługują. Działa też niestandardowa krotka (width, height), o ile jest zgodna z możliwościami wyjściowymi sensora.

clock = time.clock()

Tworzy pomocnika zegara. Każde wywołanie clock.tick() wewnątrz pętli zapisuje czas rozpoczęcia iteracji; time.clock.fps() raportuje ostatnią szybkość pętli w klatkach na sekundę.

img = csi0.snapshot()

Przechwytuje jedną ramkę z sensora i zwraca ją jako Image. Mechanika tego, jak ta ramka trafia do pamięci, zasługuje na bliższe przyjrzenie się.

4.14.3. Jak snapshot wypełnia pamięć

Sensor dostarcza piksele na magistrali danych pikseli opisanej w magistrale sensora z szybkością setek megabajtów na sekundę – o wiele za szybko, by CPU mógł kopiować je piksel po pikselu programowo.

Zamiast tego MCU przekazuje transfer do bezpośredniego dostępu do pamięci (DMA) – silnika sprzętowego oddzielnego od CPU, który kopiuje bajty z jednego miejsca do drugiego wewnątrz MCU bez żadnego udziału CPU. Urządzenie peryferyjne wejścia kamery przechwytuje każdy przychodzący bajt piksela do małego kolejki FIFO na układzie; etapy ISP działające po stronie MCU przetwarzają dane po drodze; a silnik DMA zapisuje gotowe piksele do bufora ramki w pamięci RAM przy odpowiednim przesunięciu piksela. Nic w tym łańcuchu nie potrzebuje CPU, gdy kanał DMA został już zaprogramowany.

Gdy wywoływana jest funkcja snapshot():

  1. Sterownik CSI programuje silnik DMA adresem bufora ramki, długością transferu (piksele odpowiadające jednej ramce) oraz wywołaniem zwrotnym dla przerwania zakończenia DMA.

  2. Sterownik włącza urządzenie peryferyjne wejścia kamery i czeka, aż sensor zasygnalizuje początek następnej ramki.

  3. Gdy sensor wysyła strumieniowo ramkę, urządzenie peryferyjne przekazuje każdy bajt piksela przez ISP, a dalej do silnika DMA, który zapisuje wynik do pamięci RAM przy następnym przesunięciu bufora ramki. CPU może w trakcie transferu wykonywać inny kod.

  4. Gdy nadejdzie ostatni piksel ramki, DMA wyzwala swoje przerwanie zakończenia, sterownik opakowuje bufor ramki w Image, a funkcja snapshot() zwraca go do kodu użytkownika.

Zwrócony obiekt Image nie posiada własnej kopii danych pikseli – wskazuje na jeden z buforów ramki kamery w pamięci RAM. To, ile buforów ramki utrzymuje kamera i jak są one przekazywane między DMA a kodem użytkownika przy każdym wywołaniu snapshot(), zależy od trybu buforowania wybranego przez aplikację za pomocą framebuffers().