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
Para instalar OpenCV en Anaconda python, ejecutar en el terminal
$ conda install -c conda-forge opencv
Y lo importamos junto con los otros módulos
import cv2 as cv # open vision library OpenCV
import numpy as np # funciones numéricas (arrays, matrices, etc.)
import matplotlib.pyplot as plt # funciones para representación gráfica
Python soporta los formatos de imagen más habituales. Cargemos la image lena.jpg La sintaxis de lectura es
a1 = cv.imread('lena.jpg')
Ha leído la imagen como BGR. Para funcionar con pyplot
la necesitamos RGB. La convertimos de BGR a RGB
a2 = cv.cvtColor(a1, cv.COLOR_BGR2RGB)
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.
print(type(a2))
La variable a2
es un array numpy.
print(a2.shape)
Tiene 512 filas, 512 columnas y 3 capas.
dt = np.dtype(a2[0,0,0])
print(dt.name)
Y sus elementos son enteros sin signo de 8 bits.
Para ver la imagen
plt.figure()
plt.imshow(a2)
plt.show()
Podemos convertirla a otro formato, en este caso a una imagen de escala de grises, que son con las que trabajaremos en este curso:
a3 = cv.imread('lena.jpg',cv.IMREAD_GRAYSCALE)
plt.figure()
plt.imshow(a3, cmap='gray')
plt.show()
Para guardar la imagen en disco
image = cv.imwrite('lena_gris.tif', a3)
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:
a4 = a3.astype(np.float32)
convierte una matrix uint8
en una matriz de tipo float32
.
a5 = a4.astype(np.uint8)
image = cv.imwrite('lena_gris.tif', a5)
Primero convierte la matriz a4
de float32
a uint8
, y luego la guarda en el disco.
Una vez que tenemos definida la imagen como una matriz con elementos float32
, podemos comenzar a trabajar con ella.
Ejemplo
Vamos a
Comenzamos seleccinando una parte de la imagen:
a = np.copy(a4)
ojo_de_Lena = a[251:283,317:349]
Seguimos con la creación de los plots
plt.figure()
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
ojo_de_Lena = ojo_de_Lena.astype(np.uint8)
image = cv.imwrite('OjoLena.jpg', ojo_de_Lena)
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