%matplotlib inline
import numpy as np
from matplotlib import rcParams
rcParams['figure.dpi'] = 120
import matplotlib.pyplot as plt
from scipy import fftpack
from IPython.display import YouTubeVideo
from functools import partial
YTVideo_formato = partial(YouTubeVideo, width=640, height=400, rel=0, modestbranding=1)

def color2bw(img):
    return np.dot(img, [0.299, 0.587, 0.114])

4. Introducción al procesamiento de imágenes digitales

En esta lección veremos:

  • Señales bidimensionales

  • Definición de una imagen digital

  • Percepción y modelos de color

4.1. Una imagen como caso particular de una señal 2D

El siguiente es un ejemplo de una señal que tiene dos variables independientes

\[ x, y \rightarrow I(x,y) \]
YTVideo_formato('AEJgW2jUdMs')

Podemos

  • Interpretar las variables independientes \(x\) e \(y\) como coordenadas en el espacio

  • Visualizar la variable dependiente \(I(x,y)\) como un mapa de colores

Bajo estas consideraciones se obtiene lo que llamamos una imagen

A continuación definiremos formalmente una imagen

4.2. Definiendo una imagen digital

Una imagen digital es una señal con:

  • Dos variables indepedientes discretas \(x\) e \(y\) que representan el espacio

  • Una o más variables dependientes \(I(x,y)\) que representan la intensidad del color según una codificación

En la práctica las imágenes digitales son un arreglo de NxM componentes

  • \(N\), el número de filas, representa el alto de la imagen

  • \(M\), el número de columnas, representa el ancho de la imagen

Nota

Cada elemento del arreglo se llama pixel (picture element)

Los píxeles pueden ser

  • unidimensionales (imagen en blanco y negro)

  • multidimensionales (RGB, HSV, HSL)

Un pixel suele representarse como una tupla donde cada componente

  • es un entero sin signo de 8bits: Intensidades en el rango \([0, 255]\)

  • es un valor flotante en el rango \([0.0, 1.0]\)

A estos rangos les otorgamos una interpretación como mapa de color

En la siguiente figura podemos ver una imagen digital en escala de grises

../../_images/image_matrix.png
  • A la izquierda: Representación en mapa de color, donde negro es 0.0 y blanco es 1.0

  • A la derecha: Representación numérica

Pregunta

¿Qué representa una imagen?

Comúnmente una imagen (señal) es una representación de la intensidad luminica (rango óptico)

Pero también se pueden representar otros rangos de radiación como los rayos ejes (radiografía) o la radiación infraroja (termografía) y otros fenómenos que no son electromagnéticos, por ejemplo un ultrasonido

*Todas las imágenes de esta lámina son de wikipedia

Pregunta

¿Cómo se obtiene una imagen digital?

Para el caso electromagnético se aprovecha el efecto fotoeléctrico: Cuando un fotón impacta un conductor se desprenden electrones

../../_images/photons-hit-silicon.jpg

Un sensor de tipo Charged Coupled Device (CCD) usa el efecto fotoelétrico para contar la cantidad de fotones que golpean su superficie durante un cierto tiempo

  • El CCD está dividido en una grilla

  • Cada elemento de la grilla tiene su propia “cuenta”

  • La cuenta se mapea como un valor numérico entero o flotante

  • Si la cuenta supera el valor máximo del sensor se observa saturación

  • Los receptores pueden ajustarse para aceptar fotones de un cierto rango de frecuencias (color)

Nota

Los sensores CCD y CMOS son la base de las cámaras digitales modernas

4.3. Manipulación de imágenes con Python

Podemos usar Matplotlib para importar y visualizar imágenes

  • plt.imread(): Lee una imagen y retorna un arreglo de NumPy

  • plt.imshow(): Dibuja un arreglo de NumPy como si fuera una imagen

img_color = plt.imread('../images/valdivia.jpg')
print("Tamaño: %s" %(repr(img_color.shape)))
print("Tipo: %s" %(img_color.dtype))

print("Intensidad del pixel en la posición 0, 0: %s" %(repr(img_color[0, 0, :])))
Tamaño: (350, 650, 3)
Tipo: uint8
Intensidad del pixel en la posición 0, 0: array([ 79, 112, 155], dtype=uint8)
plt.figure(figsize=(6, 3), tight_layout=True)
plt.imshow(img_color);
../../_images/04_imágenes_12_0.png

Una vez importada la imagen es un arreglo

Podemos manipularla y transformarla con las funciones de NumPy, Scipy y otras librerías de Python

Ejemplo: slicing de la imagen

plt.figure(figsize=(6, 3), tight_layout=True)
plt.imshow(img_color[100:500, 100:200]);
../../_images/04_imágenes_14_0.png

Ejemplo: Trasposición, inversión y espejado de la imagen

plt.figure(figsize=(6, 3), tight_layout=True)
plt.imshow(np.transpose(img_color, axes=[1, 0, 2]));
../../_images/04_imágenes_16_0.png
plt.figure(figsize=(6, 3), tight_layout=True)
plt.imshow(img_color[::-1, :, :]);
../../_images/04_imágenes_17_0.png
plt.figure(figsize=(6, 3), tight_layout=True)
plt.imshow(img_color[:, ::-1, :]);
../../_images/04_imágenes_18_0.png

4.4. Modelo de color RGB

La imagen que estabamos usando tiene tres canales

img_color.shape
(350, 650, 3)

Cada canal se interpreta como la intensidad de color

  • en rojo

  • en verde

  • en azul respectivamente

Esto corresponde al modelo de colores RGB (red, green, blue)

Combinando distintos valores de intensidad en rojo, verde y azul se obtienen los demás colores

Podemos ver el contenido de cada canal de color por separado como

fig, ax = plt.subplots(1, 4, figsize=(9.5, 3.2), tight_layout=True); 
ax[0].imshow(img_color[:, 350:, :])
ax[0].axis('off')
for i, color_name in enumerate(["R", "G", "B"]):
    ax[i+1].set_title(color_name)
    ax[i+1].axis('off')
    ax[i+1].imshow(img_color[:, 350:, i], cmap=plt.cm.Greys_r, vmin=0, vmax=255)
../../_images/04_imágenes_22_0.png

Una imagen a color se forma al combinar tres imágenes de escala de grises

4.5. Visión humana

El ojo humano tiene en su retina dos tipos de fotoreceptores: bastones y conos

Los bastones

  • 120 millones aprox en la retina

  • Perciben intensidad pero no color

  • Requieren poco brillo para producir una señal

  • Tienen baja agudeza (menos sencibles a los detalles)

Los conos

  • 6 millones aprox en la retina

  • Existen tres tipos de conos, cada uno sintonizado a una longitud de onda

  • Requieren mucho brillo para producir una señal

  • Tienen alta agudeza visual

../../_images/cones_rods.png

Referencia: http://www.danielgmurphy.com/physics/1_intro/contents_phyics1.html

La respuesta retinal inspira la siguiente transformación de RGB a escala de grises:

\[ I = 0.299 R + 0.587 G + 0.114 B \]
def color2bw(img):
    return np.dot(img, [0.299, 0.587, 0.114]) 
img_bw = color2bw(img_color)
plt.figure(figsize=(8, 4), tight_layout=True) 
plt.imshow(img_bw, cmap=plt.cm.Greys_r);
../../_images/04_imágenes_27_0.png

4.6. Otros modelos de colores

El modelo HSV (Hue, Saturation, Value) es muy usado en software de diseño y computación gráfica

  • Hue: Corresponde al tono o color puro

  • Saturation: Corresponde a ajustar el brillo

  • Value: Corresponde a mezclar con negro para generar sombras

../../_images/hsv_cilinder.jpg

Por ejemplo, la imagen anterior en HSV

from matplotlib import colors

img_color_hsv = colors.rgb_to_hsv(img_color)
print("Shape: %s, Type: %s" %(repr(img_color_hsv.shape), img_color_hsv.dtype))
print(img_color_hsv[0, 0, :])

fig, ax = plt.subplots(1, 4, figsize=(9.5, 3.2), tight_layout=True); 
ax[0].imshow(img_color[:, 350:, :])
ax[1].set_title('H')
ax[1].imshow(img_color_hsv[:, 350:, 0], plt.cm.hsv); 
ax[2].set_title('S')
ax[2].imshow(img_color_hsv[:, 350:, 1], plt.cm.Greys_r); 
ax[3].set_title('V')
ax[3].imshow(img_color_hsv[:, 350:, 2], plt.cm.Greys_r);
for ax_ in ax:
    ax_.axis('off')
Shape: (350, 650, 3), Type: float32
[  0.59429824   0.4903226  155.        ]
../../_images/04_imágenes_29_1.png

El estándar YCbCr corresponde a una familia de modelos muy usado en fotografía digital

  • Y, se llama luma, corresponde a la luminancia

  • Cb y Cr, se llaman chroma, corresponde a la “diferencia en azul” y “diferencia en rojo”

Existe una transformación directa entere YCbCr y RGB

\[\begin{split} Y = K_R R + K_G G + K_B B \\ C_b = 0.5 \frac{B - Y}{1 - K_B} \\ C_R = 0.5 \frac{R - Y}{1 - K_R} \end{split}\]

donde \(K_R + K_G + K_B = 1\)

Los distintos modelos de la familia YCbCr se diferencia en sus valores de \(K_R\), \(K_G\) y \(K_B\)

Por ejemplo la imagen anterior en YCbCr

# ITU-R BT.601 
Kr = 0.299
Kg = 0.587
Kb = 0.114
Y = np.dot(img_color, [Kr, Kg, Kb])
Cb = 0.5*(img_color[:, :, 2] - Y)/(1-Kb)
Cr = 0.5*(img_color[:, :, 0] - Y)/(1-Kr)

fig, ax = plt.subplots(1, 4, figsize=(9.5, 3.2), tight_layout=True); 
ax[0].imshow(img_color[:, 350:, :])
ax[1].set_title('Y')
ax[1].imshow(Y[:, 350:], cmap=plt.cm.Greys_r); 
ax[2].set_title('Cb')
ax[2].imshow(Cb[:, 350:], plt.cm.Greys_r); 
ax[3].set_title('Cr')
ax[3].imshow(Cr[:, 350:], plt.cm.Greys_r);
for ax_ in ax:
    ax_.axis('off')
../../_images/04_imágenes_31_0.png