Isidoro-TFM-Rovio02

From robotica.unileon.es
Revision as of 16:05, 18 March 2011 by Anonymous (talk)

Jump to: navigation, search

thumb|right|200px|Rovio, la cámara web móvil.

El trabajo que aquí se va a presentar trata sobre la aplicación del robot ROVIO de la empresa Wowwee Technologies a tareas de vigilancia. Para ello se utilizará la biblioteca de visión artificial OpenCV, que será la encargada de procesar la información proviniente de la cámara wifi de ROVIO en un ordenador portátil. Como sistema operativo de base para el desarrollo de esta aplicación se utilizará GNU/Linux, en concreto la distribución Ubuntu. La aplicación será desarrollada enteramente en lenguaje C.

Introducción

Pendiente de redacción...

OpenCV

Si se desea profundizar en OpenCV hay varias fuentes de donde obtener información. Una de ellas es el libro Learning OpenCV: Computer Vision with the OpenCV Library escrito por Gary Bradski y Adrian Kaehler y publicado por la editorial O'Really. En la página web de OpenCV, WillowGarage, también existe abundante información para quienes no dispongan del libro en cuestión. Y en la Red hay numerosa información relacionada con el uso de la biblioteca, que si bien puede estar más o menos desperdigada, también supone una buena fuente de consulta. Por último, los ejemplos que se incluyen en la biblioteca también son una fuente de informacación utilísima. En el desarrollo de este trabajo se han utilizado todas estas fuentes en mayor o menor medida.

Instalación de la biblioteca

Para realizar la instalación de cualquier aplicación o biblioteca en GNU/Linux lo más cómodo suele ser utilizar los paquetes correspondientes. Los usuarios de Ubuntu pueden usar Synaptic (Sistema/Administración/Gestor de paquetes Synaptic) que es la interfaz gráfica por defecto en estos momentos para gestionar los paquetes, aunque también se puede instalar desde la línea de comandos de un terminal:

<geshi lang=Bash lines=0>~$ sudo aptitude install libcv4 libhighgui4 libcvaux4 libcv-dev libhighgui-dev libcvaux-dev opencv-doc</geshi>

Los tres primeros paquetes corresponden a la biblioteca propiamente dicha, por tanto son imprescindibles. Los tres siguientes con el sufijo -dev son los ficheros de desarrollo que también los necesitaremos si queremos desarrollar aplicaciones con esta biblioteca. El último paquete contiene programas de ejemplo y documentación sobre OpenCV y aunque no es imprescidible para trabajar, sí que es recomendable como fuente de consulta. Si se quiere contar con la última versión de las bibliotecas se puede realizar una instalación manual de las mismas a partir de sus fuentes siguiendo el proceso descrito para tal fin en WillowGarage.

Operaciones básicas

Una vez que tenemos preparado el sistema con todo el software necesario, es el momento de la primera toma de contacto con OpenCV. Este apartado no pretende ser un tutorial completo y exahustivo sobre cada una de las funciones que posee OpenCV si no más bien un sendero abierto que conduce directamente hacia el objetivo principal de este trabajo. Y lo mejor es comenzar poco a poco de manera que nuestra primera incursión sea lo menos traumática posible. Por tanto, los primeros programas que vamos a desarrollar consistirán en una serie de operaciones básicas que conviene aprender bien al principio pues serán imprescidibles para el desarrollo de la aplicación.

Las primeras operaciones que se realizarán serán mostrar una imagen y un vídeo que están guardados en el disco duro e interceptar el flujo de vídeo de una cámara para mostrarlo en pantalla.

Mostrar una imagen en pantalla

El siguiente código nos muestra en pantalla la imagen que recibe como argumento en la línea de comandos.

<geshi lang=C lines=0>#include "highgui.h"

int main(int argc, char* argv[]) {

IplImage *img = cvLoadImage(argv[1]); // Carga la imagen en memoria.

cvNamedWindow("imagen", CV_WINDOW_AUTOSIZE); // Crea la ventana donde mostrar la imagen cvShowImage("imagen", img); // Muestra la imagen en la ventana.

cvWaitKey(0); // Espera que se pulse una tecla.

cvReleaseImage( &img ); // Retira la imagen de la ventana. cvDestroyWindow("imagen"); // Libera la memoria.

return 0; }</geshi>

Si guardamos este código con el nombre ejemplo_01.c y lo compilamos con g++ de la forma:

<geshi lang=Bash lines=0>~$ g++ -o ejemplo_01 ejemplo_01.c -Wall -I /usr/include/opencv -L /usr/lib -lm -lhighgui</geshi>

deberíamos poder obtener un fichero que ejecutaríamos mediante la orden

<geshi lang=Bash lines=0>~$./ejemplo_01 mi-imagen.xxx</geshi>

siendo mi-imagen.xxx el nombre del fichero con la imagen que queremos mostrar. OpenCV soporta formatos de imagen del tipo JPEG, BMP, PNG, PGM y TIFF entre otros. Si todo ha ido bien, se debería ver la ventana creada con la imagen en su interior. Si pulsamos una tecla, la ventana se cierra y finaliza el programa.

El código del ejemplo está bien comentado y apenas necesita explicación. Cada vez que se necesite mostrar una imagen en pantalla con OpenCV, el procedimiento será siempre el mismo, tal y como se ve en el ejemplo:

  • Se creará una variable de tipo IplImage que guardará la imagen en forma de matriz de puntos y se indicará a la biblioteca que la cargue en memoria mediante la orden cvLoadImage.
  • Se creará la ventana donde se va a visualizar la imagen. Esto siempre es necesario, siempre hará falta un soporte sobre el que colocar las imágenes. En todo lenguaje de programación que incluya interfaces gráficas es necesario un contenedor donde se ponen o colocan los demás componentes de la interfaz y OpenCV no es una excepción.
  • Se pedirá a la librería que muestre la imagen dentro de la ventana correspondiente mediante la orden cvShowImage.

Una vez que ya no se necesite la imagen, será necesario liberar el espacio de memoria que ocupó la imagen para evitar saturarla. Esta operación se realiza mediante las órdenes cvReleaseImage que elimina la imagen de la ventana que la soporta y cvDestroyWindow que elimina de la memoria del equipo la ventana que contenía a la imagen mostrada.

En el ejemplo anterior se podría comprobar que se ha dado el nombre/ruta de una imagen como argumento en la línea de comandos antes de cargar la imagen para no producir un error en tiempo de ejecución. También se podría comprobar que la operación de carga de la imagen ha tenido éxito --o no-- mediante la adición de código extra, pero se ha preferido evitar esto para mantener el ejemplo lo más simple y claro posible para su mejor entendimiento.

Abrir un archivo de vídeo

En el siguiente ejemplo se tratará de abrir un archivo de vídeo almacenado en el disco duro del ordenador para que pueda ser visualizado en pantalla.

<geshi lang=C lines=0>#include "highgui.h"

int main(int argc, char* argv[]) { cvNamedWindow("ventana_video", CV_WINDOW_AUTOSIZE); CvCapture* flujo_video = cvCreateFileCapture(argv[1]); IplImage *fotograma;

while(1){ fotograma = cvQueryFrame(flujo_video); if (!fotograma) break; cvShowImage("ventana_video", fotograma);

char c = cvWaitKey(33); if(c == 27) break; }

cvReleaseCapture( &flujo_video ); cvReleaseImage( &fotograma ); cvDestroyWindow("ventana_video");

return 0; }</geshi>

En este ejemplo las cosas se hacen de forma ligeramente diferente a como se hacen en el anterior, por lo que merece la pena comentar estas diferencias. Una vez se ha creado la ventana contenedor para el vídeo con la orden cvNamedWindow, se crea una estructura que almacenará el fichero mediante la orden cvCreateFileCapture. Puesto que el vídeo es una sucesión de imágenes mostradas de forma contínua, necesitaremos acceder a cada una de esas imágenes para poder ir mostrándolas en pantalla. Para esto necesitamos crear otra variable, que llamaremos fotograma, que será la encargada de ir tomando de una en una las imágenes contenidas en el fichero de vídeo.

En el bucle infinito creado con la orden while(1) se realizan las siguientes operaciones:

  • Se toma un fotograma del fichero de vídeo mediante la orden cvQueryFrame, que permite un acceso secuencial a cualquier flujo de vídeo tanto si está almacenado en un fichero como si proviene de una cámara.
  • Se comprueba si se ha podido tomar el fotograma, en caso contrario se abandona el bucle --se ha producido un error debido, por ejemplo, a un fichero corrupto--.
  • Se muestra el fotograma en la ventana creada al principio del programa de forma similar a como se hizo en el ejemplo anterior.
  • Se esperan 33 milisegundos para comprobar la pulsación de una tecla y si esta tecla corresponde a la de ESC se abandona el bucle. En caso contrario se vuelve al comienzo del bucle de nuevo.
  • El programa finaliza liberando la memoria ocupada por las variables creadas al principio para la gestión del fichero de vídeo.

Dos cosas hacen diferente a este ejemplo del anterior: el bucle que permite ir tomando una tras otra las imágenes almacenadas en el fichero de vídeo y que es necesario para mostrar todas las imágenes del vídeo, y el valor de 33 milisegundos que recibe la orden cvWaitKey. Si este valor es cero, el programa esperará a que se pulse una tecla para mostrar el siguiente fotograma.

Un apunte más sobre el manejo de ficheros de vídeo con OpenCV. Si bien el formato de vídeo por defecto que maneja OpenCV es el AVI, también se pueden abrir ficheros de otro tipo siempre que el sistema tenga instalados los codec adecuados. En caso contrario, el programa lanzará un error relacionado con el tipo de fichero que intenta abrir.

Capturar vídeo de una cámara

En el ejemplo que sigue se muestra en pantalla el flujo de vídeo proviniente de una cámara de vídeo o de un fichero dependiendo de si en la línea de comandos del terminal se llama al programa con un argumento o no --el argumento será el nombre del fichero de vídeo a mostrar--.

<geshi lang=C lines=0>#include "highgui.h"

int main(int argc, char* argv[]) { CvCapture* flujo_video; cvNamedWindow("ventana_video", CV_WINDOW_AUTOSIZE); IplImage *fotograma;

if(argc == 1){ flujo_video = cvCreateCameraCapture(0); } else { flujo_video = cvCreateFileCapture(argv[1]); } assert(flujo_video != NULL);

while(1){ fotograma = cvQueryFrame(flujo_video); if (!fotograma) break; cvShowImage("ventana_video", fotograma);

char c = cvWaitKey(33); if(c == 27) break; }

cvReleaseCapture( &flujo_video ); cvReleaseImage( &fotograma ); cvDestroyWindow("ventana_video");

return 0; }</geshi>

Como comentarios al ejemplo se mencionará que el código es similar al anterior excepto por la condición en la que se toma como fuente del flujo o bien la salida de la cámara conectada al sistema o bien el fichero de argumento. Esto es muy bueno para el programador porque le da libertad absoluta para decidir cuál será la entrada de vídeo que quiere usar sin complicar el resto del código, ya que es la propia biblioteca la que se encarga de todo el trabajo sucio relacionado con la gestión del fichero o la cámara.

El valor cero que recibe la orden o función cvCrateCameraCapture como argumento corresponde a la fuente u origen del flujo de vídeo. Normalmente cuando sólo se tiene una cámara en el sistema es suficiente con dar el valor cero que corresponde a la constante CV_CAP_ANY de los ficheros de cabecera de OpenCV. De esta manera se consigue que el programa realice una autodetección del driver necesario para la captura. Otros valores interesantes podrían ser CV_CAP_V4L y CAP_VAL_V4L2 que corresponden al controlador de vídeo compatible con sistemas Video for Linux --vídeo nativo de GNU/Linux-- y que equivalen ambas al valor numérico 200, o el valor CV_CAP_FIREWIRE --valor numérico 300-- que correspondería a una cámara de tipo firewire. En el fichero de cabecera highgui.h se relacionan todos los valores de drivers soportados. Según el libro Learning OpenCV si queremos que el programa nos pregunte qué tipo de cámara vamos a utilizar bastará con poner un -1 como argumento de la función de captura, aunque esto sólo estaría disponible para usuarios de Windows ya que no se ha conseguido durante las pruebas del código en Ubuntu.

Como comentario final sólo se añadirá que la línea assert(flujo_video != NULL) es una simple comprobación de que se ha podido o abrir el fichero o enlazar con la cámara. Es una comprobación útil y muy recomendable pues se podría dar el caso de que un determinado driver de cámara o códec de vídeo no estuviesen instalados en el sistema lo que produciría un error en la ejecución de nuestro programa.

Guardar vídeo en un fichero

Si lo que se necesita es guardar el flujo de vídeo procedente de una cámara en el disco duro, lo mejor es utilizar un código similar al que se muestra a continuación.

<geshi lang=C lines=0># include "cv.h"

  1. include "highgui.h"
  2. include "stdio.h"

int main(int argc, char* argv[]) {

   CvCapture* flujo_video_entrada = cvCreateCameraCapture(0); 
   if (!flujo_video_entrada) { 
       printf("No puedo acceder a la cámara"); 
       return 0; 
   } 
   IplImage* fotograma = cvQueryFrame(flujo_video_entrada); 
   CvSize imgSize = cvSize(
   	(int)cvGetCaptureProperty(flujo_video_entrada, CV_CAP_PROP_FRAME_WIDTH),
   	(int)cvGetCaptureProperty(flujo_video_entrada, CV_CAP_PROP_FRAME_HEIGHT)
   	); // Dimensiones del fotograma
   CvVideoWriter *flujo_video_salida = cvCreateVideoWriter( 
           "flujo_salida.avi", CV_FOURCC('M','J','P','G'), 24, imgSize); 
   for (;;) { 
       //Get a frame from the input video. 
       fotograma = cvQueryFrame(flujo_video_entrada); 
       cvWriteFrame(flujo_video_salida, fotograma); 
   } 
   cvReleaseVideoWriter(&flujo_video_salida); 
   cvReleaseCapture(&flujo_video_entrada); 
   
   return 0;

}</geshi>

Las partes más importantes de este código son la correspondiente a la función cvCreateVideoWriter y el interior del bucle for. La función cvCreateVideoWriter nos permite escribir en el fichero especificado --en este caso flujo_salida.avi-- con la codificación o códec MJPG todas las imágenes recogidas de la cámara detectada en el sistema a una tasa de 24 fotogramas por segundo. Las dimensiones de cada fotograma vienen definidas por el valor de imgSize, que se obtiene mediante los valores CV_CAP_PROP_FRAME_WIDTH y CV_CAP_PROP_FRAME_HEIGHT --en el fichero de cabecera highgui.h se pueden ver el resto de las propiedades disponibles del fotograma--.

Dentro del bucle for se produce la escritura propiamente dicha del fichero de vídeo mediante la función cvWriteFrame. Esta función escribe en el fichero que se le da como primer argumento la imagen o fotograma a la que se acaba de acceder desde la cámara del sistema. La actuación combinada de ambas funciones, cvCreateVideoWriter y cvWriteFrame, permiten en primer lugar, reservar espacio en la memoria del sistema para almacenar el vídeo y en segundo lugar, volcarlo al disco duro.

Si en lugar de utilizar la cámara del sistema como fuente del flujo de vídeo se utilizase otro fichero de vídeo, modificando el código de manera adecuada se podría conseguir un convertidor de formatos de vídeo. Como argumentos de la línea de comandos se podrían utilizar el nombre del fichero fuente y el del fichero destino incluida su extensión. Convendría recordar que el cambio de formato de vídeo depende de los códec que haya instalados en el sistema. Pretender convertir o guardar un fichero en un formato que no tiene los códecs instalados producirá un error de ejecución. Si en el valor del códec se utiliza un -1 --CV_FOURCC(-1)-- el sistema pedirá que se seleccione uno de entre varios mostrados, pero sólo es sistemas Windows.

Se deja también propuesta la posibilidad de visualizar el vídeo de la cámara mientras se realiza la captura ya que en este ejemplo, como se habrá podido comprobar, se ha omitido esta posibilidad intencionadamente.

Eventos de teclado y ratón

Las interacciones del usuario con la aplicación que se jecuta son algo imprescindible en un programa. En este subapartado se mostrará la manera de realizar cambios en el comportamiento del programa a través de la pulsación de teclas o de los botones del ratón. También se verá cómo mostrar información relacionada con el programa sobreimpresa en las ventanas de vídeo.

Eventos de teclado

Seguimiento de colores

Pendiente de redacción

Anexos

Anexo A: Configuración de ROVIO

Pendiente de redacción

Anexo B: Comandos de control de ROVIO(?)

Pendiente de redacción

Anexo C: ROVIO por dentro

Los datos que se muestran a continuación no están basados en ninguna especificación oficial porque no hay ninguna publicada, si no que están recogidos y ordenados de varios sitios web dedicados a ROVIO o a temas relacionados con él, principalmente de Device Guru y RoboComunity. Se añaden al cuerpo del trabajo como anexo porque se considera que podrían ser interesantes de cara a una potencial y futura modificación o hackeo de ROVIO, de manera que su manejo y configuración se pueda realizar de forma completamente independiente de la plataforma software que se utilice.

Prcesador principal - Marvell "PXA270M" Frecuencia de Reloj - Hay un cristal de 24 Mhz, aunque se puede ver uno de 12 en algunos modelos. Memoria Principal - 8MB RAM; 2MB flash.

Hay al menos dos puertos serie (UARTs) en el robot, el /dev/ser0 que se usa como puerto "NS" para comunicar el sub-sistema North Star, y el /dev/ser1 que parece manejar los comandos "MCU" para el control del motor. Envía los datos a la placa de motores en el cuerpo principal del robot.

El puerto USB del robot se puede usar de dos formas diferentes. Si se conecta el USB al ordenador antes de encender el robot, los 2Mb de memoria flash aparecerán como un volumen de disco sin formatear. Este método es peligroso pues podría dejar al robot totalmente inservible si se procediese al formateo. En el segundo caso, si se conecta el USB después de que se enciende el robot, el puerto proporciona una conexión serie que es necesaria par la conficuración inicial de ROVIO.

El procesador ARM ejecuta el servidor web, controla la webcam, el flujo de audio y vídeo (flujo multimedia) y realiza el control general del robot. Ejecuta el sistema operativo de fuente-abierta "eCOS". El servidor del flujo multimedia está basado en una variante del servidor de flujo "Spook". Utiliza el protocolo "RTSP" para enviar el audio y vídeo desde la cámara web y micrófono de ROVIO a nuestro ordenador. El envío del audio desde el ordenador al ROVIO (para ser reproducido en el altavoz del robot) se realiza mediante un método propietario (ver GetAudio.cgi en el API del robot).

Las características WiFi están basadas en la biblioteca "Libertas" de Marvell. Se conecta a características WiFi especiales del procesador ARM Marvell. Estás características son muy hackeables modificables en el programa principal. Se puede aceder a la memoria RAM directamente usando URLs especiales.

En estos momentos, WowWee no ha publicado ningún material open-source o de fuente-abierta, de manera que el hackeo o modificaciones del robot requiere una cosiderable cantidad de ingeniería inversa.

La programación de alto nivel del robot es posible utilizando URLs especiales bien desde una aplicación independiente del navegador o bien desde dentro de una página web mediante JavaScript. WowWee ha publicado especificaciones para la mayor parte de las URLs posibles. Las especificaciones del API se pueden encontrar en la página web de WowWee Techonologies. Existe un duplicado de las mismas en RoboCommunity.

Dentro de ROVIO (en el segmento superior del cuello, justo detrás de la cámara web) está el detector NorthStar que forma parte del sistema TrueTrack(tm) para localizar al robot en una habitación respecto de un faro (o punto fijo). Es un módulo separado que tiene su propio procesador digital de señal como cerebro. Se conecta a la CPU principal mediante un puerto serie. Se puede leer más sobre NorthStar aquí http://www.evolution.com/products/northstar/.

La cámara web usada en el ROVIO es una OV7670 de la firma OmniVision, aunque hay que decir que el firmware incluido en el robot puede controlar otros modelos.

El códec de audio y el controlador del altavoz están basados en el integrado WM8976, fabricado por Wolfson Microelectronics. Se puede acceder sin problemas a su hoja de características a través de una búsqueda en internet, ya que en su página web parece no encontrarse disponible.

Un integrado marcado como 8051uC (WinBond W99100DG) parece proporcionar cierto control periférico pero los detalles están pendientes de conformación.

Referencias(?)

Pendiente de redacción

Enlaces Externos(?)

Pendiente de redacción