lunes, 11 de febrero de 2013

Tarea 1 (Clase) Visión Computacional

Detección de bordes por medio del operador Sobel

Hola, esta es la tarea 1 de la clase de visión computacional, la actividad consiste en detectar bordes en distintas imágenes, mediante la utilización de filtros, umbrales, máscaras (técnica del operador de Sobel, o cualquier otra técnica).

Una vez detectados los bordes con las técnicas requeridas, realicé la actividad con 3 fotografías distintas, y para cada fotografía calculé el promedio que se llevaba de tiempo en ejecutar cada subrutina usada para obtener los bordes;
las subrutinas las corrí 30 veces para cada imagen (como el requisito de la tarea lo menciona).

Bien, dicho esto voy  a mencionar detalladamente el trabajo que realicé.

Escala de grises

Lo primero que realicé fue generar una imagen a escala de grises a partir de la imagen original. Está técnica la expliqué previamente en el laboratorio 1 de esta misma materia, pero repetiré en que consiste el procedimiento.
Consiste en que se recorre todos y cada uno de los pixeles de la imagen, en cada pixel que se va recorriendo, se va sacando un promedio de los valores RGB de cada uno, para luego ese promedio asignarlo como el nuevo valor de pixel en los 3 valores de RGB, de manera que cada pixel recorrido tendrá los 3 valores iguales.
Por ejemplo supongamos que tenemos un pixel con los componentes RGB (35, 100, 150), el procedimiento sería sumar los 3 valores (35+100+150), luego dividirlos entre 3 (promedio) para luego el resultado obtenido asignárselo al mismo pixel en sus 3 posiciones RGB.

A continuación les muestro la subrutina que hace este procedimiento, además de la imagen original con la que trabajé y la imagen a escala de grises obtenida.

def transformar():
 #tiempoInicial = time.time()
 i = 0
 x = 0
 y = 0
 im = Image.open("bla.jpg")
 
 for x in xrange(im.size[0]):
  for y in xrange(im.size[1]):
   pix = im.load()
   tupla = pix[x, y]
 
   a = tupla[0]
   b = tupla[1]
   c = tupla[2]
   
   prom = int((a+b+c)/3)
   newTupla = (prom,prom,prom)
   im.putpixel((x, y), newTupla)   

   #print tupla, "--", a,",",b,",",c
 im.save('meh.jpg')
 #tiempoFinal = time.time()
 #transcurso = tiempoFinal - tiempoInicial
 #print "Tiempo de escala de grises = ", transcurso 



Imagen original (660x495)
 


Imagen a escala de grises




Filtro

El siguiente procedimiento que realicé fue el de filtrado de la imagen, consiste en hacer un efecto blur en la imagen procesada anteriormente (escala de grises), para realizar esto, usé la técnica de los vecinos, que consiste en realizar un promedio con los valores de los pixeles de los vecinos que colindan con el pixel sobre el que estamos posicionados (recordemos que ahora cada pixel tiene los valores iguales de RGB, porque ahora está en escala de grises), seguido de esto, se asigna a ese pixel el valor obtenido.
Dicho esto, podemos concluir que el número máximo de vecinos que se pueden tener son 4, y el mínimo número de vecinos son 2 (los pixeles de las esquinas).

A continuación les muestro la subrutina con la que hice este proceso, además de la imagen obtenida después del filtrado:

def filtrada(): #Filtrada por el metodo de los vecinos
 #tiempoInicial = time.time()
 i = 0
 x = 0
 y = 0
 im = Image.open("meh.jpg")
 width, height = im.size
 pix = im.load() 
 promedio = 0
 width = width-1
 height = height-1

 #x = j y y = i
 for x in range(height):
  for y in range(width):
   #esquina superior izquierda
   if y == 0 and x == 0:
    promedio = (sum(pix[y + 1,x])/3 + sum(pix[y,x + 1])/3 + sum(pix[y,x])/3)/3
   #esquina superior derecha
   if y == width and x == 0:
    promedio = (sum(pix[y,x+1])/3 + sum(pix[y-1,x])/3 + sum(pix[y,x])/3)/3

   if y == 0 and x == height:
     promedio = (sum(pix[y,x-1])/3 + sum(pix[y+1,x])/3 + sum(pix[y,x])/3)/3

   if y == height and x == width:
     promedio = (sum(pix[y - 1,x])/3 + sum(pix[y,x - 1])/3 + sum(pix[y,x])/3)/3

   if y > 0 and y < width and x == 0:
    promedio = (sum(pix[y+1,x])/3 + sum(pix[y-1,x])/3 +sum(pix[y,x+1])/3+ sum(pix[y,x])/3)/4
  
   if y > 0 and y < width and x == height:
    promedio = (sum(pix[y -1,x])/3 + sum(pix[y,x-1])/3 +sum(pix[y+1,x])/3+ sum(pix[y,x])/3)/4

   if x >0 and x 0 and x < height:
    promedio = (sum(pix[y - 1,x])/3 + sum(pix[y,x-1])/3 + sum(pix[y,x +1])/3+ sum(pix[y,x])/3)/4

   if y > 0 and y< width and x>0 and x< height:
    promedio = (sum(pix[y,x])/3 + sum(pix[y + 1,x])/3 + sum(pix[y - 1,x])/3 + sum(pix[y,x + 1])/3 + sum(pix[y,x -1])/3)/5 
 


   #-----------------------------
   #tupla = pix[x, y]
   
   a = promedio
   b = promedio
   c = promedio 
   '''
   a = tupla[0]
   b = tupla[1]
   c = tupla[2]
   '''
   #prom = int((a+b+c)/3)
   pix[y, x] = (a,b,c)
   #tup = pix[]
   #im.putpixel((y, y), pix)   

   #print tupla, "--", a,",",b,",",c
 im.save('meh2.jpg')
 #tiempoFinal = time.time()
 #transcurso = tiempoFinal - tiempoInicial
 #print "Tiempo transcurrido durante la filtracion = ", transcurso



Imagen filtrada



Aplicación de máscara

En el proceso de la aplicación de la máscara, es en el que obtendremos los bordes de la imagen, existen distintos métodos para la obtención de bordes, sin embargo, el que yo usé es el del operador de Sobel, que es una  máscara típica de detección de bordes, perteneciente a las técnicas de gradiente diferencial (DG).

El procedimiento consiste en ir recorriendo toda la imagen pixel por pixel con una máscara o matriz de dimensiones 3x3 (por lo menos es de 3x3 con este método), y la idea es ir obteniendo los productos de la matriz que abarca la máscara en la imagen por los elementos de la misma matriz, de esa manera obtenemos un único valor de gradienteX y uno de gradiente Y, al final se obtiene un gradiente resultante (obviamente se obtiene apartir de los componentes de los gradientesX y gradientesY). El gradiente resultante será el nuevo valor del pixel sobre el que estamos posicionados.


Las siguientes son las matrices para obtener gradiente x (a la izquiera) y gradiente y (a la derecha), donde la A es la imagen original (en mi caso la imagen después de ser pasada por el filtro)


Luego después de obtener los componentes de los gradientes, obtenemos el gradiente resultante mediante la siguiente ecuación:



Si quieren obtener información más detallada de este operador Sobel, pueden visitar la siguiente liga: http://es.wikipedia.org/wiki/Operador_Sobel

Bien, a continuación les dejo la subrutina correspondiente a este procedimiento, además de la imagen obtenida después del proceso (nótese que la imagen tiene una línea blanca en su contorno, esto es el resultado de centrar el campo de trabajo, para que la mascara abarque siempre las dimensiones en la imagen, osea una matriz de 3x3):

def convolucion():
 #tiempoInicial = time.time()
 '''         ---       ---                     ---    ---
             | -1  0  1  |                    | 1  2  1  |
 SOBEL: Sx = | -2  0  2  |               Sy = | 0  0  0  |
             | -1  0  1  |                    |-1 -2 -1  |
             ---       ---                     ---    ---

 S = raiz(Sx2+Sy2)

 ''' 
 
 im = Image.open("meh2.jpg")
 width, height = im.size
 pix = im.load()
 resultado = 0
 gradienteX = ([-1, 0, 1], [-2, 0, 2], [-1, 0, 1])  #Valores establecidos por medio del operador Sobel
 gradienteY = ([1, 2, 1], [0, 0, 0], [-1, -2, -1])  #Para gradiente de y, el de arriba es el gradiente de x.
 sumasX = 0
 sumasY = 0  

 for x in range(height):
  for y in range(width):
   sumasX = 0
   sumasY = 0
   if x != 0 and y != 0 and y != width and x != height: #Para obtener un centrado de la mascara, evita la primera linea de pixeles de la imagen por los 4 lados
    #x = a
    #y = b   
    for a in range(3): #Debido a que la matriz de los gradientes es de 3x3
     for b in range(3):
      try:
       productosGX = gradienteX[a][b]*pix[y+b, x+a][1] #Obteniendo el valor de gradiente X
       productosGY = gradienteY[a][b]*pix[y+b, x+a][1] #Obteniendo el valor de gradiente Y
   
      except:
       productosGX = 0
       productosGY = 0
     
      sumasX = productosGX+sumasX #Adicionando los valores del gradiente X
      sumasY = productosGY+sumasY #Adicionando los valores del gradiente Y
   
    potenciaGradienteX = pow(sumasX, 2) #Obteniendo el cuadrado del gradiente X
    potenciaGradienteY = pow(sumasY, 2) #Obteniendo el cuadrado del gradiente Y
    Gradiente = int(sqrt(potenciaGradienteX+potenciaGradienteY)) #Para obtener el gradiente por medio de los componentes x, y
    #resultado = Gradiente

    if Gradiente > 255: #Por si se pasan los valores
     Gradiente = 255

    if Gradiente < 0: #Para estar dentro del rango (0, 255) 
     Gradiente = 0
  
    pix[y,x] = (Gradiente, Gradiente, Gradiente) # Creando el pixel nuevo con el gradiente obtenido   
 
 im.save('meh4.jpg')     
 #tiempoFinal = time.time() 
 #transcurrido = tiempoFinal - tiempoInicial
 #print "Tiempo transcurrido durante la convolucion = ", transcurrido


Imagen con bordes



Binarización

Para pulir los detalles y además hacer más notables los negros y blancos de la imagen, binaricé la imagen, aplicando umbrales preestablecidos, de esa manera, mi imagen ahora solo tendría pixeles negros y blancos.
El cambio en la imagen si es bastante notable y se aprecia todavía mejor el contorno.

A continuación les dejo la subrutina correspondiente a este procedimiento (no es necesario explicarla ya que el funcionamiento es demasiado sencillo), además de la imagen obtenida:

def umbrales():
 #tiempoInicial = time.time()
 i = 0
 x = 0  
 y = 0
 umbInferior = 77
 umbSuperior = 177
 im = Image.open("meh4.jpg")
 
 for x in xrange(im.size[0]):
  for y in xrange(im.size[1]):
   pix = im.load()
   tupla = pix[x, y]
 
   a = tupla[0]
   b = tupla[1]
   c = tupla[2]
   
   prom = int((a+b+c)/3)
   if prom < umbInferior:
    prom = 0
   elif prom >= umbSuperior:
    prom = 255
   newTupla = (prom,prom,prom)
   im.putpixel((x, y), newTupla)   

   #print tupla, "--", a,",",b,",",c
 im.save('meh5.jpg')
 #tiempoFinal = time.time()
 #transcurrido = tiempoFinal - tiempoInicial
 #print "Tiempo transcurrido durante la binarizacion = ", transcurrido



Imagen binarizada



Por último ejecuté cada subrutina 30 veces para poder formar un promedio de tiempo de ejecución de cada una. Así es como se ve la impresión de los tiempos de la imagen anteriormente presentada.


Y también hice lo mismo con 2 imágenes más, de distinto tamaño y contenido.

Original (900x904)


Escala de grises


Filtrada

Aplicando máscara


Binarización


Promedio de tiempos para cada subrutina

Noten que tarda más tiempo que la primera imagen, esto debido a que las dimensiones de esta imagen son más grandes


Ahora otra imagen de menores dimensiones que las 2 anteriores:

Imagen original (256x256)

Escala de grises

Filtro

Aplicando máscara

Binarización

 

Promedio de tiempo de ejecución

Nótese que los tiempos de ejecución de cada subrutina son menores que las 2 imágenes anteriores, debido a que las dimensiones de esta imagen son menores que las pasadas.


Bueno, esto es todo para esta actividad. Cualquier duda o aclaración pueden dejarla en los comentarios.

Saludos a todos.


1 comentario: