Podemos definir una imagen como una función bidimensional $f(x_1,x_2)$ donde $x=(x_1,x_2)$ son las coordenadas espaciales, y el valor de $f$ en cualquier $x$ es la intensidad de la imagen en dicho punto.
Desde este punto de vista, una imagen puede considerarse como una función continua definida sobre un conjunto continuo (imagen analógica) o como una función discreta definida sobre un dominio discreto (imagen digital). Ambos puntos de vista resultan útiles en el procesamiento de imágenes.
Convertir una imagen analógica a digital requiere que tanto las coordenadas como la intensidad sean digitalizadas. Digitalizar las coordenadas se llama muestrear, mientras que digitalizar la intensidad se denomina cuantizar. Entonces, cuando todas las cantidades son discretas, llamamos a la imagen una imagen digital.
El camino opuesto, de digital a analógico, es también posible y se denomina interpolación.
El resultado de muestrear y cuantizar es una matriz de números. El tamaño de la imagen es el número de filas por el número de columnas, $M\times N$. La indexación de la imagen en Python sigue la convención habitual
from __future__ import division # impone aritmética no entera en la división
from PIL import Image # funciones para cargar y manipular imágenes
import numpy as np # funciones numéricas (arrays, matrices, etc.)
import matplotlib.pyplot as plt # funciones para representación gráfica
Esta línea configura Matplotlib para que muestre figuras en el cuaderno IPython en lugar de en una ventana nueva. No hay que usarla en los scripts de Python.
%matplotlib inline
Python soporta los formatos de imagen más habituales. Cargemos la image lena.jpg La sintaxis de lectura es
I = Image.open("lena.jpg")
El tipo de dato habitual para una imagen es uint8, es decir, un entero sin signo representado en 8 bits. Esto nos da $2^8=256$ valores que se distribuyen en el rango de $[0,255]$ para cada pixel.
La variable, I, no es una matriz, sino un objeto. Podemos visualizarla y realizar algunas operaciones estándar con ella. Por ejemplo, podemos visualizar la imagen con el programa correspondiente del sistema
I.show()
Para ver la imagen con ipython,
plt.imshow(np.asarray(I))
plt.show()
Podemos obtener información sobre la imagen, en este caso el tamaño, tipo (escala de grises, RGB, etc.), y formato:
print I.size, I.mode, I.format
Podemos convertirla a otro formato, en este caso a una imagen de escala de grises, que son con las que trabajaremos en este curso:
I1 = I.convert('L') # convierte a escala de grises
I1.show()
print I1.size, I1.mode, I1.format
plt.imshow(np.asarray(I1), cmap='gray')
plt.show()
O grabar una imagen al disco:
I1.save('lena_gris.tif')
Existen tres tipos principales de imágenes:
Cuando realizamos transformaciones matemáticas de imágenes, normalmente necesitamos que la imagen sea de tipo float
. Pero cuando la leemos y almacenamos ahorramos espacio usando codificación entera sin signo. Podemos usar las órdenes siguientes:
a = np.asarray(I1,dtype=np.float32)
convierte el objeto I1 en una matriz de tipo float32
.
Image.fromarray(a.astype(np.uint8)).save("prueba.jpg")
primero convierte la matriz a
a tipo uint8
, y luego a un objeto "imagen".
Una vez que tenemos definida la imagen como una matriz con elementos float
, podemos comenzar a trabajar con ella.
Ejemplo
Vamos a
Comenzamos seleccinando una parte de la imagen:
ojo_de_Lena = a[251:283,317:349]
Seguimos con la creación de los plots
plt.subplot(121)
plt.imshow(a,cmap='gray',interpolation='nearest')
plt.title('Lena'),plt.axis('off')
plt.subplot(122)
plt.imshow(ojo_de_Lena,cmap='gray',interpolation='nearest')
plt.title('El ojo derecho de Lena'),plt.axis('off')
plt.show()
Y terminamos guardando la imagen
Image.fromarray(ojo_de_Lena.astype(np.uint8)).save("OjoLena.jpg")
Escribir una función con
Entrada: una imagen de cualquier tipo y el rango para los píxeles $(x,y)$ a extraer.
Salida: una matriz de datos float32
correspondiente a los índices indicados de la imagen original y una figura de ella.
Aplicar la función para extraer la cabeza del cameraman de la imagen cameraman.tif.
%run Ejercicio1.py
Las máscaras son filtros geométricos de una imagen. Por ejemplo, si queremos seleccionar una región de una imagen, podemos hacerlo multiplicando la matriz de la imagen original por una matriz de igual tamaño que contenga unos en la región que queremos conservar y ceros en el resto. En este ejercicio seleccionaremos una región circular de la imagen lena_gray_512.tif de radio 150. Seguir los pasos siguientes:
float
. Cuando se multiplica por cero, se convierten a negro los píxeles de fuera del círculo. Modifica el programa para hacer visible esos píxeles con la mitad de su intensidad.
%run Ejercicio2.py
El degradado lineal es un efecto en el que se oscurece una imagen desde una parte de la misma hasta la parte opuesta alterando la intensidad original de un modo proporcional. Por ejemplo, en la degradación vertical, podemos implementar este filtro mediante una máscara que sea constante por columnas pero tome un valor decreciente por filas, desde 1 en la primera fila a cero en la última.
Construir dicha matriz y crear el degradado de la imagen de Lena. Visualizar la imagen original y la filtrada.
Nota: un modo de resolverlo es usando bucles y condicionales. Pero vectorizar ahorra tiempo de ejecución: con el comando numpy.linspace
puede definirse la degradación, y mediante numpy.tile
puede construirse, repitiendo el vector obtenido con numpy.linspace
, la matriz máscara.
%run Ejercicio3.py
Construir, como array numpy, la imagen de un tablero de ajedrez, donde cada casilla tiene un tamaño de $250 \times 250$ pixels. Mostrar el resultado en el terminal iPython. Se puede usar la orden numpy.tile
.
%run Ejercicio4.py
Construir, como array numpy, la imagen de círculos concéntricos mostrada debajo. La imagen tiene un tamaño de $500 \times 500$ pixels. Cada circunferencia tiene una anchura aproximada de $4$ ó $5$ pixels. Mostrar el resultado en el terminal iPython.
%run Ejercicio5.py
Construir, como array numpy, la imagen de la servilleta mostrada debajo. Cada casilla tiene un tamaño de $10 \times 10$ pixels. Mostrar el resultado en el terminal iPython. Se puede usar la orden numpy.tile
.
%run Ejercicio6.py
Construir, como array numpy, la imagen mostrada debajo. La imagen tiene un tamaño de $500 \times 500$ pixels. Cada círculo tiene un radio de $10$ pixels y los centros de los círculos están separados $50$ píxeles. Mostrar el resultado en el terminal iPython.
%run Ejercicio7.py