<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Localización de objetos
Objetivo
Step1:  Luego, cargamos la imagen y la mostramos para verificar que se cargó correctamente.
Step2:  Filtrado por color
Una manera simple de reconocer un objeto es mediante su color. Si ese color es muy distintivo y uniforme (las asunciones que mencionamos arriba), podemos recorrer la imagen para encontrar los pixeles de ese color.
Enfoquémosnos en el guante
Step3:  Conversión al espacio de colores HSV
En lugar de eso vamos a convertir los píxeles desde el espacio RGB al espacio HSV, en donde cada píxel se codifica también con tres valores, pero ahora con significado diferente
Step4:  Ahora miremos los canales HSV por separado
Step5:  Filtrando por color
Para filtrar por color, debemos averiguar el rango de colores del guante. Para ello, una forma sencilla consiste en abrir la imagen en un editor de imágenes que permita ver el valor de cada pixel, y anotar los valores de varios pixeles del guante. En este caso lo hicimos con el programa Paint y obtuvimos
Step6:  Generación de la máscara de segmentación
En base a la imagen original y estos rangos de color, vamos a generar otra imagen llamada la máscara de segmentación. Esta máscara es simplemente una imagen en blanco y negro (cada pixel tiene valor $0$ o $1$). Si el pixel $i,j$ de la imagen original está en el rango de color, en la máscara el pixel $i,j$ tendra el valor $1$. Si no, tendra valor $0$.
Entonces, a partir de la imagen de la izquierda codificada en HSV, generaremos mascara de segmentación (otra imagen, pero en blanco y negro) de la derecha
Step7:  Filtro de erosión
Como se puede ver en la imagen, han quedado algunos píxeles en blanco aislados donde no está la mano que deberíamos sacar. Estos pixeles suelen llamarse ruido. 
Para eso, vamos a implementar una versión simple de un filtro de erosión, que justamente busca eliminar a estos pixeles blancos que tienen vecinos negros. 
La idea es simplemente, por cada pixel, contar cuántos vecinos inmediatos en blanco tiene, incluido a si mismo. Si todos son blancos (9 blancos), el pixel queda en blanco. Sino, se convierte en negro. 
Aqui vemos el efecto en una imagen. Los pixeles pintados de rojo son los que se convertirán de blanco a negro
Step8:  Como vemos, pudimos eliminar todos los pixeles de ruido. No obstante, el método que vamos a utilizar para encontrar el centro del guante puede tolerar algunos pixeles erróneamente etiquetados de blanco.
Encontrando el centro de la mano
Una forma sencilla de encontrar el centro de la mano es calculando el centro de masa de los pixeles blancos.
El centro de masa es el promedio de las coordenadas de los pixeles blancos (una coordenada es un par $(i,j)$ que me dice la posición del pixel, es decir, su fila y columna). 
Por ejemplo, si tengo solamente 3 pixeles blancos con coordenadas $(1,3)$, $(7,5)$, $(4,13)$, el centro de masa es
Step9:  Verificación del resultado
Utilizando la función dibujar_cuadrado que proveemos, se le puede agregar un cuadrado en una posición de la imagen, para indicar el centro de la mano. Si bien a la máscara de segmentación no le sacamos todo el ruido este método es robusto a pequeños errores.
<ASSISTANT_TASK:>
Python Code:
# comando de Jupyter para que las imagenes se muestren automaticamente 
%matplotlib inline
#numpy es la libreria de arreglos de python (la llamaremos np)
import numpy as np
# el modulo pyplot de matplotlib (que llamaremos plt) nos permite mostrar imagenes
import matplotlib.pyplot as plt
#el modulo matplotlib.colors nos permite transformar el espacio de color de una imagen
import matplotlib.colors
# el mapa de color para las imágenes escala de grises es el gris 
plt.rcParams['image.cmap'] = 'gray'
#el modulo io de skimage sirve para cargar y guardar imagenes
from skimage import io
Explanation: Localización de objetos
Objetivo: Aprender a localizar objetos mediante su color en imágenes simples.
Conocimientos previos: Conceptos básicos de imágenes digitales color y escala de grises. Programación en Python con la librería Numpy. Lectura, visualización y manipulación de imágenes con Numpy, PyPlot y skimage.
Importante: Para que este cuaderno funcione correctamente, la imagen guante.png y el directorio img deben estar en el mismo directorio que este cuaderno.
En este tutorial veremos una forma sencilla de localizar un objeto en una imagen. Para eso, vamos a realizar dos asunciones sobre el objeto:
* Tiene un color relativamente uniforme en toda su superficie
* Es el único objeto con ese color en la imagen. 
En este caso, vamos a intentar encontrar el guante rojo de la persona en la imagen:
Nuestro objetivo entonces es obtener las coordenadas $(x,y)$ del guante.
La idea principal del método es encontar los píxeles que tengan el color del guante.
Algunos pixeles que no son del guante también serán encontrados; llamaremos a estos pixeles ruido. Luego, eliminaremos el ruido.
Por último, podemos encontrar el centro de los pixels que tienen ese color, o sea, del guante.
Comenzaremos cargando varias librerías que luego nos serán útiles. Luego cargaremos la imagen y veremos las distintas operaciones necesarias para encontrar el objeto.
End of explanation
# leemos la imagen desde un archivo, y la almacenamos en memoria en un arreglo
image = io.imread('guante.png')
# Convertimos la imagen de entero a flotante, y además convertimos el rango de los pixeles
# de 0-255 (entero) al rango 0-1 (flotante)
image=image/255.
#imprimimos las tres dimensiones del arreglo: las dos primeras corresponden al alto y ancho, 
#la última a los canales de color (3 canales, por defecto RGB)
print("Dimensiones de la imagen: %s" % str(image.shape))
#guardamos las dimensiones en tres variables para usar en las celdas más abajo
h,w,c=image.shape
#mostramos el tipo de los elementos del arreglo (debería ser flotante!)
print("Tipo de los elementos de la imagen: %s" % str(image.dtype))
#visualizamos la imagen
plt.imshow(image)
Explanation: Luego, cargamos la imagen y la mostramos para verificar que se cargó correctamente.
End of explanation
#Vemos los canales individuales de RGB. 
#El fondo blanco activa los 4 canales (R,G y B)
# El buzo y el pelo, al ser negro oscuro, no se activan en ningún canal
# Por otro lado, el guante rojo se activa en el canal rojo y no en los otros dos
# Y el guante verde se activa en los canales verde (mayormente) y azul
#Anulo los canales rojo y azul para ver solo el verde
green=np.copy(image)
green[:,:,0]=0
green[:,:,2]=0
plt.figure()
plt.imshow(green)
#Anulo los canales azul y verde para ver solo el rojo
red=np.copy(image)
red[:,:,1:3]=0
plt.figure()
plt.imshow(red)
#Anulo los canales rojo y verde para ver solo el azul
blue=np.copy(image)
blue[:,:,0:2]=0
plt.figure()
plt.imshow(blue)
Explanation: Filtrado por color
Una manera simple de reconocer un objeto es mediante su color. Si ese color es muy distintivo y uniforme (las asunciones que mencionamos arriba), podemos recorrer la imagen para encontrar los pixeles de ese color.
Enfoquémosnos en el guante:
Como se puede ver no hay un solo rojo sino varios, por variaciones de la iluminación y la textura de la remera.
Entonces, en lugar de buscar un color en particular, vamos a tener que buscar un rango de colores. Pero esto va a resultar difícil con el espacio de color RGB.
End of explanation
# Convertir a modelo HSV
image_hsv=matplotlib.colors.rgb_to_hsv(image)
print("El pixel de la posición (100,100) en RGB es %s " % str(image[100,100,:]))
print("El pixel de la posición (100,100) en HSV es %s " % str(image_hsv[100,100,:]))
Explanation: Conversión al espacio de colores HSV
En lugar de eso vamos a convertir los píxeles desde el espacio RGB al espacio HSV, en donde cada píxel se codifica también con tres valores, pero ahora con significado diferente:
El canal H indica el tono (hue) del color (verde, rojo, azul, amarillo, etc).
El tono es lo que tradicionalmente entendemos como "color".
El canal S indica la saturación (saturation) de color
Si un color es más saturado, es más "puro"
Si es menos saturado, tiene más "gris"
El canal V indica la luminosidad o intensidad (value) del pixel. 
V=0 indica un pixel negro
V=1 indica un pixel blanco
La función rgb_to_hsv del módulo matplotlib.colorsnos permite hacer justamente eso.
End of explanation
#Vemos los canales individuales de HSV. 
plt.figure()
plt.imshow(image_hsv[:,:,0])
plt.colorbar()
plt.figure()
plt.imshow(image_hsv[:,:,1])
plt.colorbar()
plt.figure()
plt.imshow(image_hsv[:,:,2])
plt.colorbar()
Explanation: Ahora miremos los canales HSV por separado
End of explanation
#Rango de colores a utilizar
#guante rojo
range_h=(230/255,250/255)
range_s=(100/255,230/255)
range_v=(50/255,170/255)
Explanation: Filtrando por color
Para filtrar por color, debemos averiguar el rango de colores del guante. Para ello, una forma sencilla consiste en abrir la imagen en un editor de imágenes que permita ver el valor de cada pixel, y anotar los valores de varios pixeles del guante. En este caso lo hicimos con el programa Paint y obtuvimos:
* $(H,S,V)$
* $(245,142,61)$
* $(242,201,119)$
* $(246,192,103)$
* $(245,219,153)$
* $(241,180,139)$
* $(238,117,72)$
Entonces el rango de colores que buscamos es, aproximadamente:
H entre $238$ y $246$.
S entre $117$ y $219$.
V entre $72$ y $153$.
Como podemos observar, el canal H es el que mostró menos variación,ya que el tono se mantiene bastante constante. Por otro lado, el S y el V pueden variar mucho de acuerdo a la iluminación.
Para dar un poco más de rango, vamos a expandir un poco estos
H entre $230$ y $250$.
S entre $100$ y $230$.
V entre $50$ y $170$.
Por otro lado, el programa Paint así como otros editores de imágenes nos dan estos valores codificados en los rangos 0-255 para H, V y S, por lo que habría que convertirlos a los intervalos 0-1 que utiliza numpy dividiendo por el extremo del rango, es decir:
H entre $235/255$ y $250/255$.
S entre $140/255$ y $255/255$.
V entre $115/255$ y $230/255$.
End of explanation
# Filtrar por color para buscar el guante
h,w,c=image_hsv.shape
segmentation_mask=np.zeros((h,w))
h_min,h_max=range_h
s_min,s_max=range_s
v_min,v_max=range_v
        
for i in range(h):
    for j in range(w):
        h_val,s_val,v_val=image_hsv[i,j,:]
        #Si los valores están en rango, poner en 1 la coordenada (i,j) de la máscara de segm
        #IMPLEMENTAR - COMIENZO
        if h_min<h_val<h_max and s_min<s_val<s_max and v_min<v_val<v_max:
            segmentation_mask[i,j]=1
        #IMPLEMENTAR - FIN
plt.imshow(segmentation_mask,vmin=0,vmax=1)            
Explanation: Generación de la máscara de segmentación
En base a la imagen original y estos rangos de color, vamos a generar otra imagen llamada la máscara de segmentación. Esta máscara es simplemente una imagen en blanco y negro (cada pixel tiene valor $0$ o $1$). Si el pixel $i,j$ de la imagen original está en el rango de color, en la máscara el pixel $i,j$ tendra el valor $1$. Si no, tendra valor $0$.
Entonces, a partir de la imagen de la izquierda codificada en HSV, generaremos mascara de segmentación (otra imagen, pero en blanco y negro) de la derecha:
<img src="img/guante.png" style="width:40%;display:inline-block;"> <img src="img/mascara_de_segmentacion.png" style="width:40%;display:inline-block;">
End of explanation
eroded_mask=np.zeros(segmentation_mask.shape) # creamos otra imagen con la misma forma que la mascara de seg.
# erosionar la mascara 
for i in range(1,h-1):#evitamos los bordes
    for j in range(1,w-1): #evitamos los bordes
        #IMPLEMENTAR - COMIENZO
        suma_vecinos=(segmentation_mask[i-1,j-1]+segmentation_mask[i-1,j]+
                      segmentation_mask[i-1,j+1]+segmentation_mask[i,j-1]+
                      segmentation_mask[i,j]    +segmentation_mask[i,j+1]+
                      segmentation_mask[i+1,j-1]+segmentation_mask[i+1,j]+
                      segmentation_mask[i+1,j+1]
                     )
        if suma_vecinos==9:
            eroded_mask[i,j]=1
        #IMPLEMENTAR - FIN
        
plt.imshow(eroded_mask,vmin=0,vmax=1)            
Explanation: Filtro de erosión
Como se puede ver en la imagen, han quedado algunos píxeles en blanco aislados donde no está la mano que deberíamos sacar. Estos pixeles suelen llamarse ruido. 
Para eso, vamos a implementar una versión simple de un filtro de erosión, que justamente busca eliminar a estos pixeles blancos que tienen vecinos negros. 
La idea es simplemente, por cada pixel, contar cuántos vecinos inmediatos en blanco tiene, incluido a si mismo. Si todos son blancos (9 blancos), el pixel queda en blanco. Sino, se convierte en negro. 
Aqui vemos el efecto en una imagen. Los pixeles pintados de rojo son los que se convertirán de blanco a negro:
<img src="img/erosion.png" />
Entonces, para cada pixel, tenemos que observar a sus vecinos: si el pixel y sus vecinos son todos blancos, el resultado es un pixel blanco:
<img src="img/erosion_si.png" />
Por otro lado si el pixel o uno de sus vecinos es negro, el resultado es un pixel negro:
<img src="img/erosion_no.png" />
Notemos que aún cuando el pixel original era blanco, si algún vecino es negro, el pixel resultado quedará en negro también:
<img src="img/erosion_no2.png" />
Importante: Para implementar el filtro, es necesario crear otra matriz donde se almacenará la imagen resultado, ya que el calculo con los vecinos debe hacerse siempre con la imagen original sin modificar.
End of explanation
# Calcular centro de masa
suma_i=0
suma_j=0
count=0
for i in range(h):
    for j in range(w):
        # Si el pixel (i,j) de eroded_mask pertenece al guante (vale 1)
        # Sumar i a suma_i y lo mismo con j y suma_j
        # IMPLEMENTAR - COMIENZO
        if eroded_mask[i,j]==1:
            suma_i+=i
            suma_j+=j
            count+=1
        # IMPLEMENTAR - FIN
    
red_glove_position=np.array([suma_i,suma_j])/count # IMPLEMENTAR
print("Las coordenadas del centro de la mano con el guante rojo son:")
print(red_glove_position)
Explanation: Como vemos, pudimos eliminar todos los pixeles de ruido. No obstante, el método que vamos a utilizar para encontrar el centro del guante puede tolerar algunos pixeles erróneamente etiquetados de blanco.
Encontrando el centro de la mano
Una forma sencilla de encontrar el centro de la mano es calculando el centro de masa de los pixeles blancos.
El centro de masa es el promedio de las coordenadas de los pixeles blancos (una coordenada es un par $(i,j)$ que me dice la posición del pixel, es decir, su fila y columna). 
Por ejemplo, si tengo solamente 3 pixeles blancos con coordenadas $(1,3)$, $(7,5)$, $(4,13)$, el centro de masa es:
$ \frac{(1,3) + (7,5) + (4,13)}{3}= \frac{(1+7+4,3+5+13)}{3}=\frac{(12,21)}{3}=(4,7)$. 
Entonces, el centro de masa también es una coordenada (si no es un número entero, se redondea). Entonces lo que debemos hacer es recorrer la imagen, y realizar la suma de las coordenadas de todos los pixeles blancos (valor $1$), y contarlos. Luego, dividir la suma por la cuenta.
Veamos el calculo del centro de masa para la siguiente imagen:
<img src="img/center_mass_simple.png">
Las coordenadas a sumar son 20: $ (2,3) - (2,4) - (2,5) - (3,2) - (3,3) - (3,4) - (3,5) - (3,6) - (4,2) - (4,3) - (4,4) - (4,5) - (4,6) - (5,2) - (5,3) - (5,4) - (5,5) - (6,3) - (6,4) - (8,7)$
En este caso, hagamos la cuentas de forma separada, primero para obtener la coordenada de la fila y luego la de la columna:
La fila del centro de masa es: $(2+2+2+3+3+3+3+3+4+4+4+4+4+4+5+5+5+5+6+6+8)/20=4.05$
La columna del centro de masa es : $(2+2+2+3+3+3+3+3+4+4+4+4+4+4+5+5+5+5+6+6+7)/20=4$
Entonces el centro de masa está en el pixel $(4,4)$.
End of explanation
import matplotlib.patches as patches
def dibujar_cuadrado(position):
    size=70
    canvas = plt.gca()
    size_x,size_y=(size,size)
    position_reversed=(position[1]-size_y/2,position[0]-size_x/2)
    rectangle=patches.Rectangle(position_reversed, size_x,size_y, fill=True)
    canvas.add_patch(rectangle)
plt.imshow(image)
dibujar_cuadrado(red_glove_position)
Explanation: Verificación del resultado
Utilizando la función dibujar_cuadrado que proveemos, se le puede agregar un cuadrado en una posición de la imagen, para indicar el centro de la mano. Si bien a la máscara de segmentación no le sacamos todo el ruido este método es robusto a pequeños errores.
End of explanation
<END_TASK>