Por qué funciona: Unidad 1.2: Captura de la imagen, Unidad 1.3: La imagen como datos

Abrir una imagen y leer sus píxeles en OpenCV

Carga una foto con cv2.imread, comprueba que es una rejilla de números con forma y tipo, y lee un píxel y el brillo medio de una foto tomada dos pasos demasiado oscura.

Luis Condados · · 4 min de lectura Principiante

Traducción asistida por máquina del original en inglés. Los números, el código y las referencias se comprobaron contra él; el texto no fue revisado por un hablante nativo. Leer el original. Los labs interactivos y el código están en inglés.

Los valores de gris de un pequeño trozo de la foto oscura.
Los valores de gris de un pequeño trozo de la foto oscura.

Para una computadora, una foto es una tabla de números: un número por color en cada punto. Esta lección abre una foto y lee esa tabla.

La foto oscura de una lámpara de escritorio y una carta de colores, junto a una rejilla con los valores de gris alrededor de un punto de ella.
A la izquierda: la foto que corrige este módulo, tomada unos dos pasos demasiado oscura. A la derecha: los valores de gris de un trozo de 6 × 8, alrededor de la fila 320, columna 480. Fotograma del HDR Photographic Survey [3].

Dónde ya lo has visto: el cuentagotas de cualquier editor de imágenes, que muestra los números bajo el cursor; el diálogo Puntero de GIMP; la lectura RGB de una herramienta para capturar colores de la pantalla.

TL;DR. Abrir una foto en OpenCV te da una rejilla simple de números que puedes inspeccionar y modificar. Esta tiene 639 filas por 960 columnas por 3 canales de color con valores de 8 bits, y su brillo medio es 56,6 de 255, por eso se ve oscura.

El problema

Este módulo corrige la foto de arriba. Es una de dieciocho exposiciones de la misma lámpara y la misma carta de colores del HDR Photographic Survey [3], y se tomó a 1,3 segundos, unos dos pasos (casi cuatro veces menos luz) por debajo de la exposición de 5 segundos que el módulo usa como objetivo. Antes de corregir nada, necesitas ver qué te entrega OpenCV cuando abre el archivo.

Abre la imagen

cv2.imread lee el archivo y devuelve un array de NumPy [1]. Pulsa Ejecutar: el código es Python y OpenCV de verdad, ejecutándose en tu navegador.

Python · OpenCV
import cv2

img = cv2.imread("dark.webp")
print("shape:", img.shape)
print("dtype:", img.dtype)
cv2.imshow("dark.webp", img)
Salida
shape: (639, 960, 3)
dtype: uint8
dark.webp
dark.webp
ParteQué te dice
shape (639, 960, 3)639 filas (alto), 960 columnas (ancho), 3 canales
dtype uint8cada valor es un número entero de 0 a 255
cv2.imshowmuestra el array como imagen; aquí aparece debajo de la celda

El orden es primero filas, luego columnas. El alto va antes que el ancho, al revés de como se suelen escribir los tamaños de imagen (“960 × 639”), y es lo primero que confunde a quien empieza.

Lee un píxel, luego la imagen entera

Indexar el array con [fila, columna] da los tres valores de un píxel. OpenCV los guarda como azul, verde, rojo, no rojo, verde, azul: su modo de lectura por defecto carga “the BGR 8-bit format” [1]. Convertir a gris junta los tres en un único valor de brillo por píxel, y la media de esos valores resume en un número qué tan clara es la foto.

Python · OpenCV
# Un píxel: fila 320, columna 480. OpenCV guarda B, G, R.
print("pixel at row 320, column 480:", img[320, 480])

# La imagen entera en un número: su brillo medio
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print("mean brightness:", round(grey.mean(), 1), "of 255")
Salida
pixel at row 320, column 480: [140 175 206]
mean brightness: 56.6 of 255

Ese píxel está en el resplandor del fondo de la pantalla de la lámpara, por eso es claro. La foto en conjunto tiene una media de 56,6: casi todo es mesa y pared oscuras. La exposición de 5 segundos de la misma escena, que el módulo usa como objetivo, tiene una media de 104,7, y la lección 2 lo muestra con un histograma.

Rómpelo

Muchas otras bibliotecas de Python esperan rojo, verde, azul. Pásales un array de OpenCV sin convertir y los canales azul y rojo se intercambian. La celda de abajo hace el cambio a propósito para que veas cómo queda: la lámpara cálida se vuelve azul fría.

Rómpelo
# Finge que otra biblioteca leyó el array como R, G, B
swapped = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.imshow("blue and red swapped", swapped)
Salida
blue and red swapped
blue and red swapped

Tu turno

Encuentra el mayor valor de gris de la foto y guárdalo en brightest.

Tu turno
brightest = 0  # cámbialo por el mayor valor de gris de img
print(brightest)

Por qué funciona

Una cámara cuenta la luz que llegó a cada fotositio durante la exposición. Guardar el resultado muestrea la escena en una rejilla y cuantiza cada valor en uno de 256 niveles [2], y de ahí vienen la forma y el uint8. La lección An image is an array (en inglés) explica qué significan la forma y el tipo, From photons to counts de dónde salen los números, y Colour spaces por qué hay tres canales. Las tres están en la ruta de Fundamentos, unidades 1.2 y 1.3.

Errores comunes

  • imread devuelve None en lugar de fallar. Una ruta equivocada no da error hasta que la siguiente línea toca el array. Comprueba img is None cuando el archivo pueda no existir.
  • Azul y rojo intercambiados. OpenCV usa B, G, R; matplotlib, Pillow y casi todo el código web usan R, G, B. Convierte con cv2.cvtColor(img, cv2.COLOR_BGR2RGB) antes de pasar el array.
  • img[x, y] en lugar de img[y, x]. Las filas van primero. En una imagen que no es cuadrada, esto lee el píxel equivocado o falla con un error de índice.
  • cv2.imshow en un notebook. En un script abre una ventana y necesita cv2.waitKey; en Colab y Jupyter no funciona. El notebook del módulo muestra las imágenes con matplotlib.

Referencias

[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