Kamera zdarzeniowa GENX320

Moduł kamery zdarzeniowej GENX320 to oparty na zdarzeniach sensor wizyjny firmy Prophesee o rozdzielczości 320x320 i mikrosekundowej precyzji czasowej.

Kamera zdarzeniowa GENX320

Pełną kartę katalogową, zdjęcia oraz informacje o zamówieniu znajdziesz na stronie produktu kamery zdarzeniowej GENX320.

Informacja

Obsługiwany na OpenMV H7 Plus, RT1062 oraz N6.

Najważniejsze cechy

  • Sensor wizyjny oparty na zdarzeniach o rozdzielczości 320x320

  • Zakres dynamiki 140 dB, brak rozmycia ruchu

  • Częstotliwość wyjściowa histogramów zdarzeń ponad 375 Hz

  • Pobór mocy skaluje się z aktywnością sceny — zaczyna od ~3 mW

  • Działa od poniżej 5 luksów aż do jasnego światła słonecznego bez automatycznej ekspozycji

  • Wyprowadza ramki w skali szarości lub surowe strumienie zdarzeń

Użycie

GENX320 to sensor wizyjny oparty na zdarzeniach — zamiast odczytywać całą macierz 320x320 przy stałym zegarze ramki, każdy piksel zgłasza asynchroniczne „zdarzenia” w chwili, gdy wykryje zmianę jasności. Każde zdarzenie niesie współrzędne X/Y, polaryzację ON/OFF (jaśniej→ciemniej lub ciemniej→jaśniej) oraz znacznik czasu w mikrosekundach. Stąd biorą się mikrosekundowa precyzja czasowa sensora, brak rozmycia ruchu, bardzo wysoki zakres dynamiki oraz pobór mocy skalowany aktywnością. Statyczne sceny nie generują żadnych danych.

Oprogramowanie układowe OpenMV udostępnia GENX320 poprzez csi.CSI z cid= csi.GENX320. Dostępne są dwa tryby pracy:

  • Tryb histogramu (domyślny) — zdarzenia są kumulowane na chipie w przedziały (biny) na piksel i raportowane jako ramka 320x320 w skali szarości z konfigurowalną częstotliwością (~20-350 FPS). Sensor zachowuje się jak zwykła kamera, więc wszystkie standardowe procedury przetwarzania obrazu (Image.find_blobs, palety itd.) działają bezpośrednio.

  • Tryb zdarzeń — surowe zdarzenia trafiają do tablicy numpy ndarray z pełnymi mikrosekundowymi znacznikami czasu, dla zastosowań wymagających szczegółów czasowych zamiast wstępnie pogrupowanej ramki.

Tryb histogramu

W trybie histogramu GENX320 wyprowadza ramki w skali szarości, w których każdy piksel koduje niedawną aktywność zdarzeń w danym miejscu. Piksele powyżej bazowej jasności to zdarzenia ON (jasność rośnie), poniżej — zdarzenia OFF (jasność spada). Domyślna bazowa jasność to 128, a krok kontrastu na zdarzenie wynosi 16 — zwiększ kontrast, aby zdarzenia stały się bardziej wyraziste:

import csi
import time

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128)  # baseline (default 128)
csi0.contrast(16)     # per-event step
csi0.framerate(50)    # 20-350 FPS

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

csi.CSI.brightness, csi.CSI.contrast oraz csi.CSI.framerate to trzy pokrętła kształtujące wyjście histogramu.

Wyjście kolorowe

Ustaw csi.CSI.color_palette na image.PALETTE_EVT_LIGHT dla jasnego tła lub image.PALETTE_EVT_DARK dla ciemnego — sterownik emituje ramki RGB565 z bezpośrednim użyciem palety:

csi0.color_palette(image.PALETTE_EVT_LIGHT)

Kalibracja gorących pikseli

Sensory zdarzeniowe gromadzą „gorące piksele”, które wyzwalają się fałszywie. Uruchom csi.IOCTL_GENX320_CALIBRATE na statycznej scenie, aby je wyłączyć. Sterownik buduje licznik trafień 320x320 na piksel, oblicza średnią i odchylenie standardowe oraz wyłącza każdy piksel, którego licznik przekracza mean + sigma * stddev — wówczas wyłączone piksele przestają emitować zdarzenia na poziomie sensora.

Kalibracją sterują dwa parametry:

  • event_count — ile zdarzeń zliczyć przed obliczeniem statystyk. Pętla przechwytuje ramki, dopóki bieżąca suma zdarzeń nie przekroczy tego budżetu. Wyższe wartości dają bardziej wiarygodne oszacowanie kosztem dłuższego czasu kalibracji. 10000 to rozsądny punkt wyjścia.

  • sigma — mnożnik progu dla odchylenia standardowego. Niższe wartości są bardziej agresywne (więcej wyłączonych pikseli); wyższe są bardziej zachowawcze. 0.5 to dobra wartość domyślna.

Najpierw skieruj sensor na statyczną scenę, aby żadne zdarzenia wywołane ruchem nie były policzone przeciwko pikselom, które są w rzeczywistości sprawne:

csi0.snapshot(time=5000)  # let the user steady the camera
disabled = csi0.ioctl(csi.IOCTL_GENX320_CALIBRATE, 10000, 0.5)
print(f"disabled {disabled} hot pixels")

Filtr przeciwmigotaniowy (AFK)

Okresowe źródła światła (świetlówki, wyświetlacze sterowane LED) generują ogromne ilości zbędnych zdarzeń. Filtr AFK odrzuca zdarzenia, których piksel przełącza się z częstotliwością w danym paśmie — włącz go przez csi.IOCTL_GENX320_SET_AFK, podając granice pasma w hercach:

csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 1, 130, 160)  # 130-160 Hz
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 0)            # disable

Predefiniowane zestawy biasów

Każdy piksel w GenX320 ma analogowy tor wejściowy z kilkoma konfigurowalnymi biasami. Wspólnie regulują one czułość, szum, pasmo piksela oraz częstotliwość zdarzeń — właściwa kombinacja zależy od sceny. Poszczególne biasy to:

  • DIFF_ON — dodatni próg kontrastu komparatora. Piksel emituje zdarzenie ON, gdy jego logarytmiczne naświetlenie wzrośnie o tę wartość. Niższy = większa czułość na rozjaśnienia.

  • DIFF_OFF — ujemny próg kontrastu komparatora (symetryczny odpowiednik dla zdarzeń OFF). Niższy = większa czułość na pociemnienia.

  • FO — częstotliwość odcięcia filtra dolnoprzepustowego piksela. Wyższa = szersze pasmo piksela (szybsza reakcja, niższe opóźnienie), ale więcej aktywności szumu tła.

  • HPF — częstotliwość odcięcia filtra górnoprzepustowego. Wyższa = silniejsze odrzucanie powolnych zmian jasności; tylko szybkie przejścia docierają do komparatorów. Przydatne do ignorowania dryfu otoczenia.

  • REFR — okres refrakcji. Po wyzwoleniu piksel pozostaje w stanie resetu przez ten czas, zanim będzie mógł wyzwolić się ponownie. Wyższy = dłuższy czas martwy, przydatny do ograniczania częstotliwości zdarzeń na piksel.

Po csi.CSI.reset sterownik stosuje csi.GENX320_BIASES_LOW_NOISE, a nie csi.GENX320_BIASES_DEFAULT — domyślne wartości z karty katalogowej emitują znacznie wyższą częstotliwość zdarzeń tła, więc LOW_NOISE jest używany jako punkt wyjścia, aby utrzymać niski poziom strumienia. Wywołaj csi.IOCTL_GENX320_SET_BIASES z innym presetem, gdy aplikacja wymaga większej czułości lub pasma.

csi.IOCTL_GENX320_SET_BIASES stosuje jeden z pięciu presetów:

  • csi.GENX320_BIASES_DEFAULT — domyślne wartości z karty katalogowej GenX320. Zrównoważona czułość, szum i pasmo dla ogólnych scen.

  • csi.GENX320_BIASES_LOW_LIGHT — oba progi kontrastu poluzowane dla większej czułości, FO obniżone, aby utrzymać niski szum, a HPF ustawione na 0, aby powolne zmiany jasności nadal były rejestrowane — scena przy słabym oświetleniu sama z siebie generuje niewiele zdarzeń, więc chcemy, aby ich jak najwięcej przeszło.

  • csi.GENX320_BIASES_ACTIVE_MARKER — dostrojony do śledzenia migających diod LED o wysokim kontraście. Progi kontrastu podniesione, aby wyzwalały tylko ostre przejścia; FO i HPF ustawione wysoko, aby zmaksymalizować pasmo piksela i odrzucić powolny dryf otoczenia; REFR sprowadzone do 0, aby każda krawędź mignięcia była przechwytywana jedna po drugiej. Efekt: strumień złożony niemal wyłącznie z krawędzi LED, łatwy do śledzenia.

  • csi.GENX320_BIASES_LOW_NOISE — domyślny dla sterownika. Oba progi kontrastu podniesione względem DEFAULT (mniejsza czułość) oraz FO obniżone (wolniejszy piksel = cichszy piksel). Najlepszy dla statycznych lub powolnych scen, gdzie fałszywe zdarzenia w przeciwnym razie zdominowałyby obraz.

  • csi.GENX320_BIASES_HIGH_SPEED — FO podniesione, aby każdy piksel mógł reagować szybciej, HPF podniesione, aby odrzucać powolny dryf jasności, a REFR podniesione, aby pojedyncza szybko poruszająca się krawędź nie zalewała odczytu — dłuższy czas martwy utrzymuje ograniczoną liczbę zdarzeń przy intensywnym ruchu.

Nadpisz poszczególne biasy za pomocą csi.IOCTL_GENX320_SET_BIAS oraz jednego z csi.GENX320_BIAS_DIFF_ON, csi.GENX320_BIAS_DIFF_OFF, csi.GENX320_BIAS_FO, csi.GENX320_BIAS_HPF lub csi.GENX320_BIAS_REFR wraz z wartością DAC. Każdy bias ustawiany jest niezależnie — wybierz preset jako punkt wyjścia, a następnie dostosuj te biasy, których wymaga Twoja scena:

csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_LOW_LIGHT)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIAS, csi.GENX320_BIAS_HPF, 20)

Śledzenie

Ponieważ wyjście w trybie histogramu to po prostu obraz w skali szarości, zwykłe śledzenie plam (blob) działa bezpośrednio. Aby śledzić diodę LED jako aktywny znacznik, załaduj preset biasów active-marker i znajdź plamy (blob) na jasnym końcu histogramu:

import csi
import time

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128)
csi0.contrast(16)
csi0.framerate(200)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_ACTIVE_MARKER)

clock = time.clock()
while True:
    clock.tick()
    img = csi0.snapshot()
    for blob in img.find_blobs([(120, 140)], invert=True,
                               pixels_threshold=2, area_threshold=4,
                               merge=True):
        img.draw_detection(blob)
    print(clock.fps())

Tryb zdarzeń

Tryb zdarzeń pomija histogram na chipie i przesyła surowe zdarzenia do tablicy numpy ndarray. Każde zdarzenie to wiersz złożony z sześciu kolumn uint16:

  • [0] typ zdarzenia — zobacz poniżej

  • [1] znacznik czasu w sekundach

  • [2] znacznik czasu w milisekundach

  • [3] znacznik czasu w mikrosekundach

  • [4] współrzędna X, 0-319

  • [5] współrzędna Y, 0-319

Sterownik emituje sześć typów zdarzeń w kolumnie [0]:

  • csi.PIX_OFF_EVENT — piksel wykrył spadek jasności (przekroczono próg komparatora DIFF_OFF). X/Y wskazują piksel, który się wyzwolił.

  • csi.PIX_ON_EVENT — piksel wykrył wzrost jasności (przekroczono próg DIFF_ON). X/Y wskazują piksel.

  • csi.EXT_TRIGGER_FALLING — pin wyzwalania zewnętrznego sensora zarejestrował zbocze opadające. X/Y są nieużywane.

  • csi.EXT_TRIGGER_RISING — pin wyzwalania zewnętrznego sensora zarejestrował zbocze narastające. X/Y są nieużywane.

  • csi.RST_TRIGGER_FALLING — wyzwalanie resetu piksela, zbocze opadające. X/Y są nieużywane. Obecnie niegenerowane przez oprogramowanie układowe.

  • csi.RST_TRIGGER_RISING — wyzwalanie resetu piksela, zbocze narastające. X/Y są nieużywane. Obecnie niegenerowane przez oprogramowanie układowe.

Zewnętrzne wejście wyzwalania GENX320 jest podłączone do linii synchronizacji ramki kamery, która jest również wyprowadzona na P10 zarówno na procesorze, jak i na listwie pinów — steruj P10, aby wstrzykiwać zbocza synchronizacji do strumienia zdarzeń i odbierać je jako zdarzenia EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING obok danych pikseli.

Większość aplikacji interesuje tylko PIX_OFF_EVENT i PIX_ON_EVENT; typy wyzwalania pozwalają korelować zdarzenia z zewnętrznymi sygnałami czasowymi.

Zaalokuj bufor zdarzeń o kształcie (EVT_res, 6), gdzie EVT_res jest potęgą dwójki pomiędzy 1024 a 65536, a następnie przejdź do trybu zdarzeń poprzez csi.IOCTL_GENX320_SET_MODE z csi.GENX320_MODE_EVENT oraz rozmiarem bufora. Odczytuj zdarzenia za pomocą csi.IOCTL_GENX320_READ_EVENTS, które wypełnia bufor do jego pojemności i zwraca liczbę prawidłowych wierszy.

Image.draw_event_histogram rasteryzuje zdarzenia do obrazu w skali szarości — dla każdego zdarzenia ON dodaje contrast do binu; dla każdego zdarzenia OFF odejmuje. clear=True najpierw resetuje obraz do brightness; clear=False kumuluje przez wiele wywołań:

import csi
import image
import time
from ulab import numpy as np

img = image.Image(320, 320, image.GRAYSCALE)
events = np.zeros((2048, 6), dtype=np.uint16)

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])

clock = time.clock()
while True:
    clock.tick()
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    img.draw_event_histogram(events[:n], clear=True, brightness=128, contrast=64)
    img.flush()
    print(n, clock.fps())

Presety biasów trybu histogramu, filtr AFK oraz ioctl kalibracji gorących pikseli działają w trybie zdarzeń w ten sam sposób — wywołaj je po csi.IOCTL_GENX320_SET_MODE.

Filtrowanie według polaryzacji

Pokrój tablicę zdarzeń za pomocą ulab, aby zachować tylko zdarzenia ON (ruch w kierunku jaśniejszego stanu) lub tylko zdarzenia OFF:

TARGET = csi.PIX_ON_EVENT  # or csi.PIX_OFF_EVENT

events_slice = events[:n]
indices = np.nonzero(events_slice[:, 0] == TARGET)[0]
if len(indices):
    target_events = np.take(events_slice, indices, axis=0)
    img.draw_event_histogram(target_events, clear=True,
                             brightness=128, contrast=64)

Akumulacja długiej ekspozycji

Ustaw clear=False, aby kumulować zdarzenia w tym samym obrazie przez wiele ramek — efektem jest wizualizacja śladu ruchu. Resetuj okresowo, aby rozpocząć nową ekspozycję:

EXPOSURE_FRAMES = 30
i = 0
while True:
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    clear = (i % EXPOSURE_FRAMES) == 0
    img.draw_event_histogram(events[:n], clear=clear, brightness=128, contrast=64)
    img.flush()
    i += 1

Przetwarzanie z dużą szybkością

Pomiń wizualizację, aby uwolnić CPU na przetwarzanie zdarzeń. Drukuj statystyki tylko co N-tą iterację — wypisywanie linii print przy każdej iteracji staje się wąskim gardłem przy wysokich częstotliwościach zdarzeń:

csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])

clock = time.clock()
i = 0
while True:
    clock.tick()
    n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
    i += 1
    if not i % 10:
        print(f"{n} events  {clock.fps()} fps")

Filtr kontrastu czasoprzestrzennego (STC)

Rzeczywista poruszająca się krawędź kontrastu zwykle wyzwala zaszumioną serię zdarzeń na tym samym pikselu w krótkim oknie czasowym — niedopasowanie pikseli i szum analogowy wytwarzają dodatkowe zdarzenia wokół prawdziwego przejścia, które nie są przydatne dla aplikacji. Filtr STC to przetwarzanie końcowe na chipie, które zachowuje tylko jedno (lub kilka) zdarzeń z serii, a resztę odrzuca.

Implementuje trzy strategie, wybierane za pomocą csi.IOCTL_GENX320_SET_STC oraz stałej GENX320_STC_*. Każdy tryb jest zdefiniowany przez to, które zdarzenia z serii przepuszcza:

Tryb

Zachowuje

Odrzuca

csi.GENX320_STC_DISABLE

każde zdarzenie

nic

csi.GENX320_STC_ONLY

drugie zdarzenie serii

pierwsze + późniejsze zdarzenia

csi.GENX320_STC_TRAIL_ONLY

pierwsze zdarzenie serii

kolejne zdarzenia

csi.GENX320_STC_TRAIL

pierwsze + kolejne krawędzie

tylko zbędny szum

Szczegółowo:

  • csi.GENX320_STC_DISABLE — filtr wyłączony, każde zdarzenie przechodzi (domyślnie).

  • csi.GENX320_STC_ONLY — zachowuje drugie zdarzenie serii. Parametr: stc_threshold (ms). Jeśli nowe zdarzenie na pikselu nadejdzie w czasie stc_threshold od poprzedniego, jest uznawane za „drugie” w serii i przepuszczane — pierwsze zdarzenie oraz wszelkie kolejne zdarzenia w tej samej serii są odfiltrowywane. Najlepsze, gdy chcesz uzyskać przejście potwierdzone wobec szumu, a nie samo pierwsze trafienie.

  • csi.GENX320_STC_TRAIL_ONLY — zachowuje pierwsze zdarzenie serii. Parametr: trail_threshold (ms). Po wyzwoleniu piksela kolejne zdarzenia na tym samym pikselu są odrzucane, dopóki nie upłynie trail_threshold. Zachowuje precyzyjny moment krawędzi prowadzącej — przydatne, gdy chwila przełączenia polaryzacji ma większe znaczenie niż potwierdzenie serii.

  • csi.GENX320_STC_TRAIL — łączy oba. Parametry: stc_threshold oraz trail_threshold (oba w ms). Zachowuje krawędź prowadzącą zgodnie z trybem Trail oraz kolejne krawędzie zgodnie z trybem STC, więc wiele zdarzeń z serii nadal przechodzi — wyższa przepustowość zdarzeń niż filtry jednotrybowe, ale najbogatszy sygnał.

Oba progi muszą pozostać w przybliżeniu w stosunku 13:1 — sensor odrzuca konfiguracje, w których jeden jest większy niż ~13x od drugiego:

csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_TRAIL, 1, 2)
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_DISABLE)

Głębokość bufora

Gdy częstotliwość zdarzeń gwałtownie rośnie, domyślny potrójny potok buforów faworyzuje najnowszą ramkę i odrzuca stare. Zwiększ głębokość FIFO za pomocą csi.CSI.framebuffers, aby zamiast tego kolejkować zdarzenia — kosztem przetwarzania nieco starszych danych, gdy host nie nadąża:

csi0.framebuffers(10)  # FIFO depth, > 3 enables queueing

Strumieniowanie i wizualizacja na komputerze

Do wizualizacji GUI w czasie rzeczywistym na komputerze PC, narzędzie GenX320 Event Streaming z repozytorium openmv-projects łączy kamerę z frontendem DearPyGui. GUI na PC uruchamia dwie wizualizacje obok siebie: kanwę akumulacji zdarzeń (ta sama idea co Image.draw_event_histogram, ale z wyborem palet oraz trybami przesuwnego okna i automatycznego czyszczenia) oraz mapę częstotliwości na piksel sterowaną filtrem pasmowoprzepustowym IIR — przydatną do wykrywania sygnałów okresowych (obracające się wentylatory, migające diody LED itd.) bezpośrednio w strumieniu zdarzeń.

Dostarcza dwa skrypty strumieniujące działające na kamerze:

  • Tryb przetworzony (genx320_event_mode_streaming_on_cam.py) — kamera dekoduje zdarzenia za pomocą csi.IOCTL_GENX320_READ_EVENTS i przesyła każdy wiersz jako 12 bajtów przez USB ([0] typ, [1] sek, [2] ms, [3] us, [4] x, [5] y). Łatwy do odczytania na PC, ponieważ format transmisji odpowiada formatowi ndarray na kamerze.

  • Tryb surowy (genx320_raw_event_mode_streaming_on_cam.py) — kamera przesyła natywne 32-bitowe upakowane słowa zdarzeń chipu poprzez csi.IOCTL_GENX320_READ_EVENTS_RAW. To 4 bajty na zdarzenie zamiast 12 w trybie przetworzonym (około 3x mniej danych przez USB), więc ~3x wyższa osiągalna częstotliwość zdarzeń, gdy łącze jest wąskim gardłem. PC dekoduje upakowane słowa z powrotem do tego samego 6-kolumnowego układu zdarzeń przy użyciu wektoryzowanego numpy, więc kod wizualizatora po stronie odbiorczej jest identyczny.

Tryb surowy jest domyślny w GUI, ponieważ przepustowość USB jest wiążącym ograniczeniem przy częstotliwościach, jakie potrafi wytworzyć GenX320; przełącz się na tryb przetworzony, jeśli musisz włączyć logikę przetwarzania do skryptu działającego na kamerze.