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, timeWprowadza trzy moduły.
csisteruje sensorem,imagedefiniuje klasęImage, którą zwracasnapshot(), atimeudostępnia pomocnikatime.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 argumentemcidw 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,YUV422orazJPEGna sensorach, które go obsługują.csi0.framesize(csi.QVGA)Zapisuje rejestry, które wybierają wyjściową rozdzielczość.
QVGAto 320 × 240; nazwane rozmiary sięgają aż doWQXGA2(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():
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.
Sterownik włącza urządzenie peryferyjne wejścia kamery i czeka, aż sensor zasygnalizuje początek następnej ramki.
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.
Gdy nadejdzie ostatni piksel ramki, DMA wyzwala swoje przerwanie zakończenia, sterownik opakowuje bufor ramki w
Image, a funkcjasnapshot()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().