septiembre 22, 2018 6:00 pm

Jesús

Hay cosas que son tan prominentes en nuestra vida, que rara vez nos detenemos a pensar sobre su naturaleza y funcionamiento e, incluso, las damos por sentado.

Una de esas cosas son las imágenes.

En una época donde todos tenemos múltiples cámaras en nuestros celulares, computadores y demás dispositivos, parece tonto preguntarse qué son realmente las imágenes.

Pero… ¿qué son?

Más aún, ¿cómo se representan en un computador?

Lo que para nosotros es una foto, ¿es igual para nuestro ordenador? ?

En este artículo responderemos estas preguntas, y demostraremos, usando OpenCV, que las imágenes digitales esconden más secretos de lo que parece.

Al final de este post, sabrás:

  • Qué es exactamente una imagen.
  • Cómo funcionan las cámaras fotográficas.
  • Qué es una imagen para un computador.
  • Cómo usar OpenCV para manipular imágenes.
  • Cómo analizar los canales de color de una foto en OpenCV.
  • Cómo cambiar de espacios de color usando OpenCV.

¿Estás preparado? Comencemos, pues.

Cómo Toman las Fotos las Cámaras

Una imagen es una ilusión. Es una representación en 2D de un mundo en 3D.

Cuando te tomas un selfie, tu celular está “congelando” una representación en el tiempo de ti. Pudiera decirse que está “pintando”, con gran nivel de detalle, tu rostro y todo lo que le rodea.

Pero no te dejes engañar, ¡sigue siendo una ilusión! 

“Jesús, no entiendo nada. Explícate mejor”. 

Ok, ok. Cuando te digo que es una ilusión es por lo siguiente:

  • La imagen es bidimensional, pero emula la profundidad mediante la perspectiva.
  • Los colores capturados en la foto no coinciden exactamente con los colores en la vida real. Son una aproximación que depende de muchos factores (iluminación, lente, ángulo, etcétera).
  • Una imagen no es el objeto, escena o sujeto capturado. Es una sección, un ángulo, una descripción visual y parcial del mismo.

Ahora, vámonos más lejos todavía: ¿Cómo se capturan las imágenes? 

La gran mayoría de las cámaras fotográficas simulan el comportamiento de nuestra pupila, la cual percibe imágenes al reflejar los rayos de luz que rebotan de los objetos y la atraviesan, dando forma al mundo a nuestro alrededor.

camara-estenopeica-pinhole

Cámara Estenopeica (Pinhole camera). La luz pasa por la apertura, creando una imagen girada 180º

En la imagen de arriba, observamos el funcionamiento de una cámara estenopeica (en inglés conocida como pinhole camera), la cual constituye la base de las cámaras modernas. Funciona así:

  • La luz que rebota de los objetos pasa por una perilla o agujero ubicado al frente.
  • Los rayos de luz se reflejan en una película ubicada en el fondo de la cámara, en la posición contraria a su origen. Por ejemplo: si vienen de arriba, se reflejan abajo. Si vienen de la derecha, se reflejan en la izquierda.
  • En efecto, la imagen se graba al revés en la película.
  • Las cámaras modernas (y nuestro cerebro), lo que hacen es voltear la imagen para que la veamos correctamente.

Muy loco, ¿no?

mind-blown

Tu cabeza en este momento ?

Abajo te puedes descargar un mapa mental que resume perfectamente todo lo que hablamos en este artículo.

mapa-mental-imagenes-digitales

Mapa mental sobre imágenes digitales

Una Imagen es una Matriz Glorificada (Tensor)

¿Te acuerdas de las matrices? Básicamente, un montón de números organizados en filas y columnas, ¿cierto?

Bueno, al final del día, eso es una imagen digital.

No, en serio. Eso es

Lo verdaderamente importante en esa matriz es el contenido, los cuales se llaman píxeles.

Un píxel es un número que indica la intensidad de brillo en esa ubicación específica. Típicamente, trabajamos con valores entre 0 y 255, donde 0 significa la ausencia de brillo (negro), y 255 la máxima saturación de brillo (blanco).

Al juntar muchos de estos píxeles, efectivamente, armamos la escena que es nuestra imagen.

¡ATENTO!

Una imagen digital es una matriz que actúa como mapa, para decirle a la computadora cómo debe pintar una foto en pantalla.

“Jesús, ¿no dijiste que las imágenes eran tensores?”

Es verdad. Lo que sucede es que una matriz es un tensor. 

Un tensor es un arreglo numérico de múltiples dimensiones. Entendamos mejor mediante unos ejemplos:

  • Número o escalar (por ejemplo, 5) → Tensor de 0 dimensiones.
  • Vector (por ejemplo, [1, 2, 3]) → Tensor de 1 dimensión.
  • Matriz → Tensor de 2 dimensiones.
  • Cubo → Tensor de 3 dimensiones.
  • Teseracto (un cubo de cubos… Suerte imaginándote eso ?) → Tensor de 4 dimensiones.
  • … y así sucesivamente.
imagen-blanco-y-negro-matriz

Cada píxel en la imagen se corresponde a un valor en la matriz de la izquierda.

Las Imágenes a Color son Cubos

Te mentí… Las imágenes no son realmente matrices, o al menos no todas.

Sólo las imágenes en escala de grises lo son. Las imágenes a color, por su parte, son cubos, ya que tienen tres dimensiones.

imagen-rgb

Una imagen a color es un cubo, es decir, un arreglo de 3 matrices.

En pocas palabras, una imagen a color, tipicamente en espacio RGB (Red, Green, Blue), se compone de tres matrices:

  • Red: Representa la distribución de rojo en la imagen.
  • Green: Representa la distribución de verde en la imagen.
  • Blue: Representa la distribución de azul en la imagen.

Cuando juntamos estas tres matrices, obtenemos nuestras familiares fotos a color, gracias a la combinación de los valores en cada dimensión. 

Una analogía que me ayuda a entender mejor este concepto, es la de una impresora: Típicamente, éstas usan cuatro cartuchos (Negro, Cian, Magenta y Amarillo), cuyos colores combinan para producir impresiones a color.

Experimentando con Imágenes en OpenCV

¡Por fin! ¡Práctica!

En esta sección nos pondremos manos a la obra, usando OpenCV, la librería por excelencia de computer vision, para explorar en mayor profundidad las imágenes digitales.

Puedes descargar el código aquí para que experimentes por tu cuenta, con tus propias imágenes:

Empecemos creando el entorno de programación, con vitualenv:

Afortunadamente, sólo necesitaremos OpenCV y NumPy en este proyecto:

Para instalar estas dependencias, corre simplemente:

pip install -r requirements.txt

La estructura del proyecto es la siguiente:

.
├── LEEME.md
├── car.jpg
├── datasmarts
│   ├── __init__.py
│   └── images_demo.py
└── requirements.txt

Los archivos más relevantes son:

  • datasmarts/images_demo.py contiene el código de exploración de imágenes.
  • car.jpg es la imagen a explorar.

Cool. Abre datasmarts/images_demo.py, y empieza por importar OpenCV y NumPy:

Luego, define el menú del script, el cual recibe como único parámetro -i/--image, la ruta a la imagen de entrada:

Usando cv2.imread(), cargamos la imagen:

Acto seguido, iniciamos nuestra exploración de la imagen imprimiento su tipo y sus dimensiones:

Después, la mostramos tanto en pantalla, como en la consola. Esto lo hacemos para convencernos de que lo que vemos, no es lo que la computadora ve:

También podemos imprimir los canales de color por separado (fíjate en que el canal rojo es el último, en vez del primero, ya que OpenCV representa las imágenes en espacio BGR, no RGB):

Podemos pasar fácilmente de BGR → Grises, como vemos en el siguiente extracto, donde además mostramos en pantalla y en la consola el resultado de la conversión:

Otro experimento interesante es visualizar los canales de color por separado. Para ello, estamos creando un mosaico de la imagen de cada canal, en escala de grises, empezando por el B, pasando al G y terminando en el R:

Como las imágenes son arreglos numéricos, podemos modificar, acceder y manipular píxeles individuales:

También podemos acceder a segmentos o subsecciones, al usar slicing:

Es posible alterar porciones completas de la imagen, de nuevo, usando slicing. Abajo estamos asignando el color negro (es decir, 0), a todos los píxeles en la región seleccionada y mostramos el resultado:

Por último, las imágenes, como arreglos numéricos, responden a las reglas de la aritmética, por lo que al sumar, restar, multiplicar o dividir sus valores, afectamos la apariencia de la imagen:

¡ATENTO!

Tenemos que ser extremadamente cuidadosos al modificar los píxeles de una imagen, ya que podemos destruir el balance, la coherencia cromática y espacial de la misma.

Análisis de Imágenes con OpenCV

Lo que nos queda es correr el programa:

PYTHONPATH=. python datasmarts/images_demo.py -i car.jpg

En principio, en pantalla veremos la imagen, la cual corresponde a un carro sobre una pradera, nada del otro mundo: 

imagen-kia-stinger

Imagen de entrada de un Kia Stinger ?. Estaremos manipulando esta foto a lo largo del tutorial.

Lo interesante es lo que aparece en la consola:

¿Cuál es el tipo de la imagen?:
Ancho (número de columnas. Eje X): 900
Altura (número de filas. Eje Y): 600
Profundidad (número de canales. Eje Z): 3
Dimensiones de la imagen: (600, 900, 3)

Vemos que, en efecto, la clase de la imagen es numpy.ndarray, es decir, un arreglo numérico. También observamos que se trata de un cubo, con 900 píxeles de ancho, 600 de alto, y 3 canales de profundidad.

Después vemos en pantalla la imagen en blanco y negro:

kia-stinger-blanco-y-negro

Imagen del mismo Kia, pero en blanco y negro, gracias al cambio de espacio de color que hicimos con OpenCV.

En la consola, primero nos topamos con lo que realmente la computadora entiende como una imagen a color:

Imagen como números:
[[[251 245 240]
[251 245 240]
[251 245 240]
...
[248 237 223]
[249 238 224]
[249 238 224]]

[[251 245 240]
[251 245 240]
[251 245 240]
...
[248 237 223]
[249 238 224]
[249 238 224]]

[[251 245 240]
[251 245 240]
[251 245 240]
...
[248 237 223]
[249 238 224]
[249 238 224]]

...

[[ 5 73 80]
[ 0 70 80]
[ 15 92 108]
...
[ 23 105 116]
[ 31 106 114]
[124 179 188]]

[[ 14 72 78]
[ 14 76 86]
[ 35 106 120]
...
[ 32 109 118]
[ 12 88 94]
[ 70 133 137]]

[[ 16 68 74]
[ 12 71 81]
[ 53 118 133]
...
[ 45 123 130]
[ 0 75 76]
[ 56 129 127]]]
Canal rojo (R):
[[240 240 240 ... 223 224 224]
[240 240 240 ... 223 224 224]
[240 240 240 ... 223 224 224]
...
[ 80 80 108 ... 116 114 188]
[ 78 86 120 ... 118 94 137]
[ 74 81 133 ... 130 76 127]]
Canal verde (G):
[[245 245 245 ... 237 238 238]
[245 245 245 ... 237 238 238]
[245 245 245 ... 237 238 238]
...
[ 73 70 92 ... 105 106 179]
[ 72 76 106 ... 109 88 133]
[ 68 71 118 ... 123 75 129]]
Canal azul (B):
[[251 251 251 ... 248 249 249]
[251 251 251 ... 248 249 249]
[251 251 251 ... 248 249 249]
...
[ 5 0 15 ... 23 31 124]
[ 14 14 35 ... 32 12 70]
[ 16 12 53 ... 45 0 56]]

Cada canal tiene su propia combinación de números, la cual al juntarse, produce el resultado familiar que ya vimos. Luego, observamos las dimensiones de la imagen en blanco y negro:

Dimensiones de la imagen en escala de grises: (600, 900)

Esta vez notamos que es una matriz, ya que sólo tiene ancho y altura.

canales-de-color-kia-stinger-en-blanco-y-negro

Visualización de los canales de color, en escala de grises (De izquierda a derecha: Blue, Green, Red).

Arriba tenemos una representación en grises de los canales B (azul), G (verde) y R (rojo), en ese orden. En cada uno de ellos, mayor brillo simboliza mayor presencia de ese color específico, mientras que tonalidades más oscuras, cercanas al negro, corresponden a una menor presencia de azul, verde o rojo, dependiendo del caso.

Tomemos como ejemplo la imagen de la extrema derecha, la cual corresponde al canal rojo. ¿Te diste cuenta que el auto luce prácticamente blanco? ¡Es porque en la imagen original es rojo! 

Sigamos. Lo siguiente que aparece en la consola es el valor de un píxel, que es un arreglo de tres números, cada uno asociado a un canal de color:

Valor del píxel (250, 100): [237 234 236]
B: 237, G: 234, R: 236

Y en pantalla, vemos un segmento de la imagen, y el resultado de asignarle 0:

segmento-borrado-imagen-opencv

A la izquierda tenemos un segmento de la imagen, y a la derecha el resultado de asignarle 0 a todos los píxeles en dicho segmento.

Por último vemos lo que pasa cuando sumamos 50 (izquierda) a todos los píxeles, y cuando restamos 50 globalmente (derecha). Es evidente que deformamos, en ambas instancias, la imagen, por lo que es un recordatorio de que debemos ser cuidadosos al modificar fotos digitales:

imágenes-deformadas

Imágenes distorsionadas por alterar los píxeles sin cuidado.

Resumen

Te felicito por haber llegado al final del artículo. Sé que fue extenso, pero ahora sabes todo lo que necesitas sobre imágenes digitales, su representación y diferentes formas de experimentar con ellas, modificándolas y analizándolas con OpenCV, la librería más popular e importante en este mundo del computer vision.

Más específicamente, hoy aprendiste:

  • Que las imágenes son realmente representaciones muy buenas, pero incompletas de la vida real.
  • Que las cámaras emulan el comportamiento de nuestras pupilas, grabando y corrigiendo la proyección de los rayos de luz sobre los objetos, para formar las imágenes que conocemos.
  • Que al final del día, las imágenes son matrices o cubos.
  • Qué es un tensor.
  • Cómo manipular imágenes usando OpenCV.
  • Cómo analizar la distribución de color en una imagen, viendo sus canales por separado.

¿Por qué no te descargas el script y experimentas un poco más? 

¿Te gustó el artículo? ¡Compártelo!

¿Te quedó alguna duda? ¿Tienes un comentario? Déjalo abajo o escríbeme directamente a jesus@datasmarts.net.

¡Un abrazo!

Sobre el Autor

Jesús Martínez es el creador de DataSmarts, un lugar para los apasionados por computer vision y machine learning. Cuando no se encuentra bloggeando, jugando con algún algoritmo o trabajando en un proyecto (muy) cool, disfruta escuchar a The Beatles, leer o viajar por carretera.