6.18. Afbeeldingen en ndarrays¶
De Image-klasse is het snelle oppervlak voor camera-eigen pixelbewerkingen: elke methode erop werkt rechtstreeks op de framebuffer in het camera-eigen pixelformaat. numpy is het generieke numerieke oppervlak voor al het overige. Twee methoden vormen de brug ertussen:
image.Image.to_ndarray()– kopieert de pixels van een afbeelding naar eenndarray.De
image.Image-constructor – bouwt een nieuwe afbeelding op uit eenndarray.
Samen stellen ze een applicatie in staat om een frame vast te leggen, het door te geven aan numpy voor een aangepaste transformatie, en het resultaat vervolgens terug te plaatsen in een afbeelding om weer te geven, op te slaan of terug te voeren naar de rest van de afbeeldingsbibliotheek.
6.18.1. Van afbeelding naar ndarray¶
to_ndarray() reserveert een nieuwe ndarray en kopieert de pixelgegevens van de afbeelding ernaartoe (met de dtype-toewijzing hieronder). Het is nooit een view op de framebuffer van de afbeelding – de numpy-array bezit altijd zijn eigen bytes. De signatuur is to_ndarray(dtype, *, buffer=None), en de uitvoervorm hangt af van het afbeeldingsformaat:
GRAYSCALE – 2D-array, vorm
(height, width).RGB565 – 3D-array, vorm
(height, width, 3), vlakken in R/G/B-volgorde.
Het dtype-argument bepaalt hoe elke 8-bits pixelwaarde v wordt toegewezen:
|
element |
toewijzing voor een 8-bits pixelwaarde |
|---|---|---|
|
|
|
|
|
|
|
|
|
Voorbeeld – bekijk een grijswaarden-frame als een uint8-matrix:
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))
Met het sleutelwoord buffer= kan de applicatie een bytearray hergebruiken die ze al heeft gereserveerd, zodat de camera niet elk frame een nieuwe hoeft te reserveren:
buf = bytearray(320 * 240)
while True:
img = csi0.snapshot()
a = img.to_ndarray('B', buffer=buf)
# ... process a ...
6.18.2. Van ndarray naar afbeelding¶
Voor de andere richting geef je de ndarray als eerste argument door aan image.Image. De constructor reserveert een nieuwe afbeeldingsbuffer en kopieert de waarden van de array ernaartoe, afgekapt en afgerond op 0..255
image.Image(arr, *, buffer=None, copy_to_fb=False)
De constructor leidt de geometrie en het pixelformaat af uit de vorm van de array:
vorm
(h, w)–GRAYSCALE-afbeelding.vorm
(h, w, 3)–RGB565-afbeelding.
De ndarray moet dtype float hebben; de constructor ondersteunt op dit moment alleen dat geval. Waarden worden afgerond en afgekapt tot het bereik 0..255.
Met buffer= kan de applicatie een bytearray aanleveren die ze al heeft gereserveerd voor de resulterende afbeelding. copy_to_fb=True schrijft het resultaat naar de framebuffer van de camera, wat de juiste keuze is wanneer het resultaat in het IDE-voorbeeld moet verschijnen.
6.18.3. Heen en terug¶
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. Wanneer de brug gebruiken¶
Deze brug is het juiste antwoord wanneer de applicatie een generieke numerieke bewerking nodig heeft die de ingebouwde image-methoden niet bieden – aangepaste filters, aangepaste mengbewerkingen, ongebruikelijke niet-lineariteiten – of wanneer pixelgegevens in een enkele berekening moeten worden gecombineerd met niet-afbeeldingsgegevens (IMU-assen, audiosamples).
Het is niet het juiste antwoord voor pixelverwerking met hoge doorvoer die de Image-klasse al afdekt. De ingebouwde methoden werken rechtstreeks op de framebuffer in het camera-eigen pixelformaat en zijn veel sneller dan de equivalente numpy-expressie. Grijp naar de brug voor de bewerkingen die de afbeeldingsbibliotheek nog niet biedt.