Laboratorio 2: Imagenes 📷

MDS7202: Laboratorio de Programación Científica para Ciencia de Datos

Cuerpo Docente:¶

  • Profesor: Matías Rojas y Mauricio Araneda
  • Auxiliar: Ignacio Meza D.
  • Ayudante: Rodrigo Guerra

Equipo: SUPER IMPORTANTE - notebooks sin nombre no serán revisados¶

  • Nombre de alumno 1: Estefanía Fröhlich
  • Nombre de alumno 2: Johnny Godoy

Link de repositorio de GitHub: https://github.com/johnny-godoy/laboratorios-mds¶

Reglas:¶

  • Grupos de 2 personas
  • Ausentes deberán realizar la actividad solos.
  • Cualquier duda fuera del horario de clases al foro. Mensajes al equipo docente serán respondidos por este medio.
  • Prohibidas las copias.
  • Pueden usar cualquer matrial del curso que estimen conveniente.

Temas a tratar:¶

  • Programación Orientada a Objetos.
  • Programación Funcional.
  • Numpy para manejo de datos en arreglos/tensores.

Objetivos principales del laboratorio¶

  • Aplicar los paradigmas y buenas prácticas de programación vistas hasta este momento.
  • Comprender y aprovechar las ventajas que nos ofrece la liberia numpy con respecto a trabajar en Python 'puro'.
  • Visualizar aplicaciones de filtros de imágenes sin el uso de librerías.
  • Verificar que el uso indiscriminado de for puede afectar en la eficiencia en al procesar datos masivos.

El laboratorio deberá ser desarrollado sin el uso indiscriminado de iteradores nativos de python (aka "for", "while"). La idea es que aprendan a exprimir al máximo las funciones optimizadas que nos entrega numpy, las cuales vale mencionar, son bastante más eficientes que los iteradores nativos sobre arreglos.

El lab estará basado en algunos conceptos básicos de procesamiento de imágenes, por lo que te iremos guiando, paso a paso por cada uno de los tópicos a desarrollar.

Descripción del laboratorio.¶

En Data Science son múltiples las aplicaciones que exigen el uso exhaustivo de listas de varias dimensiones. Estas entidad reciben formalmente el nombre de arreglos o tensores.

Pensemos en que queremos almacenar objetos en un casillero común y corriente: podemos pensar que este puede ser representado por una matriz de dos dimensiones: alto y ancho. ¿Que sucederá si este casillero nos queda pequeño y queremos guardar más información del mismo tipo?: La solución es simple es agregar otro casillero. Esto puede ser pensado como el aumento de la dimensión de nuestro objeto, pasando a ser ahora (alto, ancho, id casillero). Esto no es otra cosa que un tensor de 3 dimensiones.



¿Bueno y que tiene que ver todo esto con las imágenes?¶

Una imagen es una representación visual de una matriz que contiene de números que describen intensidades de color (llamados píxeles). Esto visto desde la perspectiva de una imagen en blanco y negro, vendria siendo una matriz que reune las diferentes intensidades de los pixeles desde 0 a 255.


Representación de una imagen

Cuando las imágenes poseen colores, las imágenes vendrían siendo "sin querer queriendo", una bella representación de lo que es un tensor: estas pueden ser representadas por un tensor de 3 dimensiones que les dan el ancho, alto y el canal, en donde son alojados los colores de la imagen.


Representación de una imagen

Como pueden ver, la imagen puede ser interpretada como un tensor de tres dimensiones(un ancho, un alto y la intensidad de cada color) en el a cada posición $(i,j)$ de la imagen, le asociaremos 3 intensidades de colores RGB (Rojo, Verde y Azul). Estas intensidades irán desde el $0$ al $255$. La combinación de estos 3 canales nos permitirá representar gran parte de los colores que encontramos en la naturaleza.

Instalar paquetes: Si están usando pip

In [1]:
import sys

# !{sys.executable} -m pip install pillow plotly imageio
In [2]:
# En este lab usaremos adicionalmente pillow, una estupenda librería
# para manejar imágenes.
# https://pillow.readthedocs.io/en/stable/

from PIL import Image
import numpy as np
import plotly.express as px

foto = np.array(Image.open("./images_lab/cobija.PNG").convert("RGB"))

# Solo para ejemplificar, usaremos plotly (NO USARLO EN LO QUE QUEDA DEL LAB).
# Pero en el restro del laboratorio, matplotlib debe ser usado

fig = px.imshow(foto)
fig.show()

Luego, llamando la variable donde alojamos el array podemos ver los valores que componen a esta imagen.

In [3]:
print(f'Número de dimensiones: {foto.ndim}')
print(f'Número de elementos por dimensión: {foto.shape}')
Número de dimensiones: 3
Número de elementos por dimensión: (470, 597, 3)

Finalmente visualizamos de forma aleatoria los pixeles de cada canal para mostrar sus intensidades.

In [4]:
print(f"Ejemplo de pixel (10, 200) en el canal 0 - Red: {foto[10, 200, 0]}")
print(f"Ejemplo de pixel (10, 200) en el canal 1- Green: {foto[10, 200, 1]}")
print(f"Ejemplo de pixel (10, 200) en el canal 2- Blue: {foto[10, 200, 2]}")
Ejemplo de pixel (10, 200) en el canal 0 - Red: 97
Ejemplo de pixel (10, 200) en el canal 1- Green: 70
Ejemplo de pixel (10, 200) en el canal 2- Blue: 48

Con lo anterior, suponiendo que la imagen del "gatito" tiene una altura igual a 600 y un ancho de 400, el tensor $G$ que representa a la imagen vendrá dado por $G[600, 400, 3]$.

Videos¶

Luego, si queremos complejizar aún mas esto y queremos tener tensores que agrupen un conjunto de imágenes (de igual tamaño) tendremos lo siguiente:


Representación de una imagen

Este conjunto de imágenes nos generará la necesidad de producir una nueva dimensión, esto producto que las dimensiones son los espacios donde alojamos la información, por esto al conjunto de imágenes le agregaremos una dimensión que identifica cada una de las imágenes del conjunto, quedando representada por el tensor $G[0:n_d, 600, 400, 3]$. Por lo general, cuando tenemos imágenes con dimensionalidad 4 es porque se tratan de videos, o sea una secuencia de imágenes; el caso se complejiza aún más cuando agregamos sonido y esto se va a las pailas.

Dato: La representación que posee cada una de las dimensiones puede cambiar dependiendo de la librería utilizada, en pytorch por ejemplo las dimensiones de una imagen vienen dadas por [batch, canales, alto, ancho] y no [batch, alto, ancho, canales] como en numpy.

¿Ya, pero esto tiene aplicaciones más allá de guardar información en casilleros?¶

Para el caso de imágenes podemos encontrar múltiples aplicaciones con la manipulación de los tensores y operando matemáticamente con ellos. Algunas de las aplicaciones más conocidas (y que aplicaremos) son las siguientes:

  • Obtener el negativo de una imagen: Consiste en obtener el color complementario de una imagen, para esto debemos restar la imagen con la máxima intensidad que podemos encontrar en una imagen (o sea 255).
  • Pasar a escala de grises una imagen: Los valores RGB se convierten a escala de grises mediante la fórmula NTSC:

    $$ imagen\_gris = 0.299 * Rojo + 0.587 * Verde + 0.114 * Azul $$

    Esta fórmula representa la percepción relativa de la persona promedio del brillo de la luz roja, verde y azul.

  • Mejora de contraste: Son múltiples las técnicas que nos permiten mejorar el contraste de una imagen, pero, una técnica simple para modificar los contrastes consta en obtener un factor de corrección llamado F en base al contraste deseado (C). Luego, es aplicado en la diferencia entre la imagen y 128. De esta forma obtenemos R, que es la imagen con la mejora de contraste deseada.

    $$ F=259*(C+255)/(255*(259-C)) $$ $$ R=F*(img-128)+128 $$

  • Convolución: Consiste en recorrer una imagen por cada uno de sus canales utilizando una matriz que lleva por nombre Kernel. El kernel, examinará los conjuntos de pixeles que recorre, aplicando una multiplicación de los valores circundantes ,y sumando todos los valores generados de este producto para generar un nuevo pixel en el tensor de salida.

conv


Desarrollo¶

En base a lo explicado y visto en clases, a continuación, deben construir cada uno de los programas solicitados en las actividades señaladas más abajo. Estás, deben ser desarrollados de forma grupal (2 personas por grupo) y, la solución no debe ser compartida con personas externas al grupo; si se detecta que dos grupos entregan el mismo trabajo, será considerado plagio y se tomaran medidas al respecto.

2. Creación de Clases y Funciones.¶

Importamos librerias utiles 😸¶

In [5]:
# Libreria Core del lab.
import numpy as np
from pathlib import Path

# Librerias para graficar
import matplotlib.pyplot as plt

# Nota: Utilizar solo matplot para este lab. NO USAR PLOTLY, 
# ya que tiene problemas de compatibilidad con imagenes

# Funcionalidades dependientes del Sistema Operativo.
import os

# Librerias utiles para cargar y generar Gifs
import imageio
from PIL import Image
from scipy.signal import convolve2d

2.1. Carga de imagenes y visualización¶

Descomprima el archivo "images_lab.zip" en algún directorio de su computador o plataforma, observen las imágenes y clasifíquenlas a su gusto, para luego en un diccionario cargar y agrupar las diferentes imágenes (no cree mas de tres llaves).

Hecho esto, visualize dos imágenes y verifique la dimensionalidad de estas imágenes con el comando .shape. Comente la dimensionalidad de las imágenes.

Carga de imagenes en diccionarios¶

Las siguientes celdas de código le permitirá cargar las imágenes que utilizaremos durante este laboratorio.

La primera celda implementa la función from_jpg, la cual, dado una ruta, carga una imágen:

In [6]:
def from_jpg(path):
    ruta = Path(path)
    image = np.array(Image.open(ruta), dtype='int')
    return image

La segunda celda carga las imágenes y las guarda en un diccionario.

In [7]:
images = {
    "gatitos": [
        from_jpg("./images_lab/gato1.jpg"),
        from_jpg("./images_lab/gato2.jpg"),
        from_jpg("./images_lab/gato4.jpg"),
    ],
    "Personas": [
        from_jpg("./images_lab/personas.jpg"),
        from_jpg("./images_lab/gurus.jpg"),
    ],
    "Monos_chinos": [from_jpg("./images_lab/monitos.jpg")],
}

Plot de imagenes¶

A continuación, utilice la función def show(imagen) (definida más abajo) para explorar las imágenes cargadas en la celda anterior.

Respuesta Esperada:

In [8]:
def show(imagen):
    plt.imshow(imagen)
    plt.show()
    x, y, z = imagen.shape
    print(f'Dimensiones de la imagen: {x}x{y} (Alto x Ancho)')
In [9]:
show(images['gatitos'][0])
# usar show para mostrar las otras imágenes...
Dimensiones de la imagen: 869x750 (Alto x Ancho)

2.2 Clase imagenes [2 puntos].¶

Ahora que sabemos cómo plotear y cargar una imagen, cree una clase llamada "Imagen" la que cumpla las siguientes características:

  • [X] Un constructor que tome como argumento una imagen y que lo guarde como un atributo. __init__ debe comprobar que la imagen es un arreglo de numpy (con isinstance) y adicionalmente que este tiene 3 dimensiones. En caso contrario, debe levantar excepciones con mensajes correspondientes al error detectado (ustedes definen el mensaje).
  • [ ] Implemente el método show() que muestre la imagen usando la función plt.show().
  • [ ] Implemente el método info() que retorna un string con las dimensiones de la imagen.
  • [ ] Sobrecargue el método mágico __mul__, __add__ y __sub__ para realizar operaciones matemáticas entre el objeto y arrays, int o floats. Realice la función pensando que la operación se puede aplicar tanto para izquierda y derecha. Como estamos trabajando con imágenes los outputs deben ser enteros, por esto se le aconseja utilizar .astype(int) para transformar los arrays de salida a un formato legible por matplotlib.
  • [ ] Es importante que para las operaciones __add__ y __sub__ implementen una saturación de las imágenes. Es decir, la suma o resta deben dar como valor máximo 255 y/o como valor mínimos mayores o iguales a 0.
  • [ ] Para el caso de __mul__ deben implementar un método que nos permita saturar las imágenes (es decir que los valores del array no sobrepasen 255) y también no nos permita obtener valores inferiores a cero.

Implementadas los métodos, compruebe que la funcionalidad es la correcta mediante la ejecución de los asserts incluidos un par de celdas más abajo.

Notas:

  • Pueden reutilizar el código implementado en las celdas anteriores para implementar los métodos show e info . Sin embargo, No invoquen directamente esas funciones.
  • La idea es que la imagen contenida en la clase sea inmutable, por ende, todos los metodos que modifiquen la imagen contenida en el objeto deberan retornar un nuevo objeto de la clase Imagen que contenga la imagen modificada.
In [10]:
def show(imagen):
    plt.imshow(imagen)
    plt.show()
    x, y, z = imagen.shape
    print(f'Dimensiones de la imagen: {x}x{y} (Alto x Ancho)')
In [11]:
def saturar_arreglo(arr):
    return np.clip(arr.astype(int), 0, 255)


class DimensionException(ValueError):
    pass


class Imagen:
    """Clase contenedora de imágenes"""
    def __init__(self, img):
        if isinstance(img, np.ndarray):
            if img.ndim != 3:
                raise DimensionException("El argumento debe ser un arreglo de numpy de solo 3 dimensiones")
            if img.shape[-1] != 3:
                raise DimensionException(
                    "El argumento debe ser un arreglo de numpy de solo 3 dimensiones "
                    "tal que la última dimensión solo tiene 3 canales"
                )
            self.imagen = img
            self.x, self.y, self.z = img.shape  # Se agregan atributos que guardan la dimensión.
        else:
            raise TypeError(
                "Debes entregar un arreglo de numpy como argumento del constructor de "
                "Imagen"
            )

    def show(self):
        """Muestra la imágen contenida en el objeto.
        Su funcionalidad debe ser igual a la de la función mostrar_imagen.
        """
        plt.imshow(self.imagen)
        plt.show()

    def info(self):
        """ Imprime las características de la imagen cargada: Alto y ancho.
        """
        print(f'Dimensiones de la imagen: {self.x}x{self.y} (Alto x Ancho)')

    def __add__(self, other):
        """Redefine la operación + entre imagen y escalar.

        # Idea, usar indexado condicial (similar a los filtros de pandas).
        # Sumar y luego que en cada pixel mayor a 255 sea asignado el máximo.
        # Ver los tests para mas información.

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return Imagen(saturar_arreglo(self.imagen + other))

    def __radd__(self, other):
        """Operación conmutativa de __add__.

        Hint: debería llamar a __add__...

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return self + other

    def __sub__(self, other):
        """Redefine la operación + entre imagen y escalar.

        # Idea, usar indexado condicial (similar a los filtros de pandas).
        # Restar y luego que en cada pixel mayor a 255 sea asignado el máximo.
        # Caso similar para valores menores a 0, donde debera asignar el minimo a esos pixeles
        # Ver los tests para mas información.

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return self + (-other)

    def __rsub__(self, other):
        """Operación conmutativa de __sub__.

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return Imagen(saturar_arreglo(other - self.imagen))

    def __mul__(self, other):
        """Redefine la operación + entre imagen y escalar.

        # Idea, usar indexado condicial (similar a los filtros de pandas).
        # Sumar y luego que en cada pixel mayor a 255 sea asignado el máximo y 
        # cada valor inferior a 0 debe ser 0.
        # Ver los tests para mas información.

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return Imagen(saturar_arreglo(self.imagen * other))

    def __rmul__(self, other):
        """Operación conmutativa de __mul__.

        Hint: debería llamar a __mul__...

        Parameters
        ----------
        other : Union[int, np.ndarray]
            Escalar o arreglo que será sumado a cada pixel de la imagen
        """
        return self*other

Resultados esperados:

In [12]:
gatito = Imagen(images["gatitos"][1])
gurus = Imagen(images["Personas"][1])
In [13]:
# Test show e info.
gatito.show()
gatito.info()

gurus.show()
gurus.info()
Dimensiones de la imagen: 933x700 (Alto x Ancho)
Dimensiones de la imagen: 380x506 (Alto x Ancho)
In [14]:
np.min((1000 - gatito ).imagen)
Out[14]:
255
In [15]:
# Tests de los overload de operadores.

# Test __add__
# Idea del test: Todos los elementos de la imagen deben ser a lo más 255.

# Test __add__
assert np.max((gatito + 1000).imagen) == 255

# Test __radd__
assert np.max((1000 + gatito).imagen) == 255

# Test __sub__
assert np.min((gatito - (-1000)).imagen) == 255

# Test __sub__
assert np.max((gatito - 1000).imagen) == 0

# Test __rsub__
assert np.min((1000 - gatito ).imagen) == 255

# Test __mul__ (probar minimo)
assert np.max((-555555 * gatito).imagen) == 0

# Test __mul__ (probar maximo)
assert np.max((555555*gatito).imagen) == 255

# Test __rmul__ (probar minimo)
assert np.max((gatito*-555555).imagen) == 0

# Test __rmul__ (probar maximo)
assert np.max((gatito*555555).imagen) == 255

2.3 Clase de Procesamiento de Imagenes [2 puntos]¶

Ahora que comprenden las diferentes dimensiones que componen a una imagen (en la práctica), ahora realizaremos diferentes tareas de procesamiento de imágenes. Para esto, deben crear una clase llamada "LibImagen" que cumpla los siguientes requisitos:

  • [ ] Pasar una imagen a escala de grises, para esto utilice la ecuación (1) expuesta en este mismo notebook.
  • [ ] Obtener los canales R, G y B de forma individual.
  • [ ] Crear una función que pase a negativo de la imagen alojada.
  • [ ] Mejorar el contraste de una imagen.
  • [ ] Realizar una convolución sobre la imagen.
  • [ ] Documentar la clase creada y verificar la documentación con el comando help().

Nota 🗒️: Todo método debe tomar una Imagen y retornar una nueva Imagen.

Nota 2: El tipo de datos del arreglo de la imagen que generen o modifiquen debe ser "int". De lo contrario, puede no visualizarse correctamente.

Consiste en recorrer una imagen por cada uno de sus canales utilizando una matriz que lleva por nombre Kernel. El kernel, examinará los conjuntos de pixeles que recorre, aplicando una multiplicación de los valores circundantes ,y sumando todos los valores generados de este producto para generar un nuevo pixel en el tensor de salida.

In [16]:
imagen = gatito.imagen
new_canal = 0.299*imagen[:, :, 0] + 0.587*imagen[:, :, 1] + 0.114*imagen[:, :, 2]
new_canal
Out[16]:
array([[ 39.806,  39.806,  38.806, ...,  56.059,  56.287,  52.287],
       [ 35.806,  35.806,  35.806, ...,  59.102,  60.102,  59.102],
       [ 34.806,  34.806,  34.806, ...,  58.188,  58.188,  60.188],
       ...,
       [ 95.333,  97.333,  99.333, ..., 155.905, 155.677, 155.677],
       [ 96.333,  98.333,  99.333, ..., 155.905, 155.677, 155.677],
       [ 98.333,  98.333,  98.333, ..., 155.905, 155.677, 155.677]])
In [17]:
class LibImagen():
    """Clase para realizar tareas de procesamiento de imágenes construidas por la clase Imagen."""
    def to_negative(self, img_in):
        """Convierte imagen a negativo.

        Parameters
        ----------
        img_in : Imagen
            Objeto Imagen que contiene imagen a procesar.

        Returns
        -------
        Imagen
            Objeto Imagen con la imagen procesada.
        """
        return 255 - img_in

    def to_gray(self, img_in):
        """
        Transforma una imagen en RGB a la escala de grises.

        Parameters
        ----------
        img_in : Imagen
            Objeto Imagen que contiene una imagen.

        Returns
        -------
        Imagen
            Una que contiene una imagen con 3 canales.
            Los 3 canales deben tener los mismos valores.
        """
        imagen = img_in.imagen
        new_canal = (0.299*imagen[:, :, 0] + 0.587*imagen[:, :, 1] + 0.114*imagen[:, :, 2]).astype(int)
        new_imagen = np.dstack((new_canal, new_canal, new_canal))
        return Imagen(new_imagen)

    def get_channel(self, img_in, channel):
        """Obtiene un canal de un color seteando el resto de los canales en 0.

        Parameters
        ----------
        img_in : Imagen
            Objeto Imagen que contiene una imagen.
        channel : str
            Nombre del canal que será seleccionado. Valores posibles: ('r','g' o 'b').

        Returns
        -------
        Imagen: 
            Objeto Imagen que contiene una imagen con 3 canales. 
            Solo el canal seleccionado debe tener valores distintos a 0.
        """
        colores = {"r": [1, 2], "g": [0, 2], "b": [0, 1]}
        new_arr = img_in.imagen.copy()
        new_arr[:, :, colores[channel]] = 0
        return Imagen(new_arr)

    def set_contrast(self, img_in, C):
        """Mejora el contraste de una imagen.

        Parameters
        ----------
        img_in : Imagen
            Objeto Imagen que contiene una imagen.
        C : float
            Parámetro que define el ajuste de contraste.

        Returns
        -------
        Imagen
            Objeto Imagen que contiene una imagen con 3 canales modificados.            
        """
        F = 259*(C + 255)/(255*(259 - C))
        return F*(img_in - 128) + 128

    def conv_channel(self, img_in, kernel):
        """Consiste en recorrer una imagen por cada uno de sus canales utilizando el kernel.
        El kernel examinará los conjuntos de pixeles que recorre, aplicando una multiplicación de los valores circundantes,
        y sumando todos los valores generados de este producto para generar un nuevo pixel en el tensor de salida.

        Parameters
        ----------
        img_in : Imagen
            Objeto Imagen que contiene una imagen.
        kernel : array_like
            Matriz de kernel de la convolución.

        Returns
        -------
        Imagen
            Objeto Imagen que contiene una imagen con 3 canales modificados por convolución.
        """
        img = img_in.imagen
        img_out = []
        for i in range(img.shape[-1]):
            img_channel = convolve2d(img[:, :, i], 
                                     kernel, 
                                     mode="same", 
                                     boundary="symm")
            img_out.append(img_channel)
        new_image = np.stack(img_out, axis=2)
        new_image[new_image>255], new_image[new_image<0] = 255, 0
        return Imagen(new_image.astype(int))

2.4 Probar [0,5 puntos]¶

Respuesta Esperada:

In [18]:
gatito = Imagen(images["gatitos"][1])

gatito.show()

lib = LibImagen()

print('Negativo')
lib.to_negative(gatito).show()

print('Grayscale')
lib.to_gray(gatito).show()

print('Selección de Canales')
lib.get_channel(gatito, "r").show()
lib.get_channel(gatito, "g").show()
lib.get_channel(gatito, "b").show()

print('Mejora de Contraste')
lib.set_contrast(gatito, 0).show()

print('Convolución')
kernel = np.array([[-1, -1,  -1], 
                   [-1,  8,  -1], 
                   [-1, -1,  -1]])

lib.conv_channel(gatito, kernel).show()
Negativo
Grayscale
Selección de Canales
Mejora de Contraste
Convolución

Implementar 5 Kernels y probarlos con las imagenes¶

Referencia: https://en.wikipedia.org/wiki/Kernel_(image_processing)

In [19]:
# Convolución
kernel_1 = np.zeros((3, 3))
kernel_1[1, 1] = 1
kernel_2 = -np.ones((3, 3))
kernel_2[1, 1] = 4
kernel_3 = np.ones((3, 3))/3
kernel_4 = np.array([[0, -1, 0],
                     [-1, 4, -1],
                     [0, -1, 0]])
kernel_5 = -np.array([[1, 4, 6, 4, 1],
                      [4, 16, 24, 16, 4],
                      [6, 24, -476, 24, 6],
                      [4, 16, 24, 16, 4],
                      [1, 4, 6, 4, 1]])/512

lib.conv_channel(gatito, kernel_1).show()
lib.conv_channel(gatito, kernel_2).show()
lib.conv_channel(gatito, kernel_3).show()
lib.conv_channel(gatito, kernel_4).show()
lib.conv_channel(gatito, kernel_5).show()

Comente:

Para finalizar, comente que hace (o debería hacer) cada filtro convolucional al aplicarlas a su imagen de ejemplo.

(Escriba aquí su justificación)

El filtro...

1.- El filtro de identidad solamente considera el pixel en el que está parado, y da valor 0 a los vecinos, así que no modifica la imagen.
2.- El filtro de detección de crestas encuentra máximos locales de una función definida en las dimensiones de la imagen. Su propósito es encontrar crestas.
3.- El filtro de ruido de caja promedia todos los vecinos, perdiendo detalle en la imagen.
4.- El filtro Laplaciano detecta bordes de una imagen, pues es una aproximación (por diferencias finitas) de la segunda derivada de la intensidad de los pixeles, así que detecta cambios relevantes.
5.- El filtro de máscara de desenfoque agrega ruido gaussiano a la imagen negativa, para agregarla a la imagen positiva y así reducir el ruido final.

3. Secuencias y Detección de Movimiento¶

3.2. Secuencia de Imagenes [1,5 punto]¶

A continuación, deben programar una función que nos permite resaltar los objetos en movimientos de una secuencia de imágenes. Para esta parte del laboratorio, deberá utilizar las imágenes dispuestas en la carpeta secuencia_plaza del archivo zip subido a material docente.

Primero que todo, cargue la secuencia de imágenes que se encuentran en el directorio. Para esto, se recomienda utilizar el comando os.listdir(dir), ya que este le facilitará la carga de un gran número de imágenes (pruebe el comando y vea que sucede).

In [20]:
path = "./secuencia_plaza/"
img_names = os.listdir(path)

imagenes = np.array(list(map(lambda img: np.array(Image.open(path + img)), img_names)))

plaza se mueve

Para realizar este ejercicio utilizaremos un método super básico para la eliminación de fondo. Para esto sigue la siguiente receta:

  1. Pase a escala de grises todas las imágenes secuenciales.
  2. Almacene en una variable la resta de las imágenes consecutivas.

$$ imagen\_out = imagen(t) - imagen(t+1) $$

  1. Establezca un umbral para eliminar algunos artefactos. Pruebe con al menos 3 umbrales.
$$ imagen\_out = imagen\_out> Umbral $$

Nota: No es necesario que construya una clase para esta parte.

In [21]:
imagenes_grises = [lib.to_gray(Imagen(imagen)).imagen[:, :, 0]
                   for imagen in imagenes]
imagenes_grises_diff = [imagen_1 - imagen_2
                        for imagen_1, imagen_2 in
                        zip(imagenes_grises, imagenes_grises[1:])
                       ]
In [34]:
def dect_mov(sec_img=imagenes_grises_diff, umbral=30):
    return  [255*(img > 30).astype(np.uint8) for img in plaza]

3.3 Resultado¶

Ahora es tiempo de relajarse y ver si nuestro experimento logra resaltar los objetos en movimiento de esta polémica Plaza, para esto solo ejecute el siguiente Código y espere.

In [35]:
plaza = imagenes_grises_diff
imageio.mimsave('plaza.gif', dect_mov(plaza))
In [36]:
imageio.mimsave('plaza_15.gif', dect_mov(umbral=15))
imageio.mimsave('plaza_60.gif', dect_mov(umbral=60))

Ejemplo de resultado esperado:

resultados

Conclusión¶

Eso ha sido todo para el lab de hoy, recuerden que el laboratorio tiene un plazo de entrega de una semana y que los días de atraso no se pueden utilizar para entregas de lab solo para tareas. Cualquier duda del laboratorio, no duden en contactarnos por mail o U-cursos.

Gracias Totales!

Referencias lab 1.¶

  • http://www.cs.cornell.edu/cv/SummerSchool/Introduction.pdf
  • https://en.wikipedia.org/wiki/Tensor
  • https://support.ptc.com/help/mathcad/es/index.html#page/PTC_Mathcad_Help/example_grayscale_and_color_in_images.html
  • http://w3.unpocodetodo.info/canvas/negativo.php
  • http://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-5-contrast-adjustment/



Created in deepnote.com Created in Deepnote