Informe - Escuela de Ingeniería Eléctrica

Transcripción

Informe - Escuela de Ingeniería Eléctrica
Universidad de Costa Rica
Facultad de Ingeniería
Escuela de Ingeniería Eléctrica
IE – 0502 Proyecto Eléctrico
Sistema de Seguimiento de objetos utilizando una
cámara de video y el sistema operativo
GNU/Linux
Por:
Luis Federico Gómez Salazar
Ciudad Universitaria Rodrigo Facio
Julio del 2006
Sistema de Seguimiento de Objetos utilizando
una cámara de video y el sistema operativo
GNU/Linux
Por:
Luis Federico Gómez Salazar
Sometido a la Escuela de Ingeniería Eléctrica
de la Facultad de Ingeniería
de la Universidad de Costa Rica
como requisito parcial para optar por el grado de:
BACHILLER EN INGENIERÍA ELÉCTRICA
Aprobado por el Tribunal:
_________________________________
Ing. Federico Ruiz Ugalde
Profesor Guía
_________________________________
Ing. Francisco Siles Canales
Profesor lector
_________________________________
Ing. Andrés Díaz Soto
Profesor lector
ii
DEDICATORIA
A mis padres y hermano que siempre han estado conmigo…
iii
RECONOCIMIENTOS
Agradezco al profesor guía, Federico Ruiz, así como a los profesores lectores de este
proyecto, Francisco Siles y Andrés Díaz ya que durante todo el semestre estuvieron
anuentes a consultas y brindaron información vital para la realización de este trabajo.
Agradezco también a compañeros como Marco Álvarez y Mario Segura que también
fueron de gran ayuda en algunos problemas que se presentaron durante el desarrollo de
este proyecto.
iv
ÍNDICE GENERAL
DEDICATORIA ............................................................................................................ iii
RECONOCIMIENTOS ................................................................................................ iv
INDICE DE FIGURAS ................................................................................................. vi
ÍNDICE DE CUADROS .............................................................................................. vii
NOMENCLATURA .................................................................................................... viii
RESUMEN ..................................................................................................................... ix
CAPITULO 1 Introducción.......................................................................................... 1
1.1
Justificación ...................................................................................................... 1
1.2
Objetivos........................................................................................................... 2
1.2.1
Objetivo general ....................................................................................... 2
1.2.2
Objetivos específicos................................................................................ 2
1.3
Metodología...................................................................................................... 2
CAPITULO 2 Desarrollo teórico ................................................................................. 4
2.1
El lenguaje de Programación C++.................................................................... 4
2.2
GNU/Linux....................................................................................................... 5
2.2.1
El sistema Video4Linux ........................................................................... 5
2.2.1.1 Interfaz de programación de V4L para dispositivos de video .............. 6
2.3
La interfaz GTKmm ......................................................................................... 8
2.3.1
Programación de ventanas ........................................................................ 9
2.3.2
Programación con múltiples hilos de procesamiento ............................. 10
2.4
Procesamiento de Imágenes ........................................................................... 10
2.4.1
Representación vectorial de colores ....................................................... 11
2.4.2
Filtros...................................................................................................... 11
2.4.2.1 Filtrado de ruido ................................................................................. 13
2.4.3
Umbralización ........................................................................................ 13
2.4.3.1 Histogramas ........................................................................................ 14
2.4.3.2 Método se Otsu................................................................................... 14
2.4.3.3 Método de Kittler e Illingworth.......................................................... 16
2.5
Centroides....................................................................................................... 17
2.6
Histéresis ........................................................................................................ 19
2.7
El Robot Stäubli RX90 ................................................................................... 20
CAPITULO 3 Descripción del Sistema ..................................................................... 22
3.1
Estructura del sistema..................................................................................... 22
3.2
El programa principal ..................................................................................... 23
3.3
El ciclo infinito ............................................................................................... 24
CAPITULO 4 Algoritmos de acceso a dispositivo y procesamiento de Imagen ....... 27
4.1
Acceso al dispositivo ...................................................................................... 27
4.2
Procesamiento de imagen ............................................................................... 28
CAPITULO 5 Algoritmos de cálculo del centroide y decisión de movimiento ........ 35
5.1
Cálculo del centroide ...................................................................................... 35
5.2
Decisión de Movimiento ................................................................................ 36
5.3
Despliegue de imágenes ................................................................................. 41
CAPITULO 6 Resultados........................................................................................... 43
CAPITULO 7 Conclusiones y recomendaciones....................................................... 46
7.1
Conclusiones................................................................................................... 46
7.2
Recomendaciones ........................................................................................... 47
BIBLIOGRAFÍA .......................................................................................................... 48
APÉNDICES ................................................................................................................. 49
v
INDICE DE FIGURAS
Figura 2.1: Capas de abstracción de un computador ........................................................ 5
Figura 2.2: Representación vectorial de colores............................................................. 11
Figura 2.3: Manipulación de imágenes por máscaras matriciales .................................. 12
Figura 2.4: Máscara matricial......................................................................................... 13
Figura 2.5: Histograma ................................................................................................... 14
Figura 2.6: Curva de histéresis ....................................................................................... 19
Figura 2.7: Robot Stäubli RX90 ..................................................................................... 20
Figura 3.1: Estructura del sistema incluyendo el robot .................................................. 22
Figura 3.2: Estructura de la clase Imagen ...................................................................... 23
Figura 3.3: Estructura de la clase Acceso....................................................................... 23
Figura 3.4: Diagrama de flujo del ciclo infinito ............................................................. 24
Figura 4.1: Imagen capturada por la cámara de video.................................................... 29
Figura 4.2: Imagen resultante luego de aplicar la transformación a escala de grises.... 30
Figura 4.3: Indefinición en los bordes del cuadro. ......................................................... 31
Figura 4.4: Imagen resultante luego de aplicar el filtro.................................................. 32
Figura 4.5: Imagen resultante luego de aplicar la umbralización................................... 34
Figura 5.1: Segmentación del cuadro capturado ............................................................ 36
Figura 5.2: Movimiento del objeto dentro del cuadro .................................................... 37
Figura 5.3: Diagrama de flujo de la función de decisión de movimiento ...................... 37
Figura 5.4: Segmentación de la imagen en subcuadros.................................................. 39
Figura 6.1: Objeto identificado, alto contraste ............................................................... 43
Figura 6.2: Objeto identificado, bajo contraste .............................................................. 44
Figura 6.3: Escenario que induce a errores..................................................................... 45
Figura 6.4: Objeto no identificado.................................................................................. 45
vi
ÍNDICE DE CUADROS
Cuadro 2.1: Dispositivos disponibles controlados por V4L............................................. 6
Cuadro 2.2: Información contenida por la estructura video_capability. .......................... 6
Cuadro 2.3: Información contenida por la estructura video_buffer.................................. 7
Cuadro 2.4: Información contenida por la estructura video_window............................... 7
Cuadro 2.5: Información contenida por la estructura video_channel............................... 7
Cuadro 2.6: Información contenida en la estructura video_mbuf..................................... 8
Cuadro 2.7: Información contenida en la estructura video_mmap ................................... 8
Cuadro 2.8: Paquetes necesarios para la utilización de GTKmm..................................... 9
vii
NOMENCLATURA
GNU/Linux:
Es la denominación para el sistema operativo que utiliza el kernel Linux
en conjunto con las aplicaciones de sistema creadas por el proyecto
GNU. Comúnmente este sistema operativo es denominado simplemente
Linux.
UNIX:
UNIX® (o Unix) es un sistema operativo portable, multitarea y
multiusuario; desarrollado en principio por un grupo de empleados de los
laboratorios Bell de AT&T, entre los que figuran Ken Thompson, Dennis
Ritchie y Douglas McIlroy. En 1970 por vez primera se habla
oficialmente del sistema UNIX.
GTKmm:
Gimp Tool Kit es una interfaz de programación de ventanas para sistemas
Linux. GTKmm es la denominación para el recubrimiento a C++.
POSIX:
Acrónimo de Portable operating system interface, Unix based (Sistema
operativo portable basado en UNIX). Una familia de estándares de
llamadas al sistema definidos por el IEEE y especificados formalmente
en el IEEE 1003, intenta estandarizar las interfaces de los sistemas
operativos para que las aplicaciones se ejecuten en distintas plataformas.
pthreads:
Abreviatura para hilos POSIX y una biblioteca que proporciona
funciones POSIX para crear y manipular hilos de programación. La
biblioteca se encuentra con mayor frecuencia en sistemas Linux y Unix,
sin embargo existe también una versión para Windows.
Kernel:
También conocido como núcleo, es la parte fundamental de un sistema
operativo. Se encarga de gestionar los recursos del sistema.
viii
RESUMEN
Este proyecto tiene como objetivo crear un sistema de seguimiento de objetos que
determine la posición de un objeto y señale el movimiento adecuado que debe
brindársele al dispositivo de captura para mantener al objeto enfocado en todo
momento. Este trabajo pretende quedar implementado para luego utilizarlo
conjuntamente con el robot Staubli RX90, acoplar la cámara sobre éste y poder enviarle
comandos de forma automática para ejecutar el seguimiento del objeto deseado.
Este sistema se implementó utilizando el lenguaje de programación C++ y varias
Interfaces de programación de aplicaciones (API) que pueden ser utilizadas sobre el
sistema operativo GNU/Linux.
Para acceder al dispositivo de video se utilizó el API Video4Linux el cual provee de una
interfaz útil para poder acceder a las imágenes capturadas por la cámara de video.
Además para el desarrollo de la aplicación gráfica se utilizó la interfaz GTKmm la cual
constituye el hilo principal del programa y sobre el cual corren el resto de procesos.
La aplicación desarrollada permite identificar un objeto de cualquier forma geométrica
siempre y cuando éste sea lo suficientemente contrastante del fondo sobre el cual se
desenvuelve. Una vez identificado el objeto se determina la posición de éste con
respecto al espacio formado por todo el cuadro capturado por la cámara en algún
instante de tiempo.
Posteriormente se muestra en la aplicación gráfica un texto que indica el segmento del
cuadro en el cual se encuentra el objeto así como una señalización en forma de flechas
que indica hacia donde se debe dar movimiento a la cámara para mantener el objeto
dentro del rango establecido.
El sistema trabajó satisfactoriamente brindando las señalizaciones adecuadas para dar
movimiento a la cámara así como señales de otros eventos por ejemplo, cuando no se
registra algún objeto o cuando éste se encuentra dentro del espacio del cuadro
establecido y por lo tanto no se debe señalar movimiento.
ix
CAPITULO 1 Introducción
1.1 Justificación
Actualmente la utilización de robots para realizar distintas tareas de manufactura,
ensamble y muchas otras aplicaciones representa una de las ramas más estudiadas de la
tecnología.
Ahora no solamente se busca que un operario pueda, mediante comandos o simplemente
botones, manipular un mecanismo complejo como lo es un robot, sino que este realice
su tarea de forma automatizada. Para ello se desarrollan programas para hacer que el
robot realice una tarea específica.
Por otro lado, el desarrollo de algoritmos capaces de detectar los movimientos de un
objeto, constituyen otra área ampliamente estudiada. Para ello se deben tomar en cuenta
diversos factores que afectan el reconocimiento de un objeto sobre otros que estén a su
lado o formen parte del fondo visible. Estos factores serán considerados dependiendo de
los objetivos buscados para hacerle frente al problema.
Para la realización de este proyecto se decidió trabajar con el sistema operativo
GNU/Linux, ya que es un sistema muy robusto que cuenta con características especiales
de estabilidad y seguridad, además de que es libre. Por otro lado se utilizará una Interfaz
de Programación de Aplicaciones (API) llamada Video4Linux, el cual posibilita la
adquisición y procesamiento de video.
Gracias a la filosofía de software libre de la cual gozan los sistemas Linux, el desarrollo
de un gran número de programas y aplicaciones de gran calidad ha sido posible. Esto
pues todos los programadores constituyen una enorme comunidad que busca el
desarrollo de un producto de alta eficiencia.
Este proyecto pretende ser la base para futuros estudios dentro de la Escuela de
Ingeniería Eléctrica que puedan utilizarse para aplicaciones de seguridad o investigación
y desarrollo. Así como una plataforma para crear un sistema más complejo utilizando el
robot Staubli RX90.
1
1.2 Objetivos
1.2.1
Objetivo general
Crear un sistema de seguimiento de objetos utilizando una cámara de video y una
interfaz gráfica.
1.2.2
Objetivos específicos
1. Crear un algoritmo capaz de detectar la posición de objetos en el espacio.
2. Crear un algoritmo que indique el movimiento de la cámara con el fin de
seguir al objeto detectado.
3. Crear una interfaz gráfica para la demostración de estos algoritmos que
servirá como guía para controlar el robot Staubli RX90 en futuros
proyectos.
1.3 Metodología
•
Se definen los objetivos del proyecto para determinar las herramientas
necesarias para su desarrollo.
•
Se hace un análisis bibliográfico para determinar estudios relacionados con los
objetivos que contempla el proyecto.
•
Aprender el lenguaje de C++. Determinando las características principales de
este, así como su sintaxis.
•
Investigar sobre el sistema operativo GNU/Linux así como del sistema V4L, los
cuales serán piezas fundamentales en el desarrollo de proyecto.
•
Se investiga sobre la interfaz GTKmm, necesaria para crear una aplicación
gráfica que demuestre la funcionalidad de los objetivos.
•
Se investiga sobre las técnicas utilizadas para el procesamiento de imágenes.
•
Se utiliza un algoritmo para obtener imágenes secuenciales desde una cámara de
video.
•
Se crea un algoritmo para procesar cada imagen y así poder distinguir un objeto
en particular.
•
Se crea un algoritmo para localizar un objeto dentro del espacio del cuadro
capturado por la cámara de video.
•
En el caso de los pasos relacionados con la creación de algoritmos, se deben
2
desarrollar de forma modular, es decir, ir probando cada parte del código para
observar su funcionalidad y finalmente acoplarlo como un todo.
•
Se crea una aplicación gráfica en donde se visualice el resultado de la
implementación del sistema.
•
Se realiza las pruebas necesarias al sistema bajo diferentes escenarios para
comprobar su desempeño.
3
CAPITULO 2 Desarrollo teórico
2.1 El lenguaje de Programación C++
C++ es un lenguaje de Programación Orientada a Objetos (POO), a diferencia de su
antecesor C, el cual es un lenguaje estructurado. Fue desarrollado a finales de los 70 por
Bjarne Stroustoup bajo la denominación de C con clases, que finalmente adoptó el
nombre de C++.
Entre las características que tiene la programación orientada a objetos se encuentran:
a) Todo es un objeto: Se debe pensar un objeto, como una variable que almacena datos
pero que a su vez se le puede solicitar que haga operaciones sobre él mismo. La idea es
tomar cada componente conceptual del problema como un objeto en el programa.
b) Un programa es un lote de objetos «diciéndose» unos a los otros que hacer mediante
mensajes: Para hacer una solicitud a un objeto se le debe «enviar un mensaje». Es decir,
un mensaje es una llamada a una función que pertenece a un objeto particular.
c) Cada objeto tiene su memoria construida de otros objetos: Es decir, se crea un objeto,
a partir de un paquete que contiene otros objetos existentes. De aquí, que objetos
simples realizan tareas sencillas pero que son agrupados para tener un objeto mucho
más poderoso capaz de realizar trabajos más complejos.
d) Todo objeto tiene un tipo: Cada objeto es una instancia de una clase, en donde la
clase es el tipo. La característica más sobresaliente de C++ es el uso de clases, las cuales
a diferencia de las estructuras de C pueden involucrar funciones las cuales brindan lo
que podría definirse como el comportamiento de un objeto.
e) Todos los objetos de un tipo particular pueden recibir los mismos mensajes: He aquí
uno de los conceptos poderosos de C++. Si un objeto contiene otros objetos que realizan
ciertas tareas, estos se pueden manipular con base en el objeto de mayor jerarquía. 1
Este tipo de programación ofrece como resultado un código más ordenado, es decir, no
solo más fácil de escribir sino que también más fácil de leer. La inclusión de las clases
en este lenguaje provee la capacidad de modularizar el problema y de esta forma, dividir
las tareas de una forma adecuada entre los diferentes objetos. Además, se genera un
código totalmente reutilizable gracias al concepto de objetos.
1
Información tomada de la referencia [6]
4
2.2 GNU/Linux
GNU es un proyecto emprendido desde 1984 que pretendía desarrollar un sistema
operativo «desde cero» basado en UNIX, bajo la filosofía de software libre. Este
concepto permite la libre publicación del código fuente elaborado durante el desarrollo
para que éste pueda ser utilizado y modificado por otras personas. De esta manera,
múltiples programadores con la experiencia necesaria podrían realizar cambios, mejoras
o ajustes al código base, formando un código más robusto que pudiera satisfacer
infinidad de necesidades.
Gracias a ello el proyecto GNU desarrolló gran cantidad de herramientas necesarias
para obtener un sistema funcional, tales como aplicaciones, intérpretes y compiladores.
Sin embargo, fue hasta en 1990 cuando pudieron completar el sistema con la inclusión
del kernel Linux, creado por el finlandés Linus Torvalds.
El kernel es la pieza fundamental del sistema operativo, la cual asegura el control de
acceso a los dispositivos electrónicos, es decir, el hardware.
El kernel manipula memoria, organiza la ejecución de los procesos, permite ejecución
concurrente de procesos y provee de una capa de abstracción entre el hardware y el
software.
Programas
Shell
Kernel
Hardware
Espacio Usuario
Espacio del Kernel
Figura 2.1: Capas de abstracción de un computador
El shell es una consola de comandos que es muy poderosa en los sistemas Linux, y es
capaz de realizar numerosas y útiles tareas dentro del sistema operativo.
Linux ofrece todas la interfaces de programación comunes de los sistemas estándar
Unix, además de sus utilidades. Además de emplear eficientemente los recursos de
hardware y ser un sistema sumamente enfocado en la seguridad. Es por ello que fue el
sistema escogido para la realización de este proyecto. El compilador elegido para el
código generado fue g++.
gcc es un compilador integrado del proyecto GNU para C, C++, Objective C y Fortran;
es capaz de recibir un programa fuente en cualquiera de estos lenguajes y generar un
programa ejecutable binario en el lenguaje de la máquina donde ha de correr.
La sigla gcc significa "GNU Compiler Collection". Originalmente significaba "GNU C
Compiler"; todavía se usa gcc para designar una compilación en C. g++ refiere a una
compilación en C++.
2.2.1
El sistema Video4Linux
Video4Linux es una Interfaz de Programación de Aplicaciones o API (application
programming interface). Esto quiere decir que haciendo uso de este sistema es posible
manipular mediante un lenguaje de alto nivel dispositivos de audio y video.
5
Estos dispositivos son accedidos mediante un archivo, al igual que la mayoría de las
operaciones realizadas sobre un sistema de tipo Unix/Linux.
Cuadro 2.1: Dispositivos disponibles controlados por V4L
Nombre del
Dispositivo
/dev/video
/dev/radio
/dev/vtx
/dev/vbi
Rango
Función
0-63
64-127
192-233
224-239
Interfaz de captura de Video
Dispositivos de Radio (AM/FM)
Interfaz de Teletexto
Información VBI (Intercast/teletext)
2.2.1.1 Interfaz de programación de V4L para dispositivos de video 2
Los dispositivos de captura de video V4L son accesibles a través del archivo
/dev/video[n], donde n es un número entre 0 y 63 que corresponde al identificador del
dispositivo y se asigna de forma sucesiva al registrar el dispositivo en el kernel. Una vez
abierto este archivo es posible acceder a la mayoría de las funciones del dispositivo
utilizando la llamada al sistema ioctl().
Los controles principales utilizados en este proyecto son los siguientes:
VIDIOCGCAP: Permite obtener la información básica del dispositivo, tal como el tipo
de dispositivo, el número de canales y la resolución máxima. Recibe como argumento
una estructura del tipo video_capability, en la cual se almacena la información retornada
por la llamada. La información contenida por esta estructura se muestra en el 2.2.
Cuadro 2.2: Información contenida por la estructura video_capability.
Campo
name [32]
type
channels
audios
maxwidth
maxheight
minwidth
minheight
Descripción
Nombre del dispositivo
Tipo del dispositivo
Número de canales
Número de dispositivos de audio
Ancho máximo posible del cuadro capturado
Altura máxima posible del cuadro capturado
Ancho mínimo posible del cuadro capturado
Altura mínima posible del cuadro capturado
VIDIOCSFBUF: Permite fijar los parámetros para la escritura directa al framebuffer de
la tarjeta de video. No todos los dispositivos soportan esta característica. Esta llamada
recibe como argumento una estructura del tipo video_buffer, la cual contiene los
parámetros de acceso al framebuffer. El contenido de esta estructura se muestra en la
tabla 2.3.
2
Información tomada de la referencia [2]
6
Cuadro 2.3: Información contenida por la estructura video_buffer
Campo
Descripción
void *base
Dirección física base del buffer
int height
Altura del buffer
int width
Ancho del buffer
int depth
Profundidad del buffer
int bytesperline
Número de bytes entre el inicio de dos líneas adyacentes
VIDIOCGWIN: Obtiene la información del área de captura. Recibe como argumento
una estructura del tipo video_window, en la cual se escribe la información. El contenido
de esta estructura se describe en la tabla 2.4.
VIDIOCSWIN: Permite definir la información del área de captura requerida. Recibe
como argumento una estructura video_window la cual debe contener los parámetros
deseados.
VIDIOCCAPTURE: Permite activar y desactivar la captura en modo overlay. El cual es
una transferencia directa desde la memoria del dispositivo de captura hasta la memoria
de la tarjeta de video. Este modo incrementa la velocidad y reduce el procesamiento
necesario cuando sólo se requiere desplegar el video capturado, sin efectuar operación
intermedia.
Cuadro 2.4: Información contenida por la estructura video_window
Campo
x
y
width
height
chromakey
flags
clips
clipcount
Descripción
Coordenada x del área de captura
Coordenada y del área de captura
Ancho del área de captura
Altura del área de captura
Valor RGB32 del chroma
Banderas de captura adicionales
Lista de rectángulos que se requiere extraer (sólo VIDIOCSWIN)
Número de rectángulos que se desea extraer (sólo VIDIOCSWIN)
VDIOCGCHAN: Retorna la información de los canales del dispositivo. Almacena la
información en la estructura del tipo video_channel que recibe como argumento. Los
contenidos se esta estructura se muestran en la tabla 2.5.
Cuadro 2.5: Información contenida por la estructura video_channel
Campo
channel
name
tuners
flags
type
norm
Descripción
Número del canal
Nombre del canal
Número de sintonizadores en el canal
Propiedades del sintonizador
Tipo de entrada
Norma que utiliza el canal
7
VIDIOCSCHAN: Selecciona el canal del cual se va a capturar. Recibe un entero con el
número del canal como argumento.
VIDIOCGMBUF: Permite obtener el tamaño de la memoria del dispositivo, así como la
cantidad de cuadros que puede contener. La llamada almacena esta información en la
estructura video_mbuf que recibe como argumento, la cual es descrita en el tabla 2.6.
Cuadro 2.6: Información contenida en la estructura video_mbuf
Campo
size
frames
offsets
Descripción
Tamaño de la memoria del dispositivo
Número de cuadros que puede contener la memoria
Posición de cada cuadro dentro de la memoria
VIDIOCMCAPTURE: Inicia la captura hacia uno de los cuadros de la memoria de
video. Recibe como argumento una estructura del tipo video_mmap donde se
especifican varios parámetros tales como el cuadro que se desea capturar, el tamaño del
cuadro y el formato de la imagen. La descripción de esta estructura se presenta en la
tabla 2.7.
Cuadro 2.7: Información contenida en la estructura video_mmap
Campo
frame
height
width
format
Descripción
Número de cuadro que se desea capturar
Altura del cuadro
Ancho del cuadro
Formato de la imagen
VIDIOCSYNC: Espera a que finalice la captura de un cuadro, el cual se especifica a
través de la estructura del tipo video_mmap que recibe como parámetro. Además de
estas llamadas, existen otras que se encargan de controlar las funciones de audio
asociadas con algunos de los dispositivos.
2.3 La interfaz GTKmm
GTK es la abreviatura de Gimp Tool Kit, la cual es una biblioteca desarrollada para la
creación de interfaces gráficas. Con ella se pueden crear ventanas, botones y una
infinidad de objetos denominados Widgets, que son los objetos más simples de los
cuales hace uso esta interfaz.
GTK fue creada primeramente para C, sin embargo, es un API orientado a objetos, de
modo que ha sido extendido a lenguajes como Perl y C++.
En el caso de C++ existe un recubrimiento, llamado gtkmm, que es el que se utilizará en
este proyecto. Este provee la gran mayoría de las características de GTK y es una
manera adecuada de crear la aplicación gráfica requerida en un lenguaje como C++.
Para instalar gtkmm se deben instalar sus dependencias también, tal y como se muestra
en la tabla 2.10.
8
Cuadro 2.8: Paquetes necesarios para la utilización de GTKmm
Nombre de la aplicación
libsigc++ 2.0
pkg-config
ATK
Pango
GTKmm 2.4
2.3.1
Nombre del paquete a instalar
libsigc++-dev
pkg-config
libatk1.0-dev
libpango1.0-dev
libgtkmm-2.4-dev
Programación de ventanas
Siempre que se vaya a programar con GTKmm es necesario incluir el archivo de
cabecera o header, gtkmm.h, el cual declarará todas las variables, funciones y
estructuras necesarias para una correcta definición de las mismas.
Como ejemplo de la utilización de esta interfaz, para la creación de una ventana de
200x200 pixeles se puede compilar el siguiente código: 3
#include <gtkmm.h>
int main(int argc, char *argv[])
{
Gtk::Main kit(argc, argv);
Gtk::Window mi_ventana;
Gtk::Main::run(mi_ventana);
return 0;
}
La línea :
Gtk::Main kit(argc, argv);
crea un objeto Gtk::Main. Esto es necesario en todas las aplicaciones de gtkmm. El
constructor para este objeto inicializa gtkmm, y revisa los argumentos pasados a la
aplicación por medio de la línea de comandos, buscando por opciones standard como
–display. Esta línea se debe colocar en la función principal del programa, main(),
para una correcta inicialización de la interfaz.
Las siguientes dos líneas de código crean y despliegan una ventana:
Gtk::Window mi_ventana;
Esta línea muestra la ventana y entra al lazo principal de gtkmm, el cual finaliza cuando
la ventana es cerrada.
Gtk::Main::run(mi_ventana);
3
Ejemplo tomado de la referencia [10].
9
Si el código se guarda como ventana.cc se debe compilar de la siguiente manera:
$ g++ ventana.cc –o ventana `pkg-config gtkmm-2.4 –cflags –
libs`
El tipo de comillas utilizado hacen que el shell ejecute el comando dentro de ellas, y que
use la salida de comandos como parte de la línea de comandos.
La opción –cflags causa que pkg-config ponga en la salida una lista de
directorios incluidos para que el compilador los busque. La opción –libs pide la lista
de librerías para que el compilador las enlace y los directorios para encontrarlas.
Otros widgets, importantes son los contenedores, los cuales se utilizan para empaquetar
más de un widget por ventana. De esta manera es posible tener una ventana con varios
botones, cuadros de diálogo, imágenes y todos los demás objetos existentes dentro de
esta interfaz.
2.3.2
Programación con múltiples hilos de procesamiento
La programación utilizando múltiples hilos de procesamiento es un esquema que
permite el desarrollo de aplicaciones que ejecuten varios flujos de procesamiento dentro
de un mismo proceso principal. Esto se utiliza para que cada uno de los hilos se ejecute
de forma independiente, sin tener que esperar a que finalice otro proceso.
Además, cada uno de los hilos tiene acceso a las distintas variables y objetos dentro del
programa. La implementación de estos hilos o threads en Linux se conoce como POSIX
threads o pthreads, sin embargo la interfaz GTKmm posee su propio soporte para hilos
de procesamiento, así que es necesario utilizar estos para lograr el comportamiento
deseado de la aplicación.
Para crear un hilo y que este inmediatamente llame a una función en particular se debe
proceder de la siguiente manera:
Glib::Thread *hiloprinc = Glib::Thread::create((sigc::mem_fun
(*this,&acceso::principal)),false);
donde se observa que se debe crear un puntero tipo Glib::Thread y asignarle la llamada
a la función, en este caso la función principal() perteneciente a la clase llamada acceso.
El argumento false le indica al hilo que desaparezca una vez que la función retorne.
2.4 Procesamiento de Imágenes
La manera básica de representar una imagen digital a color en la memoria de una
computadora es un mapa de bits (bitmap). Un bitmap está constituido por filas de
píxeles, los cuales tienen un valor particular que determina el color aparente.
Este valor está dado por tres números dando la descomposición del color en los tres
colores primarios: rojo, verde y azul. Es lo que se conoce como representación RGB
(red-green-blue). Cualquier color visible por el ojo humano puede ser representado de
esta manera. Esta descomposición de un color en tres colores primarios está
10
cuantificada por un numero entre 0 y 255, por ejemplo el color blanco que es la suma de
todos los colores seria (R,G,B)=(255,255,255).
Así entonces, una imagen es un arreglo bidimensional de píxeles, codificados en 3 bytes
que representan los tres colores primarios. Esto permite que la imagen pueda contener
256*256*256=16.8 millones de colores diferentes.
El ámbito 0-255 es aceptado por dos muy buenas razones. Una de ellas es que el ojo
humano no es lo suficientemente sensible para diferenciar más de 256 niveles de
intensidad. Es decir, una imagen no se mejorará, para efectos visuales humanos, si se
codificara con más de 256 niveles.
La otra razón es la conveniencia para el almacenamiento en la computadora ya que un
byte (8 bits) puede contener 28 = 256 valores.
2.4.1
Representación vectorial de colores
Ya que como se comentó, cualquier color puede ser representado como una
combinación de tres colores primarios, se puede hacer una representación vectorial que
ilustre los componentes que contiene este modelo.
Figura 2.2: Representación vectorial de colores 4
La visualización de la dependencia de colores que muestra este modelo produce
resultados directos muy útiles para el desarrollo de este proyecto.
Por ejemplo, para cuantificar la diferencia entre dos colores basta realizar una sencilla
operación algebraica. Si se quiere determinar la diferencia entre el color C1 = (R1, G1,
B1) y el color C2 = (R2, G2, B2) se procede así:
D (C1, C 2) =
2.4.2
(R1 − R 2)2 + (G1 − G 2)2 + (B1 − B 2)2
(1)
Filtros
Desde el punto de vista matemático, si se tiene una señal y se desea realizar una
variación en esta, se puede hacer con una convolución entre dos funciones que
representan a la entrada x(t) y la respuesta del filtro a un impulso h(t), de la siguiente
manera:
4
Imagen tomada de la referencia [12]
11
+∞
y (t ) = h(t ) * x(t ) = ∑ h(t − α )g x (α )dα
(2)
−∞
Esta misma ecuación pero para tiempo discreto, sería:
+∞
y[k] = h[k] * x[k] = ∑ h[k - i]g x [i]
(3)
−∞
Por lo tanto si se desea saber como filtrar una imagen digital, se debe convolucionar el
mapa de píxeles de entrada, con el mapa de píxeles respuesta a un impulso del filtro de
la imagen.
La función impulso en una imagen de dos dimensiones es un cuadro donde todos los
píxeles tienen valor 0 a excepción de un solo píxel que tiene valor de 1.
Cuando esta función delta (impulso) pasa a través de una función lineal se obtendrá la
función H(k). Entonces al generar una convolución entre estas funciones se obtiene una
ecuación matricial parecida a la ecuación (3) de la siguiente forma:
y[r, c] =
M −1 M −1
1
∑ ∑ h[j, i]g x [r - j, c - 1]
∑ h[i, j] j =0 i =0
(4)
i, j
Esta convolución tiene a y como el mapa de píxeles obtenido a la salida del filtro y x
como el mapa de entrada, a través de un filtro con respuesta a un impulso h (con ancho
y altura igual a M).
Ahora esta función se puede representar de una manera más sencilla, donde lo que se
hace es tomar el mapa de píxeles de una imagen y multiplicarlo por una máscara
matricial y una constante de peso, obteniendo así un mapa de píxeles con las
características deseadas.
Esto se muestra de una manera más simple en la figura 2.1.
Figura 2.3: Manipulación de imágenes por máscaras matriciales 5
5
Figura tomada de la referencia [9]
12
Entonces, algunos de los métodos para aplicar filtros a una imagen digital consisten en
máscaras aplicadas a los píxeles para compararlos con los píxeles en una vecindad y así
redefinir el valor del píxel analizado.
2.4.2.1
Filtrado de ruido
Un filtro de ruido es utilizado para eliminar las posibles corrupciones en los píxeles
debido a interferencia externa.
Una de las maneras de filtrar la imagen consiste en multiplicarla por una máscara
matricial y un valor de peso, que es la suma total de valores de la matriz.
+m
Pxy =
*
∑W
i, j =− m
i, j
⋅ Px +i , y + j
(5)
+m
∑W
i , j =− m
i, j
con P como la matriz de píxeles de la imagen, P* es la matriz resultante una vez
aplicado el filtro, y W es una matriz cuadrada de dimensión 2m+1, la cual contiene los
pesos que se asignarán a cada uno de los píxeles del vecindario del píxel que está siendo
calculado.
La forma más sencilla de este filtro consiste en una matriz de 3 × 3 en la cual todos sus
pesos son 1:
Figura 2.4: Máscara matricial
2.4.3
Umbralización
La umbralización es una técnica mediante la cual una imagen es dividida en dos clases
de acuerdo con un valor umbral. La imagen se divide en un grupo de píxeles cuya
característica de intensidad sea mayor al valor umbral y en uno cuya intensidad sea
menor. Esta técnica de procesamiento parte de un concepto muy simple, el parámetro k
llamado umbral es determinado y aplicado a una imagen a[m,n] como sigue:
⎧1 si a[m, n] ≥ k
a[m, n] = ⎨
⎩0 si a[m, n]〈 k
(6)
De esta manera, la imagen queda determinada por dos únicos valores de intensidad, 1
(blanco) o 0(negro), en donde cualquiera de los dos puede representar al objeto o al
fondo.
13
La segmentación es la acción de separar un objeto de interés del fondo de la imagen.
Para ello es necesario determinar un umbral adecuado que pueda servir para realizar la
segmentación.
Hay varias formas de determinar el umbral, una de ellas es seleccionarlo
arbitrariamente, sin embargo, no resulta muy eficiente en los casos en los que se tiene
una secuencia de imágenes capturadas, en donde el entorno puede variar en cualquier
momento. Lo más adecuado es emplear alguno de los métodos existentes de
umbralización automática.
2.4.3.1 Histogramas
Un histograma es un gráfico que muestra la cantidad de píxeles en una imagen con un
mismo valor de intensidad. Los histogramas son utilizados por diversas técnicas para
determinar un umbral óptimo.
Figura 2.5: Histograma 6
El histograma de una imagen con L niveles de gris (generalmente L=256) puede ser
representado por un arreglo unidimensional con L elementos.
2.4.3.2 Método se Otsu 7
La operación de cálculo de umbral en este método, se considera como la distribución de
los píxeles de una imagen en dos clases C0 y C1 (por ejemplo objeto y fondo o
viceversa), considerando un nivel de umbral k. C0 denota los píxeles con un nivel de
gris menor o igual al umbral [0,1,…,k] y C1 representa a los píxeles cuyo nivel es mayor
al umbral [k+1,k+2,…,L-1]. A partir de esta distribución es posible calcular las
siguientes aproximaciones estadísticas:
k
ω 0 (k ) = ∑ p( g )
(7)
g =0
ω1 (k ) =
6
7
L −1
∑ p( g ) = 1 − ω
g = k +1
(8)
0
Imagen tomada de la referencia [14]
Descripción de los métodos tomada de la referencia [5]
14
k
μ 0 (k ) = ∑ g ⋅ p( g )
(9)
g =0
μ1 ( k ) =
L −1
∑ g ⋅ p( g )
(10)
g = k +1
k
σ 0 2 (k ) = ∑ ( g − μ 0 (k )) 2 ⋅ p ( g )
(11)
g =0
L −1
∑ ( g − μ (k ))
σ 1 2 (k ) =
g = k +1
2
1
⋅ p( g )
(12)
donde:
p( g ) =
h( g )
(13)
L −1
∑ h( g )
g =0
La media total y la varianza total están dadas por:
L −1
1
g =0
g =0
μ T (k ) = ∑ g ⋅ p( g ) = ∑ ω g (k ) μ g (k )
(14)
L −1
σ T 2 = ∑ ( g − μ T ) 2 ⋅ p( g ) = σ w 2 (k ) + σ B 2 (k )
(15)
g =0
donde:
1
σ w 2 (k ) = ∑ ω g (k )σ g 2 (k )
(16)
g =0
1
σ B (k ) = ∑ ω g (k )( μ g (k ) − μ T ) 2
2
(17)
g =0
son las varianzas dentro de la clase y entre clases respectivamente.
Un umbral óptimo puede ser determinado al minimizar alguna de las siguientes
funciones criterio (equivalentes), respecto al umbral k:
λ (k ) =
κ (k ) =
σ B 2 (k )
σ W 2 (k )
(18)
σT 2
(19)
σ W 2 (k )
15
σ B 2 (k )
η (k ) =
σT 2
(20)
Siendo ésta última la más simple. Por lo tanto, el umbral óptimo k es calculado de forma
secuencial hasta satisfacer la siguiente relación:
k = arg min k {η (k )}
(21)
k∈[0 , L −1]
2.4.3.3 Método de Kittler e Illingworth
También llamado método del error mínimo. En este método el histograma es visto como
un estimado de la función de densidad de probabilidad p(g) de la población mezclada
consistente de los niveles de gris de los píxeles del objeto y del fondo.
Se asume para los dos componentes de la mezcla p(g|0) y p(g|1), una distribución
normal con parámetros para las medias μ 0 , μ1 y desviaciones estándar σ 0 , σ 1 y
probabilidades a priori P0 y P1 , es decir:
1
p( g ) = ∑ Pi ⋅ p( g | i )
(22)
i =0
donde,
⎛ g − μi
exp⎜⎜ −
2
2π σ i
⎝ 2σ i
1
p( g | i) =
⎞
⎟
⎟
⎠
(23)
El valor de umbral puede ser hallado al resolver la siguiente ecuación cuadrática:
( g − μ 0 )2
σ0
+ ln σ 0 − 2 ln P0 =
2
2
( g − μ 1 )2
σ1
2
+ ln σ 1 − 2 ln P1
2
(24)
Los parámetros asociados con la densidad mixta p(g) son usualmente desconocidos. Sin
embargo, pueden ser estimados a partir del histograma:
k
P0 (k ) = ∑ h( g )
(25)
g =0
P1 (k ) =
255
∑ h( g )
(26)
g = k +1
k
μ 0 (k ) =
∑ g ⋅ h( g )
g =0
(27)
P0 (k )
16
255
μ1 ( k ) =
∑ g ⋅ h( g )
g = k +1
(28)
P1 (k )
k
σ 0 2 (k ) =
∑ (g − μ
g =0
( k ) ) ⋅ h( g )
2
0
255
σ 1 (k ) =
2
(29)
P0 (k )
∑ (g − μ (k ) )
1
g = k +1
2
⋅ h( g )
(30)
P1 (k )
Estos parámetros deben ser sustituidos en la siguiente función criterio:
J ( k ) = 1 + 2[P0 (k ) ln σ 0 + P1 ( k ) ln σ 1 ] − 2[P0 ( k ) ln P0 (k ) + P1 (k ) ln P1 (k )]
(31)
Y el valor umbral k se determinaría al minimizar la función anterior:
k = arg min k {J (k )}
(32)
k∈[0 , L −1]
Existen dos formas de implementar el algoritmo anterior. Una de ellas es seguir un
esquema secuencial en donde se evalúa la función para todos los valores de gris posibles
y se elige el que la minimice. Sin embargo puede que se elija como valor umbral alguno
de los valores de los extremos, produciéndose un resultado no deseado. La otra forma es
hacerlo en forma iterativa y no tiene este problema. Para implementarla se debe escoger
un valor umbral inicial arbitrario y calcular los parámetros antes mencionados a partir
del histograma. Luego se resuelve para g la siguiente ecuación:
⎡ 1
⎡ μ (k ) μ (k ) ⎤
1 ⎤
− 2 ⎥ − 2 g ⎢ 0 2 − 12 ⎥ +
g2⎢ 2
σ 1 (k ) ⎦⎥
⎣⎢σ 0 (k ) σ 1 (k ) ⎦⎥
⎣⎢ σ 0
⎡ μ 0 2 ( k ) μ1 2 ( k ) ⎤
− 2 ⎥ + 2[ln σ 0 (k ) − ln σ 1 (k )] − 2[ln P0 (k ) − ln P1 (k )] = 0
⎢
2
σ 1 (k ) ⎦⎥
⎣⎢ σ 0
(33)
Si el nuevo valor de umbral es igual al anterior, se finaliza el algoritmo, de lo contrario
se recalculan los parámetros y se itera.
2.5 Centroides
Para determinar matemáticamente el centro de gravedad de un cuerpo cualquiera deberá
escribirse una ecuación que establezca que el momento respecto a un punto resultante P
de las fuerzas de gravedad es igual a la suma de los momentos respecto al mismo punto
17
de las fuerzas de gravedad dP que actúan sobre cada partícula individual. Si se designa
por G el centro de gravedad, sus coordenadas por x, y, z y el peso total por P, el
principio de los momentos da:
x=
∫ xdP
P
y=
∫ ydP
P
z=
∫ zdP
P
(34)
El numerador de cada expresión representa la suma de momentos, y el producto de P por
la coordenada de G correspondiente representa el momento de la suma. La tercera
ecuación se obtiene girando 90 grados al cuerpo y al sistema de referencia respecto a un
eje horizontal, de manera que el eje z será horizontal
Siempre que la densidad μ de un cuerpo tenga el mismo valor en todos sus puntos, será
un factor constante existente en los numeradores y denominadores de las ecuaciones 5 y
por lo tanto se suprimirá. Las expresiones definen entonces una propiedad puramente
geométrica del cuerpo, ya que no hay referencia alguna a sus propiedades físicas.
Cuando el cálculo se refiera a una forma a una forma solamente geométrica se utiliza el
término centroide.
Entonces, si la densidad es la misma en todos los puntos, las posiciones del centroide y
del centro de gravedad coinciden.
En el caso de una varilla delgada o un elemento de forma lineal de longitud L y peso μ
por unidad de longitud, el cuerpo tiende a un segmento rectilíneo, y dP = μdL .
Por lo tanto, si μ es constante las ecuaciones 5 se convierten en:
x=
∫ xdL
L
y=
∫ ydL
L
z=
∫ zdL
L
(35)
Ahora bien, cuando se tiene un cuerpo de espesor pequeño y tiene a una superficie de
área A cuyo peso por unidad de área es μ entonces dP = μdA , y si μ es constante, las
ecuaciones 5 se transforman en
x=
∫ xdA
A
y=
∫ ydA
A
z=
∫ zdA
A
(36)
Si finalmente se tiene una superficie alabeada, como la de una bóveda o cáscara,
interesan las tres coordenadas. Si es una superficie plana, solo interesan las dos
coordenadas de dicho plano. Como se verá posteriormente en el desarrollo del
algoritmo, este será el caso utilizado puesto que lo que se tiene es una imagen en dos
dimensiones.
Finalmente, como es de suponerse, si lo que se tiene es un cuerpo de volumen V y
densidad μ el elemento es dP = μdV y entonces las ecuaciones buscadas serian:
18
x=
∫ xdV
V
y=
∫ ydV
V
z=
∫ zdV
V
(37)
2.6 Histéresis
La histéresis es una propiedad física que se encuentra presente en una gran cantidad de
sistemas. Uno de los más conocidos es el que se presenta en los fenómenos relacionados
con magnetismo. Si al magnetizar un material ferromagnético este mantiene la señal
magnética una vez que se retira el campo que la ha inducido, se dice que se presenta
histéresis magnética. Lo que ocurre es que los dominios magnéticos quedan
magnetizados cuando se aplica el campo externo y se puede decir que “recuerdan” su
estado. Este es el principio de memoria utilizado en las cintas magnéticas y dispositivos
de almacenamiento masivo como discos duros. El campo induce una magnetización que
es codificada como un uno o como un cero y que al retirar el campo permanece el valor,
para luego poder ser leído.
Figura 2.6: Curva de histéresis 8
Al observar la curva 2.4 se puede determinar de una forma más general el concepto de
histéresis. Una señal de algún fenómeno físico se representa mediante la curva que
indica la flecha hacia arriba, y va expresándose de acuerdo con esta curva, sin embargo,
cuando alcanza cierto valor de magnitud llamado umbral, la señal comienza a
describirse por medio de la otra curva, la que tiene la flecha hacia abajo. El fenómeno
sigue ocurriendo hasta que se alcanza el otro umbral con lo cual el comportamiento del
fenómeno se describe nuevamente mediante la primera curva.
8
Imagen tomada de la referencia [13]
19
2.7 El Robot Stäubli RX90
El brazo robot Stäubli RX90 es un robot que consta de seis articulaciones y por ende
seis grados de libertad. Las ultimas tres uniones corresponden a lo que se le denomina
“Instrumental del brazo” y las primeras tres hacen que se le clasifique al brazo como de
geometría ensamblada esférica, ya que los tres grados de libertad que posee se expresan
de forma angular, es decir, ningún grado de libertad se expresa de forma lineal, como lo
haría por ejemplo un robot de geometría esférica el cual tiene dos ejes de movimiento
angular y uno de movimiento lineal.
Estos tres ejes angulares que posee se pueden observar en las articulaciones 1, 2, y 3 en
la figura . Las restantes tres articulaciones es lo que se denomina “instrumental final del
brazo” o “end effector” que es la terminal del brazo que comprende los movimientos
más específicos.
Figura 2.7: Robot Stäubli RX90 9
Los movimientos se llevan a cabo gracias a los motores internos particulares a cada
articulación
9
Imagen tomada de la referencia [3]
20
El Sistema del robot consta de ocho partes:
a) El brazo robot: Las partes mecánicas
b) La base del brazo: La pieza que lo sujeta con el suelo
c) El gabinete: Estante metálico
d) El controlador: Hardware que envía las señales a los motores
e) El péndulo: Panel de control para usuario del robot
f) Computadora industrial: Computadora que se conecta por medio de conexión
serial con el controlador del robot y sirve para ejecutar programas de secuencias
de movimientos
g) Caja de alimentación
h) Cables
21
CAPITULO 3 Descripción del Sistema
3.1 Estructura del sistema
El sistema completo utilizando el robot Staubli RX90, del cual el presente proyecto
forma la primera parte se puede observar en la siguiente figura:
Figura 3.1: Estructura del sistema incluyendo el robot
El robot Staubli RX90 posee geometría esférica ensamblada, es decir, posee tres
extremidades principales que se mueven de forma angular. Este robot tiene lo que se
conoce con el nombre de instrumental del brazo, el cual es otro pequeño brazo ubicado
al final de la última extremidad principal que también tiene tres grados de libertad y
sirve para realizar tareas de mayor precisión.
Los movimientos del robot son manejados por el controlador que posee un puerto serie,
el cual posibilita el envío de comandos desde el programa que se corre en la
computadora. De esta manera, la secuencia de eventos sería:
1. Capturar una imagen mediante la cámara.
2. Acceder a ésta imagen mediante la interfaz Video4Linux.
3. Procesar la imagen para ubicar a determinado objeto dentro del cuadro
capturado.
4. Determinar la posición del objeto dentro del cuadro.
5. Enviar un comando al controlador del robot para que éste se mueva de tal forma
que el objeto se encuentre debidamente centrado.
6. Repetir el ciclo
Este proyecto implementa la primera parte del sistema, la cual comprende los cuatro
primeros puntos mencionados.
22
3.2 El programa principal
La estructura del programa elaborado consta de dos clases. Una de ellas denominada
clase imagen, contiene todos los métodos que se le pueden aplicar a un objeto imagen.
Es decir, contiene funciones de procesamiento como conversión a escala de grises,
filtrado y umbralización, así como la función para determinar la ubicación del centroide.
Además contiene otras funciones que utilizan el API de ImageMagick para poder
guardar en archivos algunas imágenes de importancia.
Figura 3.2: Estructura de la clase Imagen
Por otro lado, se tiene una clase denominada clase acceso de la cual se crea un objeto en
el programa principal.
Figura 3.3: Estructura de la clase Acceso
Esta clase contiene diferentes métodos relacionados con la inicialización del dispositivo
de video así como de la aplicación grafica que se muestran como un único bloque en la
figura anterior.
El método principal() es llamado al presionar un botón en la aplicación gráfica. Al ser
llamado, este entra en un ciclo infinito en el cual llama a la función captura y crea un
objeto de tipo imagen para luego ir llamando a cada una de sus funciones miembro, tal y
como se detalla en la siguiente sección.
Posteriormente se llama a la función decision que a su vez llama a fronteraext y
fronteraint que en conjunto determinan en cual porción del cuadro capturado se
encuentra el centroide del objeto y establecen la señalización adecuada.
23
/* Programa Principal */
#include "acceso.h"
#include <gtkmm.h>
int main(int argc, char *argv[])
{
Gtk::Main kit(argc, argv);
if(!Glib::thread_supported()) Glib::thread_init();
acceso X;
}
Como se observa, en el programa principal se tienen dos líneas de código necesarias
para inicializar adecuadamente GTKmm así como el soporte para sus hilos de
procesamiento. El constructor de la clase acceso contiene la definición de todos los
elementos de la aplicación gráfica, así que cuando se crea el objeto X de tipo acceso se
inicializa la ventana y esta queda a la espera de eventos definidos por los botones. Este
constructor se encarga además de llamar a las funciones que utilizan el API de V4L para
inicializar correctamente el dispositivo de video.
3.3 El ciclo infinito
La ventana permanece inalterable hasta que se presiona el botón Iniciar el sistema el
cual pone a correr en un hilo secundario la función acceso::principal() la cual entra en
el ciclo infinito de captura y procesamiento.
Este ciclo se ilustra con su diagrama de flujo mostrado en la figura 3.4
Figura 3.4: Diagrama de flujo del ciclo infinito
24
Se observa, en el código mostrado abajo que se captura un cuadro al entrar en el ciclo
pero antes se inicializan ciertas variables como el contador de cuadro cont y la bandera
de decisión de movimiento z, la cual es una variable global, declarada así para que la
función decision() llamada más abajo pueda hacer uso de ella (ver cap. 5).
Posteriormente se crea un objeto de tipo imagen.
imagen instancia(cap.maxwidth, cap.maxheight, map);
El constructor de la clase imagen que es invocado al crear el objeto, asigna memoria
para el arreglo que contendrá la imagen de acuerdo con los parámetros ancho y alto,
contenidos en las variables cap.maxwidth y cap.maxheight que le son pasadas como
argumentos. Así mismo este constructor llama a la función escalagrises() que toma el
puntero map que apunta al primer píxel de la imagen en la memoria del dispositivo, y
convierte la imagen a valores de intensidad o escala de grises (ver cap. 4).
El siguiente paso, tal y como se puede ver en el diagrama de flujo, es filtrar la imagen
para eliminar ruido, y luego umbralizarla para tener una imagen con solo dos valores de
intensidad.
Luego, ya con una imagen segmentada, se calcula el centroide de lo que es reconocido
como un objeto (píxeles con un valor de cero). Si no existe un objeto dentro de la
imagen, se coloca un texto en la aplicación, definido por la variable string texto, así
como una imagen, ambos ilustrando la condición de objeto no reconocible. Por el
contrario, si algún objeto es registrado, se le debe pasar como argumentos a la función
de decisión de movimiento (decisión) las coordenadas del centroide del objeto. Esta
función determina la posición del objeto con respecto al cuadro capturado y al llamar a
las funciones fronteraext y fronteraint establece las señalizaciones gráficas adecuadas
que indican la posición del objeto.
void acceso::principal()
{
captura(); //captura un cuadro
z=1;
int cont=1;
//bandera de decision de movimiento
//contador de cuadros
while(1){
bandera=false;
//bandera que será revisada por la función que será llamada
//cada cierto tiempo desde el GUI
captura();
data = new unsigned char[3*cap.maxwidth*cap.maxheight];
for (int i=0; i<3*cap.maxwidth*cap.maxheight; i++)
data[i] = map[i];
imagen instancia(cap.maxwidth, cap.maxheight, map);
bandera=true;
instancia.filtroruido();
banderaumb=false;
double f=130;
instancia.umbral(f);
banderaumb=true;
int *punt=instancia.centroide();
//arreglo que contiene las coordenadas del
//centroide
25
if(punt[0]<0){
//si no existe un valor verdadero para la posición del centroide
//no hay ningún objeto
pixindic = Gdk::Pixbuf::create_from_file("noobj2.png");
texto="No se registra ningun objeto\n";
}
else{
decision(punt);
}
//Se llama a la función que decidirá el movimiento
//dándole como argumento la coordenada del centroide
}
La inclusión de banderas en este código, así como una copia inicial del puntero map a
otra posición de memoria evita problemas a la hora de refrescar las imágenes
desplegadas. Esto se detalla en el capitulo 5.
26
CAPITULO 4 Algoritmos de acceso a dispositivo y
procesamiento de Imagen
Como se puede intuir de la descripción del sistema en el capítulo anterior, la primera
parte del proceso consiste en acceder el dispositivo de video, en este caso la cámara,
para obtener así una secuencia de imágenes que luego se deben procesar una por una
para depurarlas, eliminar información innecesaria así como para resaltar la que sea
importante para esta aplicación especifica.
4.1 Acceso al dispositivo
En esta sección se describen las principales rutinas utilizadas para acceder la cámara de
video mediante la interfaz de Video4Linux. 10
Estas rutinas están dentro de la clase llamada acceso, así que son funciones miembro de
dicha clase.
En el constructor de la clase acceso se debe dar la correcta inicialización del dispositivo.
Para ello, se recurre a uno de sus métodos: init_video, el cual recibe como argumento
una variable tipo char que es el nombre del archivo correspondiente al dispositivo de
video. Este se encarga de, inicializar el dispositivo, obtener las características de video
(capacidades de captura), así como de definir el puntero map que apunta al primer píxel
capturado de la imagen actual.
vfd = init_video("/dev/video0");
assert(vfd!=-1);
set_chan(vfd,0);
set_video_prop(cap.maxwidth,cap.maxheight,VIDEO_PALETTE_RGB24);
Luego se define el canal de captura, mediante el método set_chan, el cual recibe como
argumentos el descriptor de archivo que se construyó al llamar a init_video así como un
entero que representa el canal mismo.
Posteriormente se definen algunas propiedades de la imagen a recibir mediante la
función set_video_prop, la cual almacenará los valores del tamaño del cuadro
capturado, así como su formato. Los valores del tamaño fueron determinados a partir de
la obtención de las características de video, realizada en init_video, y que fueron
almacenadas en una estructura de tipo video_capability.
El formato se refiere a la combinación de colores utilizada, en este caso RGB de 24 bits
(8 bits por color).
La captura de la imagen se realiza con el siguiente método, el cual hace uso de los
controles de V4L, VIDIOCMCAPTURE y VIDIOCSYNC.
void acceso::capture(void)
{
video_buf.frame = 0;
ioctl(vfd, VIDIOCMCAPTURE, &video_buf);
10
Si se quiere una información más detallada se puede consultar la referencia [2].
27
ioctl(vfd, VIDIOCSYNC, &video_buf);
}
4.2 Procesamiento de imagen
Una vez que se ha obtenido la información necesaria del dispositivo y se captura un
cuadro en un instante determinado, se debe tomar el puntero map antes mencionado, el
cual posee la dirección del primer píxel en el cuadro capturado. Se debe colocar cada
pixel en una matriz, haciendo una conversión de colores de RGB a escala de grises o
grayscale, la cual es conveniente para eliminar información innecesaria en la imagen
recibida.
Esta rutina, así como la de otros filtros necesarios se encuentra en una clase llamada
imagen.
El constructor provee la correcta inicialización de la matriz que contendrá el cuadro.
Este recibe como argumentos las características de tamaño del cuadro determinadas
anteriormente y así provee del tamaño correcto a la matriz frame, haciendo uso de la
función new() que es una mejora de C++ con respecto a C de realizar la asignación de
memoria dinámica al tiempo de ejecución.
Además, se decidió colocar en el constructor la llamada al método escalagrises para
transformar la imagen de RGB a escala de grises para que de esta manera, cada vez que
se cree el objeto, la matriz frame contenga la imagen ya convertida a escala de grises.
También se reserva memoria y se inicializa apropiadamente el arreglo que contendrá el
histograma de la imagen.
imagen::imagen(int width, int height, unsigned char *pixel)
{
frame=NULL;
cols=width;
fils=height;
frame = new float[fils*cols];
arreglo= new int[2];
//arreglo que contendrá las coordenadas del centroide
escalagrises(pixel);
histograma = new unsigned char[255];
for(int i=0;i<=255;i++)
histograma[i]=0;
//arreglo que contendrá el histograma
//el histograma debe ser inicializado con ceros
}
Este es el método de la clase utilizado para transformar la imagen a formato de escala de
grises.
void imagen::escalagrises(unsigned char* pixel)
float temp=0;
int tam;
int k=0;
unsigned char* gris;
28
tam=cols*fils;
gris=new unsigned char[tam];
for (int i=0; i<tam*3; i+=3){
temp = 0.11*pixel[i] + 0.59*pixel[i+1] + 0.3*pixel[i+2];
if(temp >= 255)
gris[k] = 254;
else
gris[k] = static_cast<char>(temp);
k++;
}
El método escalagrises va almacenando en una variable temporal, temp, el resultado de
sumar la multiplicación de tres valores por números decimales. Cada uno de estos tres
valores representan los tres colores: rojo, verde y azul para determinar el valor del píxel
correspondiente. Es decir, cada píxel consta de una combinación de 8 bytes de
información para el color rojo, 8 para el azul y también 8 para el verde con lo cual se
tiene que cada píxel es representado por 24 bytes.
La variable temp se va almacenando en un arreglo llamado graypic para luego ser
introducido a la matriz frame.
k=0;
//Llenado de la matriz a procesar: frame
for(int i=0; i<fils;i++){
for(int j=0; j<cols;j++){
frame[i*cols+j]=static_cast<float>(gris[k]/254.0);
k++;
Si la cámara de video captura una imagen como la mostrada en la figura 4.1, al aplicar
la transformación a escala de grises sobre ella se obtiene una imagen como la de la
figura 4.2.
Figura 4.1: Imagen capturada por la cámara de video
29
Figura 4.2: Imagen resultante luego de aplicar la transformación a escala de grises
El siguiente paso a ejecutar es pasar la imagen almacenada en frame, por un filtro que
elimine las variaciones abruptas de la imagen. Esto se realiza mediante el método
filtroruido, el cual define una máscara de 3x3 que revisa los píxeles vecinos del píxel
actual y redefine el valor de este último.
void imagen::filtroruido()
{
float *new_frame;
new_frame = new float[fils*cols];
//Se define una nueva matriz
float weights[9] = {0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11,
0.11, 0.11};
for(int i=1;i<(fils-1);i++)
for(int j=1;j<(cols-1);j++){
new_frame[i*cols + j] = weights[0]*frame[(i-1)*cols + (j-1)]+
weights[1]*frame[(i-1)*cols + j]+
weights[2]*frame[(i-1)*cols + (j+1)]+
weights[3]*frame[i*cols + (j-1)]+
weights[4]*frame[i*cols + j]+
weights[5]*frame[i*cols + (j+1)]+
weights[6]*frame[(i+1)*cols + (j-1)]+
weights[7]*frame[(i+1)*cols + j]+
weights[8]*frame[(i+1)*cols + (j+1)];
}
Como se observa, primero se define una nueva matriz llamada new_frame sobre la cual
se escribirá el cuadro ya filtrado. Un vector de números en punto flotante representa la
máscara a utilizar.
Luego simplemente se va tomado cada píxel y se multiplican los píxeles vecinos por el
valor en el vector para obtener un valor final y asignarlo al píxel analizado.
Se observa como en los ciclos anidados no se toman en cuenta la primera ni la última
fila, así como la primera ni la última columna. Esto es porque si se aplicara esta
máscara en los píxeles de los bordes del cuadro, ocurriría una violación de segmento, ya
que no todos los 9 píxeles existirían. Esto queda más claro en la siguiente figura.
30
Figura 4.3: Indefinición en los bordes del cuadro.
Como se aprecia en la figura, si se intentara evaluar el píxel en la posición (0, 0) del
cuadro, solo habrá cuatro píxeles a evaluar dentro de la imagen, los otros cinco quedan
fuera de ella.
for(int i=0; i<fils;i++){
new_frame[i*cols] = 1;
new_frame[i*cols + (cols-1)] = 1;
}
for(int i=0; i<cols;i++){
new_frame[i] = 1;
new_frame[(fils-1)*cols + i] = 1;
}
for(int i=0;i<fils;i++)
for(int j=0;j<cols;j++)
frame[i*cols + j] = new_frame[i*cols + j];
Entonces, una manera de evitar esto es obviar los bordes y definirlos arbitrariamente de
un color, en este caso blanco (el cual tiene un valor de uno), y finalmente se copia la
matriz new_frame en _frame para así tener siempre una referencia del cuadro.
Finalmente en este método se calcula el histograma de la imagen el cual será utilizado
por la función de umbralización para el cálculo del valor umbral.
for(int i=0; i <fils; i++)
for(int j=0; j <cols; j++){
histograma[static_cast<unsigned char>(getpixel(i,j)*255)]++;
}
El arreglo histograma contiene 256 posiciones, cada una de las cuales representa un
valor posible de los píxeles. De este modo, se va sumando uno a cada posición cuando
al recorrer la imagen se encuentre un píxel con ese valor.
La función getpixel() simplemente recibe las coordenadas del píxel deseado y ésta
devuelve su valor.
La imagen resultante al aplicar este filtro sobre la imagen de la figura 3.1 se observa en
la siguiente figura.
31
Figura 4.4: Imagen resultante luego de aplicar el filtro
Ya con la imagen filtrada, para poder realizar la detección del objeto determinado de
una forma fácil se procede a calcular un umbral para luego redefinir el valor de cada
píxel en la imagen, de esta forma se obtendrá una imagen completamente en blanco y
negro. Para el cálculo automático del umbral se utilizó el método de Kittler e
Illingworth en la forma iterativa el cual fue explicado en la parte teórica.
Este método fue escogido dado que el método de Otsu y el mismo de Kittler e
Illingworth en su forma secuancial, necesitan calcular el valor de las constantes y la
minimización de la ecuación para todos los valores posibles de intensidad. Esto es
ineficiente dado que ésta aplicación requiere que se minimice el tiempo que dura cada
ciclo. Además el método de Kittler e Illingworth en forma iterativa demostró ser
bastante efectivo en la determinación del umbral, ya que se alcanza el valor deseado con
pocas iteraciones y el valor resulta apropiado.
Para implementar este algoritmo, primero se debe calcular todos los parámetros
necesarios para luego introducirlos en la función de costo.
void imagen::umbral(double k)
{
for(int i=0; i<=k; i++)
P0=P0+histograma[i];
//determinacion de P0
for(int i=(k+1); i<=255; i++)
P1=P1+histograma[i];
if(P0!=0 && P1!=0){
for(int i=0; i<=k; i++)
ut0=ut0+(i*histograma[i]);
u0=ut0/P0;
//determinacion de P1
for(int i=(k+1); i<=255; i++)
ut1=ut1+(i*histograma[i]);
u1=ut1/P1;
//determinacion de u1
//determinacion de u0(medias)
//determinacion
for(int i=0; i<=k; i++)
st0=st0+(pow((i-u0),2))*histograma[i];
s0=st0/P0;
de s0 (desv. Estandar)
//determinacion
for(int i=(k+1); i<=255; i++)
st1=st1+(pow((i-u1),2))*histograma[i];
s1=st1/P1;
de s1
32
Como se observa, este método recibe como argumento un valor flotante k, el cual en
primera instancia es arbitrario, tal y como se explicó en la parte teórica. En el ciclo
infinito se le da inicialmente un valor de 130 (un valor central, ya que son 256
posibilidades) para calcular los parámetros.
Luego de este cálculo se procede a resolver la ecuación cuadrática:
if(s0!=0 && s1!=0){
//las desv. Estandar no
/* Resolución de la ecuación cuadrática */
deben ser cero
double a = ((1/s0)-(1/s1));
double b = -2*((u0/s0)-(u1/s1));
double c = (pow(u0,2)/s0)-(pow(u1,2)/s1) +
2*(log(s0/s1)/log(e))-2*(log(P0/P1)/log(e));
double discr = pow(b,2)-(4*a*c);
double result = (-b + sqrt(discr))/(2*a);
Simplemente se calculan los coeficientes de la ecuación y se utiliza el método general
para la resolución de la misma. Si las desviaciones estándar son cero, la población es
uniforme y por lo tanto no hay objeto identificable.
Este método se itera hasta que el valor de entrada de la función (k) sea igual al valor
umbral calculado, result. Si son iguales, se umbraliza la imagen de acuerdo con ese
valor, de lo contrario, se llama a la función misma (recursividad) dándole como
argumento el valor calculado el cual queda contenido en la variable result.
if(k==result){
float umb= static_cast<float>(result/255);
for(int i=0; i <fils; i++)
for(int j=0; j <cols; j++){
if(frame[i*cols + j]<=umb){
frame[i*cols + j]=0.0001;
}
else if(frame[i*cols + j]>umb){
frame[i*cols + j]=1;
}
}
save_image(frame, filename3, "I");
//se guarda la imagen en un archivo
else{
umbral(result);
}
}
}
}
Además, si se encuentra el umbral, se salva la imagen en un archivo para poder ser
desplegada en la aplicación gráfica y así tener una referencia para comparar con el
resultado de la decisión de movimiento.
33
Por otro lado, si ocurriera que alguna de las variables P0, P1, s0, o s1 fuera cero,
existiría una indefinición, pues en algunas partes de la ecuación estas variables se
encuentran en el denominador. Si alguna de las desviaciones estándar es cero, quiere
decir que todos los datos se encuentran distribuidos uniformemente y por lo tanto no
hay una diferenciación clara entre un objeto y su fondo. Cuando esto ocurre se decidió
dejar la imagen totalmente blanca para que en la determinación del centroide se
establezca la condición de objeto no reconocible.
Este último formato en que queda la imagen es el más conveniente para poder
identificar el objeto sobre su fondo, calcular su centroide y decidir los movimientos
necesarios.
La imagen resultante luego de la umbralización se muestra en la siguiente figura.
Figura 4.5: Imagen resultante luego de aplicar la umbralización
Finalmente, se crearon dentro de la clase imagen, un par de métodos más que hacen uso
de las funciones de una interfaz llamada ImageMagick, la cual sirve para manipular
imágenes. En este caso se utilizaron para guardar en archivos los diferentes cuadros
procesados en cada paso y así poder visualizar el efecto de los filtros.
Los algoritmos utilizados fueron tomados de la referencia [1].
Mediante ImageMagick es posible manipular imágenes para obtener otras imágenes con
características especiales buscadas. Se les puede variar el color, tamaño, atributos de
luminosidad, tono y un gran número de operaciones que pueden ser útiles dependiendo
de la aplicación específica.
34
CAPITULO 5 Algoritmos de cálculo del centroide y decisión
de movimiento
5.1 Cálculo del centroide
En el capitulo 2 se comentó que la forma para determinar el centroide de un objeto de
área A, consiste en calcular una integral sobre todos sus puntos y luego dividirlos entre
el área total del objeto, es decir, entre todos los puntos que lo componen.
En el caso general, se recurre a una integral de área, y se debe establecer una relación
matemática para relacionar cada punto con alguna referencia, particularmente, el centro
de coordenadas.
Ahora bien, como para la imagen almacenada luego de ser pasada por los filtros se
conoce el estado de cada píxel (su valor y ubicación de la matriz), basta con realizar la
sumatoria de las distancias de cada píxel que forma parte del objeto y luego dividirla
entre el numero total de píxeles. Esto brinda la posibilidad de analizar objetos sin una
forma definida, e igualmente se podrá detectar su centro geométrico.
Entonces se creó un método llamado centroide miembro de la clase imagen (para que
pueda tener acceso a la matriz frame) que calcule dicho centroide:
int *imagen::centroide(){
int n=0;
xsum=0;
ysum=0;
for (int i=0; i<fils; i++){
for(int j=0;j<cols; j++){
if(frame[i*cols + j] < 0.5){
n++;
xsum+=j;
ysum+=i;
}
}
}
if(n!=0){
arreglo[0]=static_cast<int>(xsum/n);
arreglo[1]=static_cast<int>(ysum/n);
return(arreglo);
}
else{
cout<<"No se registra ningun objeto"<<endl;
}
}
Se tienen tres enteros: n, el cual contabiliza los píxeles que forman parte del objeto (los
píxeles negros) y que por lo tanto provee el área total del objeto, xsum el cual contiene
la sumatoria de las distancias desde el eje x hasta cada píxel, y ysum el cual contiene la
misma la sumatoria de las distancias a cada píxel desde el eje y.
35
Así entonces, se va revisando cada píxel en la imagen y cada vez que se encuentre un
píxel negro se guardan sus coordenadas y se suma una unidad a n. Posteriormente la
sumatoria de las coordenadas de cada punto se divide por n, y se convierte a un entero,
en caso de que la división diera un número decimal, para así obtener las coordenadas
absolutas del centroide.
En el caso de que no se encuentre ningún píxel negro, n sería igual a cero y por lo tanto
no se realiza la división.
Como se observa, éste método devuelve un puntero a un arreglo, el cual contendrá en su
posición [0] la coordenada x del centroide y en su posición [1] la coordenada y.
5.2 Decisión de Movimiento
En el caso de este proyecto, se utilizará la propiedad de histéresis para decidir las
órdenes de movimiento, de acuerdo con la posición del objeto en determinada posición
del cuadro capturado.
Figura 5.1: Segmentación del cuadro capturado
El cuadro capturado para cada instante de tiempo se dividirá en tres secciones, tal y
como se muestra en la figura 4.1.
Cuadro 1: representa la sección más externa del cuadro. Cuando el centroide del objeto
se encuentra dentro de esta sección, se debe dar la indicación de movimiento necesaria
para introducir el objeto dentro del cuadro 2.
Cuadro 2: este cuadro circunscribe al cuadro uno y representa una zona de transición.
Es en este cuadro donde se contempla la condición de histéresis. Si el objeto se
encontraba en el cuadro 1 e ingresa al cuadro 2, como indica la figura 4.2B, se debe dar
la indicación de movimiento para introducirlo en el cuadro 3. Si por el contrario el
objeto se encontraba en el cuadro 3 y posteriormente ingresa al cuadro 2, como se
observa en la figura 4.2 A, no se debe dar indicación de movimiento sino hasta cuando
haya ingresado nuevamente al cuadro 1.
36
A
B
Figura 5.2: Movimiento del objeto dentro del cuadro
Cuadro 3: es el cuadro objetivo, es decir, cuando el centroide del objeto se encuentra
dentro de este cuadro, se encuentra en la posición correcta y por lo tanto no hay que dar
ordenes de movimiento.
Cada cuadro es la mitad del tamaño del cuadro que lo circunscribe. Es decir, el cuadro 3
es la mitad de tamaño que el cuadro 2, y un cuarto del tamaño del cuadro 1.
Figura 5.3: Diagrama de flujo de la función de decisión de movimiento
El puntero generado por el método centroide de la clase imagen, es luego utilizado en la
clase acceso, mediante el método decision, el cual determinará en cual sector del cuadro
capturado se encuentra el centroide del objeto y además mediante la llamada a otros dos
métodos llamados fronteraint (FI en el diagrama de flujo) y fronteraext(FE) se decidirá
hacia donde se debe dar el movimiento de la cámara.
37
Como se observa, es en este método en donde se expresa la funcionalidad de la
histéresis, mediante el uso de la bandera de decisión z. Al inicio del programa la
bandera z es definida a 1, entonces el if es verdadero y por tanto se llama al método
fronteraext, pasándole como argumentos las coordenadas del centroide. Si este método
devuelve un uno indica que el objeto se encuentra más allá de su territorio y por lo tanto
se encuentra en el cuadro exterior. Sino es así, se llama a la función fronteraint que
revisa si el objeto se encuentra dentro o fuera del límite que establece.
En el caso que el objeto no se encontrara en el cuadro exterior ni en el cuadro central es
porque se encuentra en el cuadro interior el cual es el cuadro objetivo. En este caso, la
bandera z se pone en cero para indicar que el objeto se encuentra en posición correcta y
que no debe de indicarse movimiento sino hasta cuando el objeto haya sobrepasado
nuevamente la frontera exterior.
/* Funcion que decidirá en cual cuadro se encuentra el objeto y llama
a otras funciones
para establecer la directriz de movimiento */
void acceso::decision(int *arr)
{
if(z==1){
if(fronteraext(arr[0],arr[1])==1){
cout<<"Cuadro exterior..."<<endl;
texto="Cuadro exterior...\n";
}
else{
if(fronteraint(arr[0],arr[1])==1){
cout<<"Cuadro central..."<<endl;
texto="Cuadro central...\n";
}
else{
cout<<"Cuadro interior... posicion correcta!!!"<<endl;
texto="Cuadro interior... posicion correcta!!!\n";
pixindic = Gdk::Pixbuf::create_from_file("centro.png");
z=0;
}
}
}
else if(z==0){
if(fronteraext(arr[0],arr[1])==1){
cout<<"Cuadro exterior..."<<endl;
texto="Cuadro exterior...\n";
z=1;
}
else{
cout<<"Se encuentra dentro del rango...!!!"<<endl;
texto="Se encuentra dentro del rango...!!!\n";
pixindic = Gdk::Pixbuf::create_from_file("centro.png");
z=0;
}
}
}
38
Tanto fronteraext como fronteraint se basan en una subdivisión de cada cuadro en 8
subcuadros más, los cuales definirán la dirección del movimiento. Esto se puede ver
más claramente en la figura 4.3.
De este modo, si el objeto se encuentra en el cuadro más exterior, se determina en cual
de los subcuadros se encuentra para así mostrar la orden de movimiento. Por ejemplo si
el centroide se localiza en el subcuadro 5 del cuadro más exterior, la orden
correspondiente sería la de mover la cámara hacia abajo y también hacia la izquierda.
De esta manera, cuando el objeto se encuentre en el cuadro exterior se redirigirá
primeramente hacia el cuadro central y posteriormente hacia el cuadro interior.
Como se observa, en el caso de que el objeto se encuentre en la posición correcta, se
coloca una imagen que indica no movimiento dentro del objeto pixbuf (de la interfaz
GTKmm), el cual será desplegado en la pantalla.
Figura 5.4: Segmentación de la imagen en subcuadros
/*Cuadro exterior*/
int acceso::cuadroext(int x, int y)
{
int xsi1=static_cast<int>(a/4);
int ysi1=static_cast<int>(b/4);
int xsd1=static_cast<int>(a-(a/4));
int ysd1=static_cast<int>(b/4);
int xii1=static_cast<int>(a/4);
int yii1=static_cast<int>(b-(b/4));
int xid1=static_cast<int>(a-(a/4));
int yid1=static_cast<int>(b-(b/4));
//160
//120
//480
//120
//160
//360
//480
//360
if((x>xsd1)&&(y<ysd1)){
//cout<<"Derecha y Arriba"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("arribader.png");
return 1;
}
if((x>xid1)&&(y>yid1)){
// cout<<"Derecha y Abajo"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("abajoder.png");
return 1;
}
if((x<xsi1)&&(y<ysi1)){
//cout<<"Izquierda y Arriba"<<endl;
39
pixbuf3 = Gdk::Pixbuf::create_from_file("arribaizq.png");
return 1;
}
if((x<xii1)&&(y>yii1)){
//cout<<"Izquierda y Abajo"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("abajoizq.png");
return 1;
}
if((x<xsd1)&&(x>xsi1)&&(y<ysi1)){
//cout<<"Arriba"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("arriba.png");
return 1;
}
if((x<xid1)&&(x>xii1)&&(y>yii1)){
// cout<<"Abajo"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("abajo.png");
return 1;
}
if((y<yid1)&&(y>ysd1)&&(x>xsd1)){
//cout<<"Derecha"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("derecha.png");
return 1;
}
if((y<yid1)&&(y>ysd1)&&(x<xsi1)){
// cout<<"Izquierda"<<endl;
pixbuf3 = Gdk::Pixbuf::create_from_file("izquierda.png");
return 1;
}
}
El método fronteraext devolverá un uno si alguna condición if se cumple, indicando por
lo tanto que el centroide se encuentra dentro del cuadro exterior. Además, indicará el
movimiento necesario para ubicar al centroide en el cuadro siguiente, colocando la
imagen correspondiente en el objeto pixbuf. Este se basa en la delimitación de fronteras
para los subcuadros como se puede apreciar en la figura 4.1.
Estas fronteras están determinadas por los pares ordenados de las esquinas de los
cuadros. Donde por ejemplo xid1 sería la coordenada x de la esquina inferior derecha
del cuadro 1.
El método fronteraint trabaja exactamente igual, la única diferencia consiste en que los
puntos de referencia, es decir, las fronteras, estarán ahora con base en el cuadro interior,
en lugar del cuadro central.
Ahora bien, cuando la cámara realiza los movimientos adecuados para que el centroide
se ubique en el cuadro central, se desactiva la bandera z, para que así en el método
decision se ejecute la entrada al otro if el cual evita que se den decisiones de
movimiento a menos que el objeto se localice nuevamente en el cuadro más exterior,
40
momento en el cual la bandera z se vuelve a establecer en uno, para así comenzar
nuevamente con las directrices de movimiento.
Esta característica de histéresis evita que el programa oscile en un ámbito pequeño, es
decir, si no se estableciera un cuadro de transición (cuadro central), el programa estaría
en todo momento dando órdenes de movimiento siempre que el objeto no se encuentre
dentro del cuadro interior, lo cual no sería conveniente por cuestiones de recursos del
sistema.
5.3 Despliegue de imágenes
Uno de los puntos importantes del proyecto consiste en la correcta visualización de las
indicaciones de movimiento así como de la secuencia de imágenes que la cámara de
video está capturando una vez que se corre el programa.
Para desplegar las imágenes, GTKmm cuenta con objetos llamados pixbufs con los
cuales se pueden manipular arreglos de píxeles que estén guardados en un archivo así
como arreglos que se encuentren en la memoria del programa.
El código para mostrar en la aplicación las señalizaciones y la secuencia de imágenes se
muestra a continuación.
void acceso::refrescar(void)
{
pixbuf = Gdk::Pixbuf::create_from_data((const guint8*)data,
Gdk::COLORSPACE_RGB,
FALSE, 8, cap.maxwidth, cap.maxheight, cap.maxwidth*3);
imagen.set(pixbuf);
imagen.show();
//imagen
imagenindic.set(pixindic);
imagenindic.show();
imagenfoto.set(pixfoto);
imagenfoto.show();
//señalizaciones
//foto en blanco y negro
agregartexto(texto);
}
Como se observa, primero se construye el pixbuf al cual se le da como argumento el
puntero data que apunta a la imagen capturada. Luego para poder mostrarse, este pixbuf
debe ser introducido en otro objeto de tipo Gtk::Image, mediante el método set() y
luego con el método show() se muestra.
Los objetos imagen se deben introducir en otros objetos llamados contenedores para
poder desplegarse, esto se encuentra definido en el constructor de la clase acceso.
Por otro lado, se observa que se tienen tres pixbufs. pixbuf se refiere a la secuencia de
imágenes, pixindic contiene la imagen de señalización que se introduce en los métodos
fronteraext y fronteraint según la posición del objeto. En pixfoto se introduce, al
41
presionar un botón, la imagen blanco y negro correspondiente a la imagen mostrada
actualmente.
Finalmente agregartexto() es una rutina creada para poder desplegar mensajes en la
aplicación y se le pasa texto como argumento, el cual es una variable string que se
define en distintos sectores del programa para mostrar mensajes relevantes.
Un punto muy importante acerca de la rutina refrescar() es que debe ser llamada desde
el ciclo principal de GTK. Lo que sucede es que no es conveniente tratar de alterar algo
en el hilo principal (el de GTK) desde un hilo secundario (el hilo donde corre el resto
del programa). Entonces existe una forma de llamar a la rutina refrescar() desde el GUI
(Graphical User Interface):
sigc::slot<bool> conexion=sigc::bind(sigc::mem_fun(*this,&acceso::timeout),0);
sigc::connection llamada=Glib::signal_timeout().connect(conexion, 30);
Mediante estas dos líneas de código que deben ser colocadas durante la definición de
todos los objetos de la ventana gráfica, se define una llamada a la función timeout() de
la clase acceso cada 30 milisegundos. Esta función llama a la función refrescar()
dependiendo del estado de una bandera:
bool acceso::timeout(int c)
{
if(bandera==true){
refrescar();
}
else if(bandera==false){cout<<endl;}
return true;
}
Esta bandera lo que hace es proteger la imagen para que no sea refrescada en un
momento en el cual no está lista aún. Para ello simplemente se establece en false
durante el tiempo que tarde la rutina capture() que captura cada cuadro.
42
CAPITULO 6 Resultados
El sistema mostró un buen desempeño en distintas pruebas que se le aplicaron. El
tiempo de refrescamiento fue de aproximadamente 0.9 segundos. Este tiempo es
definido por la duración del ciclo de captura y procesamiento, en el cual se debe realizar
todas las operaciones sobre la imagen capturada y además realizar el despliegue de la
misma. Esta última operación retarda de forma significativa el proceso, sin embargo no
se debe omitir para efectos de éste proyecto debido a que constituye la referencia
principal del desempeño del sistema.
En las figuras siguientes se muestra el comportamiento del sistema sobre distintos
escenarios.
Figura 6.1: Objeto identificado, alto contraste
Como se observa, se colocó en la aplicación una foto escalada en blanco y negro. Esta
es la imagen producto de la umbralización y por lo tanto, es la imagen sobre la cual se
calcula el centroide. Al presionar el botón Imagen de referencia la imagen actual es
desplegada, una vez aplicada la umbralización. Esta funcionalidad permite que se pueda
estar monitoreando el correcto desempeño del sistema ya que de esta manera se tiene
acceso a la imagen de referencia.
En esta primera imagen se observa como efectivamente el sistema logra determinar la
posición del objeto. En este caso hay un contraste alto entre el objeto y el fondo. En la
siguiente imagen el contraste disminuye significativamente y aún así el sistema trabaja
adecuadamente.
43
Figura 6.2: Objeto identificado, bajo contraste
En la siguiente imagen se observa como las esquinas oscuras evitan que se pueda
determinar adecuadamente la posición del centroide, ya que las regiones oscuras
también formarán parte del objeto.
Al examinar éste resultado se infiere que si se detectara más de un objeto, el sistema lo
visualizaría como un único objeto, determinando el centroide de éste al identificar todos
los puntos de color negro presentes en la imagen.
44
Figura 6.3: Escenario que induce a errores
En esta última figura se observa como al no haber suficiente contraste entre el fondo y
el objeto, la desviación estándar de la distribución tiende a cero y por ende no existe un
objeto claramente reconocible.
Figura 6.4: Objeto no identificado
45
CAPITULO 7 Conclusiones y recomendaciones
7.1 Conclusiones
-
Se creó un algoritmo capaz de segmentar una imagen para poder distinguir un
objeto de interés y localizar su posición dentro del cuadro capturado.
-
Se creó un algoritmo que indica de manera gráfica la posición del objeto y por
ende el movimiento necesario que debe brindársele al dispositivo de video para
mantener el objeto en el centro del cuadro capturado.
-
Se implementó una interfaz gráfica para la demostración de estos algoritmos, la
cual permite además poder visualizar en algún momento la imagen de referencia
que resulta de los procesamientos y mediante la cual se calculará el centroide del
objeto.
-
La programación en el lenguaje C++ resulta en una forma ordenada de escribir
un programa. La modularización del problema en clases que realicen distintas
tareas representa una manera eficiente y fácil tanto escribir como de leer el
código.
-
La interfaz Video4Linux representa una manera realmente sencilla de poder
acceder al dispositivo de video. En este caso particular resultó muy eficaz para
poder manipular la información brindada por la cámara de video.
-
La interfaz GTKmm constituyó un elemento de gran importancia para el
desarrollo de la aplicación. Esta interfaz permitió elaborar la ventana gráfica
para poder visualizar el comportamiento y resultados del programa.
-
Fue necesaria la utilización de múltiples hilos de procesamiento, para los cuales
GTKmm tiene su propio soporte, para poder ejecutar varios procesos de forma
simultanea.
-
Los algoritmos existentes para la manipulación y procesamiento de imágenes
resultan muy eficientes, además son de fácil comprensión e implementación.
-
El programa elaborado logra identificar un objeto lo suficientemente
contrastante para diferenciarse de su fondo, despliega en la pantalla las imágenes
capturadas por la cámara, brinda información acerca de la localización del objeto
y señalizaciones que indican el movimiento necesario que debe dársele al
dispositivo para mantener el objeto centrado.
-
Se creó una plataforma para que pueda ser utilizada como base en el desarrollo
de más proyectos que involucren el tema estudiado así como la inclusión del
robot Staubli RX90 para utilizarlo como el mecanismo que brinde el
movimiento a la cámara.
46
7.2 Recomendaciones
-
Se puede mejorar de forma significativa el desempeño del sistema si se utilizara
otra cámara de video, ya que para este proyecto se utilizó una de tipo webcam
que no es de muy buena calidad.
-
Se debe tener muy en cuenta las características del espacio físico en donde se
van a realizar pruebas ya que la iluminación debe ser adecuada para que no
existan elementos que interfieran con la determinación del cuerpo del objeto.
-
Se puede estudiar con más detalle los métodos de procesamiento de imágenes
para reducir el tiempo que dura cada ciclo y así hacer el sistema más eficiente.
-
Se puede implementar en la aplicación gráfica una forma para que el usuario
pueda establecer el tamaño de los cuadros de referencia, ya que para este
proyecto se definieron arbitrariamente, tal y como se menciona en el capitulo 5.
-
En el caso de la implementación conjunta con el robot Staubli se debe tener en
cuenta ciertas variables para que este pueda brindar el movimiento adecuado a la
cámara de video. El envío de los comandos necesarios para mover el brazo y el
resultado del procesamiento de las imágenes que determinan la posición del
objeto debe estar sincronizada adecuadamente.
-
Se deben establecer relaciones adecuadas de distancias y ángulos del objeto con
respecto a la lente de la cámara para poder determinar el giro adecuado que se le
debe dar a las extremidades del brazo. Esto por cuanto el brazo Staubli es de
geometría esférica ensamblada y por tanto sus tres extremidades principales se
mueven de forma angular.
47
BIBLIOGRAFÍA
1. Alvarez, M. “Diseño e Implementación de un mensajero automático capaz de
recorrer una ruta preestablecida”, Costa Rica, 2005.
2. Díaz, A. “Implementación de un sistema de captura de video y detección de
movimiento utilizando el sistema Video4Linux, Costa Rica, 2005.
3. Montes, C. “Envío de video hacia un cliente remoto utilizando el sistema
Video4Linux y desarrollo de una aplicación gráfica GTK sobre una plataforma
GNU/Linux para el control del brazo robot Staubli RX90”, Costa Rica, 2005.
4. Segura, M. “Estación experimental de redes neuronales artificiales”, Costa Rica,
2006.
5. Siles, F. “Selección de un método de umbralización para segmentar la silueta de
una persona a partir de un análisis estadístico”, Costa Rica, 2003
6. Eckel, Bruce. “Thinking in C++”, Prentice Hall, Estados Unidos, 2000.
7. Kernighan, B, Ritchie, D. “El lenguaje de programación C”, Prentice Hall,
México, 1991.
8. Merian, J. “Estática”, Editorial Reverté, Barcelona, 1968.
9. Patin,
F,.
“An
introduction
to
digital
image
processing”,
http://www.gamedev.net/reference/programming/features/imageproc/page2.asp.
Octubre, 2005.
10. “Página oficial de GTKmm”, http://www.gtkmm.org
11. “Wikipedia”, http://www.wikipedia.com
12. http://es.wikipedia.org/wiki/Teor%C3%ADa_del_color
13. http://es.wikipedia.org/wiki/Hist%C3%A9resis
14. http://www.ph.tn.tudelft.nl/Courses/FIP/noframes/fip-Segmenta.html
48
APÉNDICES
Código Fuente
acceso.h
SSO v0.7
Sistema de Seguimiento de Objetos
acceso.h.: declaraciones de la clase acceso
Copyright (C) 2006 Luis Federico Gómez Salazar <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
#include
#include
#include
#include
#include
#include
"imagen.h"
<gtkmm.h>
<fcntl.h>
<sys/ioctl.h>
<sys/mman.h>
<linux/videodev.h>
#ifndef CLASEACCESO
#define CLASEACCESO
class acceso{
private:
unsigned char* map;
struct video_capability cap;
struct video_mbuf buf_info;
struct video_mmap video_buf;
int vfd;
int cont;
int z;
int a;
int b;
bool bandera, banderaumb;
//Puntero que apunta al primer pixel del frame
//Caracteristicas del dispositivo de video (cámara)
//Información de la memoria de video
//Caracteristicas de los cuadros de la memoria de video
//Descriptor de archivo del dispositivo de video
//contador de cuadros
//bandera de decision de movimiento
//variables para cap.maxwidth y cap.maxheight solo para
//tratarlas con un nombre más corto!
//bandera de sincronizacion de refrescamiento
/* Objetos de GTKmm */
Gtk::Notebook tabs;
//objeto cuaderno (pestañas)
Gtk::VBox caja, cajafoto, cajaindic;
//contenedores
Gtk::VBox cajanom, cajanom1;
Glib::RefPtr<Gdk::Pixbuf> pixbuf, pixfoto, pixindic;
//objetos pixbuf
Glib::RefPtr<Gdk::Pixbuf> pixscal, pixnom, pixnom1;
Gtk::Image image, imagefoto, imageindic, imagenom, imagenom1; //objetos imagen
Gtk::Table tabla, tablabotones;
//tablas para acomodar objetos
Gtk::Frame framevideo; Gtk::Table tablavideo;
Gtk::Frame framecapturas, frameconsola, framebotones, frameflechas; //frames
Gtk::Frame frameinfo1, frameinfo2, frameinfo3; Gtk::Table tablainfo1, tablainfo2, tablainfo3;
Gtk::Button biniciar, bcerrar, bumbral;
//botones
Gtk::TextView consolaview;
//Consola de texto
Gtk::ScrolledWindow consolaventana;
//Ventana que despliega la consola
Glib::RefPtr<Gtk::TextBuffer> consolabuffer;
//Buffer de la consola
public:
acceso();
virtual ~acceso(void);
int init_video(char*);
int set_chan(int, int);
int set_video_prop(int, int, int);
void captura(void);
49
void principal(void);
void decision(int*);
int fronteraext(int, int);
int fronteraint(int, int);
Gtk::Window ventana;
void agregartexto(string);
void on_click(string);
void refrescar(void);
bool timeout(int);
string texto;
unsigned char *data;
};
#endif
acceso.cc
SSO v0.7
Sistema de Seguimiento de Objetos
acceso.cc: Implementación de la clase acceso
Copyright (C) 2006 Luis Federico Gómez Salazar <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
// Clase acceso
#include
#include
#include
#include
#include
"acceso.h"
<cassert>
<vector>
<glibmm/thread.h>
<glib.h>
using namespace std;
acceso::acceso()
//constructor que llama a las funciones para abrir el dispositivo de video
{
/* Inicialización del dispositivo */
vfd = init_video("/dev/video0");
assert(vfd != -1);
set_chan(vfd,0);
set_video_prop(cap.maxwidth, cap.maxheight, VIDEO_PALETTE_RGB24);
cout<<"Comienza el programa...\n"<<endl;
a=cap.maxwidth; //640 //solo para utilizarlo con un nombre màs corto
b=cap.maxheight; //480
texto="Iniciando...\n";
/* Aplicación Gráfica */
tabla.resize(5,10);
//Inicializa cada tabla (filas, columnas)
tablabotones.resize(5,5);
//Inicializa cada tabla de cada frame
tablainfo1.resize(2,2);
tablainfo2.resize(3,3);
tablainfo3.resize(2,2);
tablabotones.set_homogeneous(true);
tabla.set_homogeneous(true);
ventana.set_title("Sistema de Seguimiento de Objetos");
ventana.set_resizable(true);
//Podra cambiarse de tamano
ventana.set_border_width(1);
ventana.maximize();
//Maximiza la ventana
framevideo.set_label("Video");
frameconsola.set_label("Localizacion");
framecapturas.set_label("Capturas");
50
//frames (para segmentar la ventana)
framebotones.set_label("Opciones");
frameflechas.set_label("Directriz de movimiento");
Gtk::Label inf, inf1, inf2, inf3;
//etiquetas
inf.set_label("Este sistema permite identificar un objeto sobre su fondo\nsiempre que
exista suficiente contraste.\nLa forma del objeto puede ser cualquiera.");
inf1.set_label("El objeto se encuentra debidamente centrado");
inf2.set_label("No hay ningun objeto reconocible");
inf3.set_label("Universidad de Costa Rica\nEscuela de Ingenieria Electrica\nProyecto
Electrico\nSistema de Seguimiento de Objetos\nLuis Federico Gomez Salazar\nA22186");
pixnom = Gdk::Pixbuf::create_from_file("centro.png");
imagenom.set(pixnom);
cajanom.pack_start(imagenom);
pixnom1 = Gdk::Pixbuf::create_from_file("noobj2.png");
imagenom1.set(pixnom1);
cajanom1.pack_start(imagenom1);
cajanom.show();
tablainfo1.attach(inf,0,1,0,1);
tablainfo2.attach(cajanom, 0,1,0,1);
tablainfo2.attach(cajanom1, 0,1,2,3);
tablainfo2.attach(inf1, 1,2,0,1);
tablainfo2.attach(inf2, 1,2,2,3);
tablainfo3.attach(inf3, 0,1,0,1);
//tablas para acomodar mas de un widget
tabs.append_page(frameinfo1, "Informacion"); //anexa los frames a cada tab del cuaderno
tabs.append_page(frameinfo2, "Nomenclatura");
tabs.append_page(frameinfo3, "++...");
/* Botones */
biniciar.set_label("Iniciar el sistema");
biniciar.signal_clicked().connect(sigc::bind<string>( sigc::mem_fun(*this, &acceso::on_click),
"biniciar") );
bcerrar.set_label("Terminar");
bcerrar.signal_clicked().connect(sigc::bind<string>( sigc::mem_fun(*this, &acceso::on_click),
"bcerrar") );
bumbral.set_label("Imagen de referencia");
bumbral.signal_clicked().connect(sigc::bind<string>( sigc::mem_fun(*this, &acceso::on_click),
"bumbral") );
/* Ubicacion de los botones y frames dentro de la ventana */
tablabotones.attach(bumbral,0,3,4,5);
tablabotones.attach(bcerrar,0,3,1,2);
tablabotones.attach(biniciar,0,3,0,1);
//adjunta los botones a la tabla
Gtk::VSeparator linea;
tabla.attach(framevideo,0,7,0,8);
tabla.attach(framebotones,7,10,0,3);
tabla.attach(frameconsola,0,3,8,10);
tabla.attach(framecapturas,7,10,3,8);
tabla.attach(frameflechas,3,5,8,10);
tabla.attach(linea,5,6,8,10);
tabla.attach(tabs,6,10,8,10);
//adjunta los frames a la tabla
framebotones.add(tablabotones);
frameconsola.add(consolaventana);
frameinfo1.add(tablainfo1);
frameinfo2.add(tablainfo2);
frameinfo3.add(tablainfo3);
//adjunta la tabla al frame
//adjunta la consola de texto al frame
/* Inicializa Consola */
consolaventana.add(consolaview);
//Agrega la consolaview a la ventana
consolaview.set_sensitive(false);
//No podra ser editada
//Despliega scrollbars si son necesarias
consolaventana.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
consolabuffer= consolaview.get_buffer(); //Obtiene el buffer del VIEW
consolaview.set_editable(false);
//Evita que pueda editarse la ventana
consolaview.set_cursor_visible(false);
//Evita que pueda que el cursor sea visible
consolaview.set_overwrite(false);
consolaview.show();
caja.pack_start(image);
framevideo.add(caja);
caja.show();
//adjunta la imagen al contenedor
//adjunta el contenedor al frame
//muestra el contenedor
cajafoto.pack_start(imagefoto);
framecapturas.add(cajafoto);
cajafoto.show();
cajaindic.pack_start(imageindic);
frameflechas.add(cajaindic);
cajaindic.show();
51
ventana.add(tabla);
ventana.show_all_children();
//agrega la tabla principal a la ventana
//muestra todos los objetos hijos de ventana
/* Llama a una funcion desde la aplicacion cada cierto intervalo de tiempo en ms
esto es necesario ya que no es conveniente tratar de modificar algo en el hilo
principal desde un hilo secundario */
sigc::slot<bool> conexion = sigc::bind(sigc::mem_fun(*this,&acceso::timeout),0);
sigc::connection llamada = Glib::signal_timeout().connect(conexion, 30);
Gtk::Main::run(ventana);
//Entra al ciclo principal de GTKmm
}
acceso::~acceso()
{
//destructor
}
void acceso::agregartexto(string linea)
{
consolabuffer->insert_at_cursor(linea);
//Agrega una linea a la consola
//Agrega al buffer el texto recibido
//Asegura que el texto introducido sea visible
//Verifica si hay mas texto del que la ventana puede desplegar
if (
consolaventana.get_vadjustment()->get_value() >= (
this->consolaventana.get_vadjustment()->get_upper() this->consolaventana.get_vadjustment()->get_page_size() - 1e-12)
)
{
Glib::RefPtr<Gtk::TextBuffer::Mark> pos;
pos = consolabuffer->create_mark(consolabuffer->end());
//Posicion
consolabuffer->move_mark(pos, consolabuffer->end());
//Buffer al final
consolaview.scroll_to_mark(pos, 0.0);
//Mueve el texto a la ventana
}
}
void acceso::principal()
{
captura(); //captura un cuadro
z=1;
//bandera de decision de movimiento
int cont=1; //contador de cuadros
while(1){
bandera=false;
//bandera que será revisada por la función que será llamada
//cada cierto tiempo desde el GUI
captura();
data = new unsigned char[3*cap.maxwidth*cap.maxheight]; //copia map en otra posicion de memoria
for (int i=0; i<3*cap.maxwidth*cap.maxheight; i++)
data[i] = map[i];
imagen instancia(cap.maxwidth, cap.maxheight, map); //se crea un objeto de tipo imagen
bandera=true;
//Una vez que se captura un cuadro, se
//pone esta bandera en true para desplegar la imagen
cout<<"Cuadro: "<<cont++<<endl<<endl;
//instancia.colocar(data);
instancia.filtroruido();
//Se pasa la imagen por el filtro de ruido
//cout<<"Filtro de ruido... hecho!"<<endl;
banderaumb=false;
double f=130;
instancia.umbral(f);
banderaumb=true;
//Se deja una imagen en blanco y negro
//cout<<"Umbral... hecho!"<<endl;
int *punt=instancia.centroide();
//determina la posicion del centroide
//cout<<"Coordenadas del centroide:"<<endl;
//cout<<"x="<<punt[0]<<endl;
//cout<<"y="<<punt[1]<<endl;
//cout<<"Decision"<<endl;
if(punt[0]<0){
pixindic = Gdk::Pixbuf::create_from_file("noobj2.png");
texto="No se registra ningun objeto\n";
}
else{
decision(punt);
}
//Se llama a la función que decidirá el movimiento
//dandole como argumento la coordenada del centroide
}//llave del while
}
/* Inicializa el dispositivo */
52
int acceso::init_video(char *video_dev)
{
int fd;
/* Apertura del dispositivo */
if((fd=open(video_dev, O_RDWR))==-1) {
cout<<stderr<<"Error al abrir el dispositivo %s\n"<<video_dev<<endl;
return -1;
}
/* Información de los buffers */
if((ioctl(fd, VIDIOCGMBUF, &buf_info))==-1) {
cout<<"Error adquiriendo los buffers (VIDIOCGMBUF)\n"<<endl;
return -1;
}
/* Obtención de las capacidades de captura del dispositivo */
if(ioctl(fd,VIDIOCGCAP, &cap)==-1){
cout<<stderr<<"Error al obtener las capacidades de captura (VIDIOCGCAP)\n"<<endl;
return -1;
}
/* Determinación del puntero map que contendrá la dirección del primer pixel del cuadro */
map = (unsigned char*)mmap(0, buf_info.size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(map==NULL){
cout<<"Error en el mapeo de la memoria\n"<<endl;
return -1;
}
return fd;
}
/* Define el canal */
int acceso::set_chan(int fd, int chan)
{
if(ioctl(fd, VIDIOCSCHAN, chan)==-1)
return -1;
else
return 0;
}
/* Establece el tamaño del frame */
int acceso::set_video_prop(int width, int height, int format)
{
video_buf.width = width;
video_buf.height = height;
video_buf.format = format;
return 0;
}
/* Captura */
void acceso::captura(void)
{
video_buf.frame = 0;
ioctl(vfd, VIDIOCMCAPTURE, &video_buf);
ioctl(vfd, VIDIOCSYNC, &video_buf);
}
//Lee el frame (cuadro)
//sincroniza la obtención de los cuadros
/* Funcion que decidirá en cual cuadro se encuentra el objeto y llama a otras funciones
para establecer la directriz de movimiento */
void acceso::decision(int *arr)
{
if(z==1){
if(fronteraext(arr[0],arr[1])==1){
cout<<"Cuadro exterior..."<<endl;
texto="Cuadro exterior...\n";
}
else{
if(fronteraint(arr[0],arr[1])==1){
cout<<"Cuadro central..."<<endl;
texto="Cuadro central...\n";
}
else{
cout<<"Cuadro interior... posicion correcta!!!"<<endl;
texto="Cuadro interior... posicion correcta!!!\n";
pixindic = Gdk::Pixbuf::create_from_file("centro.png");
z=0;
}
}
}
53
else if(z==0){
if(fronteraext(arr[0],arr[1])==1){
cout<<"Cuadro exterior..."<<endl;
texto="Cuadro exterior...\n";
z=1;
}
else{
cout<<"Se encuentra dentro del rango...!!!"<<endl;
texto="Se encuentra dentro del rango...!!!\n";
pixindic = Gdk::Pixbuf::create_from_file("centro.png");
z=0;
}
}
}
//cuadro1=exterior
//cuadro2=interior
/* Estas dos siguientes funciones le asociarán al objeto pixindic la imagen correspondiente
de acuerdo con la posición del objeto en el cuadro.
Los diferentes puntos van formando los pares ordenados de las esquinas de los cuadros.
De esta manera, ysd1 por ejemplo, seria la coordenada "y" de la esquina Superior Derecha del
cuadro 1.*/
/*Cuadro exterior*/
int acceso::fronteraext(int xx, int
{
int xsi1=static_cast<int>(a/4);
int ysi1=static_cast<int>(b/4);
int xsd1=static_cast<int>(a-(a/4));
int ysd1=static_cast<int>(b/4);
int xii1=static_cast<int>(a/4);
int yii1=static_cast<int>(b-(b/4));
int xid1=static_cast<int>(a-(a/4));
int yid1=static_cast<int>(b-(b/4));
yy)
//160
//120
//480
//120
//160
//360
//480
//360
if((xx>xsd1)&&(yy<ysd1)){
//cout<<"Derecha y Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arribader.png");
return 1;
}
if((xx>xid1)&&(yy>yid1)){
// cout<<"Derecha y Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajoder.png");
return 1;
}
if((xx<xsi1)&&(yy<ysi1)){
//cout<<"Izquierda y Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arribaizq.png");
return 1;
}
if((xx<xii1)&&(yy>yii1)){
//cout<<"Izquierda y Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajoizq.png");
return 1;
}
if((xx<xsd1)&&(xx>xsi1)&&(yy<ysi1)){
//cout<<"Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arriba.png");
return 1;
}
if((xx<xid1)&&(xx>xii1)&&(yy>yii1)){
// cout<<"Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajo.png");
return 1;
}
if((yy<yid1)&&(yy>ysd1)&&(xx>xsd1)){
//cout<<"Derecha"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("derecha.png");
return 1;
}
if((yy<yid1)&&(yy>ysd1)&&(xx<xsi1)){
// cout<<"Izquierda"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("izquierda.png");
return 1;
}
}
54
/*Cuadro interior*/
int acceso::fronteraint(int xx, int yy)
{
int xsi2=static_cast<int>((a/4)+(a/8));
int ysi2=static_cast<int>((b/4)+(b/8));
int xsd2=static_cast<int>((a/2)+(a/8));
int ysd2=static_cast<int>((b/4)+(b/8));
int xii2=static_cast<int>((a/4)+(a/8));
int yii2=static_cast<int>((b/2)+(b/8));
int xid2=static_cast<int>((a/2)+(a/8));
int yid2=static_cast<int>((b/2)+(b/8));
//240
//180
//400
//180
//240
//360
//400
//360
if((xx>xsd2)&&(yy<ysd2)){
//cout<<"Derecha y Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arribader.png");
return 1;
}
if((xx>xid2)&&(yy>yid2)){
//cout<<"Derecha y Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajoder.png");
return 1;
}
if((xx<xsi2)&&(yy<ysd2)){
//cout<<"Izquierda y Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arribaizq.png");
return 1;
}
if((xx<xii2)&&(yy>yii2)){
//cout<<"Izquierda y Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajoizq.png");
return 1;
}
if((xx<xsd2)&&(xx>xsi2)&&(yy<ysi2)){
//cout<<"Arriba"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("arriba.png");
return 1;
}
if((xx<xid2)&&(xx>xii2)&&(yy>yii2)){
//cout<<"Abajo"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("abajo.png");
return 1;
}
if((yy<yid2)&&(yy>ysd2)&&(xx>xsd2)){
//cout<<"Derecha"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("derecha.png");
return 1;
}
if((yy<yii2)&&(yy>ysi2)&&(xx<xsi2)){
//cout<<"Izquierda"<<endl;
pixindic = Gdk::Pixbuf::create_from_file("izquierda.png");
return 1;
}
}
/* Funciones de los botones de la aplicación */
void acceso::on_click(string boton)
{
if (boton=="biniciar"){
//Al activar el boton iniciar, se crea un hilo secundario
//que llamará al ciclo infinito de captura y procesamiento
Glib::Thread *hiloprinc = Glib::Thread::create((sigc::mem_fun
(*this,&acceso::principal)),false);
}
if (boton=="bcerrar"){
exit(0);
}
if (boton=="bumbral"){
if(banderaumb==true){
pixscal = Gdk::Pixbuf::create_from_file("foto3.png");
pixfoto = pixscal->scale_simple(210, 210, Gdk::INTERP_BILINEAR);
}
}
}
55
bool acceso::timeout(int c)
{
if(bandera==true){
refrescar();
}
else if(bandera==false
return true;
}
//Esta funcion es la que es llamada cada cierto tiempo
//desde el ciclo principal de GTK para revisar la bandera
&& banderaumb==true){cout<<endl;}
void acceso::refrescar(void)
//Si la bandera es true, se refresca la imagen!
{
pixbuf = Gdk::Pixbuf::create_from_data((const guint8*)data, Gdk::COLORSPACE_RGB,
FALSE, 8, cap.maxwidth, cap.maxheight, cap.maxwidth*3);
image.set(pixbuf);
image.show();
imageindic.set(pixindic);
imageindic.show();
imagefoto.set(pixfoto);
imagefoto.show();
agregartexto(texto);
}
imagen.h
SSO v0.7
Sistema de Seguimiento de Objetos
imagen.h: declaraciones de la clase imegen
Copyright (C) 2006 Luis Federico Gómez Salazar <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
#include
#include
#include
#include
<iostream>
<string>
<vector>
<Magick++.h>
using namespace std;
#ifndef CLASEIMAGEN
#define CLASEIMAGEN
class imagen{
protected:
int cols;
int fils;
int xsum;
int ysum;
float valpixel;
float *frame;
//matriz que contiene en todo momento la imagen a analizar
int *arreglo;
//arreglo que contiene las coordenadas del centroide
unsigned char *histograma;
double P0;
double P1;
double u0, ut0, s0, st0;
double u1, ut1, s1, st1;
double e;
//variables necesarias para el metodo de umbral
public:
imagen(int, int, unsigned char*);
virtual ~imagen(void);
void escalagrises(unsigned char*);
56
void colocar(unsigned char*);
void filtroruido(void);
void umbral(double);
int* centroide(void);
int column(void);
int fil(void);
float getpixel(int, int);
void salvar_imagen(unsigned char*, char* , char*);
void salvar_imagen(float*, char*, char*);
};
#endif
imagen.cc
SSO v0.7
Sistema de Seguimiento de Objetos
imagen.cc: implementación de la clase imagen
Copyright (C) 2006 Luis Federico Gómez Salazar <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
#include "imagen.h"
#include <math.h>
using namespace Magick;
imagen::imagen(int width, int height, unsigned char *pixel)
{
frame=NULL;
cols=width;
fils=height;
frame = new float[fils*cols];
arreglo= new int[2];
//Constructor
//Crea la memoria para la matriz que contendrá
//los diferentes frames a procesar
//arreglo que contendrá las coordenadas del centroide
escalagrises(pixel);
//Se llama a esta función para que cuando se cree el objeto
histograma = new unsigned char[255];
for(int i=0;i<=255;i++)
histograma[i]=0;
//arreglo que contendrá el histograma de cada imagen
e=2.7182818284590452354;
}
imagen::~imagen(void)
{
delete []frame;
}
//Se libera memoria con el destructor
void imagen::colocar(unsigned char* pixel)
{
//Esta función es solo para prueba
//y sirve para obtener la imagen pura de la cámara
/*Foto0*/
char *filename0;
filename0 = "foto0.png";
salvar_imagen(pixel, filename0, "RGB");
}
void imagen::escalagrises(unsigned char* pixel)
{
float temp=0;
int tam;
int k=0;
unsigned char* gris;
tam=cols*fils;
gris=new unsigned char[tam];
57
//conversión de la imagen a escala de grises
for (int i=0; i<tam*3; i+=3){
temp = 0.3*pixel[i] + 0.59*pixel[i+1] + 0.11*pixel[i+2];
if(temp >= 255)
gris[k] = 254;
else
gris[k] = static_cast<char>(temp);
k++;
}
k=0;
//Llenado de la matriz a procesar: frame
for(int i=0; i<fils;i++){
for(int j=0; j<cols;j++){
frame[i*cols+j]=static_cast<float>(gris[k]/254.0);
k++;
}
}
}
void imagen::filtroruido()
{
float *new_frame;
new_frame = new float[fils*cols];
//Se define una nueva matriz
float weights[9] = {0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11};
for(int i=1;i<(fils-1);i++)
for(int j=1;j<(cols-1);j++){
new_frame[i*cols + j] = weights[0]*frame[(i-1)*cols + (j-1)]+
weights[1]*frame[(i-1)*cols + j]+
weights[2]*frame[(i-1)*cols + (j+1)]+
weights[3]*frame[i*cols + (j-1)]+
weights[4]*frame[i*cols + j]+
weights[5]*frame[i*cols + (j+1)]+
weights[6]*frame[(i+1)*cols + (j-1)]+
weights[7]*frame[(i+1)*cols + j]+
weights[8]*frame[(i+1)*cols + (j+1)];
}
for(int i=0; i<fils;i++){
//Se definen los bordes de la imagen de color blanco!
new_frame[i*cols] = 1;
new_frame[i*cols + (cols-1)] = 1;
}
for(int i=0; i<cols;i++){
new_frame[i] = 1;
new_frame[(fils-1)*cols + i] = 1;
}
for(int i=0;i<fils;i++)
//Se coloca la imagen filtrada en la matriz a analizar:frame
for(int j=0;j<cols;j++)
frame[i*cols + j] = new_frame[i*cols + j];
for(int i=0; i <fils; i++)
//determinacion del histograma
for(int j=0; j <cols; j++){
histograma[static_cast<unsigned char>(getpixel(i,j)*255)]++;
}
delete []new_frame;
}
void imagen::umbral(double k)
{
//Se define un umbral para obtener una imagen solamente blanco y negro
P0=0, P1=0, ut0=0, ut1=0, u0=0, u1=0, st0=0, st1=0, s0=0, s1=0;
char *filename3;
string archivo3 = "foto3.png";
filename3 = &archivo3[0];
for(int i=0; i<=k; i++)
P0=P0+histograma[i];
//archivo que contendrá la foto
//determinacion de P0
//cout<<"hist"<<histograma[3]<<endl;
cout<<"P0 = "<<P0<<endl;
for(int i=(k+1); i<=255; i++)
P1=P1+histograma[i];
cout<<"P1 = "<<P1<<endl;
//determinacion de P1
58
if(P0!=0 && P1!=0){
for(int i=0; i<=k; i++)
ut0=ut0+(i*histograma[i]);
u0=ut0/P0;
for(int i=(k+1); i<=255; i++)
ut1=ut1+(i*histograma[i]);
u1=ut1/P1;
//determinacion de u0
//determinacion de u1
for(int i=0; i<=k; i++)
//determinacion de s0
st0=st0+(pow((i-u0),2))*histograma[i];
s0=st0/P0;
cout<<"s0= "<<s0<<endl;
for(int i=(k+1); i<=255; i++)
//determinacion de s1
st1=st1+(pow((i-u1),2))*histograma[i];
s1=st1/P1;
cout<<"s1= "<<s1<<endl;
if(s0!=0 && s1!=0){
/* Resolución de la ecuación cuadrática */
double a = ((1/s0)-(1/s1));
// cout<<"a= "<<a<<endl;
double b = -2*((u0/s0)-(u1/s1));
// cout<<"b= "<<b<<endl;
double c = (pow(u0,2)/s0)-(pow(u1,2)/s1) +
2*(log(s0/s1)/log(e))-2*(log(P0/P1)/log(e));
// cout<<"c= "<<c<<endl;
double discr = pow(b,2)-(4*a*c);
// cout<<"discr= "<<discr<<endl;
double result = (-b + sqrt(discr))/(2*a);
cout<<"result= "<<result<<endl;
if(k==result){
//Se umbraliza la imagen contenida en
float umb= static_cast<float>(result/255); //frame de acuerdo con el valor
for(int i=0; i <fils; i++)
//determinado
for(int j=0; j <cols; j++){
if(frame[i*cols + j]<=umb){
frame[i*cols + j]=0.0001;
}
else if(frame[i*cols + j]>umb){
frame[i*cols + j]=1;
}
}
salvar_imagen(frame, filename3, "I");
cout<<"umbral "<<umb<<endl;
//se guarda la imagen en un archivo
}
else{
umbral(result);
}
} //llave del if interno
else{
cout<<"aja"<<endl;
for(int i=0; i <fils; i++)
for(int j=0; j <cols; j++)
frame[i*cols + j]=1;
//determinado
salvar_imagen(frame, filename3, "I");
}
} //llave del if externo
else{
cout<<"ajaaa"<<endl;
for(int i=0; i <fils; i++)
for(int j=0; j <cols; j++)
frame[i*cols + j]=1;
//determinado
salvar_imagen(frame, filename3, "I");
}
}
int imagen::column(void)
{
return cols;
}
int imagen::fil(void)
{
59
return fils;
}
float imagen::getpixel(int i, int j)
{
//obtiene el valor de un pixel
float valpixel;
valpixel = frame[i*cols + j];
return valpixel;
}
int *imagen::centroide(){
//La función centroide calculará el centroide del objeto
int n=0;
xsum=0;
ysum=0;
for (int i=0; i<fils; i++){
for(int j=0;j<cols; j++){
if(frame[i*cols + j] < 0.5){
n++;
xsum+=j;
ysum+=i;
}
}
}
if(n!=0){
arreglo[0]=static_cast<int>(xsum/n);
arreglo[1]=static_cast<int>(ysum/n);
return(arreglo);
}
else{
cout<<"No se registra ningun objeto"<<endl;
}
}
/* Estas funciones que utilizan ImageMagick sirven para colocar en
archivos las imagenes generadas al concluir cada procesamiento */
void imagen::salvar_imagen(unsigned char *data, char *filename, char *colorspace)
{
MagickLib::ExceptionInfo exception;
MagickLib::Image *image;
MagickLib::ImageInfo *image_info;
/* Bug? de imagemagick: 255 -> 0 si la imagen está en valores de intensidad*/
if(strcmp(colorspace, "I") == 0) {
int i;
for(i = 0; i < cols * fils; i++)
if(data[i] == 255)
data[i] = 254;
}
MagickLib::InitializeMagick((char *)NULL);
MagickLib::GetExceptionInfo(&exception);
image_info = MagickLib::CloneImageInfo((MagickLib::ImageInfo *) NULL);
image = ConstituteImage(cols, fils, colorspace, MagickLib::CharPixel, data, &exception);
(void)strcpy(image->filename, filename);
MagickLib::WriteImage(image_info, image);
MagickLib::DestroyImage(image);
MagickLib::DestroyExceptionInfo(&exception);
MagickLib::DestroyImageInfo(image_info);
MagickLib::DestroyMagick();
}
void imagen::salvar_imagen(float *data, char *filename, char *colorspace)
{
unsigned char* imaprub;
imaprub=new unsigned char [cols*fils];
int k=0;
for (int i=0; i<fils; i++){
for(int j=0; j<cols; j++){
imaprub[k]=static_cast<unsigned char>(data[i*cols+j]*254.0);
k++;
}
}
MagickLib::ExceptionInfo exception;
MagickLib::Image *image;
MagickLib::ImageInfo *image_info;
60
/* Bug? de imagemagick: 255 -> 0 si la imagen está en valores de intensidad*/
if(strcmp(colorspace, "I") == 0) {
int i;
for(i = 0; i < cols * fils; i++)
if(imaprub[i] == 255)
imaprub[i] = 254;
}
MagickLib::InitializeMagick((char *)NULL);
MagickLib::GetExceptionInfo(&exception);
image_info = MagickLib::CloneImageInfo((MagickLib::ImageInfo *) NULL);
image = ConstituteImage(cols, fils, colorspace, MagickLib::CharPixel, imaprub, &exception);
(void)strcpy(image->filename, filename);
MagickLib::WriteImage(image_info, image);
MagickLib::DestroyImage(image);
MagickLib::DestroyExceptionInfo(&exception);
MagickLib::DestroyImageInfo(image_info);
MagickLib::DestroyMagick();
delete [] imaprub;
}
programaprincipal.cc
SSO v0.7
Sistema de Seguimiento de Objetos
programaprincipal.cc: implementación del programa principal
Copyright (C) 2006 Luis Federico Gómez Salazar <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not,
write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
/* Programa Principal */
#include "acceso.h"
#include <gtkmm.h>
int main(int argc, char *argv[])
{
Gtk::Main kit(argc, argv);
//Inicializa GTKmm
if(!Glib::thread_supported()) Glib::thread_init();
//Inicializa threads para GTKmm
acceso X;
//Se instancia un objeto de tipo acceso
}
61

Documentos relacionados