HOWTO · Matplotlib

Converter uma NumPy Array em imagem PIL em Python

Este tutorial explica como podemos converter um array NumPy para uma imagem PIL usando o Image.fromarray() do pacote PIL.

Nesta página

Use Image.fromarray() do Pillow para criar uma imagem PIL a partir de um array NumPy. Um array bidimensional representa pixels em tons de cinza; arrays com formato (height, width, 3) ou (height, width, 4) representam pixels RGB ou RGBA. O dtype e os valores do array também importam, então confira-os antes da conversão. PIL é o nome de importação conhecido, enquanto Pillow é a biblioteca mantida atualmente.

Para uma imagem RGB, crie um array de três canais do tipo uint8 e passe-o para Image.fromarray(). A saída informa o modo da imagem, seu tamanho (width, height) e o pixel no canto superior esquerdo.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import numpy as np
from PIL import Image

pixels = np.array(
    [[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [255, 255, 255]]],
    dtype=np.uint8,
)
image = Image.fromarray(pixels)
print(image.mode, image.size, image.getpixel((0, 0)))
RGB (2, 2) (255, 0, 0)

A imagem resultante é um objeto Pillow mantido na memória. Você pode conferir seu modo e suas dimensões e, em seguida, exibi-la em um ambiente compatível com pré-visualizações de imagem ou salvá-la em um arquivo.

Converter um array NumPy em imagem PIL com Image.fromarray()

Image.fromarray(array) infere o modo da imagem com base no formato e no dtype do array. Para dados em tons de cinza, use um array bidimensional com formato (height, width). Para dados RGB, use (height, width, 3); um quarto canal opcional corresponde a RGBA. As duas primeiras dimensões são linhas e colunas, portanto um array com 40 linhas e 60 colunas cria uma imagem com 60 pixels de largura e 40 de altura.

Use np.uint8 para amostras de imagem comuns de 8 bits quando os valores já forem inteiros entre 0 e 255. Não faça a conversão de tipo sem verificar: converter dados de ponto flutuante trunca as partes fracionárias, e valores fora do intervalo representável podem sofrer wraparound. Para dados medidos em outra escala, decida como eles devem ser mapeados para a intensidade de exibição antes da conversão. Por exemplo, valores no intervalo de 0 a 1 podem ser multiplicados por 255 e arredondados; dados em outro intervalo precisam de uma normalização explícita, adequada ao seu significado.

Preparar o formato, o dtype e o intervalo de valores do array

Um array com formato (height, width) representa uma imagem em tons de cinza. Arrays RGB e RGBA acrescentam uma dimensão de canais no final, com três ou quatro valores por pixel. Um formato como (height, width, 1) não é o formato bidimensional usual para tons de cinza; remova esse último eixo unitário com array[..., 0] quando ele representar tons de cinza. Antes da conversão, confira array.shape, array.dtype e os valores mínimo e máximo. O array deve conter uma grade retangular regular; listas aninhadas irregulares não são dados de imagem.

Veja uma conversão autossuficiente para tons de cinza. Os valores já estão no intervalo de 8 bits, então não é necessário redimensioná-los. A imagem resultante usa o modo de tons de cinza L e tem tamanho (3, 2).

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import numpy as np
from PIL import Image

gray = np.array([[0, 64, 128], [32, 160, 255]], dtype=np.uint8)
image = Image.fromarray(gray)
print(image.mode, image.size)
L (3, 2)

Se a origem for um array de ponto flutuante com valores de 0 a 1, faça a conversão de forma deliberada com np.round(array * 255).astype(np.uint8). Essa transformação só é adequada quando esses valores são intensidades normalizadas. Para medições como temperaturas ou pontuações arbitrárias, escolha limites inferior e superior significativos, limite ou normalize os valores a esse intervalo e documente o mapeamento; caso contrário, o contraste e os valores dos pixels podem induzir a erro.

Aplicar um mapa de cores do Matplotlib antes de criar uma imagem PIL

Use um mapa de cores do Matplotlib quando a origem for um campo escalar bidimensional e você quiser representar seus valores por meio de cores. Normalize os dados para o intervalo esperado pelo mapa de cores escolhido e, em seguida, converta o resultado em um array RGB ou RGBA de 8 bits. O mapa de cores plasma retorna canais normalizados de vermelho, verde, azul e alfa; remover o canal alfa com [..., :3] produz um array RGB. Essa etapa é separada de Image.fromarray(): o mapa de cores gera as cores, e o Pillow envolve esses pixels em uma imagem.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import numpy as np
import matplotlib
from PIL import Image

values = np.array([[0.0, 0.25], [0.75, 1.0]])
colors = matplotlib.colormaps["plasma"](values)[..., :3]
rgb = np.round(colors * 255).astype(np.uint8)
image = Image.fromarray(rgb)
print(image.mode, image.size)
RGB (2, 2)

Este exemplo pressupõe que values já esteja normalizado entre 0 e 1. Se o intervalo escalar real for diferente, normalize os dados com limites apropriados antes de aplicar o mapa de cores. Um mapa de cores é útil para visualização, mas transforma valores escalares em cores de exibição; portanto, não substitua por ele as medições originais quando elas ainda forem necessárias para análise. A imagem resultante é RGB e tem tamanho (2, 2).

Redimensionar e salvar a imagem PIL

Depois da conversão, redimensione a imagem com image.resize((width, height)); o Pillow recebe o novo tamanho na ordem largura e depois altura. Salvar é diferente de exibir: show() abre uma pré-visualização usando o ambiente local, enquanto save() grava um arquivo de imagem. Escolha uma extensão de arquivo compatível com o formato desejado, como .png para uma saída sem perdas ou .jpg para JPEG.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import numpy as np
from PIL import Image

pixels = np.zeros((20, 30, 3), dtype=np.uint8)
pixels[:, :15] = [255, 80, 0]
pixels[:, 15:] = [20, 90, 220]
image = Image.fromarray(pixels)
resized = image.resize((60, 40))
resized.save("resized-image.png")
print(resized.size)

O exemplo cria a entrada na memória e grava resized-image.png no diretório de trabalho atual. Em uma aplicação, use um caminho adequado ao local onde deseja salvar a saída. Salvar não modifica o array NumPy, e redimensionar altera as dimensões em pixels da imagem Pillow.

Problemas comuns de conversão

  • Modo inesperado ou formato rejeitado: confira array.shape. Use (height, width) para tons de cinza e três ou quatro canais na última dimensão para RGB ou RGBA. Remova um canal unitário indesejado ou reordene os canais se a origem usar outra convenção.
  • Brilho ou cores incorretos: confira o intervalo dos valores antes da conversão. Valores armazenados como ponto flutuante entre 0 e 1 precisam ser escalados deliberadamente para uma imagem de 8 bits; valores que já estejam entre 0 e 255 não devem ser multiplicados novamente. Para outros intervalos, normalize os dados com limites documentados.
  • dtype inesperado: confira array.dtype e decida como convertê-lo. Uma conversão de tipo, por si só, não redimensiona os valores; preserve uma cópia da origem se a precisão original for importante.
  • Resultado do mapa de cores incorreto: confirme que a entrada escalar foi normalizada como esperado e confira se o resultado tem três canais RGB ou quatro canais RGBA. Converta os canais de cor normalizados para uint8 antes de criar a imagem.
  • Arquivo de imagem não criado: chame save() com um caminho gravável e uma extensão compatível. Use show() apenas para pré-visualizar; ele não salva a imagem.