AlvaroB-PFC-Drone02
- Project Name: Tele-Operación y Navegación Autónoma de un Mini-UAV
- Author: Álvaro Botas Muñoz
- Email Addres: infabm01@estudiantes.unileon.es
- Academic Year: 2010-2011
- Degree: Undergraduate
- Technologies: c, gtk, Api Ar.drone:sdk 1.7
- Status: Finished
Contents
- 1 Parte 1
- 2 Parte 2
- 2.1 Creación de nuestra propia aplicación: Un tele-operador para el control del ArDrone
- 2.1.1 Primeros pasos con GTK
- 2.1.2 Funciones de mi aplicación
- 2.1.3 Comandos que se envían al ArDrone
- 2.1.4 Creación de ficheros de log
- 2.1.5 Uso de Subversion
- 2.1.6 Creación del Makefile para la compilación de nuestro proyecto
- 2.1.7 Visualización de datos de Navegación
- 2.1.8 Funcionamiento general
- 2.1.9 Pintar imágenes en GTK
- 2.1 Creación de nuestra propia aplicación: Un tele-operador para el control del ArDrone
- 3 Parte 3
Parte 1
Configurando la api de ArDrone (sdk 1.5) con el mando de la ps3 en Linux
Obtención del SDK
- En primer lugar, deberías registrarte en la página web de ardrone https://projects.ardrone.org/
- Descarga el sdk de la página https://projects.ardrone.org/attachments/download/221/ARDrone_SDK_1_5_Version_20101004.tar.gz
- Copia el archivo tar.gz en el directorio donde quieras realizar la instalación
- Descomprime el archivo con el comando:
tar zxvf archivo.tar.gz
Configuración de los ejemplos para Linux
sdk_demo
Se trata del ejemplo más sencillo, no dispone de interfaz gráfico pero contiene lo suficiente para controlar el ArDrone con un joystick y la creación de tuberías para la recepción de video asi como diferentes hilos Debemos modificar dos archivos para el correcto funcionamiento de nuestro mando de la ps3:
a) En primer lugar :
En el archivo ardrone_testing_tool.c (/’tu directorio’/ARDrone_SDK_1_5_V ersion_20101004/Examples/Linux/sdk_demo/Sources/ardrone_t esting_tool.c) , añadiéndole esta línea para añadir el mando:
ardrone_tool_input_add( &ps3pad );
Téngase en cuenta que este archivo es el que contiene el main de la aplicación, aquella que pone en funcionamiento todo.
b) En segundo lugar :
A continuación modificaremos el archivo gamepad.h (/’tu directorio’/ARDrone_SDK_1_5_V ersion_20101004/Examples/Linux/sdk_demo/Sources/UI), donde le asignaremos valores a los botones del mando:
#define GAMEPAD_PLAYSTATION3_ID 0x054C0268 typedef enum { PS3BTN_SELECT=0, PS3BTN_L3=1, PS3BTN_R3=2, PS3BTN_START=3, PS3BTN_UP ARROW=4, PS3BTN_RIGHTARROW=5, PS3BTN_DOWNARROW=6, PS3BTN_LEFTARROW=7, PS3BTN_L2=8, PS3BTN_R2=9, PS3BTN_L1=10, PS3BTN_R1=11, PS3BTN_TRIANGLE=12, PS3BTN_CIRCLE=13, PS3BTN_CROSS=14, PS3BTN_SQUARE=15, PS3BTN_PS3=16, }PS3P AD_BUTTONS;
Se trata de un ejemplo mas completo, en el que se puede observar un interfaz gráfico, con la inserción de las imágenes del video, entre muchas otras opciones adicionales.
Cómo compilar y ejecutar estos ejemplos
- En primer lugar vamos a obtener algunas de las librerías necesarias para la compilación y posterios ejecución.
$sudo apt-get install libsdl-dev libgtk2.0-dev libiw-dev
- Debemos crear la biblioteca ArDroneLib usando el makefile dado.
$cd SDK /ARDroneLib/Soft/Build $make
- Compilar el ejemplo sdk_demo
$cd SDK /Examples/Linux/sdk_demo/Build $make
- Compilar el ejemplo Navigation
$cd SDK /Examples/Linux/Navigation/Build $make
Ejecutar el ejemplo
Tras haber realizado estos pasos, deberíamos de tener 2 ejecutables en : /’tu directorio’/ARDrone_SDK_1_5_V ersion_20101004/Examples/Linux/Build/Release /. Conecta tu mando de la PS3 y ahora serás capaz de hacer volar tu ArDrone.
- Para el ejemplo sdk_demo:
./linux_sdk_demo
- Para el ejemplo Navigation:
./ardrone_navigation
Parte 2
Creación de nuestra propia aplicación: Un tele-operador para el control del ArDrone
Se trata de crear un pequeño interfaz gráfico con las opciones básicas para el manejo del ArDrone, con la visualización del vídeo de la cámara del ArDrone. Está realizada en gtk y como base el ejemplo sdk_demo sobre el cual eliminaremos todo lo que necesitamos y ampliaremos las funciones necesarias.
Primeros pasos con GTK
Gtk es un conjunto de librerías cuya función es la de pintar ventanas Para su utilización debemos tener en cuenta que debemos incluir en nuestro proyecto las siguientes librerías:
#include <gtk/gtk.h>
Aquí tenemos un pequeño ejemplo de una ventana muy básica:
int main (int argc, char *argv[]){ GtkWidget *window; gtk_init (&argc, &argv); window = create_window (); gtk_widget_show_all (window); gtk_main (); return 0; }
Podemos observar un pequeño tutorial aquí con algunas de las funciones básicas que nos ofrece gtk: http://www.linuxlots.com/~barreiro/spanish/gtk/tutorial/gtk_tut.es.html
Y la api: http://developer.gnome.org/gtk/2.24/
Se ha desarrollado con la version de GTK 2 ya que en el momento del comienzo del proyecto aun no había salido la version oficial de GTK 3.
Funciones de mi aplicación
Aquí se muestran las funciones que utilizo para la creación del entorno gráfico de la aplicación:
void delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) void escribeCuadro (char* data) void cbdespegar (GtkWidget *widget, gpointer data) void cbaterrizar (GtkWidget *widget, gpointer data) void cbTurnLeft (GtkWidget *widget, gpointer data) void cbForward (GtkWidget *widget, gpointer data) void cbTurnRight (GtkWidget *widget, gpointer data) void cbLeft (GtkWidget *widget, gpointer data) void cbBackward (GtkWidget *widget, gpointer data) void cbRight (GtkWidget *widget, gpointer data) void cbUp (GtkWidget *widget, gpointer data) void cbDown (GtkWidget *widget, gpointer data) void cbcontrol(GtkWidget *widget, gpointer data) void cbShowControl(GtkWidget *widget, gpointer data) void cbSave (GtkWidget *widget, gpointer data) void cbSaveStop (GtkWidget *widget, gpointer data) void cbDetails (GtkWidget *widget, gpointer data) void cbReset (GtkWidget *widget, gpointer data) void cbChangCam (GtkWidget *widget, gpointer data) GtkWidget *new_windowControl() GtkWidget *new_window () gboolean update_display(gpointer pData) int crea_ventana() GtkWidget *new_zoneVideo() GtkWidget *new_zoneStadistics(GtkWidget *data) GtkWidget *new_zoneButton() GtkWidget *new_TextZone() void create_tags (GtkTextBuffer * buffer)
Comandos que se envían al ArDrone
Aquí se listan los comandos que enviamos al ardrone para la realización de las diferentes funciones o movimientos que deseamos que realice:
a) Despegar
ardrone_tool_set_ui_pad_start(1);
b) Aterrizar:
ardrone_tool_set_ui_pad_start(1);
c) Mover hacia adelante:
ardrone_at_set_progress_cmd(1,0,-0.3,0,0);
d) Mover hacia atrás:
ardrone_at_set_progress_cmd(1, 0, 0.3, 0, 0);
e) Mover hacia arriba:
ardrone_at_set_progress_cmd(1, 0.3, 0, 0.5, 0);
f) Mover hacia abajo:
ardrone_at_set_progress_cmd(1, 0.3, 0, 0.5, 0);
d) Mover hacia la derecha:
ardrone_at_set_progress_cmd (1, 0.3, 0, 0,0);
e) Mover hacia la izquierda:
ardrone_at_set_progress_cmd (1, -0.3, 0, 0,0);
f) Girar hacia la derecha:
ardrone_at_set_progress_cmd(1, 0.3, 0, 0,0);
g) Girar hacia la izquierda:
ardrone_at_set_progress_cmd(1, 0, 0, 0, -0.3);
h) Enviar un reset:
ardrone_tool_set_ui_pad_select(1);
i) Cambiar la camara que estamos viendo:
ardrone_at_zap //Revisar
j) Señal de que el ardrone esta en una superficie horizontal para un vuelo estable:
ardrone_at_set_flat_trim
Creación de ficheros de log
En “directorio”/Version1.0/basico/File disponemos de 2 ficheros que se encargan de la gestion de guardar en un fichero de log (en “directorio”/Version1.0/Build/Debug) con el instante y el mensaje de la acción realizada. En el fichero save_file.h se declaran las funciones y en save_file.c se implementan. En la función InsertaDatos se usa la funcion fopen(ruta, “a”) siendo ruta el directorio donde lo guardamos y “a” el mensaje y se encarga de crear un fichero si este no existe o añadir mensajes si ya existia.
Uso de Subversion
Se trata de un sistema de control de versiones que viene a sustituir al antiguo cvs. Alguna de las operaciones realizadas para subir archivos y gestionarlos:
- Creación de una nueva rama para nuestro proyecto:
$ svn copy http://robotica.unileon.es/svn/Projects/PFC/ardrone \ http://robotica.unileon.es/svn/Projects/PFC/ardrone/branches/Version1_albo_08062011 \ -m "Creating a new branch Version1_albo_08062011."
- Subida de nuestro proyecto
$svn import /(YOUR PROJECT DIR) http://robotica.unileon.es/svn/Projects/PFC/ardrone/branches/Version1_albo_08062011 -m "Version1 import"
- Descarga de nuestro proyecto
$svn checkout http://robotica.unileon.es/svn/Projects/PFC/ardrone/branches/Version1_albo_08062011
- Eliminación de un directorio de svn
$svn delete http://(URL DIRECTORY) -m "DIRECTORY DELETED"
Creación del Makefile para la compilación de nuestro proyecto
Vamos a partir del Makefile que viene por defecto con el ejemplo en el sdk_demo pero modificandolo para nuestro proyecto. Debemos añadir los fuentes de nuestro proyecto de esta forma :
GENERIC_BINARIES_COMMON_SOURCE_FILES+= \ Navdata/navdata.c \ Ihm/Principal.c \ Ihm/Principal_o_gtk.c \ File/save_file.c
Debemos añadir las librerías que necesitemos, en este caso las de gtk de esta manera:
GENERIC_LIBS=-lpc_ardrone -lgtk-x11-2.0 -lrt
Para la salida de datos de navegación, tras varios problemas para visualizar los valores, se descubrió que era un problema de la versión del SDK utilizada, la 1.5, tras lo que se procedió a migrar el proyecto a una versión mejorada, la 1.7. Para la migración se tuvo que realizar algunos cambios en el Makefile, así como en algún otro archivo fuente.
Funcionamiento general
Nuestro main se encuentra en el fichero fuente ardrone_testing_tool.c, se trata del fichero donde se va a proceder a la inicialización de todos los hilos y llamadas necesarias para la comunicación con ArDrone. Veamos, por ejemplo, como se haría para crear un nuevo hilo que se encargue de la interfaz gráfica de nuestro proyecto:
#include <Ihm/Principal.h> static int32_t exit_ihm_program = 1; C_RESULT ardrone_tool_init_custom(int argc, char **argv) { gtk_init(&argc, &argv); START_THREAD( Principal, NULL ); return C_OK; }
C_RESULT ardrone_tool_shutdown_custom() { JOIN_THREAD( Principal ); return C_OK; } bool_t ardrone_tool_exit() { return exit_ihm_program == 0; }
C_RESULT signal_exit() { exit_ihm_program = 0; return C_OK; }
/* Implementing thread table in which you add routines of your application and those provided by the SDK */
BEGIN_THREAD_TABLE THREAD_TABLE_ENTRY( ardrone_control, 20 ) //obligada para el control del ardrone THREAD_TABLE_ENTRY( Principal, 20) THREAD_TABLE_ENTRY( navdata_update, 20 ) //obligada para los datos de navegación END_THREAD_TABLE
Al igual que con la interfaz gráfica, se realizaría con todos los hilos que creemos a parte, como por ejemplo, el hilo para la creación de las tuberías para la recepción y decodificación de los datos de vídeo enviados por la cámara del ArDrone.
Pintar imágenes en GTK
Vamos a ver ahora cómo pintar imágenes en un widget de GTK: En primer lugar, debemos saber que para pintar imágenes sobre un widget de GTK se utiliza un tipo de estructura denominada pixbuf. He aquí la construcción de una nueva estructura que se utiliza:
pixbuf = gdk_pixbuf_new_from_data(pixbuf_data, GDK_COLORSPACE_RGB, FALSE, 8, pixbuf_width, pixbuf_height, pixbuf_rowstride, NULL, NULL);
La imagen en sí, viene dada por el tipo de datos pixbuf_data, que se trata de un unsigned char uint_8t. En éste se introducen los datos decodificados mediante un conjunto de tuberías de la información recibida por la cámara del ArDrone, en el archivo principal_o_gtk.c. En este archivo se define el hilo encargado de recibir los datos de vídeo, tuberías, y pintar en un widget.
DEFINE_THREAD_ROUTINE(ihm_stages_vision, data)
Aquí vemos la inserción de las imágenes en el widget (se trata de un widget externo perteneciente a principal.c), la función se denomina update_vision():
if( image == NULL ) { image = (GtkImage*) gtk_image_new_from_pixbuf( pixbuf ); gtk_container_add( GTK_CONTAINER( boxVideo ), (GtkWidget*)image ); else { gtk_image_set_from_pixbuf(image, pixbuf);
siendo image el widget de GTK. Por otra parte, para recargar la imagen y repintarla debemos usar una función que tenemos en el archivo principal.c :
gboolean update_display(gpointer pData) { update_vision(); return TRUE; }
y se llama desde la función new_window() de esta manera :
g_timeout_add(100, (GtkFunction)update_display, NULL );
update_vision() es la función que se encarga de pintar el widget con las imágenes y de la cual se habló antes.
Parte 3
Instalación OpenCV
Código desde el svn
Para la instalación desde Ubuntu podemos realizarlo de la siguiente forma, desde la línea de comandos:
cd ~/<my_working_directory> svn co https://code.ros.org/svn/opencv/trunk
Instalación de librerías requeridas y herramientas
- Compilador C/C++
- CMake 2.6 o superior
- (Opcional) svn
- (Opcional) Python 2.6.x or 2.7.x
- (Opcional) Intel TBB para permitir el código paralelo en OpenCV.
- (Opcional) Qt 4.6 o posterior.
- (Opcional) IPP 5.1 o 6.1 (no 7).
- (Opcional) Distribución LiveTeX para construir el manual de referencia. actualizado de OpenCV en pdf.
- (Opcional) El último kit de herramientas CUDA para construir con soporte GPU.
Prerequisitos extra
- pkg-config.
- (Opcional) gtk+ 2.x y paquetes relacionados(glib, gthread, etc.).
- (Opcional) libjpeg, libtiff, libjasper, libpng y zlib, openexr.
- (Opcional) ffmpeg, libgstreamer, libxine, unicap, libdc1394 2.x.
Construir la librería OpenCv desde el código usando CMake y tu compilador C++
Dirigete al directorio donde descargaste los paquetes del svn de OpenCV, si por ejemplo este fuera ~/projects/opencv podrías realizar las siguientes operaciones:
cd ~/projects/opencv # el directorio que contiene el INSTALL, CMakeLists.txt etc. mkdir release cd release cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_PYTHON_SUPPORT=ON ..
Compilar usando GCC
Una vez realizado el paso previo, debería haberse creado una serie de librerías que previamente no existían junto con un makefile. Procederemos a la instalación mediante estas operaciones:
make sudo make install
Deberías añadir ~/projects/opencv/lib/[debug/] a /etc/ld.so.conf o a LD_LIBRARY_PATH de esta manera:
export LD_LIBRARY_PATH=~/projects/opencv/release/lib:$LD_LIBRARY_PATH sudo ldconfig
Compilar archivos C
Debes configurar la variable PKG_CONFIG_PATH:
cd /where/you/have/the/source/code PKG_CONFIG_PATH=~/projects/opencv/lib/pkgconfig:${PKG_CONFIG_PATH} export PKG_CONFIG_PATH
Puedes comprobar que la variable está correcta de este modo:
pkg-config --cflags opencv pkg-config --libs opencv
De manera que deberías tener algo como:
$ pkg-config --cflags opencv -I~/projects/opencv/include/opencv $ pkg-config --libs opencv -L~/projects/opencv/lib -lcxcore -lcv -lhighgui -lcvaux
Después compila de esta forma:
gcc `pkg-config --cflags --libs opencv` -o my-opencv-prgm my-opencv-prgm.c
Desarrollo de nuestra aplicación para implementar la visión artificial en el ArDrone
Ayudas
http://www.amazon.com/Learning-OpenCV-Computer-Vision-Library/dp/0596516134
http://opencv.willowgarage.com/wiki/FullOpenCVWiki
http://opencv.willowgarage.com/documentation/index.html
http://www.cs.iit.edu/~agam/cs512/lect-notes/opencv-intro/index.html
http://www.softintegration.com/products/thirdparty/opencv/demos/
http://opencv.willowgarage.com/documentation/c/index.html
Añadir librerías al Makefile de nuestra aplicación
Directorio de OpenCV
En primer lugar debemos añadir el directorio donde tenemos instalado opencv para que sepa añadir las librerías correspondientes al compilar:
USR_PATH:=$(shell $HOME)/../../usr GENERIC_INCLUDES+= $(USR_PATH)/local/include/opencv
Librerías a añadir
En segundo lugar debemos añadir las librerías siguientes:
GENERIC_LIBS=-lopencv_core -lopencv_imgproc -lopencv_calib3d -lopencv_video - lopencv_features2d -lopencv_ml -lopencv_highgui -lopencv_objdetect -lopencv_contrib - lopencv_legacy
Preparación de imágenes para su procesado
Una vez que tenemos disponibilidad de todas las librerías nos metemos ya a realizar nuestros procesados de las imágenes. En primer lugar, debemos saber que la estructura con la que trabaja OpenCV es IplImage. Como nosotros estamos trabajando con la estructura pixbuf, en primer lugar debemos hacer una conversión pixbuf – IplImage de esta manera :
auxImage->imageData = (char*)pixbuf_data; auxImage = cvCloneImage(auxImage);
auxImage es de tipo IplImage, y su campo imageData es de tipo char. De esta manera guardamos en el campo imageData la imagen que tenemos en pixbuf_data. El cvCloneImage se realiza para poder trabajar con una imagen nueva, ya que si lo hacemos directamente sin la dúplica vamos a tener problemas con la imagen procesada. Por otra parte, el espacio de colores de IplImage es BGR mientras que el de pixbuf es RGB, por lo que lo primero que se debe hacer es un cambio de esta forma:
void insertBGRimage(IplImage *dst, IplImage *src) { cvCvtColor(src,dst,CV_RGB2BGR); insertImage(dst); }
Filtros desarrollados
Ahora veremos un poco los filtros desarrollados
Filtro de contornos
Primero pasamos la imagen a escala de grises, luego la binarizamos y le encontramos todos los contornos mediante la instrucción:
cvFindContours( imageGray, storage, &contour, sizeof(CvContour),CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, punto);
y para pintar los contornos mediante la expresión :
for( ; contour != 0; contour = contour->h_next ) { CvScalar color = CV_RGB( rand()&255, rand()&255, rand()&255 ); // replace CV_FILLED with 1 to see the outlines cvDrawContours( dst, contour, color, color, -1, CV_FILLED, 8, punto ); }
así tendremos la imagen con todos los contornos pintados. Para poder pintarla de nuevo en el widget de GTK debemos volver a pasarlo a espacio de colores RGB y posteriormente pasar de IplImage a pixbuf. Esto se realizará del mismo modo en todos los filtros que se verán en el proyecto.
Filtro de colores
Pasamos de BGR a HSV (para que sea más independiente de la iluminación). Recorremos la imagen píxel a píxel, y los que se adecúen a los valores de cada color nos quedamos con las coordenadas para posteriormente dibujar un rectángulo que nos indique la zona con el color correspondiente. Para pintar el rectángulo necesitamos esta función:
cvRectangle(processedImage, cvPoint(maxYRojo,maxXRojo), cvPoint(minYRojo,minXRojo),colorRed, 3, 8, 0);
Filtro de letras
El primer paso es cargar los patrones de las letras, es decir, guardar en memoria las letras con las que vamos a hacer la comparación para saber si se trata de una A, una B, ... Una vez cargados en memoria las letras, pasamos unos filtros para reconocer la parte perteneciente y procedemos a compararlas con los patrones. En caso de que el nivel de similitud sea mayor de un 75% (se puede modificar el porcentaje), nos dirá la letra que ve y la guarda en un array, de forma que si ve varias letras seguidas, sea capaz de decirnos la frase que ha visto.
Para poder decir una frase se utiliza el comando:
system ("espeak -p 40 -s 150 -v es 'A'");
Para que sea más efectivo, se reducen las imágenes para mayor eficiencia.
====Filtro de Bola roja====
Lo que hace esta función es detectar una bola roja, saca un círculo en la zona de la bola y calcula el centroide, así como el tamaño en píxeles de ésta. Las coordenadas del centroide se guardan en un CvPoint que guarda tanto la coordenada x como y. El círculo se realiza con la instrucción:
cvCircle(originalImage,coordenadasCentroide,10,color,1,8,0);
Filtro de Rostros
Esta función detecta los rostros que halla en la imagen, En el más cercano dibuja un círculo y calcula el centroide. Para detectar los rostros hace uso de un archivo de características en xml. Hace uso de esta función en un primer momento que inicializa la cascada( el clasificador resultante se compone de varios clasificadores simples (etapas) que se aplican posteriormente a una región de interés hasta que en algún momento el candidato es rechazado o aprobado) :
void init_detect_and_draw() { const char* cascade_name="./haarcascades/haarcascade_frontalface_alt.xml"; cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0); storage0 =cvCreateMemStorage(0); }
Guarda la secuencia de rostros de esta forma:
CvSeq* faces = cvHaarDetectObjects( originalImage, cascade, storage0, 1.2, 3,CV_HAAR_DO_CANNY_PRUNING, cvSize(40, 40), cvSize(0,0) );
Tracking
Para el seguimiento se ha desarrollado una función que gracias a los centroides nos indica en qué parte de la imagen se encuentra la bola, y en función de la distancia con el centro de la imagen, se desplaza más o menos. También se queda con la última posición de la bola, por si la pierde, si por ejemplo era a la derecha, se moverá a la derecha para ver si la encuentra.
Veamos un pequeño vídeo demostración del tracking del ArDrone de una bola roja:
<wikiflv width="300" height="250" logo="true">/images/d/d8/ArDroneTracking.swf</wikiflv>