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:

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:

dtype

element

mapowanie dla 8-bitowej wartości piksela v

'B'

uint8

v (surowa)

'b'

int8

v - 128 (wyśrodkowana wokół zera)

'f'

float32

float(v) (0.0 … 255.0)

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) – obraz GRAYSCALE.

  • kształt (h, w, 3) – obraz RGB565.

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.