Multispektralna kamera zdarzeniowa

Moduł Multispectral Event Camera łączy sensor zdarzeniowy GENX320 z 1-megapikselowym sensorem kolorowym PAG7936 z migawką globalną na jednym module — zsynchronizowany potok zdarzenia + kolor do śledzenia szybko poruszających się obiektów, śledzenia diod LED, przepływu cieczy i innych dynamicznych scen.

Multispektralna kamera zdarzeniowa

Pełną kartę katalogową, zdjęcia i informacje o zamawianiu znajdziesz na stronie produktu Multispectral Event Camera.

Informacja

Obsługiwane wyłącznie na OpenMV N6.

Najważniejsze cechy

  • Sensor zdarzeniowy 320x320, zakres dynamiki >140 dB, histogramy 375 Hz+

  • Kolor PAG7936: 1280x800 @ 120 FPS, 640x400 @ 240 FPS

  • Zsynchronizowane znaczniki czasu zdarzeń ze wspólnym wyzwalaniem ekspozycji

  • Widzi poniżej 5 luksów bez automatycznej ekspozycji

  • Pobór mocy zaczyna się od ~3 mW dla strumienia zdarzeń

  • Przeznaczona do szybkiego śledzenia, śledzenia diod LED oraz przepływu cieczy/cząstek

Użycie

Sensor kolorowy oraz sensor zdarzeniowy GENX320 otrzymują każdy własną instancję csi.CSI. Pierwsze wywołanie domyślnie odnosi się do sensora głównego (PAG7936); drugie wiąże się z GENX320 poprzez przekazanie cid= csi.GENX320. Wykonaj twardy reset sensora kolorowego za pomocą csi.CSI.reset (hard=True), aby podnieść szynę zasilania, a GENX320 skonfiguruj z hard=False, tak aby jego sterownik jedynie przeprogramował układ bez ponownego przełączania resetu.

GENX320 wyprowadza obraz 320x320 w trybie histogramu; PAG7936 przy csi.QVGA wyprowadza 320x200. Poniższa podstawowa nakładka obcina dolne 120 wierszy ramki GENX320. Użyj transformacji homograficznej (poniżej), aby uzyskać dopasowaną nakładkę lub większy rozmiar ramki PAG7936.

Dwa bufory robocze pozostają niezmienne przez całą pętlę ramek — paleta alfa 256x1 przechowywana jako image.Image, dzięki której piksele histogramu na poziomie bazowym średniej szarości (128) stają się przezroczyste, a zarówno wyróżnienia zdarzeń ON, jak i cienie zdarzeń OFF stają się nieprzezroczyste, oraz bufor ramki GENX320 wstępnie zaalokowany za pomocą image.Image, tak aby csi.CSI.snapshot (blocking=False, image=...) mógł go wypełniać w miejscu w każdej iteracji bez ponownej alokacji:

import time
import csi
import image
import math

# V-shaped alpha: pixels far from the baseline 128 become opaque.
alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow(abs(i - 128) / 128.0, 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)  # force hardware reset.
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

csi1 = csi.CSI(cid=csi.GENX320)
csi1.reset(hard=False)  # no hardware reset - just configure GENX320
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize((320, 320))
csi1.brightness(128)  # histogram baseline (default)
csi1.contrast(64)     # per-event step

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_EVT_LIGHT,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR)
    print(clock.fps())

Każda iteracja wykonuje blokujący zrzut obrazu kolorowego i nieblokujący zrzut GENX320. Następnie Image.draw_image komponuje oba: color_palette= image.PALETTE_EVT_LIGHT (lub image.PALETTE_EVT_DARK dla ciemnego tła) odwzorowuje histogram w skali szarości z GENX320 na rampę kolorów, alpha_palette= miesza każdy piksel przy użyciu mapy alfa w kształcie litery v, tak aby spokojne obszary sceny przepuszczały obraz kolorowy, a hint= image.BILINEAR wygładza skalowanie w górę, gdy sensor kolorowy pracuje w wyższej rozdzielczości niż GENX320.

Presety biasów GENX320, filtr AFK, kalibracja gorących pikseli oraz ioctl filtra STC działają w tej konfiguracji z dwiema kamerami dokładnie tak samo — wywołuj je na csi1 po csi.CSI.reset. Szczegóły znajdziesz w sekcjach poniżej.

Wyrównanie z akceleracją GPU

Image.draw_image przyjmuje argument transform= — macierz homograficzną 3x3 jako 2-wymiarową tablicę ulab.numpy. Na OpenMV N6 GPU wykonuje projekcję per-piksel podczas tego samego rysowania, dzięki czemu ramkę GENX320 można ponownie wyrównać do perspektywy kamery kolorowej bez osobnego przebiegu warpowania — przydatne, gdy dwa sensory mają nieco inną optykę lub pola widzenia, albo gdy kamera kolorowa pracuje w wyższej rozdzielczości. Skalibruj macierz dla każdej kamery za pomocą narzędzia GenX320 Overlay Calibration, które wyświetla migającą szachownicę, dzięki czemu sensor zdarzeniowy generuje zdarzenia narożne bez żadnego fizycznego ruchu:

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

# Calibration matrix from the GenX320 Overlay Calibration tool.
m = np.array([
    [2.000000, 0.000000,   0.000000],
    [0.000000, 2.000000,  80.000000],
    [0.000000, 0.000000,   1.000000],
])

alpha_pal = image.Image(256, 1, image.GRAYSCALE)
for i in range(256):
    alpha_pal[i] = int(math.pow(abs(i - 128) / 128.0, 2) * 255)

# Setup the color camera sensor.
csi0 = csi.CSI()
csi0.reset(hard=True)
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.VGA)

csi1 = csi.CSI(cid=csi.GENX320)
csi1.reset(hard=False)
csi1.pixformat(csi.GRAYSCALE)
csi1.framesize((320, 320))
csi1.brightness(128)
csi1.contrast(64)

clock = time.clock()

img1 = image.Image(csi1.width(), csi1.height(), csi1.pixformat())

while True:
    clock.tick()
    img0 = csi0.snapshot()
    csi1.snapshot(blocking=False, image=img1)
    img0.draw_image(img1, 0, 0, color_palette=image.PALETTE_EVT_LIGHT,
                    alpha_palette=alpha_pal,
                    hint=image.BILINEAR,
                    transform=m)
    print(clock.fps())

Ten wariant uruchamia kamerę kolorową w csi.VGA (640x480), a GENX320 w jego natywnej rozdzielczości 320x320 — homografia rzutuje mniejszą ramkę GENX320 na większą ramkę kolorową w ramach rysowania, więc współczynnik skalowania w górę jest wbudowany w samą macierz, a nie stosowany osobno.

Szczegóły kamery zdarzeniowej

GENX320 jest sensorem wizyjnym opartym na zdarzeniach — zamiast odczytywać całą macierz 320x320 według stałego zegara ramek, każdy piksel zgłasza asynchroniczne „zdarzenia” w momencie wykrycia zmiany jasności. Każde zdarzenie niesie współrzędną X/Y, polaryzację ON/OFF (jasny→ciemny lub ciemny→jasny) oraz mikrosekundowy znacznik czasu. Stąd biorą się mikrosekundowa precyzja czasowa sensora, brak rozmycia ruchu, bardzo wysoki zakres dynamiki oraz pobór mocy skalujący się z 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ą akumulowane na układzie do binów per-piksel i zgłaszane 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 są strumieniowane do tablicy numpy ndarray z pełnymi mikrosekundowymi znacznikami czasu, dla zastosowań wymagających szczegółów czasowych zamiast wstępnie zbinowanej ramki.

Tryb histogramu

W trybie histogramu GENX320 wyprowadza ramki w skali szarości, gdzie każdy piksel koduje niedawną aktywność zdarzeń w danym miejscu. Piksele powyżej poziomu bazowego jasności to zdarzenia ON (jasność rosnąca), poniżej to zdarzenia OFF (jasność malejąca). Domyślna jasność bazowa wynosi 128, a krok kontrastu na zdarzenie wynosi 16 — zwiększ kontrast, aby zdarzenia były bardziej widoczne:

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, używając palety bezpośrednio:

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ń per-piksel 320x320, oblicza średnią i odchylenie standardowe, a następnie wyłącza każdy piksel, którego licznik jest powyżej 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, aż bieżąca suma zdarzeń przekroczy ten budżet. 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 na odchyleniu standardowym. Niższe wartości są bardziej agresywne (więcej wyłączonych pikseli); wyższe są bardziej zachowawcze. 0.5 to dobra wartość domyślna.

Skieruj najpierw sensor na statyczną scenę, tak aby żadne zdarzenia wywołane ruchem nie został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 antymigotaniowy (AFK)

Okresowe źródła światła (świetlówki, wyświetlacze sterowane diodami LED) generują ogromne ilości nadmiarowych zdarzeń. Filtr AFK odrzuca zdarzenia, których piksel przełącza się z częstotliwością wewnątrz danego pasma — włącz go za pomocą csi.IOCTL_GENX320_SET_AFK z krawędziami 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

Presety biasów

Każdy piksel w GenX320 ma analogowy front-end z kilkoma konfigurowalnymi biasami. Wspólnie regulują one czułość, szum, pasmo piksela oraz częstość 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 log-naświetlenie wzrosło o tę wartość. Niżej = większa czułość na jasne przejścia.

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

  • FO — dolnoprzepustowa częstotliwość odcięcia piksela. Wyżej = szersze pasmo piksela (szybsza odpowiedź, niższe opóźnienie), ale większa aktywność szumu tła.

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

  • REFR — okres refrakcji. Po wyzwoleniu piksel pozostaje w resecie przez ten czas, zanim będzie mógł wyzwolić się ponownie. Wyżej = dłuższy czas martwy, przydatny do ograniczania częstości zdarzeń per-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ęstość zdarzeń tła, więc LOW_NOISE jest używany jako punkt wyjścia, aby utrzymać strumień spokojnym. Wywołaj csi.IOCTL_GENX320_SET_BIASES z innym presetem, gdy zastosowanie 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 ograniczyć szum, oraz HPF ustawione na 0, tak aby powolne zmiany jasności wciąż były rejestrowane — scena słabo oświetlona sama generuje niewiele zdarzeń, więc chcemy, aby przeszło ich jak najwięcej.

  • csi.GENX320_BIASES_ACTIVE_MARKER — dostrojony do śledzenia migających diod LED o wysokim kontraście. Progi kontrastu podniesione, tak aby wyzwalały tylko ostre przejścia; FO i HPF podkręcone w górę, aby zmaksymalizować pasmo piksela i odrzucić wszelki powolny dryft otoczenia; REFR ściągnięte do 0, tak aby każda krawędź migotania była przechwytywana jedna po drugiej. Rezultat: strumień, który składa się niemal wyłącznie z krawędzi diod LED, łatwy do śledzenia.

  • csi.GENX320_BIASES_LOW_NOISE — domyślny dla sterownika. Oba progi kontrastu podniesione względem DEFAULT (mniejsza czułość) i FO obniżone (wolniejszy piksel = cichszy piksel). Najlepszy dla scen statycznych lub powolnych, gdzie fałszywe zdarzenia inaczej by dominowały.

  • csi.GENX320_BIASES_HIGH_SPEED — FO podbite, tak aby każdy piksel mógł reagować szybciej, HPF podniesione, aby odrzucać powolny dryft jasności, oraz REFR podniesione, tak aby pojedyncza szybko poruszająca się krawędź nie zalewała odczytu — dłuższy czas martwy utrzymuje wolumen zdarzeń w ryzach 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 i wartości DAC. Każdy bias ustawiany jest niezależnie — wybierz preset jako punkt wyjścia, a następnie dostrój te biasy, których potrzebuje 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 trybu 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 układzie i strumieniuje surowe zdarzenia do tablicy numpy ndarray. Każde zdarzenie to wiersz sześciu kolumn uint16:

  • [0] typ zdarzenia — patrz 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 (przekroczony został próg komparatora DIFF_OFF). X/Y wskazują piksel, który się wyzwolił.

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

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

  • csi.EXT_TRIGGER_RISING — pin zewnętrznego wyzwalania sensora zaobserwował 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.

Wejście zewnętrznego wyzwalania GENX320 jest podłączone do linii frame-sync kamery, która jest również wyprowadzona na P10 zarówno na procesorze, jak i na listwie pinów — wysteruj P10, aby wstrzyknąć zbocza synchronizacji do strumienia zdarzeń i odebrać je jako zdarzenia EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING obok danych pikselowych.

Większość zastosowań interesuje tylko PIX_OFF_EVENT i PIX_ON_EVENT; typy wyzwalania pozwalają skorelować 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 wejdź w tryb zdarzeń poprzez csi.IOCTL_GENX320_SET_MODE z csi.GENX320_MODE_EVENT i rozmiarem bufora. Odczytuj zdarzenia za pomocą csi.IOCTL_GENX320_READ_EVENTS, które wypełnia bufor do jego pojemności i zwraca liczbę poprawnych 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 akumuluje 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ń dokładnie tak samo — wywołuj je po csi.IOCTL_GENX320_SET_MODE.

Filtrowanie według polaryzacji

Wytnij fragment tablicy zdarzeń za pomocą ulab, aby zachować tylko zdarzenia ON (ruch w stronę 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 układać zdarzenia w tym samym obrazie przez wiele ramek — rezultatem 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ą

Zrezygnuj z wizualizacji, aby zwolnić CPU na przetwarzanie zdarzeń. Wypisuj statystyki tylko co N-tą iterację — wypychanie linii wydruku w każdej iteracji staje się wąskim gardłem przy wysokich częstoś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 czasowo-przestrzennego (STC)

Rzeczywista poruszająca się krawędź kontrastu zwykle wyzwala zaszumiony wybuch zdarzeń na tym samym pikselu w krótkim oknie czasowym — niedopasowanie pikseli i szum analogowy generują dodatkowe zdarzenia wokół prawdziwego przejścia, które nie są przydatne dla aplikacji. Filtr STC to wykonywany na układzie post-proces, który zachowuje tylko jedno (lub kilka) zdarzeń na wybuch, a resztę odrzuca.

Implementuje trzy strategie, wybierane za pomocą csi.IOCTL_GENX320_SET_STC i stałej GENX320_STC_*. Każdy tryb definiowany jest przez to, które zdarzenia przekazuje z wybuchu:

Tryb

Zachowuje

Odrzuca

csi.GENX320_STC_DISABLE

każde zdarzenie

nic

csi.GENX320_STC_ONLY

drugie zdarzenie wybuchu

pierwsze + późniejsze zdarzenia

csi.GENX320_STC_TRAIL_ONLY

pierwsze zdarzenie wybuchu

kolejne zdarzenia

csi.GENX320_STC_TRAIL

pierwsze + kolejne krawędzie

tylko nadmiarowy szum

Szczegółowo:

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

  • csi.GENX320_STC_ONLY — zachowuje drugie zdarzenie wybuchu. Parametr: stc_threshold (ms). Jeśli nowe zdarzenie na pikselu nadejdzie w czasie stc_threshold od poprzedniego zdarzenia, uznawane jest za „drugie” w wybuchu i zostaje przekazane — pierwsze zdarzenie oraz wszelkie kolejne zdarzenia w tym samym wybuchu są odfiltrowywane. Najlepsze, gdy chcesz przejścia potwierdzonego szumem, a nie samego pierwszego trafienia.

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

  • csi.GENX320_STC_TRAIL — łączy oba. Parametry: stc_threshold i trail_threshold (oba w ms). Zachowuje zbocze wiodące zgodnie z trybem Trail plus kolejne zbocza zgodnie z trybem STC, tak aby przeszło wiele zdarzeń z wybuchu — wyższa przepustowość zdarzeń niż w filtrach jednotrybowych, ale najbogatszy sygnał.

Oba progi muszą pozostawać 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ęstości zdarzeń gwałtownie rosną, domyślny potok z potrójnym buforowaniem 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 się opóźnia:

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 w repozytorium openmv-projects łączy kamerę z front-endem DearPyGui. GUI na PC uruchamia dwie wizualizacje obok siebie: kanwę akumulacji zdarzeń (ta sama idea co Image.draw_event_histogram, ale z wybieralnymi paletami oraz trybami okna przesuwnego vs. automatycznego czyszczenia) oraz mapę częstotliwości per-piksel sterowaną filtrem pasmowo-przepustowym IIR — przydatną do wykrywania sygnałów okresowych (obracające się wentylatory, migające diody LED itd.) bezpośrednio w strumieniu zdarzeń.

Dostarcza dwa skrypty strumieniowania 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 strumieniuje każdy wiersz jako 12 bajtów przez USB ([0] typ, [1] sek, [2] ms, [3] us, [4] x, [5] y). Łatwy do konsumpcji na PC, ponieważ format przesyłany odpowiada formatowi ndarray na kamerze.

  • Tryb surowy (genx320_raw_event_mode_streaming_on_cam.py) — kamera strumieniuje natywne 32-bitowe upakowane słowa zdarzeń układu 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ęstość 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 zwektoryzowanego numpy, więc kod wizualizatora po stronie pobierającej jest identyczny.

Tryb surowy jest domyślny w GUI, ponieważ przepustowość USB stanowi wiążące ograniczenie przy częstościach, jakie GenX320 jest w stanie wygenerować; przełącz na tryb przetworzony, jeśli musisz wpiąć logikę przetwarzania w skrypt działający na kamerze.