6.18. Imagens e ndarrays¶
A classe Image é a superfície rápida para o trabalho com pixels nativos da câmera: todos os seus métodos operam diretamente no frame buffer no formato de pixel nativo da câmera. O numpy é a superfície numérica genérica para todo o resto. Dois métodos fazem a ponte entre eles:
image.Image.to_ndarray()– copia os pixels de uma imagem para umndarray.O construtor
image.Image– cria uma nova imagem a partir de umndarray.
Juntos, eles permitem que uma aplicação capture um quadro, o entregue ao numpy para uma transformação personalizada e, em seguida, coloque o resultado de volta em uma imagem para exibir, salvar ou realimentar o restante da biblioteca de imagens.
6.18.1. Imagem para ndarray¶
to_ndarray() aloca um novo ndarray e copia os dados de pixel da imagem para ele (com o mapeamento de dtype abaixo). Nunca é uma visualização do frame buffer da imagem – o array numpy sempre é dono de seus próprios bytes. A assinatura é to_ndarray(dtype, *, buffer=None), e o formato da saída depende do formato da imagem:
GRAYSCALE – array 2-D, formato
(height, width).RGB565 – array 3-D, formato
(height, width, 3), planos na ordem R/G/B.
O argumento dtype controla como cada valor de pixel v de 8 bits é mapeado:
|
elemento |
mapeamento para um valor de pixel |
|---|---|---|
|
|
|
|
|
|
|
|
|
Exemplo – visualizar um quadro em escala de cinza como uma matriz 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))
A palavra-chave buffer= permite que a aplicação reutilize um bytearray que já alocou, de modo que a câmera não precise alocar um novo a cada quadro:
buf = bytearray(320 * 240)
while True:
img = csi0.snapshot()
a = img.to_ndarray('B', buffer=buf)
# ... process a ...
6.18.2. ndarray para imagem¶
No sentido inverso, passe o ndarray como primeiro argumento para image.Image. O construtor aloca um novo buffer de imagem e copia os valores do array para ele, restringidos e arredondados para 0..255
image.Image(arr, *, buffer=None, copy_to_fb=False)
O construtor infere a geometria e o formato de pixel a partir do formato do array:
formato
(h, w)– imagemGRAYSCALE.formato
(h, w, 3)– imagemRGB565.
O ndarray deve ter dtype float; o construtor só oferece suporte a esse caso atualmente. Os valores são arredondados e restringidos ao intervalo 0..255.
buffer= permite que a aplicação forneça um bytearray que já alocou para a imagem resultante. copy_to_fb=True grava o resultado no frame buffer da câmera, o que é a escolha certa quando o resultado deve aparecer na pré-visualização do IDE.
6.18.3. Ida e volta¶
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. Quando fazer a ponte¶
Essa ponte é a resposta certa quando a aplicação precisa de uma operação numérica genérica que os métodos integrados de image não fornecem – filtros personalizados, mesclagens personalizadas, não-linearidades incomuns – ou quando os dados de pixel precisam ser combinados com dados que não são de imagem (eixos da IMU, amostras de áudio) em um único cálculo.
Ela não é a resposta certa para o processamento de pixels de alto throughput que a classe Image já cobre. Os métodos integrados operam diretamente no frame buffer no formato de pixel nativo da câmera e são muito mais rápidos do que a expressão numpy equivalente. Recorra à ponte para as operações que a biblioteca de imagens ainda não fornece.