jueves, 15 de noviembre de 2012

reconocer umbrales de color con Pygame y una webcam

pygame es una librería de python pensada para hacer vídeo juegos de una forma fácil, en sus ultimas versiones trae un modulo para manejar cámaras webs como las que podemos tener instaladas en una notebook.
Lo interesante, es que con esta librería podemos hacer cosas muy potentes para dotar de "inteligencia" a nuestro robot autonomo.
En este tutorial, voy a mostrar como manejar la libreria pygame.camera para levantar la camara web de una notebook y reconocer un umbral de color X y determinar su par ordenado (x,y) en la pantalla (con lo cual despues se puede tranquilamente mover nuestro robot).

ES IMPORTANTE TENER MINIMOS CONOCIMIENTOS DE PYTHON

Como recomendación instalar ipython y python-tools. ipython es un interprete python con múltiples opciones, la que mas me gusta es la de auto completado (me ahorra muchos dolores de cabeza). python-tools es una serie de herramientas para python, como idle, que es un interprete gráfico de python, con capacidad de auto completado y depuración de código que nos va a servir mucho para ir viendo el codigo.

#!/usr/bin/env python
import pygame
import pygame.camera
pygame.init()
pygame.camera.init()

Como podemos verlo principal es iniciar el pygame y pygame.camera, si todo arranca como corresponde, no debería pasar nada, todavía no declaramos nuestra pantalla ni capturamos las imágenes de la cámara.


#!/usr/bin/env python
import pygame
import pygame.camera
pygame.init()
pygame.camera.init()
######## declaro las variables necesarias ######################
# para manejar la ventana de pygame donde se mostraran las imagenes
Pantalla=pygame.display.set_mode((320,240),0)
# crea una tupla con las camaras reconocidas (el cero es la primer camara)
ListaCamaras=pygame.camera.list_cameras()
# instancia el objeto pygame.camera.Camera con la primer webcam 
# y una matriz de 320x240 pixeles
camara=pygame.camera.Camera(ListaCamaras[0],(320,240))
# inicio la camara
camara.start()


Con este código nuestra cámara ya esta iniciada y debería aparecer una ventana de pygame. Sin embargo la ventana apenas aparece, esta toda en negro y al rato desaparece, eso pasa porque no hicimos el bucle principal para controlar la camara.
Para no complicar el codigo fuente no voy a poner lineas de código para controlar errores ni cosas por el estilo (por ejemplo determinar si ListaCamaras tiene alguna cámara, o si tira un error camara.start() o cosas de esas). Por lo tanto el codigo siguiente si bien funciona, no es optimo (por ejemplo para cerrar la ventana hay que matar el proceso python).


#!/usr/bin/env python
import pygame
import pygame.camera
pygame.init()
pygame.camera.init()
######## declaro las variables necesarias ######################
# para manejar la ventana de pygame donde se mostraran las imagenes
Pantalla=pygame.display.set_mode((320,240),0)
# crea una tupla con las camaras reconocidas (el cero es la primer camara)
ListaCamaras=pygame.camera.list_cameras()
# instancia el objeto pygame.camera.Camera con la primer webcam 
# y una matriz de 320x240 pixeles
camara=pygame.camera.Camera(ListaCamaras[0],(320,240))
# inicio la camara
camara.start()
while True:
    imagen=camara.get_image()
    Pantalla.blit(imagen,(0,0))
    pygame.display.update()

¡Ya tenemos nuestra cámara!... y VIVE! muajua jua jua jua (risa malevola de cientifico loco).


Ahora solo falta reconocer un umbral de color. Para ello agregamos una variable mas a nuestro programa:

ccolor = pygame.transform.average_color(imagen, (160,120,10,10))

La variable ccolor captura un umbral de color de la imagen delimitada en por un rectangulo de 10x10 pixeles en la posicion x,y de 160x120 pixeles.
Con esa variable nosotros podemos usar para crear una mascara con pygame.mask.from_threshold.
con nuestra mascara, ya podemos contar la cantidad de pixeles y ver si pasa de cierta cantidad (se puede calibrar para obtner mejores resultados), y en base a eso obtener el par (x,y) del centro de nuestra mascara.

#!/usr/bin/env python
#!/usr/bin/env python
import pygame
import pygame.camera
pygame.init()
pygame.camera.init()
Pantalla=pygame.display.set_mode((320,240),0)
ListaCamaras=pygame.camera.list_cameras()
camara=pygame.camera.Camera(ListaCamaras[0],(320,240))
camara.start()
imagen=camara.get_image()
#obtenemos el umbral de color del centro de la escena en el primer cuadro
ccolor = pygame.transform.average_color(imagen, (160,120,10,10))
while True:
    imagen=camara.get_image()
    Pantalla.blit(imagen,(0,0))
    #creamos una mascara de la imagen con nuestro umbral 
    #(elimina todo menos el umbral)
    mask = pygame.mask.from_threshold(imagen, ccolor, (30,30,30))
    # se fija si la imagen tiene alguna conexion con nuestra mascara
    connected = mask.connected_component()
    # si los numeros de pixeles dentro de la mascara es mayor a 100
    if mask.count() > 100:
        # busca el centro
        coord = mask.centroid()
        #imprime el par ordenado x,y
        print coord
        # dibujo un circulo para poder ver las cordenadas que reconoce
        pygame.draw.circle(Pantalla, (0,255,0), coord, 10,5)
        pygame.display.flip()
    pygame.display.update()


Con esto capturamos el primer umbral de la imagen, antes de iniciar el bucle while. Esto puede ser medio molesto, en caso de que querramos elegir un punto de la pantalla y que el circulito ver (y posiblemente nuestro robot) pueda seguir colores arbitrarios.
Para eso seguiremos con las opciones de pygame y vamos a usar sus funciones para manejo del mouse.


    pygame.event.get()
    mouse= pygame.mouse.get_pressed()
    if mouse[0]==1:
        x,y=pygame.mouse.get_pos()
        ccolor = pygame.transform.average_color(imagen, (x,y,10,10))

Básicamente lo que hacemos es capturarlos eventos con pygame, gravar los "cliks" del raton en una lista y despues fijarnos si el boton esta presionado.

la variable mouse va a tener una lista de la siguiente forma : [0,0,0] donde cada cero es uno de los 3 botones del mouse (boton izquierdo, rueda del centro y boton derecho). Si el boton izquierdo esta presionado la lista quedaria: [1,0,0] enotnces con fijarnos en el primer elemento de la lista podemos saber si un boton del mouse fue presionado.
despues capturamos la posición x,y del puntero y volvemos a crear la variable ccolor con el umbral de color que queremos que siga nuestro robot.
el codigo completo quedaria asi:


#!/usr/bin/env python
import pygame
import pygame.camera
pygame.init()
pygame.camera.init()
Pantalla=pygame.display.set_mode((320,240),0)
ListaCamaras=pygame.camera.list_cameras()
camara=pygame.camera.Camera(ListaCamaras[0],(320,240))
camara.start()
imagen=camara.get_image()
ccolor = pygame.transform.average_color(imagen, (10,10,10,10))
while True:
    pygame.event.get()
    mouse= pygame.mouse.get_pressed()
    if mouse[0]==1:
        x,y=pygame.mouse.get_pos()
        ccolor = pygame.transform.average_color(imagen, (x,y,10,10))
    imagen=camara.get_image()
    Pantalla.blit(imagen,(0,0))
    mask = pygame.mask.from_threshold(imagen, ccolor, (30,30,30))
    connected = mask.connected_component()

    if mask.count() > 100:
        coord = mask.centroid()
        pygame.draw.circle(Pantalla, (0,255,0), coord, 10,5)
        pygame.display.flip()
    pygame.display.update() 
 



 Video de muestra del programa.

Capturando el par ordenado x,y podemos hacer cualquier cosa con nuestro robot, como seguir el punto, usar trigonometria para determinar distancias (usando un laser y matematica sencillas).
En el proximo tutorial veremos de mover nuestro robot en funcion de las imagenes de la camara.

un robot usando el umbral de color para reconocer vasos de distintos colores

3 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Estimado Guille
    Estoy realizando u proyecto de grado en el que quiero desarrollar un software de escaneo de objetos para poder llevarlos a 3d algo como el software David Lesner Scaner, y para ello quisiera saber mas sobre python ya que me parece un buen lenguaje, sobre comando que me puedan ayudar. muchas gracias de antemano.

    ResponderEliminar