6.18. Obrazy i tablice ndarray¶
Klasa Image to szybka warstwa do pracy z pikselami w natywnym formacie kamery: każda jej metoda działa bezpośrednio na buforze ramki w natywnym formacie pikseli kamery. numpy to ogólna warstwa numeryczna do wszystkiego pozostałego. Łączą je dwie metody:
image.Image.to_ndarray()– kopiuje piksele obrazu dondarray.Konstruktor klasy
image.Image– buduje nowy obraz zndarray.
Razem pozwalają aplikacji wykonać zrzut ramki, przekazać go do numpy w celu niestandardowej transformacji, a następnie umieścić wynik z powrotem w obrazie, aby go wyświetlić, zapisać lub przekazać dalej do reszty biblioteki obrazów.
6.18.1. Obraz na tablicę ndarray¶
to_ndarray() alokuje nową ndarray i kopiuje do niej dane pikseli obrazu (z mapowaniem dtype opisanym poniżej). Nigdy nie jest to widok na bufor ramki obrazu – tablica numpy zawsze posiada własne bajty. Sygnatura to to_ndarray(dtype, *, buffer=None), a kształt wyniku zależy od formatu obrazu:
GRAYSCALE – tablica 2-W, kształt
(height, width).RGB565 – tablica 3-W, kształt
(height, width, 3), płaszczyzny w kolejności R/G/B.
Argument dtype kontroluje sposób mapowania każdej 8-bitowej wartości piksela v:
|
element |
mapowanie dla 8-bitowej wartości piksela |
|---|---|---|
|
|
|
|
|
|
|
|
|
Przykład – potraktowanie ramki w skali szarości jako macierzy uint8
import csi
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('B') # shape (240, 320), dtype=uint8
print(a.shape, a.dtype)
print("mean brightness:", np.mean(a))
Słowo kluczowe buffer= pozwala aplikacji ponownie wykorzystać już zaalokowaną bytearray, dzięki czemu kamera nie musi alokować nowej dla każdej ramki:
buf = bytearray(320 * 240)
while True:
img = csi0.snapshot()
a = img.to_ndarray('B', buffer=buf)
# ... process a ...
6.18.2. Tablica ndarray na obraz¶
W drugą stronę: przekaż ndarray jako pierwszy argument do image.Image. Konstruktor alokuje nowy bufor obrazu i kopiuje do niego wartości tablicy, przyciête i zaokrąglone do zakresu 0..255
image.Image(arr, *, buffer=None, copy_to_fb=False)
Konstruktor wnioskuje geometrię i format pikseli z kształtu tablicy:
kształt
(h, w)– obrazGRAYSCALE.kształt
(h, w, 3)– obrazRGB565.
ndarray musi mieć dtype float; konstruktor obsługuje obecnie tylko ten przypadek. Wartości są zaokrąglane i przycinane do zakresu 0..255.
buffer= pozwala aplikacji dostarczyć już zaalokowaną bytearray dla wynikowego obrazu. copy_to_fb=True zapisuje wynik do bufora ramki kamery, co jest właściwym wyborem, gdy wynik ma się pojawić w podglądzie IDE.
6.18.3. Pełny obieg¶
import csi
import image
from ulab import numpy as np
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
img = csi0.snapshot()
a = img.to_ndarray('f') # work in float space
a = 255.0 * (a / 255.0) ** 0.5 # gamma correction
out = image.Image(a, copy_to_fb=True) # back to an image
6.18.4. Kiedy stosować pomost¶
Ten pomost jest właściwą odpowiedzią, gdy aplikacja potrzebuje ogólnej operacji numerycznej, której nie zapewniają wbudowane metody image – niestandardowych filtrów, niestandardowego mieszania, nietypowych nieliniowości – lub gdy dane pikseli muszą być połączone z danymi spoza obrazu (osie IMU, próbki dźwiękowe) w jednym obliczeniu.
Nie jest natomiast właściwą odpowiedzią dla przetwarzania pikseli o wysokiej przepustowości, które obsługuje już klasa Image. Wbudowane metody działają bezpośrednio na buforze ramki w natywnym formacie pikseli kamery i są znacznie szybsze niż równoważne wyrażenie numpy. Po pomost sięgaj dla operacji, których biblioteka obrazów jeszcze nie zapewnia.