Por que funciona: Unidade 1.2: Captura da imagem, Unidade 1.3: A imagem como dado

Abrir uma imagem e ler seus pixels no OpenCV

Carregue uma foto com cv2.imread, veja que ela é uma grade de números com forma e tipo, e leia um pixel e o brilho médio de uma foto tirada dois stops escura demais.

Luis Condados · · 4 min de leitura Iniciante

Tradução assistida por máquina do original em inglês. Números, código e referências foram conferidos com ele; o texto não foi revisado por um falante nativo. Ler o original. Os labs interativos e o código estão em inglês.

Os valores de cinza de um pequeno trecho da foto escura.
Os valores de cinza de um pequeno trecho da foto escura.

Para um computador, uma foto é uma tabela de números: um número por cor em cada ponto. Esta lição abre uma foto e lê essa tabela.

A foto escura de um abajur de mesa e uma carta de cores, ao lado de uma grade com os valores de cinza em volta de um ponto dela.
À esquerda: a foto que este módulo corrige, tirada cerca de dois stops escura demais. À direita: os valores de cinza de um trecho de 6 × 8 dela, em volta da linha 320, coluna 480. Quadro do HDR Photographic Survey [3].

Onde você já viu isso: o conta-gotas de qualquer editor de imagem, que mostra os números sob o cursor; a janela Ponteiro do GIMP; a leitura RGB de uma ferramenta de captura de cor da tela.

TL;DR. Abrir uma foto no OpenCV te dá uma grade simples de números que você pode inspecionar e alterar. Esta tem 639 linhas por 960 colunas por 3 canais de cor de valores de 8 bits, e seu brilho médio é 56,6 de 255, por isso ela parece escura.

O problema

Este módulo corrige a foto acima. Ela é uma de dezoito exposições do mesmo abajur e da mesma carta de cores do HDR Photographic Survey [3], e foi tirada em 1,3 segundo, cerca de dois stops (quase quatro vezes menos luz) abaixo da exposição de 5 segundos que o módulo usa como alvo. Antes de corrigir qualquer coisa, você precisa ver o que o OpenCV te entrega quando abre o arquivo.

Abra a imagem

cv2.imread lê o arquivo e devolve um array NumPy [1]. Aperte Rodar: o código é Python e OpenCV de verdade, rodando no seu navegador.

Python · OpenCV
import cv2

img = cv2.imread("dark.webp")
print("shape:", img.shape)
print("dtype:", img.dtype)
cv2.imshow("dark.webp", img)
Saída
shape: (639, 960, 3)
dtype: uint8
dark.webp
dark.webp
ParteO que ela diz
shape (639, 960, 3)639 linhas (altura), 960 colunas (largura), 3 canais
dtype uint8cada valor é um número inteiro de 0 a 255
cv2.imshowmostra o array como imagem; aqui ela aparece embaixo da célula

A ordem é linhas primeiro, depois colunas. A altura vem antes da largura, o contrário de como tamanhos de imagem costumam ser escritos (“960 × 639”), e essa é a primeira coisa que confunde quem está começando.

Leia um pixel, depois a imagem inteira

Indexar o array com [linha, coluna] dá os três valores de um pixel. O OpenCV os guarda como azul, verde, vermelho, e não vermelho, verde, azul: o modo de leitura padrão carrega “the BGR 8-bit format” [1]. Converter para cinza junta os três num único valor de brilho por pixel, e a média desses valores resume em um número o quão clara a foto é.

Python · OpenCV
# Um pixel: linha 320, coluna 480. O OpenCV guarda B, G, R.
print("pixel at row 320, column 480:", img[320, 480])

# A imagem inteira em um número: o brilho médio
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print("mean brightness:", round(grey.mean(), 1), "of 255")
Saída
pixel at row 320, column 480: [140 175 206]
mean brightness: 56.6 of 255

Esse pixel fica no brilho do fundo da cúpula do abajur, por isso é claro. A foto como um todo tem média 56,6: a maior parte dela é mesa e parede escuras. A exposição de 5 segundos da mesma cena, que o módulo usa como alvo, tem média 104,7, e a lição 2 mostra isso com um histograma.

Quebre

Muitas outras bibliotecas Python esperam vermelho, verde, azul. Passe um array do OpenCV para elas sem converter e os canais azul e vermelho trocam de lugar. A célula abaixo faz a troca de propósito para você ver como fica: o abajur quente vira azul frio.

Quebre
# Finge que outra biblioteca leu o array como R, G, B
swapped = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.imshow("blue and red swapped", swapped)
Saída
blue and red swapped
blue and red swapped

Sua vez

Encontre o maior valor de cinza da foto e guarde em brightest.

Sua vez
brightest = 0  # troque pelo maior valor de cinza de img
print(brightest)

Por que funciona

Uma câmera conta a luz que chegou a cada fotossítio durante a exposição. Guardar o resultado amostra a cena numa grade e quantiza cada valor em um de 256 níveis [2], e é daí que vêm a forma e o uint8. A lição An image is an array (em inglês) explica o que a forma e o tipo significam, From photons to counts de onde vêm os números, e Colour spaces por que existem três canais. As três estão na trilha de Fundamentos, unidades 1.2 e 1.3.

Erros comuns

  • imread devolve None em vez de falhar. Um caminho errado não dá erro até a próxima linha mexer no array. Teste img is None quando o arquivo puder não existir.
  • Azul e vermelho trocados. O OpenCV usa B, G, R; matplotlib, Pillow e a maior parte do código web usam R, G, B. Converta com cv2.cvtColor(img, cv2.COLOR_BGR2RGB) antes de passar o array adiante.
  • img[x, y] em vez de img[y, x]. Linhas vêm primeiro. Numa imagem que não é quadrada, isso lê o pixel errado ou falha com erro de índice.
  • cv2.imshow num notebook. Num script ele abre uma janela e precisa de cv2.waitKey; no Colab e no Jupyter ele não funciona. O notebook do módulo mostra as imagens com matplotlib.

Referências

[1] OpenCV. Getting Started with Images (tutorial, doc/tutorials/introduction/display_image/display_image.markdown). OpenCV 4.11.0, commit 31b0eee. github.com/opencv/opencv

[2] Gonzalez, R. C., & Woods, R. E. (2018). Digital Image Processing (4th ed.), section 2.4 “Image Sampling and Quantization”, p. 47. Pearson.

[3] Fairchild, M. D. (2007). The HDR Photographic Survey. IS&T 15th Color Imaging Conference, pp. 233–238. doi:10.2352/CIC.2007.15.1.art00044