Universidad de Valladolid T Tratamiento de Imagen y Sonido DR AF Notas de Clase Author: Luis M. Fuentes March 8, 2013 Chapter 1 Qt 1.1 1.1.1 Introducción a Qt Presentación DR AF T Podemos definir Qt como una libreria/biblioteca de clases para el desarrollo de interfaces de usuario basados en C++. La aplicación ası́ desarrollada puede ser compilada y ejecutada en Windows, Mac OSX, linux y varias ramas de Unix. Existe una versión de Qt para la ejecución en plataformas móbiles. Qt is a cross platform development framework written in C++ Qt incluye soporte para otras tecnologı́as como OpenGL, XML, bases de datos, Webkit, multimedia, multithreading, iternacionalización, etc. Dispone además de enlaces o bindings para utilizar otros leguajes de programación como Python, Ruby o C#. DR AF T Qt proporciona además un entorno de programación o IDE propio: Qt Creator ası́ como un programa para la creación gráfica de los GUI s Qt Designer y un sistema de ayuda propio Qt Assistant, como programa externo, además del incorporado en el entorno de programación. Figure 1.1: Program de ayuda para Qt: Assistant Multitud de programas comerciales utilizan Qt, podemos citar Autodesk Maya, The Foundry’s Nuke, Adobe Photoshop Album, Google Earth, Skype, VLC media player, VirtualBox, Dassault DraftSight, Mathematica, etc. y empresas o entidades como la Agencia Espacial Europea, DreamWorks, HP, KDE, Lucasfilm, Panasonic, Philips, Samsung, Siemens, Volvo, Walt Disney Animation Studios o Research In Motion, etc. En resumen, Qt presenta una opción para la programación de GUI s con multiples ventajas, entre las que destacan la portabilidad entre los sistemas operativos más comunes y la presencia de untorno de programación integrado. Para finalizar, Qt es gratuito para el desarrollo de aplicaciones de código abierto: http://qt-project.org Figure 1.2: Algunos usuarios y aplicaciones Qt Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 3 1.1.2 Instalación La forma más sencilla de instalar Qt es la descarga del Qt SDK que contiene: • El entorno de trabajo Qt: las librerias de clases tanto en binario como en código fuente, los ficheros de cabecera y la documentación. • El entorno de desarrollo integrado IDE Qt Creator • Una utilidad para la actualizacion de los módulos del kit • Las APIs para desarrollar una gran variedad de aplicaciones y servicios de plataformas móviles o familiares para los usuarios de dichas plataformas, Qt Mobility. DR AF T • La nueva tecnologı́a declarativa para el desarrollo rápido de GUIs, Qt Quick. Incluye QML, un lenguaje similar a CSS + JavaScript. Al escribir estas notas, el SDK conteniendo las librerias Qt 5.0.1 y el entorno de desarrollo Qt Creator 2.6.2 se puede descargar de (http://qt-project.org/downloads). El archivo descargado es un ejecutable (.exe en Windows, .dmg en MacOSX y .run en linux) preparado para instalar las librerı́as en formato y localización acorde con el sistema operativo (es probable que, bajo linux, haya primero que hacer ejecutable el fichero de instalacion chmod u+x instalador.run). No obstante, el paquete de linux está desarrollado para Ubunto, por lo que, para evitar complicaciones, es recomendable instalar las librerias y el entorno de desarrollo usando los gestores de paquetes de la distribución que se utilice: • (K)ubuntu : qt-sdk • Debian, OpenSUSE y Gentoo : qt-creator • Arch Linux : qt qt-doc qt-creator • Chakra linux : qt5-5.0.1-1 qtcreator-2.6.2-1 ..... Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 4 En particular, en las distribuciones usando el escritorio KDE (entre las que recomendamos Chakra) las librerias ya están instaladas por defecto, luego solo se precisa la instalación del entorno de desarrollo y la documentación. Por último señalar que Qt está programada en C++ y los programas que se creen con esta librerı́a deberan ser compilados con un compilador de C++. Esto es posible hacerlo en un entorno de programacio como Qt Creator u otros como Visual Studio o Eclipse descargando e instalando los plug-ins o añadidos necesarios. Sin embargo, es tambien posible compilar y ejecutar desde la linea de comando. Para ello existe la utilidad qmake que simplifica este proceso y lo hace viable independientemente de la plataforma utilizada. qmake genera el archivo makefile, requerido para la compilacion con make, o proyectos para Microsoft Visual Studio basandose en la inforación sobre el programa contenida en el fichero proyecto (programa.pro). 1.1.3 Qt Creator DR AF Introducción T Qt Creator1 es un entorno de desarrollo integrado (IDE) pensado para la creación de aplicaciones que utilicen el entorno de programación Qt, desarrollado para la creación de programas con interfaces de usuario complejas para su posterior distribución a varios R R R sistemas operatorios (Microsoft Windows , Mac OS X , and Linux ). Qt Creator fué desarrollado para responder a las necesidades de los drogramadores que utilizaban Qt y buscaban simplicidad, productividad, capacidad de expansión y apertura manteniendo a la vez una curva de aprendizaje asumible para los recien llegados. Las caracterı́sticas de Qt Creator permiten a los programadores: • iniciar el desarrollo de una aplicación de una forma sencilla y rápida con el Project Wizard o reanudar el trabajo donde se dejó mediante un acceso rápido y sencillo a las sesiones y proyectos más recientes. • utillizar el editor integrado (Qt Designer ) para el desarrollo de aplicaciones con interfaz de usuario basado en las widgets de Qt. • desarrollar aplicaciones utilzando el editor de código C++ . • construir, ejecutar y empaquetar proyectos basados en Qt para multitud de plataformas desktop y móbiles como Microsoft Windows, Mac OS X, Linux, Symbian, MeeGo, o Maemo. • hacer debug con los debuggers GNU y CDB usando el entorno gráfico • acceder de forma rápida y sencilla a la documentación con el sistema integrado de ayuda Qt Help Qt Creator está también disponible (http://qt-project.org/downloads#qt-creator) para su descarga individual, proporcionando archivos de instalación para distintas plataformas. 1 http://qt-project.org/wiki/QtCreatorWhitepaper Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 5 Crear un proyecto Al ejecutar por primera vez Qt Creator, o si no está programado para cargar automáticamente el último proyecto, el programa despliega el modo Welcome con las siguientes pestaña: • Getting started, con una introducción al IDE, su interfaz de usuario, etc. • Develop, que muestra los proyectos y archivos más recientes • Examples, que permite el estudio y ejecución de la colección de ejemplos que ilustra el funcionamiento de los distintos módulos de Qt • Tutorials, videos para el aprendizaje de Qt Como ejemplo sobre el que trabajar más adelante, creemos un proyecto con el sistema automático que proporciona el programa: T 1. Para ello seleccionamos la pestaña Develop en el modo Welcome (los modos se encuentra en la parte superior del lateral izquierdo), seleccionando del panel de la izquierda la opción Create Project. DR AF 2. Se abre una ventana para elegir el template del nuevo proyecto: seleccionamos Applications y de las opciones ofrecidas para este template, elegimos Qt Gui Application y presionamos el botón choose. 3. Ahora seleccionamos el lugar en nuestro sistema de archivos donde se almacenrá el proyecto y su nombre prueba. Acontinuación se selecciona el kit, es decir, la plataforma para la que se desarrolla, elegiremos Desktop y sólo el modo Debug, presionando continue. 4. A continuación se nos pregunta por el nombre de la clase principal de la GUI de nuestro programa, normalmente la llamarameos MainWindow y heredará la clase QMainWindow de Qt; los nombres de los archivos cabecera mainwindow.h y fuente mainwindow.cpp serán sugeridos teniendo en cuenta el nombre elegido para la clas. Por último nos aseguramos que la opción para le edición de la interfaz con Qt Designer está desactivada (generate form sin seleccionar). 5. Tras presionar continue nos aparece un resumen del proyecto y se nos pregunta si queremos mantener un sistema de versiones, que no seleccionaremos, por lo que terminaremos presionando Done. El proyecto ası́ creado ya es compilable y ejecutable y crea una ventana vacia. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 6 T DR AF Figure 1.3: Pantalla de entorno de desarrollo qt-creator Escribiendo código Para empezar a escribir código se selecciona el archivo en la lista de la derecha estando en el modo Edit. El editor de texto ofrece, entre otras, las siguientes ayudas al programador: • resalta de las palabras clave, sı́mbolos, constantes y macros en los ficheros C++. • ofrece completar la palabra para elementos, propiedades, incluyendo las clases creads por el usuario. • comprueba la sintaxis del código, marcando errores mientras se escribe, haciendo fácil la localización de errores tipográficos sin necesidad de compilar. • puede comprimir o expandir (code folding) funciones en el código fuente, facilitando la navegación por el mismo. • localiza de forma rápida, ficheros, funciones, sı́mbolos y otro tipo de información • permite hacer debug con inspección de variables, puntos de ruptura, etc. • integra un sistema de ayuda presionando F1 sobre la palabra clave o clase elegida, o activando el modo de ayuda con busqueda por temas, etc. En definitiva, Qt Creator ofrece un entorno de desarrollo bastante completo para la creación de aplicaciones basadas en Qt. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 7 1.1.4 Módulos de Qt Qt es una librerı́a modular que se ha ido expandiendo desde el módulo principal de widgets para la creación de interfaces gráficos de usuario. QtCore Es el módulo que contiene el nucleo de las clases no gráficas. Todos los demás módulos se basan en el. Contiene clases como QDir para gestionar directorios, QFile para escritura o lectura de ficheros o QVector para el manejo de matrices dinámicas. QtGui QtOpenGL DR AF T El módulo que contiene los componentes necesarios para la creacion de interfaces gráficas, widgets, es QtGui. Entendemos por widget cualquiera de los elementos que forma parte de una interfaz gráfica de usuario, desde la ventana principal a un botón, ası́ como otras necesarias para definir sus atributos, comportamiento o colocación. Podemos poner como ejemplo QColor para la definición y manejo de colores, QPushButton para la creación de botones para presionar o QToolBar para la creación de una barra de herramientas. Este módulo ofrece clases que facilitan el uso de OpenGL en aplicaciones Qt, siendo la más importante QGLWidget, una widget que admite y ejecuta todos los comandos OpenGL. QtNetwork Aglomera las clases creadas para interaccionar con la red y crear aplicaciones que lo hagan, tales como QFtp, QSslKey o QTcpSocket. QtMultimedia Es un módulo esencial que proporciona un conjunto de clases para el manejo de contenido multimedia y el acceso al hardware multimedia, camara, microfono y altavoces, tales como QCamera, QAudioInput o QMediaPlayer. QtSql Permite la programación de aplicaciones para el tratamiendo de bases de datos. Las APIs de este módulo están divididas en tres capas: driver, SQL API e interfaz de usuario. La primera de ellas Driver Layer proporciona el puente a bajo nivel para conectar la base de datos y las APIs SQL usando clases como QSqlDriver o QSqlResult. La capa API SQL API Layer proporciona acceso a las bases de datos usando las clases QSqlDatabase o QSqlQuery. Finalemenet, la capa de interfaz de usuario User Interface Layer comunica los batos de la base de datos con las widgets preparadas para acogerlos usando clases como QSqlQueryModel. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 8 Otros Qt dispone además de otros módulos para la programación de dontenidos especı́ficos, como QtSvg, QtQml, etc. Se puede obtener una información más completa ası́ como la enumeración de todas las clase implicads y ejemplos de uso en: h t t p : // q t −p r o j e c t . o r g / doc / q t −5.0/ q t d o c / modules . html 1.2 qmake y archivos de proyecto .pro Los ficheros de proyecto *.pro contienen toda la información necesaria para que qmake construya la aplicación, librerı́a o plugin. Las fuentes utilizadas en el proceso se especifcan en el fichero mediante una serie de declaraciones. Modificadores adicionales permiten la especificación de recursos especı́ficos de cada plataforma. A continuación aparece un resumen del contenido del manual on-line sobre qmake, 1.2.1 Variables T h t t p : // q t −p r o j e c t . o r g / doc / q t −4.8/ qmake−manual . html DR AF En el fichero de proyecto las varables se utlizan como contenedores de listas de cadenas de caractéres (strings). En un fichero de proyecto muy simple aparecen únicamente la configuracion a usar y los nombres de ficheros involucrados TARGET = prueba TEMPLATE = app SOURCES += main . cpp mainwindow . cpp HEADERS += mainwindow . h qmake busca ciertas variables en el fichero de proyecto y usa su contenido para la creación del fichero Makefile. En el ejemplo anterior, se debe de construir una aplicación llamada prueba con los ficheros fuente main.cpp y mainwindow.cpp y el cabecera mainwindow.h. Veamos algunas de estas variables: • CONFIG Opciones generales del proyecto (release, debug, warn on, ...) • DESTDIR El directorio en el que se situará el ejecutable o fichero final • FORMS Lista de ficheros .ui con la información de la interfaz gráfica • HEADERS Lista de ficheros cabecera .h usados en el proyecto • QT Opciones de configuración especı́ficas de Qt (+opengl) • RESOURCES lista deficheros .rc a incluir eb el proyecto final • SOURCES lista de ficheros de código fuente utilizados en la construcción del programa • TEMPLATE el patró usado en la construcción de la aplicación (app, lib, vcapp, ...) Se puede acceder a los contenidos de una variable con el operador $$ y asignar, por ejemplo, los contenidos de una variable a otra: TEMP SOURCES = $$SOURCES Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 9 1.2.2 Otros conceptos Espacios en blanco Normalmente, las variables contiene listas de valores separados por espacios en blanco. Sin embargo, cuando uno de estos valores contiene un espacio en blanco, el valor debe aparecer entre comillas, DEST = ” Program F i l e s ” INCLUDEPATH += ”C: / m y l i b s / e x t r a h e a d e r s ” tratandose al texto entre comillas como un elemento de la lista de valores. Se usa también el entrecomillado para especificar paths que contengan espacios Comentarios Es posible añadir comentarios en un fichero de proyecto. Los comentarios deben comenzar con el carácter # y pueden extenderse sólo una lı́nea DR AF T # Comments u s u a l l y s t a r t a t t h e b e g i n n i n g o f a l i n e , but they # can a l s o f o l l o w o t h e r c o n t e n t on t h e same l i n e . Funciones internas y control del flujo Existen funciones internas a qmake que permiten el procesamiento del contenido de las variables. La más usada es la función include seguida del nombre de un fichero de proyecto cuyo contenido es incluido en lugar de la función. i n c l u d e ( o t h e r . pro ) . Se pueden usar tambien contenidos condicionados, como los llamados scope que permiten condicionar una parte del fichero a la plataforma, pues sólo se ejecutan cuando la condición se cumple: win32 { SOURCES += p a i n t w i d g e t w i n . cpp } La variable win32 adquiere automáticamente el valor true bajo el sistema opertivo Windows, pero puede inicializarse tambien en otras plataformas con la opción -win32 en la ejecución de qmake. hay que destacar que el primer paréntesis debe situarse en la misma lı́nea que la condición. Hay que destacar que kas opciones especificadas en CONFIG pueden usarse tambien como condicionante: CONFIG( o p e n g l ) { message ( B u i l d i n g with OpenGL s u p p o r t . ) } else { message (OpenGL s u p p o r t i s not a v a i l a b l e . ) } Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 10 1.2.3 Declaración de librerı́as Si la variable CONFIG contiene el valor qt se habilita el soporte para aplicaciones Qt. Esto posibilita ajustar los módulos de Qt que van a utilizarse en la aplicación. Esto se sonsigue utilizando la variable QT, que puede utilizarse para declarar los módulos que requerirá la aplicación. Ası́, por ejemplo, para utlizar los módulos de red y bases de datos introducirı́amos en el fichero de proyecto: CONFIG += qt QT += network xml Por defecto, qmake incluye los dos módulod básicos en la construcción del programa QtCore y QtGui. Si, por ejemplo, no queremos usar interfaz gráfica podemos eliminar el segundo de ellos con QT −= g u i LIBS += −L/ u s r / l o c a l / l i b −lmath T Si se desea utilizar otras librerı́as externas en la aplicación, hay que decalararlas en el fichero de proyecto. Los directorios en los que qmake debe buscar las librerı́as especificadas se añaden a la variable LIBS usando la notación tı́pica de los sistemas estilo UNIX: −L para especificar directorios y −l para ficheros, sin espacios en blanco: DR AF La especificación de las directorios de los ficheros cabecera se realiza usando la variable INCLUDEPATH, separados por un espacio en blanco. INCLUDEPATH = c : / msdev/ i n c l u d e d : / s t l / i n c l u d e 1.2.4 Resources: Imagenes, iconos, etc Se pueden usar imágenes para mejorar el impacto visual, iconos para personalizar el ejecutable, sonidos para determinados eventos, etc. Qt permite que sus proyectos utilicen o incorporen dichos elementos, llamándolos fuentes (resources). Este tipo de archivos están almacenados en diferentes directorios del disco lo que dificulta la portabilidad del proyecto. La ventaja de incorporar dependencias como éstas en el fichero de proyecto es que se puede acceder a ellas usando paths que no dependen de la computadora en uso y que se incorporan automáticamente al instalar el ejecutable. Qt proporciona dos métodos para usar iconos estándar: uno es obtenerlo del estilo usado en la presentación del desktop QStyle::standardIcon() y el otro del algun plugin con temas o iconos QIcon::fromTheme(). También es posible usar iconos o imágenes de otras fuentes. Para ello se crea un fichero de fuentes (.qrc), en formato XML, que se ha de añadir al proyecto: <!DOCTYPE RCC> <RCC v e r s i o n=” 1 . 0 ”> <q r e s o u r c e > < f i l e >images / copy . png</ f i l e > < f i l e >images / c u t . png</ f i l e > < f i l e >images /new . png</ f i l e > </q r e s o u r c e > </RCC> Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 11 Por defecto, los ficheros son accesibles desde la aplicación usando el mismo nombre especificado en el fichero de fuentes, con el prefijo :/ (:/images/cut.png) o accediendo en formato URL qrc (qrc:///images/cut.png). Pero podemos ismplificar el acceso, caso de que el nombre resultaria demasiado largo, usando el atributo alias del comenado file: < f i l e a l i a s=” c u t . png”>images / c u t . png</ f i l e > y accediendo a el como :/cut.png. También es posible especificar un path comun para todos los ficheros usando el atributo prefix del comando qresource y accediendo con el path :/resources/cut.png: <q r e s o u r c e p r e f i x=” / r e s o u r c e s ”> < f i l e a l i a s=” c u t . png”>images / c u t . png</ f i l e > </ q r e s o u r c e Antes de usarse, el fichero de fuentes ha de ser compilado. Para ello se introduce en el proyecto añadiendo en el archivo .pro la lı́nea (normalmente nombre coincide con el nombre de la aplicacion): RESOURCE += nombre . q r c 1.3 1.3.1 DR AF T Qt-Creator es también un editor de archivos de fuentes y puede ser utilizado para su creación o modificación, siendo más sencillo añadir ası́ nuevos ficheros. QObject: Signals y Slots QObject QObject es la clase base de todos los objetos Qt (clases derivadas de ella), la base de su modelo de funcionamiento con signals y slots para comunicarse con otros objetos. Se puede conectar una signal con un slot con connect() y deshacer la conexión con disconnect(), bloquear señales con blockSignals() o seguir la estela de conexiones con las funciones protegidas connectNotify() y disconnectNotify(). Los objetos se organizan en árboles. Cuando se crea unobjeto nuevo especificando otro objeto como padre, este último le añade de forma automática a su lista de hijos, tomando posesión de él, es decir, destruirá automáticamente todos sus hijos en su destrutor. Cuando se destruye un objeto, se emite de forma autmática la señal destroyed(). QObject es la clase base en la mayorı́a de las clases QT. Algunos ejemplos de excepciones son las clases que deben ser pequeñas o ligeras (como las primitivas gráficas), las clases que son contenedores de datos (como QString o Qlist) y las clases que necesitan la posibilidad de ser copiadas (los QObjects no puden copiarse). Esto se debe a una caracterı́stica de QObject, cada instancia es única: QObject instances are individuals! y, por lo tanto, pueden tener un nombre propio (QObject::objectName),pueden comunicarse con otros objetos y ocupan un lugar en la jerarquı́a de objetos. Todos los objetos se preprocesan y convierten en meta-objetos. Cada clase que se derive de un QObject (o de otra clase derivada) y quiera contar con el mecanismo de signals Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 12 y slots debe incorporar en la primera linea de coódigo de su definición la macro Q OBJECT. El meta-objeto tiene información sobre la clase del objeto (QObject::className), sus propiedades, sus signals y slots e información en general suminstrada en la definición de la clase. Esta información se recopila en tiempo de compilación de los ficheros de cabecera que contienen las declaraciones de las clases mediante el met-object compiler o moc: T Figure 1.4: Proceso de construcción de un proyecto Qt C++ DR AF Las propiedades de los QObjects son manipuladas usando métodos estádar denominados coloquialmente setter y getter, uno para dar un valor a la propiedad y otro para obtenerlo: setter devuelve void y lleva el valor a asignar como único argumento mientras que getter no tiene argumentos y devuelve el valor. La existencia de este tipo de funciones otorga ciertas ventajas, como la posibilidad de comprobar los valores asignados o reaccionar a los cambios. Existe también un convenio para sus nombres: • No boleanas : propiedad - setPropiedad • boleanas : isEnabled - setEnabled Estas propiedades pueden accederse directamente, usando getter y setter o a través del sistema de meta-objetos: QString t e x t = l a b e l −>t e x t ( ) ; QString t e x t = o b j e c t −>p r o p e r t y ( ” t e x t ” ) . t o S t r i n g ( ) ; l a b e l −>s e t T e x t ( ” H e l l o World” ) ; o b j e c t −>s e t P r o p e r t y ( ” t e x t ” , ” H e l l o World” ) ; para finalizar volvamos con el manejo de memoria. Como se mencionó con anterioridad, cuando en la creación de un objeto se especifica su padre, el objeto creado pasa a pertenecer al arbol del progenitor y por lo tanto será destruido de forma automática cuando este se destruya, lo que simplifica enormementa la programación al no tener que verificar la destrucción de dichos objetos; resulta muy útil al implementar jerarquias visuales en las ventanas: QDialog ∗ padre = new QDialog ( ) ; QGroupBox ∗ c a j a = new QGroupBox ( padre ) ; QPushButton ∗ boton = new QPushButton ( padre ) ; QRadioButton ∗ o p c i o n 1 = new QRadioButton ( c a j a ) ; QRadioButton ∗ o p c i o n 2 = new QRadioButton ( c a j a ) ; delete p a r e n t ; Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 13 donde padre destruye caja y boton mientras caja borra opcion1 y opcion2. Por supuesto, los objetos pueden cambiar de progenitor: obj−>s e t P a r e n t ( nuevoPadre ) ; 1.3.2 Signals y Slots Signals y Slots, o señales y procedimientos, constituyen la herramienta básica en la comunicación entre QObjects y la caracterı́stica principal de las librerı́as Qt, que hace fácil la implementación del Observer Pattern. Cualquier Widget (o clase derivada de QObject creada por el usuario) emite señales cuando ciertos eventos tienen lugar. Por ejemplo, un botón emite la señal clicked cuando es presionado. El usuario puede optar por conectar esa señal (signal) creando una función (slot) y luego conectando ambas con la función connect(). c o n n e c t ( button , SIGNAL( c l i c k e d ( ) ) , qApp , SLOT( q u i t ( ) ) ) ; DR AF T Esta comunicación no requiere que las clases que emite la señal y la que ejecutará el procedimiento se conozcan entre sı́, lo que simplifica la reutilización de las clases. Signals y Slots son type-safe, es decir, sólo admiten argumentos del tipo declarado y los errores de tipo se anuncian como advertencias sin causar la interrupción del programa. Figure 1.5: Mecanismo Signal-Slot de Qt Además, se pueden añadir o eliminar conexiones en tiempo de ejecución, se pueden ejecutar los meétodos de forma inmediata cuando se emite la señal o ponerlos en cola para una ejecución retardada e incluso se pueden comunicar objetos que se están ejecutando en distintos hilos. La implementación del mecanismo signal-slot se realiza en C++ usando el preprocesador y el compilador moc (Meta-object Compiler). La generación de éste código se realiza Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 14 de forma automática y el programador no necesita editar o incluso ojear dicho código. Para finalizar, un apunte sobre codificación 2 , Signals y Slots son más reutilizables si no utilizan argumentos de tipos especiales. Signals Slots DR AF T Un objeto emite una señal cuando su estado interno ha cambiado de forma que pueda resultar interesante para el cliento o el propietario de dicho objeto. Sólo la clase en la que está definida una señal, o sus derivadas, pueden emitir dicha señal. Cuando se emite una señal, los slots conectados a ella suelen ser ejecutados inmediatamente, como cualquier llamada a una función, haciendo al mecanismo signal-slot totalmente independiente de cualquier evento generado por la GUI. La ejecución del código situado detras de la función emit se llevará a cabo una vez que todos los slots hayan terminado su ejecución. Cuando se ejecutan conexiones a cola, el codigo que sigue a emit se ejecuta inmediatamente y los slots se ejecutarán posteriormente. Si hay varios slots conectados con la misma signal, éstos serán ejecutados uno tras otro en el mismo orden en que se declaró la conexión tras la emisión de la señal. El moc genera automáticamente la implementación de las señales que el programador debe únicamente definir, en el fichero de declaración de la clase (un fichero cabecera .h), usando el tipo void, ya que no pueden devolver ningún valor. Un slots es ejecutado cuando la señal a la que está conectado es emitida. Los slots son funciones C++ normales y pueden ser ejecutados fuera de la conexión signal-slots, siendo si caracterı́stica peculiar el hecho de que pueden ser conectados con señales. Al ser funciones normales, siguen las reglas básicas de acceso según su tipo de declaraciõ (public, protected o private). Como slots, sin embargo, peden ser llamados fuera de esas reglas via conexión con señales. Esto significa que una señal emitida por un ejemplar de una clase arbitraria puede causar la ejecución de un private slot en un ejemplar de una clase no relacionada. Comparados con otro tipo de tratamiento de eventos, como los callbacks, signals and slots es logeramente más lento debido a su mayor flexibilidad, aunque dicha diferencia en aplicaciones reales es insignificante. En general, emitir una señal conectada a varios slots es unas 10 veces más lento que evocar los procedimientos directamente. Aunque pueda parecer mucho, este retraso es menor que le conlleva laejecución de operaciones como new o delete. Para una mayor ilustración del tiempo requerido, comentar que con un procesador i586-500 se pueden emitir unos 2.000.000 de señales por segundo, si están conectadas a un sólo slot o unas 1.200.000 si tienen dos receptores. Conexión Una señal de un objeto puede conectarse con slots de uno o más objetos siempre y cuando dichos slots existan y los parámetro sean compatibles. la sintaxis completa de la función de asignación connect es : 2 http://qt-project.org/doc/qt-4.8/signalsandslots.html Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 15 Figure 1.6: Conexiones viables segun los argumentos QObject : : c o n n e c t ( QObject ∗ , char ∗ , QObject ∗ , char ∗ , Qt : : ConnectionType ) siendo su redacción usual algo como: connect T bool ( e m i s o r ∗ , SIGNAL( s i g n a l ( type ) ) , r e c e p ∗ , SLOT( s l o t ( type ) ) ) ; DR AF El último argumento enum Qt::ConnectionType es opcional siendo e valor por defecto Qt::AutoConnection. Esta variable enum describe el tipo de conexión a establecer, en particular, determina cuando la señal es enviada al receptor inmediatamente o a través de una cola. Los posible valores que puede tomar son: • Qt::AutoConnection (0) : es el valor por defecto. Si la señal es emitida desde un hilo distinto al del receptor, se situa a la cola (Qt::QueuedConnection), si no ocurre ası́, el slot es invocado inmediatamente (Qt::DirectConnection). Esto hace que el tipo de conexión se determine en el momento en que se emite la señal. • Qt::DirectConnection (1) : el slot se ejecuta justo después de emitirse la señal. • Qt::QueuedConnection (2) : la ejecución del slot se retrasa hasta el momento en que el control vuelve al ciclo de eventos del hilo del receptor: el slot se ejecuta en el ciclo del receptor. • Qt::BlockingQueuedConnection (4) : Igual que el anterior salvo que el hilo en ejecución se bloquea hasta que el slot devuelve el control. Esta conexxión sólo debe usarse cuando emisor y receptor están en distintos hilos de ejcución, de no hacerse ası́ la aplicación puede entrar en un ciclo infinito. • Qt::UniqueConnection (0x80) : Igual que el primero pero la conexión se realiza sólo si no repite una conexión pre-existente, es decir, si la misma señal ya está conectada con el mismo slot. • Qt::AutoCompatConnection (3) : Es la conexión por defecto cuando se habilita la compatibilidad con Qt 3. Es igual que la primera pero causará algunas alarmas en determinadas situaciones. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 16 Con conexión a cola, los parámetros deben ser de tipos conocidos por moc, puesto que Qt debe almacenarlos en un evento oculto. Qt puede ignorar argumentos pero no puede crearlos de la nada. Esta regla sirve para saber qué conexiones son posibles y cuales no, como muestra la figura ??. La conexión signal-slot se rompe cuando cualquiera de los objetos involucrados es destruido o cuando se realiza dicha desconexión usando la funcion bool 1.3.3 QObject : : d i s c o n n e c t ( QObject ∗ , char ∗ , QObject ∗ , char ∗ ) QApplication y el bucle de eventos Las aplicaciones Qt con interfaces gráficas interactivas tienen un control de flujo distinto de las aplicaciones de linea de comando: estan guiadas por eventos (event-driven). Patrón de Diseño OBSERVER DR AF T Al escribir programas guiados por eventos las GUI necesitan responder al cambio de estado de ciertos objetos. Cunado se produce un cambio de estado en un objeto se necesita una forma de advertir (y quizas enviar información) a los objetos son observadores. Los observadors son objetos que están esperando eventos de cambio de estado y, cuando se producen, responden adecuadamente. El patrón de diseño que permite los mecanismos de transmisión de mensajes de este tipo entre objetos se llama Patrón Observvador. Por supuesto, hay muchas formas de implementar este patrón, pero todas ellas tienen algunas caracterı́sticas comunes: • Se permite la separación concreta entre clases sujeto y clases observador • Se permite la emisión múltiple de comunicaciones 1 a n • El mecanismo utilizado para enviar la información del sujeto al observador queda especificado en la definición de clase del sujeto La clase QEvent encapsula un evento de bajo nivel. Es la clase base para las clases asociadas con eventos especı́ficos como QActionEvent, QFileOpenEvent, QHoverEvent, QInputEvent o QMouseEvent. El sistema gráfico crea eventos de este tipo como respuesta a acciones del usuario (QMouseEvent), a intervalos de tiempo determinados (QTimerEvent) o de forma explı́cita por una aplicación. La función miembro type() devuelve una variable enum identificando el evento (Close, DragEnter, Enter, MouseMove, Resize, etc). El bucle de eventos (event-loop) es una estructura de programa que permite priorizar, poner en lista de espera y enviar eventos y mensajes entre objetos. Escribir una aplicación basada en eventos implica implementar una estructura pasiva de funciones que respondan a eventos como clicks de ratón, teclas, señales emitidas, eventos del gestor de ventanas o mensajes de otros prgramas hasta la recepción del evento que pare su ejecución. Este bucle de eventos se gestioan en una aplicación Qt en el programa main mediante el procedimiento exec() de la clase QApplication, como sucede en la aplicación ejemplo creada anteriormente: #i n c l u d e ”mainwindow . h” #i n c l u d e <QApplication > Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 17 int main ( int argc , char ∗ argv [ ] ) { Q A p p l i c a t i o n a ( argc , argv ) ; MainWindow w ; w. show ( ) ; return a . e x e c ( ) ; } La llamada a la función QApplication::exec() se ejecuta en la sentencia return al final del programa dando lugar al inicio de la parte interactiva de la aplicación. 1.4 Widgets DR AF T Una widget es un objeto gráfico de una clase derivada de QWidget que tiene una represntación visual en la pantalla. La estructura original de QWidget, figura ??, muestra multi-herencia: es un QObject, lo que le permite tener padres e hijos junto con señales y slots, y un QPaintDevice, la clase base de todos los objetos mostrados en pantalla. Figure 1.7: Linaje de QWidget en el arbol de clases de Qt Al ser elementos gráficos, las widgets incorporan relaciones visuales en el parentaje: una widget sin padre es una ventana o una widget hijo está contenida dentro del padre y colocada según indique la disposició asociada a ésta. Aunque QWidget es la más sencilla de las clases de QtGui (consiste en un rectángulo vacı́o), es una cas complicada con cientos de funciones y capaz de manejar eventos respondiendo a mensajes, pintarse en la panatalla, borrarse de la misma y respetando siempre el resto de elementos presentes. Una aplicación con GUI para computadoras Desktop puede contener muchas (a veces cientos) de widgets creadas segú una relación padre-hijo determinada y dispuestas de acuerdo a las espcificaciones de ciertos layouts. 1.4.1 Tipos o categorı́as de widgets Las QWidgets se pueden ordenar en tipos o categorı́as par facilitar su búsqueda. Algunas de ellas, las más complicadas, podrı́an pertenecer a más de una de las cuatro categorı́as Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 18 Figure 1.8: Algunos ejemplos de widgtes 1.4.2 DR AF T básicas: Button widgets (QPushBotton), Input widgets (QSpinBox), Display widgets (QLabel) y Container widgets (QToolBar). Estas widgets básicas son los ladrillos con los que se construyen widgets más complicadas con funciones muy determinadas como QFileDialog (para seleccionar un archivo del sistema) o QErrorMessage (para mostrar mensajes de error). Layouts: disposición espacial de las widgets Hay widgets que se presentan como ventanas individuales (QDialog) pero la mayorı́a pueden formar parte de una ventana de mayor tamaño. Cuando queramos organizar widgets pequeñas dentro de una mayor debemos usarLayouts. Un layout es un objeto (hereda QObject pero no QPaintDevice, luego no es una widget) que pertenece (es hijo de) una sóla widget y su tarea es controlar de forma dinámica el espacio ocupado y la distribución de las wodget hijas de su dueña. Hay varios tipos de layouts que organizan las widgets de distinta forma: QHBoxLayout horizontalmente, QVBoxLayout verticalmente, QGridLayout en una tabla o QFormLayout en un impreso. Los layouts pueden usarse recurrentemente (de forma anidada). Las widgets se organizan secuencialmente cuando se añaden al layout usando la función: void QLayout : : addWidget ( QWidget ∗ ) Cuando se añade una widget a un layout, pasa a ser hija de la widget que posee el layout (porque una widget no puede ser nunca hija de un layout y sólo puede tener un hijo layout). Finalmente, los layouts pueden tener hijos layouts: pueden añadirse igual que una widget usando le función: void QLayout : : addLayout ( QLayout ∗ ) 1.4.3 Tamaño de widgets y layouts Aunque cada widget puede tener un tamaño fijo establecido con setGeometry(), posiciones y ta,años absolutos no suelen usarse por eliminar la flexibilidad del cambio de Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 19 tamaño de la ventana. Sin embargo, puede controlarse el comportamiento por defecto cuando haya un redimensionamiento usando sizePolicy() y sizeHint(). La priopiedad sizeHint almacena el tamaño recomendado para cada widget (el utilizado en su primera presentación en la pantalla) mediante un valor QSize (almacena anchura y altura). Hay tambien varias funciones que permiten controlar el tamaño de una widget en tiempo de ejcución como setMinimumSize(), setMaximumSize(), setMinimumHeight(), setMaximumWidth(), setSizeHint(), etc. Además, cada widget (y layout) tienen reglas para regular su redimensionamiento vertical y horizontal expresados usando QSizePolicy. QBoxLayout tiene funciones especı́ficas para regular los espacios entre las widgets que maneja: • addSpacing(int size) añade un número fijo de pixels al final del layout • addStretch(int stretch=0) añade un numero de pixels expandible: empieza con un mı́nimo y se puede expandir hasta usar todo el espacio disponible. • addStrut(int size) impone un mı́nimo al tamaño del layout en su dirección perpendicular (anchura de un QVBoxLayout o altura de un QHBoxLayout). 1.4.4 DR AF T Estas caracterı́sticas se pueden utilizar para regular el comportamiento de la widget cuando ésta se redimensiona. Widgets complejas Frente a las widgets simples que componen los elemntos básicos de una GUI, Qt ofrece también widgets más complejas, productos terminados, listos para ser utilizados con un propósito muy determinado. Veamos algunos ejemplos. • Para la obtención del nombre de un archivo con el que posteriormente trabajar, Qt ofrece un procedimiento asociado con la clase QFileDialog:getOpenFileName() con una flexibilidad total de uso, sin argumentos o especificando path inicial y filtros para los ficheros a mostrar: QString f i c h e r o = Q F i l e D i a l o g : : getOpenFileName ( ) ; QString f i c h e r o = Q F i l e D i a l o g : : getOpenFileName ( this , ”Open F i l e ” , ” /home” , ” Images ( ∗ . png ∗ . xpm ∗ . j p g ) ” ) ; Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 20 • Para mostrar un dialogo de opción SI o NO (por ejemplo respondiendo a la pregunta Desea abandonar la aplicación? ), utilizando el procedimiento ligado a la clase QMessageBox, una heredera de QDialog, QMessageBox::question int r e s p u e s t a = QMessageBox : : q u e s t i o n ( this , NULL, ” Desea abandonar l a a p l i c a c i o n ? ” , QMessageBox : : Yes | QMessageBox : : No ) ; T • Existen también dialogos ya creados para elegir un color o un tipo de letra usando los métodos QColorDialog::getColor y QFontDialog::getFont, por ejemplo de la siguiente forma: DR AF bool ok ; QColor c o l o r = QColorDialog : : g e t C o l o r ( ) ; QFont f o n t = QFontDialog : : getFont (&ok , QFont ( ” H e l v e t i c a ” , 1 0 ) , t h i s ) ; 1.5 1.5.1 Odds and Ends Memoria A la hora de crear interfaces gráficas se nos plantea el problema de la localización: heap o stack. Cuando se crea un objeto con el comando new la memoria utilizada está situada en el heap y debe ser liberada explicitamente con el comando delete, aunque hasta ese momento esta accesible. Las variables locales se almacenan en el stack y son Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 21 destruidas automaticamente al abandonar el entorno (normalmente una función) en el que se crearon. Recordando entonces la relación de los objetos con sus progenitores, sólo el padre debe ser almacenado en la pila o stack: int main ( int argc , char ∗∗ argv ) { Q A p p l i c a t i o n a ( argc , argv ) ; mainWindow w ; w. show ( ) ; return a . e x e c ( ) ; } mainWindow : : mainWindow ( . . . ) { QLabel ∗ l b = new QLabel ( t h i s ) ; new . . . . . . } To get automatic memory management, only the parent needs to be allocated on the stack. 1.5.2 Constructores T Existen algunas reglas de etiqueta a seguir en la codificación de constructores. Veamos algunas de ellas: DR AF • Casi todos los QObjects pueden tener un progenitor con valor por defecto nulo 0 QObject ( QObject ∗ p a r e n t =0); • Los progenitores de QWidgets son otras QWidgets • En Qt las clases tienden a proporcionar muchos constructores, incluyendo uno con el progenitor como único argumento. QPushButton ( QWidget ∗ p a r e n t =0); QPushButton ( const QString &t e x t , QWidget ∗ p a r e n t =0); QPushButton ( const QIcon &i c o n , const QString &t e x t , QWidget ∗ p a r e n t =0); • El progenitor es, normalmente, el primer argumento con un valor por defecto QLabel ( const QString &t e x t o , QWidget ∗ p a r e n t =0, Qt : : WindowFlags f =0); Según esto, al crear un QObject (una QWidget por ejemplo) conviene permitir que el progenitor tenga valor nulo, tener un constructor que tenga como único argumento al progenitor y que cuando tenga más, que sea éste el primero con valor por defecto. 1.5.3 Impresión de informacion en tiempo de ejecución Para obtener información de algunos parámetros en tiempo de ejecución sin entrar en modo Debug, podemos usar la clase QDebug. Su utlización es muy sencilla, se debe incluir el include en el fichero cpp correspondiente y luego redirigir variables o cadenas segun formato C++ Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 22 c l a s s MiClase : public QObject { Q OBJECT \\ Macro Q CLASSINFO( ” a u t o r ” , ” L u i s M. Fuentes ” ) \\ I n f o r m a c i o n G e n e r a l Q PROPERTY ( Dato dato READ dato WRITE s e t D a t o ) public : MiClase ( const Dato &dat , QObject ∗ p a r e n t =0) ; Dato dato ( ) const ; \\ G e t t e r public s l o t s : \\ Pa labr a Clave de Qt void s e t D a t o ( const Dato &dat ) ; \\ S e t t e r signals : \\ Pa lab ar a Clave de Qt void datoCambiado ( Dato ) ; private : Dato mDato ; } ; T Figure 1.9: Ejemplo de código de una clase basada en QObject DR AF #i n c l u d e <QDebug> ............. int v a l o r = 234 , v a l o r 1 = 564 ; QString s t = QString ( ” terminada mainWindow” ) ; QDate hoy = QDate ( 2 0 1 3 , 2 , 2 1 ) ; QSize s z ( 1 8 , 9 ) ; qDebug ( ) << v a l o r << ” metros y ” << v a l o r 1 << ” c e n t i m e t r o s ” ; qDebug ( ) << s t << hoy << hoy . t o J u l i a n D a y ( ) << s z ; Es capaz de mostrar variables definidas por Qt como el dı́a con QDate, un tamaño con QSize, ası́ en el ejemplo mostrado se obtendrı́a una salida como: 234 metros y 564 centimetros "terminada mainWindow" QDate("Thu Feb 21 2013") 2456345 QSize(18, 9) 1.6 Ejercicios y preguntas 1. Que es Qt? 2. Que lineas de codigo necesitas para una aplicacion minima de Qt? 3. Que es un fichero .pro? 4. Que es qmake y cuando es una buena idea su uso? 5. Que es un modulo Qt y como lo habilitas en tu proyecto? 6. Conexión entre widgets. Relacionar dos widgets ( QSpinBox y QSlider o QSpinBox y QDial) de forma que el cambio en una se refleje inmediatamente en la otra. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 23 T DR AF Figure 1.10: Ejemplo de una aplicación Qt para el tratamiento de sequencias de video Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 24 Chapter 2 OpenCV 2.1 2.1.1 Introducción a OpenCV Presentación DR AF T OpenCV (Open Source Computer Vision) es una librerı́a abierta que contiene más de 500 algoritmos oprimizados para el análisis de imágenes y videos y disponible en http://SourceForge.net/projects/opencvlibrary. Esta librerı́a está escrita en C y C++, existiendo versiones para Windows, Linux y Mac OS X, con desarrollo activo para otros lenguajes como Python, Ruby, Matlab etc. Desde su creación en 1999 ha sido adoptada como la herramienta de referencia por la comunidad de investigadores en Computer Vision1 . Desarrollado originalemente como R un proyecto de Intel liderado por Gary Bradski como una iniciativa para lanzar la investigación el algoritmos de visón artificial. Después de una serie de lanzamientos beta, la versión 1.0.0 se lanzó en octubre de 2006. A ella le siguieron la 2.0.0 en octubre de 2009 y la 2.1.0 en abril del 2010. A partir de entonces los lanzamientos se sucedieron a un ritmo mayor hasta llegar a la versión actual 2.4.3 lanzada en noviembre del 2012. OpenCV fué diseñada pensando en su eficiencia computacional y con vistas a aplicaciones a tiempo real (desarrollado en un principio en paralelo con la librerı́a Intel’s Integrated Performance Primitives -IPP- y que OpenCV utiliza de forma automática si están disponibles). Desarrollada inicialmente en C y con posibilidad de utilizar procesadores multinucleo, la versión 2.0 introdujo un cambio sustancial al incorporar el uso de C++. Las funciones disponibles abarcan multitud de areas dentor de Computer Vision como inspección de productos, tratamiento de imágenes médicas, calibración de cámaras, visión estereo, robótica e inteligencia artificial (Machine Learning Library). 1 En estas notas se usará el término inglés Computer Vision en vez de su traducción al español Visión por ordenador o visión artificial 2.1.2 Instalación Se pueden descargar los ficheros de instalación del link principal del proyecto en SpurceForge o de la nueva web OpenCV.org: • http://SourceForge.net/projects/opencvlibrary • http://opencv.org/downloads.html • http://opencv.willowgarage.com/ que ofrece versiones para Windows, Linux/Mac, Android e iOS, con links a las páginas correspondientes en SourceForge. En el primer caso el archivo descargado es un ejecutable (.exe) y en el segundo un archivo comprimido (.tar.bz2). Para obtener información sobre OpenCV, como su instalación, se puede consultar también la tercera opción. Windows DR AF T La instalación es extremadamente sencilla, simplemente se ejecuta el programa de instalación que instalará los ficheros necesarios, registraá los filtros DirectShow y creará las variables de entorno necesarias para que el compilador encuentre los archivos. Linux Debido a que los sistemas linux son muy variados y las versiones del compilador gcc y las librerias básicas como glibc varı́an con la distribución, bajo linux deben compilarse todas las librerı́as de OpenCV. Como regla general se necesitará la instalción previa de las siguientes librerı́as y cabeceras: pkconfig, libpng, zlib, libjpeg, libtiff and libjasper. Tambié se necesitará Python 2.6 en versión development ası́ como las librerı́as asociadas a ffmpeg: libavcodec, libavformat and libavutil. Para compilar e instalar OpenCV, se ejecutará desde un terminal en el directorio principal creado al descomprimir el archivo: $> $> $> $> ./ configure make sudo make i n s t a l l sudo l d c o n f i g La librerı́as se instalarán, por defecto, en /usr/local/lib/ y los ficheros cabecera en /usr/local/include/opencv/. Para que los compiladores o IDEs encuentren dichas librerı́as, se puede especificar dicho directorio en el fichero de configuración o añadirlo en el archivo /etc/ld.so.con y ejecutar ldconfig después. También existe la posibilidad de añadirlo al la variable de entorno LD LIBRARY PATH. 2.1.3 Linux o Mac También es posible construir OpenCV usando el compilador del sistema, gcc 4.3 o superior en linux y XCode 3.2 o superior en Mac OSX, y el programa CMake (http://www.cmake.org) en versión 2.6 o superior. El sistema debe tener instalado el siguiente software opcional en version de desarrollo (librerı́as y ficheros de cabecera): Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 26 1. Python en versiones 2.6.x o 2.7.x también en version de desarrollo 2. Intel TBB versión 2.2 o superior, para permitir codificación en paralelo de algunos algoritmos (opción WITH TBB=ON en CMake). 3. Qt versión 4.6 o superior, con lo que las ventanas HighGUI utilizarán esas librerı́as gráficas (opción WITH QT=ON en CMake). 4. CUDA la versión del toolkit más reciente, para aprovechar la GPU (solo para NVidia GPUs). DR AF T A continuación ejecutamos el programa CMake con interfaz gráfica. Tras elegir el directorio donde está descomprimida la versión de OpenCV a construir y el directorio donde se generarán los binarios, se presiona la opción Configure y se enmendan los errores que surjan o se completa la información requerida (valores en rojo). Se presiona Configure otra vez y, cuando no se reclame correcciones u opciones se prsiona Generate, que generará los archivos necesarios (Makefile) para que la orden make compile OpenCV para el sistema operativo instalado. Figure 2.1: Pantalla de Cmake tras ejecutar Configure Si la compilación se llevó a cabo sin errores, ya hay una librerı́a OpenCV instalada y lista para su uso en el directorio especificado en CMake. Comprobar que las variables de entorno están definidas para que los compiladores puedan acceder a los ficheros de cabecera y librerı́as (en Windows → Control Panel → System Utility → Advanced Tab → Environment variables). Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 27 2.2 Usando OpenCV Desde la versión 2.2 OpenCV se divide en varios módulos (algo similar a lo que ocurre con Qt). Estos módulos se corresponden con ficheros de librerı́as que habrá que incluir en el proyecto: • opencv core, el módulo básico que contiene las funcionalidades centrales, incluyendo las estructuras de datos básicas y las funciones aritméticas. • opencv imgproc contiene las principales funciones usadas en el tratamiento de imágenes. • opencv highgui es el módulo que contiene las funciones de lectura y escritura de imágenes y videos, ası́ como la creación de interfaces de usuario ofrecida por OpenCV. • opencv features2d contiene las funcionalidades para Feature Point Detectors y el marco de trabajo de Feature Point Matching. T • opencv calib3d que contiene las funciones para la calibración de cámaras, estimación de la geometrı́a con dos vistas y las funciones de visión estereo. DR AF • opencv video se utiliza en la estimación de movimiento, seguimiento y extracción de objetos. • opencv objdetect contiene las funciones de detcción de objetos, especialmente de caras y personas. Existen además otros módulos que contienen utilidades como funciones de aprendizaje Machine Learning (opencv ml), algoritmos de geometrı́a computacional (opencv flann) y código especializado (opencv contrib), obsoleto (opencv legacy) o diseñado para sacar provecho de la GPU (opencv gpu). Cada uno de estos módulos dispone del fichero de cabecera asociado y que tiene que ser incluı́do si vamos a hacer uso de las funciones incluı́das en el módulo: #i n c l u d e <opencv2 / c o r e / c o r e . hpp> #i n c l u d e <opencv2 / imgproc / imgproc . hpp> #i n c l u d e <opencv2 / h i g h g u i / h i g h g u i . hpp> Además hay que proporcionar el acceso a la ruta de dichos includes, lo que haremos en el fichero de proyecto de Qt añadiendo tantas como sistemas operativos en los que se pueda compilar el proyecto, por ejemplo, Windows en el ordenador de casa y Linux en el del laboratorio: macx : INCLUDEPATH += / opt / l o c a l / i n c l u d e l i n u x −g++: INCLUDEPATH += / u s r / i n c l u d e win32 : INCLUDEPATH += C: \ OpenCV2 . 2 \ i n c l u d e \ macx : LIBS += −L” / opt / l o c a l / l i b ” −l o p e n c v c o r e l i n u x −g++: LIBS += −l o p e n c v c o r e −l o p e n c v h i g h g u i −l o p e n c v i m g p r o c win32 : LIBS += −LC: \ OpenCV2 . 2 \ l i b −l o p e n c v c o r e 2 2 0 Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 28 Bibliografı́a DR AF 2.3 T Figure 2.2: Vistazo modular de la bibioteca de funciones OpenCV Se pueden encontrar textos de OpenCV, • Learning OpenCV: Computer Vision with the OpenCV Library. • OpenCV 2 Computer Vision Application Programming Cookbook • Mastering OpenCV with Practical Computer Vision Projects. Figure 2.3: Libros de OpenCV Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 29 Chapter 3 Imágenes : Qt + OpenCV 3.1 Introducción T Todos tenemos una idea de lo que constituye una imagen. El diccionario de la Real Academia de la Lengua nos dá varias definiciones: • Figura, representación, semejanza y apariencia de algo. DR AF • Estatua, efigie o pintura de una divinidad o de un personaje sagrado. • Reproducción de la figura de un objeto por la combinación de los rayos de luz que proceden de él. • Representación viva y eficaz de una intuición o visión poética por medio del lenguaje. Todas ellas identificadas en el momento de leerlas. Ninguna de estas definiciones es válida en el marco en el que nos encontramos. Necesitamos hablar de la imagen digital. En el caso partucular de imagen digital monocroma, la definición es sencilla: una imagen digital es una representación bidimensional de una imagen a partir de una matriz numérica, es decir, es una función discreta I de N2 en N restringida a unos intervalos determinados: I : [0, w] × [0, h] −→ [0, 2n − 1] (x, y) −→ I(x, y) donde w es la anchura y h la altura de la imagen y n es la profundidad de bits (normalmente n = 8). Cada punto del conjunto P = [0, w]×[0, h] se denomina Pixel mientras que el valor I(x, y) está relacionado con la irradiancia de la zona representada por el punto (x, y) en el mundo real. La generalización a imagen digital en color es inmediata: en lugar de asignar a cada pixel de la imagen un valor numérico correspondiente a su intensidad luminosa, le asociamos un conjunto de números que nos permita reproducir su color y alguna caracterı́stica adicional. En general se utilizan tres valores numéricos que determinan su color RGB, pudiendo añadir uno extra para informacion adicional (como el grado de transparencia o el valor γ en un monitor), RGBA. En general para una imagen en color de tres canales, hay tres valores numéricos (u1 ,u2 ,u3 ), normalmente de 8 bits, asociados con cada pixel p0 (x, y) de la imagen. Estos valores caracterizan el color del pixel en el espacio de color elegido[?]. Desde el punto de vista de la imagen digital, existen dos grupos de espacio de color, aquellos que separan la información de luminancia o intensidad luminosa de la información de color (Y U V, Y CB CR , Lab, ...) y los basados en las curvas de sensibilidad espectral de los receptores dej ojo humano (RGB, XY Z, ...). La gran mayoria de las imágenes en formato digital se almacenan en el sistema RGB, pero el tratamiento o la codificación pueden mejorarse usando otros sistemas con separación de color como el Lab o el Y uv respectivamente. 3.2 Imágenes en Qt 3.2.1 DR AF T Las librerı́as de Qt proporcionan tres clases para trabajar con imágenes: QImage, QPixmap and QPicture. La primera de ellas, QImage está diseñada para permitir el acceso directo a los pı́xeles, su manipulación y sencillas operaciones de lectura/escritura (I/O). QPixmap está diseñada y optimizada para su representación en pantalla mientras que QPicture es un lienzo (paint device) que permite grabar y reproducir comandos QPainter (de escritura de palabras, dibujar cı́rculos, rectángulos, lı́neas, etc.). Parece claro que para el tratamiendo digital de imágenes nos interesa trabajas con QImage1 . Antes de desarrolar las caracetrı́sticas especı́ficas de QImage, debemos mencionar que hereda QPaintDevice y, por lo tanto, se puede utilizar directamente como lienzo en cualquier comando de QPainter. Formatos QImage está diseñada para almacenar imágenes y puede hacerlo usando diferentes formatos, descritos por una variable enum denominada Format. A continuación se enumeran algunas de los posibles formatos para el almacenamiento de la imagen: • QImage::Format Invalid (0) : La imagen no es válida. • QImage::Format Mono (1) : se usa un bit por pixel (blanco o negro), almacenando primero el bit más significativo (MSB). • QImage::Format Indexed8 (3) : ı́ndice de 8 bits a un mapa de color. • QImage::Format RGB32 (4) : formato RGB con 32 bits (0xffRRGGBB). • QImage::Format ARGB32 (5) : formato ARGB usando 32 bits (0xAARRGGBB). • QImage::Format RGB16 (7) : formato RGB de 16 bits (5-6-5). • QImage::Format RGB888 (13) : formato RGB de 24 bits (8-8-8). Además de almacenar la imagen, la clase QImage proporciona una colección de métodos para la obtención de una gran variedad de información sobre la imagen, para su manipuación o su transformación. 1 http://qt-project.org/doc/qt-5.0/qtgui/qimage.html Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 31 Extensión BMP GIF JPG JPEG PNG PBM PGM PPM TIFF XBM XPM Descripción Soporte Qt Windows Bitmap Read/write Graphic Interchange Format (optional) Read Joint Photographic Experts Group Read/write Joint Photographic Experts Group Read/write Portable Network Graphics Read/write Portable Bitmap Read Portable Graymap Read Portable Pixmap Read/write Tagged Image File Format Read/write X11 Bitmap Read/write X11 Pixmap Read/write Lectura/Escritura DR AF 3.2.2 T Table 3.1: Formatos manejados por QImage La clase QImage proporciona varios métodos para cargar una imagen con los datos que la representan. Se pueden extraer de un archivo en disco, bien al crear el objet0: QImage ( const char ∗ const [ ] xpm ) QImage ( const QString & fileName , const char ∗ format = 0 ) leyendo una imagen XPM en el primer caso o un archivo imagen con extensiones varias, tabla ??, o posteriormente leyendo de un archivo con el método load: bool QImage : : l o a d ( QString & fileName , char ∗ format = 0 ) que intentará leer una imagen del archivo filename con el formato format, de no proveer dicho formato, el método lee la cabecera del fichero para conocer el formato. Finalmente, también es posible crear una QImagen con una conjunto de datos si se especifica el formato para poder interpretarlos: QImage QImage QImage QImage ( ( ( ( uchar uchar uchar uchar ∗ data ∗ data ∗ data ∗ data , , , , int int int int width , int h e i g h t , Format fm width , int h e i g h t , Format fm wid , int h e i , int b y t e s L i n e , wid , int h e i , int b y t e s L i n e , ) ) Format fm ) Format fm ) Para grabar la imagen almacenada por QImage se usa el método save(), con posibilidad de especificar el formato (“PNG”) siendo el factor de calidad un valor entero comprendido entre 0 (pequeña y muy comprimida) y 100 (grandes y sin comprimir) siendo −1 el valor por defecto. bool s a v e ( QString &fileName , char ∗ format =0, int q u a l i t y=−1 ) Es, por lo tanto, muy sencillo leer y escribir imágenes desde QImage. Existe un añadido o plug-in que proporciona soporte para otros tipos de formato (MNG, TGA, TIFF o WBMP). Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 32 3.2.3 Información sobre la imagen La clase QImage proporciona una colección muy completa de funciones que proporcionan una muy variada información sobre la imagen almacenada por ella: • Geometrı́a. Podemos obtener información sobre la forma de la imagen con métodos como size() para obtener el tamaño de la imagen en una varoable QSize o los métodos width() y height() para obtener la anchura y la altura separadamente. También hay acceso a datos más técnicos como el equivalente a pı́xeles por pulgada (ppi) dotsPerMeterX() o dotsPerMeterY() mientras que rect() devuelve un objeto QRect con el rectángulo ocupado por la imagen, o (0, 0, width(), height()). T • Color. El color correspondiente a un pixel de la imagen se obtiene con la función pixel() que devuelve una variable QRgb (una definición de tipo equivalente a cuatro bytes para almacenatr el formato 0xAARRGGBB) independientemente del formato de la imagen o su equivalente para establecer el valor (230,128,45) del pixel en un punto dado P (a, b): DR AF s e t P i x e l ( QPoint ( a , b ) , QRgb( 2 3 0 , 1 2 8 , 4 5 ) ) En caso de imágenes monocromas o de color a 8-bits, las funciones colorCount() y colorTable()+pixelIndex() proveen de la información necesaria para interpretar el valor almacenado. Podemos extraer información genérica sobre si hay canal alpha presente hasAlphaChannel() o si la imagen es en balnco y negro (niveles de gris en lugar de colores, los tres colores on el mismo valor) isGrayscale() o allGray(). • Low level. La información de la imagen a bajo nivel nos servirá para interaccionar con otras librerı́as, como OpenCV, para inetrcambiar los datos que constituyen la imagen bits() o scanline(). Asi la función depth() devuelve el tamaño en bits del pixel, con valores posibles 1 para imágenes monocromas o 8, 16, 24 y 32 bits. Funciones como bitPlaneCount(), format(), bytesPerLine() y byteCount() proporcionan información adicional. Por último, una función curiosa: cacheKey() que devuleve un úmero que identificará el contenido de la imagen de forma única. 3.2.4 Mostrar una imagen en Qt La forma más sencilla de mostrar una imagen en Qt es asociarla con el pixmap de una widget QLabel. Si tenemos ya una imagen cargada en un objeto QImage llamado img y el label ya esta creado siendo un puntero llamado lab: lab−>setPixmap ( QPixmap : : fromImage ( img ) ) ; Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 33 T Figure 3.1: Programa de lectura de imágenes DR AF Código MainWindow : : MainWindow ( QWidget ∗ p a r e n t ) : QMainWindow ( p a r e n t ) { QWidget ∗cw = new QWidget ( t h i s ) ; imgL = new QLabel ( ) ; imgL−>setPixmap ( QPixmap ( 3 8 4 , 2 8 8 ) ) ; QPushButton ∗ l o a d I = new QPushButton ( ”Load Image ” ) ; QPushButton ∗ s a l i r = new QPushButton ( ” S a l i r ” ) ; QHBoxLayout ∗ h l = new QHBoxLayout ( ) ; QVBoxLayout ∗ v l = new QVBoxLayout ( ) ; hl−>addWidget ( l o a d I ) ; hl−>addWidget ( s a l i r ) ; vl −>addWidget ( imgL ) ; vl −>addLayout ( h l ) ; cw−>s e t L a y o u t ( v l ) ; s e t C e n t r a l W i d g e t ( cw ) ; c o n n e c t ( s a l i r , SIGNAL( c l i c k e d ( ) ) , this , SLOT( c l o s e ( ) ) ) ; c o n n e c t ( l o a d I , SIGNAL( c l i c k e d ( ) ) , this , SLOT( loadImage ( ) ) ) ; setFixedSize ( sizeHint ()) ; } MainWindow : : ˜ MainWindow ( ) { } bool MainWindow : : loadImage ( ) { QString fname = Q F i l e D i a l o g : : getOpenFileName ( this , ” A b r i r Imagen ” , ” path / t o / Images ” , ” Images ( ∗ . png ∗ . j p g ) ” ) ; imgL−>setPixmap ( QPixmap ( fname ) ) ; return true ; } Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 34 3.3 Imágenes en OpenCV La evolución de OpenCV ha producido varias formas de almacenamiento de imágenes2 : IplImage, CvMat y Mat. La primera y más antigua se corresponde con la época de desarrollo paralelo con las Intel Performance Libraries, la segunda es la version en C y la tercera es la clase C++. Por ser la versión más avanzada y en la que se utilzará en posteriores versiones, usaremos la version C++. Como no especificaremos espacios de trabajo (namespaces) por defecto, la clase debe ir asociada con su namespace, cv::Mat. Esto lo usaremos con todas las clases y funciones de OpenCV. La clase cv::Mat es la estructura de datos utilizada en OpenCV para almacenar imágenes. Como su nombre indica, es la misma estructura que se utiliza con matrices de datos. The class cv::Mat is the data structure used to hold your images (and obviously other matrix data). Por defecto, crea una matriz de tamaño nulo, pero puede especificarse su tamaño en el constructor de múltiples maneras. Por ejemplo, los creadores siguientes crean una imagen de tamaño 240×320) de 1 byte de profundidad (monocroma) y llena con el valor 100: cv : : Mat imagen ( 2 4 0 , 3 2 0 , CV 8U , cv : : S c a l a r ( 1 0 0 ) ) ; T donde aparece una variable CV 8U, que especifica la profundidad de la imagen (numero de bits por pixel), si es de variable entera sin signo o con signo (U o S) o real(F) y el número de canales (imagen en color tres o cuatro canales): DR AF CV <b i t d e p t h >(U| F)C<n u m b e r o f c h a n n e l s > es decir, para crear una imagen en color compatible con el formato Qt deberı́amos poner: cv : : Mat imagen ( 2 4 0 , 3 2 0 , CV 8UC4 , cv : : S c a l a r ( 1 0 0 ) ) ; OpenCV también implementa un mecanismo de borrado cuando se abandona el ámbito de aplicación de un objeto cv::Mat. Además, esta clase implementa un mecanismo de referencias y asigna copias huecas hasta que una de ellas se modifica (cuando una imagen se asigna a otra con un = los datos no se copian sino que ambos objetos comparten la dirección de memoria de los datos). Mantener la cuenta de las referencias a los mismos datos permite que sólo al destruirse la última se destruyan los datos (se incluye imágenes devueltas por funciones que se asignan antes de destruirse al acabr la función). Para crear una copia real (dos imágenes en memoria) se debe usar el procedimiento copyTo(): cv : : Mat image2 , image3 ; image2= r e s u l t ; // l a s dos usan l o s mismos d a t o s r e s u l t . copyTo ( image3 ) ; // s e c r e a una c o p i a nueva El almacenamiento en memoria de la imagen por OpenCV presenta una peculiaridad adicional: se almacena por tripletes ordenados BGR en vez de hacerlo según el orden convencional RGB (sigue siendo el mismo espacio de color). OpenCV siempre ha usado este orden, que se corresponde con el orden del espctro de luz visible en longitudes de onda, azlu la más corta y rojo la más larga. Finalmente indicar que, por supuesto, OpenCV también dispone de funciones para la lectura y escritura de imágenes de disco: cv : : Mat bool 2 cv : : imread ( s t r i n g& fname , int f l a g s ) cv : : i m w r i t e ( s t r i n g& fname , InputArray img , v e c t o r <int>& par ) http://docs.opencv.org Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 35 3.4 Conversión entre formatos OpenCV y Qt La conversión de imágenes entre formatos Qt y OpenCV tiene dos puntos delicados, el orden de los canales de color y el canal alpha adicional. Veamos en primer lugar como pasar de una imagen Mat a una QImage. Si la transformación es únicamente para mostrar la imagen sólo requiere de la reordenación de los canales, para lo que se utiliza la función de OpenCV cvtColor: void c v t C o l o r ( InputArray s r c , OutputArray dst , int code , int dstCn=0) DR AF T una función generalista que sirve para cambiar los valores de los pı́xeles entre distintos espacios de color (CV BGR2Lab, CM RGB2YUV, etc.). Posteriormente, sólo hay que acceder a los datos de la imagen con imageD.data y usarlos en la construcción de un objeto QImage junto con el numero de filas imageD.rows y el de columnas imageD.cols teniendo en cuenta que no hay canal alpha (formato RGB de 24 bits). Finalmente se escribe en el QLabel usado para mostrar imágenes: cv : : c v t C o l o r ( imageO , imageD ,CV BGR2RGB) ; QImage img = QImage ( ( const unsigned char ∗ ) ( imageD . data ) , imageD . c o l s , imageD . rows , QImage : : Format RGB888 ) ; imgL−>setPixmap ( QPixmap : : fromImage ( img ) ) ; O, de un modo alternativo, podemos poner también, aprovechando para cambiar el formato a ARGB (32 bits) y usando la función de QImage para rotar canales: Código QImage i 1 , i 2 , imgF ; cv : : Mat frm ; const uchar ∗ qIB ; \\ Se o b t i e n e u o p e r a con l a imagen en OpenCV frm qIB = frm . p t r ( ) ; i 1 = QImage ( qIB , frm . c o l s , frm . rows , frm . s t e p , QImage : : Format RGB888 ) ; i 2 = i 1 . convertToFormat ( QImage : : Format ARGB32 , Qt : : AutoColor ) ; imgF = i 2 . rgbSwapped ( ) ; La conversión opuesta, es un poco más complicada. Implica la creacion de un objeto Mat de cuatro canales para almacenar los datos de la imagen, ası́ como otros dos objetos Mat, uno con tres canales, para la imagen, y otro de uno para almacenar los datos del canal alpha. Aprovechando el método mixChannels, una funcón que permite barajar y separar canales dentro de una imagen, para cambiar el orden de los canales y separar el canal alpha. Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 36 Código DR AF T cv : : Mat matF ; QImage imgO ; uchar ∗ p t r ; int a l t o , ancho , b p l ; \\ Se o b t i e n e u o p e r a con l a imagen en Qt imgO a l t o = imgO . h e i g h t ( ) ; ancho = imgO . width ( ) ; p t r = ( uchar ∗ ) b i t s ( ) ; bpl = bytesPerLine ( ) ; cv : : Mat mat = cv : : Mat ( a l t o , ancho , CV 8UC4 , ptr , b p l ) ; cv : : Mat r g b I = cv : : Mat ( mat . rows , mat . c o l s , CV 8UC3 ) ; cv : : Mat a l p h = cv : : Mat ( mat . rows , mat . c o l s , CV 8UC1 ) ; cv : : Mat o c v I [ ] = { r g b I , a l p h } ; int fromTo [ ] = { 0 , 2 , 1 , 1 , 2 , 0 , 3 , 3 } ; cv : : mixChannels ( &mat , 1 , ocvI , 2 , fromTo , 4 ) ; matI = r g b I ; Luis M. Fuentes Tratamiento de Imagen y Sonido, UVa 37