Universidad Politécnica de Valencia

Transcripción

Universidad Politécnica de Valencia
Universidad Politécnica de Valencia
Departamento de Comunicaciones
Proyecto Final de Carrera
Desarrollo de una interfaz gráfica
para programa de reconocimiento
audiovisual
Presentado por:
Javier Ferrandis San Cirilo
Dirigida por:
Dr. Alberto Albiol Colomer
València, 24 de Octubre de 2003.
Índice
1. Introducción y objetivos
1.1. Contenido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2. DirectX
2.1. DirectX . . . . . . . . . . . . . .
2.2. DirectShow . . . . . . . . . . . .
2.3. COM, Component Object Model
2.3.1. Objetos COM . . . . . . .
2.3.2. Interfaces y objetos . . . .
2.3.3. GUIDs . . . . . . . . . . .
2.4. Objetos COM en DirectShow . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3. Manejo de funciones multimedia
3.1. Vı́deo . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1.1. Librerı́a VICTOR . . . . . . . . . . . . . . . .
3.1.2. Problemática . . . . . . . . . . . . . . . . . .
3.1.3. Extracción de fotogramas . . . . . . . . . . .
3.1.4. Ejemplo Básico del DirectX . . . . . . . . . .
3.2. Audio . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1. Multimedia Streaming . . . . . . . . . . . . .
3.2.2. Extracción de audio . . . . . . . . . . . . . . .
3.2.3. Funciones para el tratamiento del audio . . . .
3.2.4. Ejemplo: Reproducción de Audio con DirectX
3.3. Librerı́a creada . . . . . . . . . . . . . . . . . . . . .
3.3.1. Vı́deo . . . . . . . . . . . . . . . . . . . . . .
3.3.2. Audio . . . . . . . . . . . . . . . . . . . . . .
4. Teorı́a del reconocimiento
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
5
.
.
.
.
.
.
.
7
8
9
12
15
17
18
19
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
22
22
24
37
39
39
43
43
44
46
46
61
67
1
2
ÍNDICE
4.1. Reconocimiento empleando información visual . . . . . . . . . . .
4.2. Reconocimiento empleando información del audio . . . . . . . . .
5. Programación en Visual Basic y Visual
5.1. Creación de DLLs en VC++ . . . . . .
5.2. Llamada a DLLs en VB . . . . . . . .
5.3. Llamada a DLLs en VC++ . . . . . .
5.3.1. Forma directa . . . . . . . . . .
5.3.2. Forma indirecta . . . . . . . . .
5.4. Uso de librerı́as estáticas . . . . . . . .
C++
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
67
73
.
.
.
.
.
.
77
77
80
83
83
84
85
6. Entorno gráfico
6.1. FaceDemo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.2. Recognition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3. ModelGeneration . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
87
91
94
7. Conclusiones
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
101
A. MATLAB
103
A.1. Compilador de MATLAB . . . . . . . . . . . . . . . . . . . . . . . 103
A.2. Generación de código . . . . . . . . . . . . . . . . . . . . . . . . . 104
A.3. Optimizaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
B. Tabla de conversiones entre VB y VC++
107
Capı́tulo 1
Introducción y objetivos
En primer lugar deberemos dar a conocer los objetivos de este proyecto, las
motivaciones que hicieron que surgiera, y las metas que se deben cumplir.
El tratamiento de vı́deos es el principal objetivo del proyecto, es decir, que se
debe conseguir el poder manipular los vı́deos de una manera sencilla y cómoda,
tanto para el usuario como para el futuro programador, que podrá aprovechar
perfectamente cualquiera de las funciones que se lleven a cabo en este proyecto.
Por ello, otro de los objetivos del proyecto es el de crear una librerı́a con todas
las funciones que se hayan creado, de forma que, en un futuro, cualquier programador que tenga acceso a esta librerı́a podrá usar las funciones sin necesidad de
saber cómo funcionan internamente ni introducirse a fondo en ellas.
En primer lugar se nos plantea el problema de elección de la herramienta
que vamos a usar durante el desarrollo del proyecto. Deberemos tener claro que
necesitamos un lenguaje que nos sea útil para crear la librerı́a de funciones, pero
también que sea capaz de crear un entorno gráfico capaz de llevar a cabo todas
las funciones creadas.
Con lo que respecta al entorno gráfico, tras revisar diversos lenguajes de programación, ası́ como distintas plataformas, se opta por elegir como lenguaje de
programación el BASIC (Beginners All-Purpose Symbolic Instruction Code), que
ofrece una gran facilidad con respecto a la creación del entorno gráfico del proyecto. Ası́, se elige el Microsoft Visual Basic 6.0 Edición Profesional como plataforma
para llevar a cabo todo el entorno gráfico del programa. Este programa ofrece una
gran variedad de utilidades para la reproducción de videos, ası́ como para la perfecta representación de imágenes, idea básica para nuestro proyecto. El lenguaje
BASIC se ha utilizado a lo largo de la historia de la informática por más programadores que ningún otro lenguaje. Visual Basic ha evolucionado a partir del
3
4
CAPı́TULO 1. INTRODUCCIÓN Y OBJETIVOS
lenguaje BASIC original y ahora contiene centenares de instrucciones, funciones
y palabras clave, muchas de las cuales están directamente relacionadas con la
interfaz gráfica de Windows. Los principiantes pueden crear aplicaciones útiles
con sólo aprender unas pocas palabras clave, pero, al mismo tiempo, la eficacia
del lenguaje permite a los profesionales acometer cualquier objetivo que pueda
alcanzarse mediante cualquier otro lenguaje de programación de Windows.
En el proyecto, se empleó este lenguaje de programación hasta que surgieron
ciertas dificultades, debidas a que este lenguaje es perfecto para la creación del
entorno gráfico de nuestro proyecto, pero no es nada útil a la hora de programar
ciertas funciones, normalmente de bajo nivel, que eran necesarias para el desarrollo de las utilidades del trabajo. Por ello, se añade otro lenguaje de programación
a nuestro proyecto, que será el C++, y la plataforma que se usa es el Microsoft
Visual C++ 6.0. Este lenguaje es el más óptimo para llevar a cabo todas las
funcionalidades que más adelante explicaremos, ya que es un potente motor de
programación. Pero ahora necesitamos enlazar las dos plataformas, es decir, que
las funciones que se crean en el C++ se deben usar después en el Visual Basic,
que será donde finalmente se lleven a cabo todas las tareas que el usuario desee.
Para hacer esto, se introdujo todas las funciones programadas en C++ en librerı́as
dinámicas de Windows (DLL, Dynamic Link Library), que después pueden ser
llamadas por el Visual Basic a través de módulos. Cuando más adelante se detalle
los aspectos prácticos de la programación, se mostrará algún ejemplo de estos
conceptos.
Cuando vamos a trabajar con vı́deos, no podemos aplicar las funciones de
transformación de imágenes directamente sobre ellos. Los vı́deos llevan una compresión espacial, que reduce el tamaño del fichero resultante, sin mermar mucho la
calidad de la imagen. Pero donde de verdad se lleva a cabo una compresión fuerte
es en la compresión temporal, que aprovecha la redundancia entre un fotograma
y el siguiente y anterior, para no codificar toda la información, sino sólo la que
no se repite. Ası́ se consigue una importante reducción de información, sin que
se deteriore demasiado la calidad del vı́deo resultante. Por lo tanto, previo a la
aplicación de estas funciones, es necesario extraer cada uno de los fotogramas que
se van a usar. Para ello, se hace uso del DirectX, y de todas las funcionalidades
que tiene para el tratamiento de imágenes, tanto estáticas como en movimiento
(vı́deos). En concreto, para llevar a cabo estas funciones de extracción, dentro el
DirectX existe un subconjunto, denominado DirectShow, que es el que se ocupa
realmente de todo lo relacionado con imágenes.
Cabe destacar que ya existen gran cantidad de programas que llevan a cabo
lo que nosotros estamos intentando realizar aquı́. El verdadero problema surge
cuando queremos integrar todas esas funciones a nivel de programador. Con esto
1.1. Contenido
5
quiero decir que ya existen muchos programas disponibles, incluso gratuitamente,
que extraen fotogramas de una secuencia de video, y otros programas que llevan
a cabo otras de las funciones que he llevado a cabo en el trabajo; sin embargo, no
son integrables de forma eficiente en un entorno de trabajo como el que nosotros
buscamos.
Recalcar también que no es objetivo de este proyecto el estudiar ni desarrollar
técnicas de reconocimiento de personajes, sino simplemente adaptar las técnicas
existentes a nuestras necesidades.
1.1.
Contenido
Pasaremos ahora una breve introducción a lo que podemos encontrar en cada
uno de los capı́tulos de esta memoria.
En primer lugar veremos los detalles de la tecnologı́a DirectX, y en concreto el
DirectShow, en el cual se basan la mayorı́a de funciones creadas en este proyecto.
A continuación veremos qué partes concretas de todo el conjunto del DirectX
se han implementado en este proyecto, tanto en la parte visual como de audio,
añadiendo ejemplos prácticos para un mejor entendimiento. Además, se ha añadido una relación de todas y cada una de las funciones creadas, en forma de manual
para los futuros programadores.
El siguiente de los capı́tulos nos presenta una breve introducción a las técnicas
de reconocimiento visual y de audio utilizadas en los programas creados.
Se dedica el siguiente capı́tulo a explicar la forma de programar en cada una
de las dos plataformas utilizadas, para poder enlazar las funciones creadas en
cada una de ellas.
En el último de los capı́tulos, se muestra un detalle de los tres programas
creados, tanto a nivel de entorno gráfico, como a nivel de uso por parte del
usuario final.
Para finalizar, se han añadido dos anexos, dedicándose el primero de ellos al
compilador de MATLAB, y el segundo de ellos a la correspondencia de datos
entre las dos plataformas usadas en el proyecto. ,fig:DirectX
6
CAPı́TULO 1. INTRODUCCIÓN Y OBJETIVOS
Capı́tulo 2
DirectX
Como se ha comentado, el manejo de vı́deos a nivel de programación no es
tan fácil como podrı́a parecerle a un usuario inexperto en los temas multimedia.
En primer lugar, y debido al sistema de compresión que llevan los ficheros
multimedia, vemos que no podremos ”trabajar”1 directamente sobre los ficheros
de vı́deo, ya que lo que realmente nos interesa es el poder extraer la información
de un fotograma en concreto, por lo que se deberá crear algún tipo de función que
permita extraer dicho fotograma de la secuencia de vı́deo, para después poder
trabajar sobre él. Al decir extraer lo que queremos decir es que se deberán ejecutar
los algoritmos necesarios para eliminar las dependencias entre fotogramas, para
poder tener ası́ una imagen estática que represente al fotograma correspondiente.
A la hora de comprimir las secuencias de vı́deo, existen muy diversas maneras
de llevarlo a cabo. Entre todas ellas, prevalecen una serie de estándares, que serán
en los que hemos centrado el proyecto.
Ası́ mismo, en el proyecto también es de gran importancia el tratamiento del
audio. Por ello, también se han estudiado diversas formas de extracción de audio
de los ficheros multimedia, y se comprobó que la forma más práctica y funcional
era mediante el DirectX.
Debido a la importancia del DirectX dentro del proyecto, dedicaremos el
primer punto de este capı́tulo a explicar qué es el DirectX y de los componentes
de los que está compuesto, entre los cuales destaca el DirectShow , dedicándole
por tanto el segundo de los puntos. Dentro de la tecnologı́a DirectX toma gran
importancia el COM (Component Object Model ), por lo que será necesario dedicarle el tercero de los puntos, reservando el último de los puntos a detallar como
1
Con trabajar nos referimos a aplicar funciones de tratamiento digital de imágenes.
7
8
CAPı́TULO 2. DIRECTX
se relacionan el DirectShow y el COM.
2.1.
DirectX
r es un conjunto avanzado de interfaces de aplicación a
Microsoft DirectX°
la programación multimedia (API, Application Programming Interface) desarrolladas expresamente para los sistemas operativos Microsoft Windows. DirectX
proporciona una plataforma de desarrollo estándar para los PCs basados en
Windows, para que los diseñadores de software puedan acceder a caracterı́sticas
concretas del hardware sin tener que escribir código especı́fico. Esta tecnologı́a
fue introducida por primera vez en 1995 y es un estándar reconocido para el
desarrollo de aplicaciones multimedia dentro del entorno Windows.
De forma muy simple, podrı́amos decir que DirectX es una tecnologı́a Windows que permite el tratamiento y representación de gráficos y sonido, usada muy
frecuentemente cuando se están ejecutando juegos o cuando se está viendo vı́deo
en el PC.
En el núcleo del DirectX están su interfaces de aplicación a la programación,
o APIs. Las APIs actúan como un tipo de puente entre el hardware y el software
para ”hablar”entre ellos. Las APIs DirectX dan a las aplicaciones multimedia
acceso a las caracterı́sticas avanzadas del hardware de alto nivel, tal como chips
de aceleración de gráficos tridimensionales y tarjetas de sonido. Controlan además
las funciones de bajo nivel, incluyendo la aceleración de gráficos dimensionales;
dan soporte a los dispositivos de entrada, como el joystick, teclado y micrófono;
y controlan además el tratamiento del sonido y su salida. Gracias al DirectX el
trabajo con los gráficos 3-D y la creación de efectos de música y audio son mucho
más fáciles y productivos.
Microsoft DirectX está compuesto por los siguientes componentes:
DirectX Graphics combina los componentes DirectDraw y Direct3D de versiones anteriores dentro de una única interfaz de aplicación a la programación (API) que puede usarse para toda la parte de programación con
gráficos. Este componente incluye la librerı́a Direct3DX que simplifica las
tareas de programación con gráficos.
DirectX Audio combina los componentes DirectSound y DirectMusic de
versiones anteriores dentro de una única API que puede usarse para toda
la parte de programación con audio.
DirectInput proporciona soporte para una gran variedad de dispositivos de
entrada, incluyendo soporte total para la tecnologı́a force-feedback.
9
2.2. DirectShow
DirectPlay proporciona soporte para la programación de juegos multijugador en red.
DirectShow nos permite capturar audio y video con una gran calidad,
además de reproducción de archivos multimedia.
DirectSetup es un API simple que proporciona la instalación de todos los
componentes DirectX de una sola vez.
En la figura 2.1 podemos ver de forma gráfica cada uno de los componentes
de la tecnologı́a DirectX.
Figura 2.1: Componentes del DirectX.
2.2.
DirectShow
Microsoft DirectShow es una arquitectura, incluida dentro del grupo del DirectX, que se creó para ayudar a la manipulación de archivos multimedia dentro
de la plataforma Windows. DirectShow proporciona funciones para la captura
de multimedia con buena calidad, además de reproducción de streams multimedia (ya sean audio, vı́deo,...). Soporta gran variedad de formatos, incluyendo
ASF (Advanced Streaming Format), MPEG (Motion Picture Experts Group), AVI
(Audio-Video Interleaved ), MP3 (MPEG Audio Layer-3 ), y archivos WAV. Soporta también la captura usando los dispositivos WDM (Windows Driver Model )
o incluso los más viejos dispositivos de ’Video for Windows’. La forma de trabajar de esta arquitectura es utilizando el hardware de aceleración de vı́deo y
10
CAPı́TULO 2. DIRECTX
audio, cuando detecta que está disponible, aunque también existe la posibilidad
de trabajar sin estos dispositivos.
DirectShow simplifica todas las tareas de tratamiento de multimedia, proporcionando acceso a la arquitectura de control de streams, para que las aplicaciones
puedan crear sus propias soluciones propietarias, como por ejemplo el aplicar
efectos sonoros a archivos de audio, o dar soporte a cualquier nuevo formato de
archivo.
Algunos ejemplos de los tipos de aplicaciones que podemos escribir con el
DirectShow incluyen reproductores de DVD, aplicaciones de edición de vı́deo,
conversores de AVI a ASF, reproductores de MP3, o aplicaciones de captura de
vı́deo digital.
DirectShow está basado en el COM (Component Object Model ). Para escribir
una aplicación en DirectShow, se deberá entender perfectamente la programación
COM. De forma muy resumida, podremos decir que el COM es un estándar que
contiene cierto número de interfaces, las cuales a su vez contendrán funciones
especı́ficas para cada tarea que deseemos llevar a cabo. Ası́, en nuestra aplicación
tendremos objetos COM, que representarán a las interfaces, y desde los que podremos llamar a los métodos correspondientes. Explicaremos estos conceptos con
detalle en la sección 2.3 (véase figura 2.4 en la página 14 para tener una visión
general de estos conceptos).
En la mayorı́a de aplicaciones, no se necesitan implementar nuestros propios
objetos COM. DirectShow ya proporciona los componentes que necesitamos. Pero
si queremos escribir nuestros propios componentes, como ocurre en nuestro caso,
deberemos implementarlos usando los objetos COM (véase sección 2.3.1).
Arquitectura
La arquitectura DirectShow define cómo controlar y procesar los streams de
datos multimedia, usando componentes modulares llamados filtros. Los filtros
tienen pins de entrada, pins de salida o ambos, y están conectados entre ellos
en una configuración llamada gráfico de filtros. Un gráfico de filtros completo
está compuesto de un cierto número de filtros, unidos en una sucesión lógica desde
la fuente de datos hasta el reproductor. Las aplicaciones usan un objeto COM
llamado administrador de gráfico de filtros para ensamblar los filtros dentro
del gráfico y mover los datos a través del mismo. Por defecto, el administrador
de gráfico de filtros trata los datos automáticamente; por ejemplo, si queremos
reproducir un vı́deo, él mismo se encarga de insertar automáticamente el codec
2.2. DirectShow
11
adecuado, si es que es necesario, e inmediatamente conecta la salida de dicho
codec a un filtro transformador, que adaptará los datos para pasarlos a un filtro
reproductor, que finalmente reproducirá el vı́deo que querı́amos, y todo ello de
forma automática, sin tener que dar nosotros ninguna orden en especial. Podemos
ver de forma gráfica los filtros y pines de entrada y salida en la figura 2.2.
Figura 2.2: Gráfico de filtros para la reproducción de un vı́deo.
Por supuesto, si queremos podremos especificar los filtros que queremos que
use y las conexiones que deseemos, si es que no queremos usar la configuración
por defecto que nos presenta el DirectShow.
El administrador de gráfico de filtros proporciona un conjunto de interfaces
COM para que las aplicaciones puedan acceder al gráfico de filtros. Las aplicaciones pueden llamar directamente a las interfaces del administrador de gráfico de
filtros para controlar los streams multimedia o recibir eventos de filtros; o pueden
usar el control ActiveMovie1 para reproducir los archivos multimedia.
De esta forma, vemos que tenemos varios caminos para acceder a la tecnologı́a
DirectShow:
Mediante el COM, tal y como veremos en el punto siguiente, y que será la
forma con la que hemos trabajado en nuestro proyecto.
Mediante el control ActiveMovie, mucho más fácil e intuitivo, pero con
mucha menos movilidad y con muy pocas opciones de configuración.
1
ActiveMovie es un control válido para Visual Basic y para páginas HTML. Proporciona una
interfaz de programación fácil de usar, mediante el uso de los eventos, propiedades y métodos
del control.
12
CAPı́TULO 2. DIRECTX
Mediante el MCI (Media Control Interface), de gran potencia pero muy
tedioso y sin demasiadas ventajas al primer punto.
En la figura 2.3 representamos estas formas de acceso con mayor claridad.
Figura 2.3: Métodos de acceso al DirectShow.
2.3.
COM, Component Object Model
Desde un punto de vista práctico, un objeto COM es básicamente una caja
negra que nuestra aplicación puede usar para llevar a cabo una o más tareas.
El COM (Component Object Model ) es un estándar binario1 que define cómo
se crean y destruyen los objetos y, lo más importante, cómo interactúan entre
ellos. La idea es que distintas aplicaciones con distintos códigos fuente puedan
comunicarse entre ellas mediante procesos, siempre que dichas aplicaciones sigan
el estándar COM. Normalmente se usa el COM para establecer fácilmente comunicaciones con otros procesos.
1
Se reconoce un estándar binario porque sus componentes se componen básicamente de
datos binarios. Es decir, se centran en un entorno de ejecución y no son válidos para todas las
plataformas.
2.3. COM, Component Object Model
13
Debido a que el COM es un estándar binario, podremos decir que es un
lenguaje independiente. Es decir, que no necesariamente tendremos que usar el
C++ para implementar objetos COM. Se podrá usar cualquier lenguaje que
soporte tablas de punteros a función1 .
Existen ciertos conceptos básicos dentro del COM, que nos ayudarán a entender el funcionamiento de dicho estándar. Pasamos a continuación a dar una
breve reseña de dichos conceptos, para pasar después a explicarlos con detalle:
Una interfaz COM es una colección de métodos lógicamente relacionados
que llevan a cabo una única funcionalidad. Todas las interfaces COM están
basadas en la interfaz IUnknown, y todas ellas están identificadas mediante
un identificador único de interfaz (IID).
Una clase COM es la implementación de una o más interfaces COM.
Un objeto COM es una instancia a una clase COM. Un filtro DirectShow,
por ejemplo, es un objeto COM. Cada objeto tiene un identificador globalmente único de clase (CLSID).
Los GUIDs son números que identifican a las interfaces y objetos COM,
y son usados para eliminar las coincidencias de nombres entre distintas
aplicaciones. Los CLSID y los IID son subconjuntos de este grupo.
La diferencia entre clase COM y objeto COM es mı́nima, por lo que en muchas
ocasiones nos referiremos a los objetos COM como clases, y viceversa.
En la figura 2.4 vemos una representación gráfica de estos conceptos, en la que
observamos que cada clase COM puede tener una o varias interfaces; y que los
objetos COM son instancias de las clases COM, obtenidas mediante la función
CoCreateInstance. Mediante la llamada a esta función, también obtenemos un
puntero a la interfaz que indiquemos como parámetro. Existirán métodos para
obtener los punteros a las demás interfaces existentes en el objeto.
Cualquier acceso a un objeto COM se llevará a cabo mediante punteros a sus
interfaces. Los métodos de la interfaz son puramente virtuales y se almacenan
en una tabla llamada ”vtable”. El puntero de interfaz apuntará al principio de la
vtable. Una interfaz COM define los tipos de parámetros y la sintaxis para cada
uno de esos métodos. Las clases COM proporcionan una implementación para
cada método de la interfaz.
1
Las tablas de punteros a función básicamente son arrays, solo que sus componentes no son
variables, sino funciones.
14
CAPı́TULO 2. DIRECTX
Figura 2.4: Métodos de acceso al DirectShow.
Una vez se ha definido un objeto COM y se le ha asignado un CLSID, ya
podremos crear instancias de dicho objeto. Existen diferentes formas de crear
dicha instancia, pero la más utilizada será el método COM CoCreateInstance.
Cuando creamos una instancia del objeto, se nos devolverá un puntero a una
de las interfaces del objeto, exactamente a la interfaz que le indiquemos en la
función CoCreateInstance. Una vez ya tengamos el puntero inicial a la interfaz
principal del objeto, podremos usar el método IUnknown::QueryInterface para
averiguar si el objeto soporta alguna otra interfaz especı́fica y, si lo hace, obtener
un puntero a dicha interfaz.
Para clarificar estos conceptos, pondremos un sencillo ejemplo, en el cuál
crearemos un gráfico de filtros, comprobaremos si es factible el añadir un filtro
llamado Sample Grabber y, si lo es, lo incluiremos en el gráfico creado.
Podemos ver este código de ejemplo en la figura 2.5, en la cual vemos que,
después de declarar las variables necesarias, se llama al método CoCreateInstance
para obtener una instancia del objeto Gráfico de Filtros. A continuación deberemos crear el filtro, también mediante el método CoCreateInstance. Con esta
2.3. COM, Component Object Model
15
llamada, obtenemos un puntero a la interfaz IBaseFilter, que será el que utilizaremos a continuación, mediante el método QueryInterface, para comprobar si
es posible añadir el filtro ”Sample Grabber” a dicho gráfico. Si el valor de retorno
es distinto de NULL, significará que el resultado es satisfactorio, por lo que simplemente nos faltará el llamar al método AddFilter de la interfaz IGraphBuilder
para insertar el filtro creado dentro del gráfico de filtros.
Figura 2.5: Ejemplo de código de creación de objetos COM.
En la figura 2.5 también podemos ver un ejemplo de los números que identifican a cada objeto, los CLSID. Vemos que, en las llamadas a la función CoCreateInstance, para cada objeto deberemos indicar un número diferente, correspondiente al identificador de dicho objeto. Igualmente, podemos ver el IID que se le
indica para que nos devuelva el puntero a la interfaz deseada.
A continuación describiremos con detalle cada uno de estos conceptos.
2.3.1.
Objetos COM
Como hemos dicho con anterioridad, y desde un punto de vista práctico, un
objeto COM es básicamente una caja negra que nuestra aplicación puede usar
para llevar a cabo una o más tareas.
Los objetos COM (Component Object Model ) son componentes de software
reutilizables que se adhieren a la especificación del COM. La adhesión a esta
especificación asegura que los objetos COM trabajan bien juntos y pueden ser
fácilmente incorporados dentro de las aplicaciones. Los objetos COM son implementados mayoritariamente como DLLs (Dynamic Link Libraries). Tal y como
una DLL convencional, los objetos COM exponen métodos que nuestra aplicación
16
CAPı́TULO 2. DIRECTX
puede llamar para llevar a cabo cualquiera de las tareas soportadas. Nuestra aplicación interactúa con un objeto COM tal y como lo harı́a con un objeto C++.
Sin embargo, hay algunas diferencias significativas:
Los objetos COM hacen valer una encapsulación más estricta. No se puede
simplemente crear el objeto y llamar a cualquier método público. Los métodos públicos de un objeto COM están agrupados dentro de una o más interfaces. Tı́picamente una interfaz contiene un conjunto determinado de
métodos, cada uno de los cuales da acceso a una caracterı́stica del objeto.
Por ejemplo, existe una interfaz que nos permite controlar la reproducción
de un vı́deo, que tendrá métodos como play, stop, pause,... Cualquier método que no sea parte de una interfaz no podrá usarse.
Los objetos COM se crean de manera distinta a los objetos C++. Existen
varias formas diferentes de crear un objeto COM, pero todas ellas involucran
las técnicas especı́ficas COM. Las APIs de DirectX incluyen gran cantidad
de funciones de ayuda y métodos que simplifican la creación de la mayorı́a
de los objetos DirectX.
Se deben usar las técnicas especı́ficas COM para controlar la vida del objeto. Es decir, que deberemos tener muy en cuenta las veces que creamos
y destruimos un objeto COM (para ello utilizaremos una técnica existente
llamada Reference Counting, tal y como se verá más adelante).
Los objetos COM no necesitan ser cargados explı́citamente, normalmente
están contenidos en una DLL. Sin embargo, no se necesita cargar explı́citamente la DLL o enlazar a una librerı́a estática para usar un objeto COM.
Cada objeto COM tiene un único identificador registrado que se usa para
crear el objeto. COM automáticamente carga la DLL correcta.
Como se ha dicho con anterioridad, el COM es una especificación binaria.
Los objetos COM pueden ser agregados y ser accedidos por multitud de
lenguajes de programación. No necesitamos conocer nada acerca del código
fuente de los objetos. Por ejemplo, las aplicaciones en Microsoft Visual Basic
normalmente utilizan objetos COM que han sido escritos en C++. Con esto
podemos ver que no necesitamos conocer la total compatibilidad que tienen
estos objetos COM con cualquier lenguaje de programación que usemos.
Reference counting es la técnica mediante la que un objeto (o, estrictamente,
una interfaz) decide cuando no va a ser usada más veces y puede destruirse a si
misma. Los objetos COM están asignados dinámicamente a múltiples clientes que
pueden usarlos simultáneamente. Para evitar memorias activas, el objeto COM
debe conservar una cuenta del número de clientes que están usándolo, y destruirse
2.3. COM, Component Object Model
17
cuando ningún cliente lo necesite más. El número de clientes que están usando el
objeto se almacena en la cuenta de referencia (reference count). Cada vez que se
crea un nuevo puntero a una interfaz de un objeto COM, el cliente que usa dicho
objeto debe aumentar la cuenta de referencia llamando a la función AddRef del
puntero de interfaz. Cada vez que un cliente destruye un puntero de interfaz a un
objeto, deberá primero decrementar la cuenta de referencia mediante la función
Release del puntero de interfaz. Ası́, cuando no haya ningún usuario que haga uso
de un objeto COM, se destruirá el mismo para gestionar bien el uso de memoria.
2.3.2.
Interfaces y objetos
Es importante entender la diferencia entre objetos e interfaces. La mayorı́a
de las veces, llamamos a un objeto por el nombre de su principal interfaz. Sin
embargo, hablando estrictamente, los dos términos no son intercambiables, ya que
tienen algunas diferencias importantes. Veremos a continuación las principales
diferencias:
Un objeto contiene un número indeterminado de interfaces. Para usar un
método en particular, no solo se debe crear el objeto, además se debe obtener el puntero a la interfaz correspondiente.
Más de un objeto puede ofrecer la misma interfaz. Una interfaz es un grupo
de métodos que llevan a cabo un conjunto especificado de operaciones. La
definición de interfaz especifica sólo la sintaxis de los métodos y su funcionalidad general. Cualquier objeto COM que necesite soportar un conjunto
particular de operaciones puede hacerlo ofreciendo la interfaz correspondiente. Algunas interfaces están muy especializadas y son expuestas sólo
por un único objeto. Otras se usan en una gran variedad de circunstancias,
ası́ que son ofrecidas por muchos objetos. El caso extremo es la interfaz
IUnknown, que debe ser ofrecida por todos los objetos COM 1 .
Pondremos ahora un ejemplo práctico para establecer definitivamente la diferencia entre objetos e interfaces COM. El ejemplo más claro lo tenemos en el objeto
FilterGraph, que nos creará un gráfico de filtros (ver sección 2.2). Dentro de
1
Si un objeto ofrece una interfaz, ésta debe soportar todos los métodos que se detallan en la
definición de interfaz. Es decir, que se puede llamar a cualquier método y estar seguro de que
existe. Sin embargo, los detalles de implementación de un método en particular pueden variar
de un objeto a otro. Por ejemplo, distintos objetos pueden usar algoritmos distintos para llegar
al mismo resultado final. Algunas veces un objeto ofrece una interfaz usada comúnmente, pero
necesita soportar sólo un subconjunto de sus métodos. Lo que se hace es no implementar los
métodos que no se necesitan, y si se llaman devolverán la constante E NOTIMPL.
18
CAPı́TULO 2. DIRECTX
este objeto, nos encontramos con varias interfaces, como por ejemplo la interfaz IGraphBuilder, que será la que nos ofrece objetos para insertar filtros dentro
del gráfico; por ser esta la de uso más habitual, se suele llamar a este objeto
GraphBuilder, aunque como hemos dicho es un error. Además de esta interfaz,
el objeto nos ofrece también la interfaz IMediaControl, que nos ofrecerá métodos
para controlar el flujo de datos dentro del gráfico. También nos ofrece la interfaz IMediaEvent, que nos ofrece métodos para obtener información de eventos
sucedidos en el gráfico de filtros. Vemos por lo tanto que un objeto puede ofrecer
varias interfaces.
El estándar COM requiere que una definición de interfaz no cambie una vez ha
sido publicada. No se puede, por ejemplo, añadir un nuevo método a una interfaz
existente. Para hacerlo, se deberı́a crear una nueva interfaz. No hay ninguna restricción acerca de qué métodos deber estar incluidos en una interfaz, una práctica
común es hacer que la próxima interfaz incluya todos los métodos de la interfaz
vieja, mas los nuevos métodos.
No es demasiado inusual tener varias versiones de una misma interfaz. Generalmente, todas las versiones llevan a cabo esencialmente la misma tarea, pero
difieren en los detalles. Frecuentemente, un objeto ofrece varias versiones de
la interfaz, con lo que se permite que las viejas aplicaciones sigan usando las
mismas interfaces de los objetos viejos, mientras las nuevas aplicaciones pueden
aprovecharse de las ventajas de las nuevas interfaces. Normalmente, una familia
de interfaces tiene el mismo nombre, añadiendo un entero que referencia la versión. Ası́, se mantiene la total compatibilidad entre una nueva versión de esta
tecnologı́a y las versiones ya existentes.
2.3.3.
GUIDs
GUID son las siglas de Globally Unique IDentifiers, y son una parte clave
del modelo de programación COM. En su forma más básica, un GUID es una
estructura de 128 bits. Sin embargo, los GUIDs se han creado de tal forma que
garantizan que ningún GUID se repita. COM usa GUIDs principalmente por dos
propósitos:
1. Para identificar de forma única un objeto COM en particular. Un GUID que
se asigna a un objeto COM se llama una clase ID (CLSID). Deberemos usar
un CLSID cuando queramos crear una instancia del objeto COM asociado.
2. Para identificar de forma única una interfaz COM. El GUID que se asocia con una interfaz COM en particular se llama un ID de interfaz (IID).
Deberemos usar un IID cuando se requiera una interfaz particular de un
2.4. Objetos COM en DirectShow
19
objeto. Un IID de interfaz será lo mismo, sin tener en cuenta que el objeto
ofrece la interfaz.
2.4.
Objetos COM en DirectShow
Los filtros DirectShow, el administrador de gráfico de filtros,... son todos ellos
objetos COM. Se ha adoptado un diseño general para la forma en que el DirectShow implementa los objetos COM. Este diseño está disponible para ayudarnos
a implementar nuestros propios filtros o cualquier objeto COM que deseemos.
Tı́picamente, una clase única C++ implementa una única clase COM. El espacio de trabajo COM de DirectShow requiere que las clases C++ implementen
los objetos COM conforme a unas simples reglas básicas. Una de esas reglas es
que el programador proporciona una plantilla para cada una de las clases. Dicha
plantilla contendrá la información de la clase que es fundamental para el espacio
de trabajo. Las plantillas se definen dentro de la DLL usando dos variables globales: g Templates y g cTemplates tal y como se muestra en el ejemplo siguiente:
CFactoryTemplate g Templates[]=
{
{L"My class name", &CLSID MyClass, CMyClass::CreateInstance,
CMyClass::Init},
{L"My class name2", &CLSID MyClass2, CMyClass2::CreateInstance}
};
int g cTemplates = sizeof(g Templates)/sizeof(g Templates[0]);
Los nombres y tipos de esas variables deben aparecer exactamente igual que
el ejemplo anterior. A causa de que una DLL puede contener varias clases COM,
cada una de ellas requerirá una plantilla diferente. La plantilla está definida en
un array y el número de elementos en el array se almacena en otra variable. Cada
elemento del array contiene los siguientes campos:
Una descripción textual de la clase (usando caracteres anchos, por lo que
tendremos anteponer el prefijo ”L”).
Un puntero al CLSID (identificador de clase) de la clase.
Un puntero a un método estático de la clase que puede crear instancias de
la clase (CFactoryTemplate::CreateInstance).
20
CAPı́TULO 2. DIRECTX
Un puntero a un método estático de la clase. Se llama a este método cuando la DLL se carga o se descarga y puede trabajar como la antigua inicialización y finalización. Si no se requiere este método, puede ser omitido,
inicializándose a NULL, y será ignorado.
Un puntero a una estructura AMOVIESETUP FILTER. Se requiere cuando
se usan servicios de auto-registro de filtros.
El esquema DirectShow COM usa la información que contiene esta plantilla
para crear instancias de la clase especı́fica, y para registrar y quitar del registro
las clases COM.
Capı́tulo 3
Manejo de funciones multimedia
En este capı́tulo nos dedicaremos a explicar todos los detalles concernientes a
la programación. Ası́, podremos ver las funciones que se han creado para el manejo
de los vı́deos, las interfaces y librerı́as de las que se ha hecho uso para llegar a las
funciones finales, problemas surgidos conforme la marcha del proyecto,...
Se ha dividido el capı́tulo en dos puntos fundamentales: vı́deo y audio. Se ha
hecho ası́ porque aunque desde el punto de vista de la programación sean bastante
similares, no lo es ası́ en el modelo conceptual que perseguimos con este proyecto.
Por lo tanto, encontraremos en primer lugar un punto dedicado al vı́deo, en el
que podremos encontrar el conjunto de tecnologı́as utilizadas para su tratamiento. A continuación encontramos un segundo punto dedicado al audio, en el que
se mostrarán, al igual que con el vı́deo, las soluciones que se han tomado para
su manejo. Además, en ambos puntos, encontraremos ejemplos de fácil implementación que aclararán toda la teorı́a expuesta.
En el tercer y último punto se ha llevado a cabo una relación de todas las
funciones creadas y que estarán disponibles en la librerı́a, además de su uso y el
detalle de sus parámetros.
3.1.
Vı́deo
En este punto veremos las soluciones que se han adoptado en lo referente a la
extracción de fotogramas, es decir, qué interfaces de las que se veı́an en el capı́tulo
anterior hemos usado, qué librerı́as adicionales se utilizan,...
En primer lugar explicaremos básicamente qué es la librerı́a VICTOR, librerı́a
21
22
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
de tratamiento digital de imágenes, de gran utilidad para el tratamiento de los
fotogramas extraı́dos.
En el punto 3.1.2 se explica la problemática con que nos encontramos a la
hora de la extracción de fotogramas; estos problemas provienen de que en el
entorno Windows existen dos tipos principales de plataformas, la del Windows
98 y la del Windows NT. En un principio se desarrolló la aplicación en el sistema
operativo w98, de forma que cuando se hicieron las pruebas pertinentes en el
sistema operativo wXP (perteneciente al grupo de Windows NT), las funciones no
funcionaban correctamente, ya que existen diferencias significativas entre ambos.
Por ello, se debe optar por soluciones distintas en cada una de las plataformas.
En el punto 3.1.3 se explican las interfaces del DirectShow que se han usado,
ası́ como la librerı́a utilizada para el manejo de los vı́deos MPEG-2.
Por último, en el punto 3.1.4 encontraremos un práctico ejemplo de cómo
reproducir un vı́deo utilizando la tecnologı́a DirectShow.
3.1.1.
Librerı́a VICTOR
Antes de empezar a explicar cualquier cosa concerniente al tratamiento de las
imágenes, es imprescindible hacer una referencia a la librerı́a de tratamiento de
imágenes llamada VICTOR. Esta librerı́a nos permitirá trabajar con los fotogramas que vayamos extrayendo de los vı́deos, ya que nos proporciona funciones
para abrir y guardar imágenes, para convertir de formatos, para manipular las
imágenes una vez cargadas en memoria, entre otras muchas aplicaciones.
Quizás las funciones que más hayamos utilizado en este proyecto hayan sido
las de guardar imágenes y las de abrir imágenes guardadas en disco. Pero además
de estas dos funciones, se han utilizado las funciones de diezmado e interpolación
de imágenes, las funciones de dibujar lı́neas dando las coordenadas pertinentes,...
3.1.2.
Problemática
Cuando se llegó al momento de la extracción de fotogramas, se comprobó que
todo lo que funcionaba en un sistema operativo, no funcionaba exactamente igual
en otro sistema operativo diferente. Esto ocurrı́a porque normalmente se trabajaba en un entorno FAT32, tal como el Windows 95, 98 o Millenium. El problema
llegó cuando se pasó a trabajar en un entorno NT, tal como el Windows NT o el
XP. Y dado la amplia expansión del sistema operativo Windows XP, se debieron
3.1. Vı́deo
23
realizar funciones para que este proyecto funcionara correctamente, además de
con cualquier formato de vı́deo, en cualquier plataforma Windows en la que se
trabajara.
Por ello, y antes de empezar a explicar cada una de las interfaces que hemos
usado para llevar a cabo la extracción de fotogramas, veremos primero qué opciones se eligieron dependiendo del sistema operativo y del formato de vı́deo en
concreto.
Cuando se trabaja con el formato de vı́deo MPEG-2 no existe ningún tipo de
problema con respecto al sistema operativo. Para ello debemos usar la librerı́a que
describiremos en el punto 3.1.3, llamada Mpeg2Lib, y que se encarga de extraer
los fotogramas mediante las llamadas oportunas a sus funciones, sea cual sea la
plataforma en la que nos encontremos. Esta librerı́a soluciona los problemas vistos
porque no se basa en funciones del sistema operativo, por lo que es independiente
de la plataforma en la que se utilice.
Con el formato AVI tampoco tenemos ningún problema, ya que se utiliza la
librerı́a AVIFile (tal y como veremos en el punto 3.1.3), que nos proporciona
movilidad suficiente para hacer lo que deseemos con los vı́deos AVI.
El problema llega cuando trabajamos con MPEG-1, en los que se utilizan
distintas interfaces del DirectX SDK.
Como veremos en el siguiente punto, la forma más fácil de extraer los fotogramas es mediante la interfaz de DirectShow IMediaDet, la cual se encarga de abrir
el stream de vı́deo, extraer el fotograma y guardarlo en un buffer de memoria,
para después cerrar el archivo multimedia y poder trabajar con dicho fotograma.
Además de ser la forma más fácil, es la que menos recursos utiliza, con la ventaja adicional de que no importa que el vı́deo esté abierto en otras partes (como
por ejemplo en nuestra aplicación, en la que está reproduciéndose en la ventana
principal). Por ello, en los casos en los que sea posible elegiremos esta solución
como la óptima. Este será el caso del MPEG-1, ya que esta interfaz no es válida
cuando trabajamos con MPEG-2.
Esta interfaz funciona correctamente con estos vı́deos pero sólo cuando estamos en un entorno Windows 98. Cuando trabajamos en el entorno NT (es decir,
en Windows XP) esta interfaz no es válida, y no nos extrae el fotograma que
deseamos. Simplemente se limita a abrir el vı́deo y extraernos el primer fotograma, sin tener posibilidad de elegir el fotograma que quesiéramos; a cause de este
grave problema, se debe encontrar otra solución alternativa.
24
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
MPEG-1
MPEG-2
AVI
VOB
Windows 98
IMediaDet
Mpeg2Lib
AVIFile
Mpeg2Lib
Windows XP
ISampleGrabber
Mpeg2LIb
AVIFile
Mpeg2Lib
Cuadro 3.1: Relación de sistemas operativos e interfaces utilizadas.
Dicha solución la encontramos con la interfaz ISampleGrabber (ver punto 3.1.3),
la cuál nos realiza las mismas funciones que la interfaz anterior, pero además trabaja perfectamente en un entorno Windows NT o XP. Esta interfaz también nos
es válida para vı́deos MPEG-2 en un entorno Windows 98, pero es mucho más
compleja y utiliza más recursos que las funciones de la librerı́a que hemos descrito
para el MPEG-2.
Por lo tanto, y resumiendo, lo que hacemos a la hora de extraer los fotogramas
es lo siguiente:
1. Primero comprobamos si es VOB. Si es ası́, utilizamos la librerı́a Mpeg2Lib
y obtenemos directamente el fotograma.
2. Si no es VOB, comprobamos si es AVI. Si es ası́, utilizamos la librerı́a
AVIFile y obtenemos directamente el fotograma.
3. Si no es VOB ni AVI, comprobamos si es MPEG, y si es ası́ hacemos una
segundo comprobación para ver si es MPEG-1 o MPEG-2. Si es MPEG-2
utilizamos la librerı́a MPEG2Lib y obtenemos directamente el fotograma.
4. Si es MPEG-1 o AVI lo que hacemos es comprobar la plataforma en la que
estamos trabajando. Si es Windows 98, utilizamos la interfaz IMediaDet, y
si estamos en Windows NT o XP, utilizamos la interfaz ISampleGrabber,
con lo que ya tendrı́amos el fotograma que deseábamos.
3.1.3.
Extracción de fotogramas
Veremos en este punto las dos interfaces principales del DirectShow que se
han usado en cuanto a la extracción de fotogramas. Ası́, se verá con detalle lo
3.1. Vı́deo
25
que ya se podı́a entrever en el punto anterior. Además, se profundizará también
en la librerı́a Mpeg2Lib, que como hemos dicho se encargará del manejo de vı́deos
en formato MPEG-2, ası́ como en la librerı́a AVIFile, válidad para los vı́deos en
formato AVI.
IMediaDet
Cuando se nos presenta el problema de extraer fotogramas, la solución la
hemos encontrado en el DirectShow, tal y como hemos explicado anteriormente.
En concreto, dentro del DirectShow encontramos una interfaz, denominada IMediaDet que nos lleva a cabo perfectamente las opciones de extracción de fotogramas.
La interfaz IMediaDet se utiliza en general para obtener información acerca
de un fichero multimedia, tal como el número de streams, el tipo de medio (audio,
vı́deo, ...), duración total o velocidad de reproducción de cada uno de los streams.
Pero además de estas importantes funciones, también contiene métodos para
obtener fotogramas especı́ficos de un stream de vı́deo.
Figura 3.1: Extracción de un fotograma mediante la interfaz IMediaDet.
Ası́, de una forma muy general, explicaremos ahora los pasos que deben
seguirse para conseguir extraer los fotogramas (en la figura 3.1 podemos ver un
diagrama de bloques del proceso). En primer lugar, se debe inicializar el COM,
mediante la función CoInitialize, pudiendo a partir de entonces utilizar cualquiera
de las interfaces del COM. A continuación, deberemos inicializar la interfaz, enlazándola con el COM, tal y como se ha explicado en capı́tulos anteriores. Para
ello, usaremos la función CoCreateInstance, indicándole que la instancia que queremos crear es de la interfaz IMediaDet, mediante el GUID CLSID MediaDet.
26
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
A continuación, deberemos indicarle el nombre y la ruta completa del fichero
de vı́deo del que queremos extraer el fotograma. Para ello, usaremos el método
put Filename, que nos provee la interfaz. Esta función tiene un matiz, y es que
no podemos pasarle como parámetro el tipo de texto al que estamos acostumbrados (char* ), sino que se le debe pasar un tipo de datos llamado CComBSTR,
por lo que deberemos hacer la correspondiente translación. Una vez hecho esto, buscaremos el stream de vı́deo, ya que los otros streams no nos interesan
de momento. Para ello, obtenemos primero el número de streams que contiene
el vı́deo, mediante el método get OutputStreams, y a continuación, mediante un
bucle, obtenemos el tipo de datos que contiene cada stream, mediante el método
get StreamType, y lo comparamos con el tipo MEDIATYPE Video, de forma que
cuando coincidan querrá decir que hemos encontrado el stream de vı́deo.
Una vez hemos conseguido el stream de vı́deo, debemos obtener la información necesaria para el consiguiente tratamiento, es decir, el ancho y alto del vı́deo,
para poder guardar después espacio en memoria para el fotograma extraı́do. Para
ello llamamos al método get StreamVideoType, que nos devuelve una estructura
del tipo AM MEDIA TYPE. A continuación mostramos un detalle de esta estructura, con todos los campos que contiene:
typedef struct MediaType {
GUID majortype;
GUID subtype;
BOOL bFixedSizeSamples;
BOOL bTemporalCompression;
ULONG lSampleSize;
GUID formattype;
IUnknown *pUnk;
BYTE *pbFormat;
} AM MEDIA TYPE;
De todos ellos, nos interesa uno en especial, el pbFormat, que contiene toda
la información que nosotros necesitamos. En este caso, ese campo toma la forma
de la estructura VIDEOINFOHEADER*, dentro del cuál encontramos el alto del
vı́deo, el ancho, entre otros parámetros de utilidad. Mostramos a continuación el
detalle de esta estructura, con todos los campos que contiene:
3.1. Vı́deo
27
typedef struct tagVIDEOINFOHEADER {
RECT rcSource;
RECT rcTarget;
DWORD dwBitRate;
DWORD dwBitErrorRate;
REFERENCE TIME AvgTimePerFrame;
BITMAPINFOHEADER bmiHeader;
} VIDEOINFOHEADER;
Una vez obtenida esta información, liberamos el espacio en memoria que
estábamos ocupando, con la función FreeMediaType, y nos ponemos en la labor de la extracción del fotograma. Para ello, deberemos ejecutar un método, el
EnterBitmapGrabMode, que nos sitúa la interfaz en un nuevo modo llamado modo
de grabación. Además de esto, nos sirve para posicionar el vı́deo en el fotograma
que deseamos. Una reseña tendremos que hacer ahora, y es que no podemos indicarle el número de fotograma en concreto, sino la posición en segundos de la que
queremos extraer el fotograma. Sin embargo, este es un problema de fácil solución,
ya que tenemos el número de fotogramas por segundo, por lo que con una simple
operación podremos saber perfectamente la correspondencia entre el fotograma
que queremos extraer y el segundo en que está posicionado dicho fotograma.
Por último, ejecutaremos el método GetBitmapBits en dos ocasiones: la primera
para obtener el tamaño del buffer que debemos reservar para guardar la imagen,
y la segunda para obtener el fotograma en concreto, indicando en ambas el ancho
y el alto del vı́deo, que habı́amos obtenido con anterioridad. Una vez llevado a
cabo estas acciones, ya tenemos en memoria el fotograma deseado, por lo que lo
único que se debe hacer ahora es guardarlo en disco, para poder trabajar con él.
Para ello usamos la librerı́a de VICTOR, que nos permite guardar y recuperar
imágenes de disco.
Para finalizar, deberemos finalizar el COM que habı́amos inicializado al principio, con la función CoUninitialize.
ISampleGrabber
Esta interfaz está basada en el filtro Sample Grabber , y nos permite obtener
fotogramas de casi cualquier formato de vı́deo. Podrı́a parecer que es redundante
28
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
a todo lo que hemos explicado en el punto anterior, pero debido a las incompatibilidades que existen entre las distintas plataformas en las que se va a ejecutar
este programa, esta interfaz se convierte en imprescindible para la extracción de
fotogramas.
Pasaremos a continuación a explicar con detalle cómo funciona esta interfaz, y
los pasos a seguir para conseguir obtener al final el fotograma que consideremos
necesario. En la figura 3.2 podemos ver un diagrama de bloques de todas las
funciones necesarias para la extracción del fotograma.
Figura 3.2: Extracción de un fotograma mediante la interfaz ISampleGrabber.
En primer lugar, deberemos inicializar la librerı́a COM, mediante la llamada
a la función CoInitialize. Este paso es muy importante, ya que si no lo realizamos
no podremos trabajar con el COM, y por lo tanto no será posible llevar a cabo
ninguna de las tareas siguientes.
A continuación, deberemos crear la interfaz que contendrá todos los filtros
necesarios. Para ello, inicializamos la interfaz IGraphBuilder mediante el método CoCreateInstance. Además, mediante la misma función, deberemos inicializar
también el filtro Sample Grabber, contenido en la interfaz ISampleGrabber.
Seguido, deberemos incluir este filtro en el gráfico principal, tarea que lleva
a cabo el método AddFilter. También deberemos añadir el filtro que contiene al
vı́deo origen; para ello, llamaremos al método RenderFile, al que simplemente
deberemos indicarle el nombre del archivo de vı́deo que queremos abrir, pero
no mediante la forma a la que estamos acostumbrados, sino mediante caracteres
anchos (del tipo WCHAR), por lo que previamente deberemos convertir la cadena
3.1. Vı́deo
29
de caracteres (normalmente de tipo char* ) que contiene el nombre del vı́deo a
este formato (mediante la función MultiByteToWideChar ).
Ahora deberemos inicializar tres interfaces que nos serán necesarias en los siguientes pasos. La primera de ellas es la interfaz IMediaControl, que nos servirá para
reproducir el gráfico cuando sea necesario. La segunda es la interfaz IMediaEvent,
que nos servirá para indicarle al gráfico ciertos estados que debe cumplir. Y por
último la interfaz IMediaSeek, la cuál nos servirá para colocarnos en el fotograma
que deseemos.
Ahora empezaremos a llamar a ciertos métodos de las interfaces anteriores.
Primero llamaremos al método SetBufferSamples, el cual especifica que los datos
que copie el filtro se guarden en un buffer y no se pierdan una vez hayan pasado. A continuación, mediante el método SetOneShot, indicamos que el gráfico
reproduzca un fotograma y pare, para que nos de tiempo a procesar esos datos.
Podremos obtener la duración del vı́deo mediante el método GetDuration, lo que
nos será útil para comprobar si el fotograma que hemos indicado está dentro
del intervalo de fotogramas que contiene el vı́deo. Una vez comprobado esto,
pasaremos a posicionar el vı́deo en el fotograma indicado, mediante el método
SetPositions, al cual deberemos indicarle la posición inicial y final a reproducir.1
Ya para finalizar ordenaremos al gráfico que se reproduzca, mediante el método
Run.
Ahora ya tenemos el fotograma en un buffer; lo único que debemos hacer
ahora es recoger la información para procesarla después. Para ello, averiguamos
primero las dimensiones del fotograma, mediante el método GetConnectedMediaType de la interfaz ISampleGrabber, la cuál nos devolverá una estructura
AM MEDIA TYPE (que hemos mostrado con detalle en el punto anterior).
Para finalizar, simplemente deberemos guardar memoria para el fotograma,
y llamar al método GetCurrentBuffer de la interfaz ISampleGrabber, el cuál nos
devolverá, en la dirección de memoria que habı́amos guardado previamente, el
fotograma que le habı́amos indicado.
No debemos olvidar el finalizar la librerı́a COM, mediante la función CoUninitialize, que además descargará las DLLs que se hayan cargado anteriormente,
liberará toda la memoria que se haya almacenado durante la aplicación,...
1
Recalcar que como sólo queremos extraer un fotograma, la posición inicial y la final serán
la misma.
30
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
MPEG2Lib
Existen ocasiones en las que la calidad del MPEG-1 no nos es suficiente para
nuestros fines, y debemos utilizar el MPEG-2 con todas las ventajas que ello
conlleva. Por ello, también deberemos añadir facilidades para la extracción de
fotogramas para este tipo de formato.
Todas las interfaces que posee el DirectX no nos sirven para la extracción de
fotogramas en el MPEG-2, ya que como máximo lo que nos hacen es extraer el
primer fotograma, pero sin posicionarse en el fotograma que deseamos. Por esto
mismo, se debió buscar otra opción en el caso de trabajar con este formato de
vı́deo.
La solución la encontramos en una librerı́a desarrollada por un programador
llamado Michael Vinther, el cual integra en su librerı́a una combinación de un
programa de libre distribución llamado DVD2AVI y las funciones desarrolladas
por el grupo de investigación del MPEG, en concreto el MSSG (MPEG Software
Simulation Group).
Esta librerı́a nos ofrece opciones para abrir y cerrar vı́deos, ası́ como para
abrir directamente un disco (DVD, ...). Además, encontramos opciones para posicionarse en cualquier fotograma del vı́deo, extraer un fotograma en concreto o
elegir el formato de salida de dicho fotograma. En concreto, los formatos de salida
que soporta la librerı́a son tres: RGB, YUV o escala de grises. En la mayorı́a de
casos, trabajaremos con el formato RGB, que es el que realmente nos interesa,
aunque es el que más tiempo de decodificación requiere, ya que se debe hacer una
transformación lineal de los colores guardados en el fichero MPEG-2.
Lo que se ha intentado con esta librerı́a es crear otra mucho más simple, con
funciones tales como ExtraerFotograma, o ExtraerGrupoFotogramas. La primera
función nos extrae un fotograma como una imagen de VICTOR, simplemente
pasándole el nombre del vı́deo y el número de fotograma que queremos sacar.
La segunda nos guarda los fotogramas en disco, pasándole como parámetros el
nombre del vı́deo, el nombre con que queremos que guarde los fotogramas, y el
intervalo que deseamos extraer.
Esta librerı́a también nos ofrece opciones para el tratamiento del audio, pero
son bastante más complejas que las que se han desarrollado anteriormente y requieren más tiempo computacional, por lo que para el audio trabajaremos siempre
con las funciones que describiremos en el punto siguiente, y que se basan en el
DirectShow.
31
3.1. Vı́deo
A continuación pasaremos a explicar con detalle todas las funciones con las
que cuenta esta librerı́a, que básicamente son diez, englobadas en cuatro grandes
grupos: apertura y cierre de archivos, información, posicionamiento, y extracción
de fotogramas.
Apertura y cierre de archivos
En el bloque de apertura y cierre de archivos, nos encontramos con tres funciones:
OpenMPEG2File
OpenMPEG2Disk
CloseMPEG2File
La función OpenMPEG2File se utiliza para abrir un archivo de vı́deo con
formato MPEG2. Si el vı́deo no tiene este formato, esta función fallará. Por esto
mismo, podremos usar esta función para diferenciar entre MPEG1 y MPEG2, ya
que a simple vista no existe nada que nos indique qué tipo de formato tiene el
archivo(ambos tienen la misma extensión (*.mpg)). A esta función simplemente
deberemos pasarle tres parámetros: el primero de ellos será el nombre del vı́deo
que deseamos abrir, el segundo es el offset, que será siempre 0, y el tercero el
tamaño del archivo, que como normalmente será desconocido, deberemos pasarle
el valor de -1. El valor de retorno será 0 si la función falla, y otro valor si la
función ha conseguido abrir el archivo.
La función OpenMPEG2Disk es básicamente igual que la anterior, con el único
cambio de que no nos abre un archivo de vı́deo determinado, sino que abrirá un
disco (por ejemplo un DVD). Por ello, no deberemos pasarle un nombre de archivo
de vı́deo, sino el identificador del disco que queremos abrir. El valor de retorno
será el mismo que en el caso anterior.
Por último, la función CloseMPEG2File se utilizará para cerrar el archivo que
previamente habı́amos abierto con una de las funciones anteriores. Esta función
no tiene parámetros, ni nos devolverá ningún valor, ya que internamente ya se
encarga de cerrar el archivo que tenı́a abierto , y no existe la posibilidad de que
falle.
32
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
Información
Dentro de este bloque nos encontramos con dos funciones:
GetMPEG2FrameInfo
GetMPEG2FileInfo
La primera de ellas, GetMPEG2FrameInfo, nos ofrece información acerca del
fotograma, es decir, nos proporciona el alto y el ancho del fotograma. Esta información es muy importante, ya que cuando vayamos a guardar espacio en memoria
para el fotograma, deberemos saber estas cantidades que serán las que nos indiquen el tamaño de bloque que debemos guardar.
La función GetMPEG2FileInfo nos da información del archivo de vı́deo, como
su tamaño, el fotograma en que estamos en el momento,... De la información que
nos proporciona esta función, la que más usaremos será la que nos indica en
qué fotograma estamos, ya que para saltar a otro fotograma deberemos conocer
en cual de ellos estamos situados exactamente, para indicar después a la función
de posicionamiento el parámetro adecuado.
Posicionamiento
En este bloque nos encontramos con dos funciones:
SkipMPEG2Frames
MPEG2Seek
La función SkipMPEG2Frames, como su propio nombre indica, nos sirve para
saltar el número de fotogramas que le indiquemos. Esta función es muy práctica,
pero cuando deseamos realizar grandes saltos dentro del vı́deo, resulta bastante
ineficiente, ya que lo que hace es ir buscando las cabeceras de los fotogramas
para comprobar si es el que realmente deseamos, por lo que se hace especialmente
ineficiente cuando trabajamos con vı́deos grandes, que tengan muchos fotogramas.
Por lo tanto, esta función deberá usarse cuando trabajemos con vı́deos pequeños
y en los que tengamos que desplazarnos pocos fotogramas.
La otra función, MPEG2Seek, lo que hace es moverse en el fichero de vı́deo una
cierta cantidad de bytes, la que nosotros le indiquemos como parámetro. Después
deberemos encargarnos de comprobar si estamos en el fotograma que realmente
3.1. Vı́deo
33
deseábamos o no, haciendo uso de una de las estructuras que proporciona la
librerı́a. Por esto, esta función es mucho más eficiente que la anterior cuando
deseamos grandes saltos dentro del vı́deo.
Extracción
En esta sección nos encontramos con tres funciones, aunque no todas ellas
realizan la extracción de un fotograma, pero están relacionadas con dicha tarea.
Las funciones son las siguientes:
SetMPEG2PixelFormat
SetRGBScaleFlag
GetMPEG2Frame
La función SetMPEG2PixelFormat lleva a cabo la función de elegir el tipo
de salida que queremos, es decir, si queremos que el fotograma de salida esté en
formato RGB, YUV o en escala de grises. Como hemos explicado anteriormente,
el formato que más usaremos será el RGB, por ello si no especificamos nada, el
formato por defecto será este.
La función SetRGBScaleFlag se utiliza para que en el caso que trabajemos
con el formato de salida RGB, poder elegir la escala del fotograma de salida, es
decir, que podremos ampliarlo o reducirlo a nuestro gusto.
Por último, la función GetMPEG2Frame es la que extrae el fotograma realmente. Esta función será la última que llamemos, ya que previamente tenemos
que haberle especificado el formato que queremos, el fotograma que realmente
queremos extraer,... Como salida, esta función nos devolverá un buffer que contendrá el fotograma deseado.
AVIFile
Básicamente esta es una librerı́a de Windows, llamada AVIFile Functions
and Macros, que encontramos dentro del archivo vfw32.dll y que podremos
utilizar simplemente con añadir el archivo vfw.h dentro de los includes de nuestro
programa.
Antes de continuar, deberemos reseñar que el AVI no es ningún formato de
compresión en concreto, sino que utiliza una serie de compresores ya desarrollados, tales como el DivX, para formar los archivos comprimidos. Por lo tanto,
34
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
cuando hagamos uso de esta librerı́a, deberemos asegurarnos antes de que en el
ordenador se encuentra instalado el compresor correspondiente al vı́deo con el
cual queremos trabajar.
Las funciones que encontramos en esta librerı́a tratan la información de los
ficheros multimedia como uno o más streams de datos. Un fichero AVI puede
contener tipos de datos diferentes, tales como una secuencia de vı́deo, una pista
de audio en inglés, una pista de audio en español,. . . Usando las funciones de
esta librerı́a, una aplicación puede acceder a cada uno de estos componentes
separadamente y trabajar con cada uno de ellos independientemente de lo que
pase con el otro; es decir, que podremos trabajar con el stream de vı́deo sin
importarnos cuantos streams de audio existan, ni si el vı́deo contiene un stream
de datos,...
Ya que todas estas funciones están contenidas dentro de una dll (Dynamiclink library), cuando vayamos a utilizar cualquiera de ellas se deberá ejecutar una
función para que las reconozca: AVIFileInit. Después de inicializar la librerı́a, se
podrán usar cualquiera de las funciones para el tratamiento de ficheros AVI. Por
supuesto, una vez finalizado el tratamiento del vı́deo y no se vayan a usar más
estas funciones, se deberá ejecutar otra función para liberar la librerı́a: AVIFileExit. La librerı́a AVIFile mantiene un cuenta de referencia de las aplicaciones que
están usando la librerı́a, pero no sobre las veces que se ha liberado. De tal forma,
nuestras aplicaciones deberán llevar cuidado de que cada vez que se inicialice la
librerı́a, se cierre adecuadamente.
Nos encontraremos con dos tipos de funciones bien diferenciados:
Funciones para trabajar con ficheros: Nos encontraremos funciones para
abrir y cerrar ficheros, ası́ como funciones para obtener información de
dicho fichero, o para escribir datos dentro del fichero. Por ejemplo, las funciones para abrir y cerrar ficheros serán AVIFileOpen y AVIFileRelease,
respectivamente.
Funciones para trabajar con streams: Nos encontraremos funciones para
abrir y cerrar streams, funciones para extraer datos del stream (por ejemplo para extraer un fotograma), funciones para extraer información de un
stream (por ejemplo para obtener el ancho y alto de los fotogramas). Por
ejemplo, para extraer un fotograma, necesitaremos tres funciones básicas:
AVIFileStreamGetOpen, AVIFileStreamGetFrame y AVIFileStreamGetFrameClose, que se encargan de preparar el stream para la extracción, de extraer
y guardar el frame, y de cerrar el stream, respectivamente.
En la figura 3.3 podemos ver un ejemplo de cómo abrir un vı́deo AVI y obtener
35
3.1. Vı́deo
después el número de streams que contiene. En dicha figura vemos algunas de las
funciones que encontramos dentro de la librerı́a AVIFile.
Figura 3.3: Código de ejemplo de la librerı́a AVIFile.
Lo primero que tenemos que hacer es obtener un puntero al fichero multimedia
abierto. Para ello, deberemos usar la función AVIFileOpen, a la que pasaremos
como parámetros:
La dirección del puntero que apuntará al fichero abierto.
El nombre del vı́deo a ser abierto, con el path completo donde se encuentra
ubicado.
A continuación indicaremos el modo como queramos sea tratado el vı́deo,
36
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
es decir, si queremos abrirlo como sólo lectura, como es ahora el caso, en
modo escritura,. . .
La última variable la pondremos siempre al valor de 0L.
A continuación, iremos recorriendo el fichero para comprobar cuantos son
los streams que contiene. Para ello, usamos el bucle for, indicándole un lı́mite
(MAXNUMSTREAMS), el cual consideremos adecuado. Normalmente, no suelen
existir más de tres streams dentro del vı́deo, pero previniendo podremos fijar esta
variable a diez, por ejemplo.
Como vemos, para obtener un puntero al stream usamos la función AVIFileGetStream, a la cual deberemos pasar ciertos parámetros:
El puntero que identifica al fichero abierto en la función anterior.
Un puntero que identificará al stream abierto en ese momento, ya sea el de
vı́deo, el de audio, . . .
Indica el tipo de stream a abrir. Ponemos cero para poder abrir cualquier
stream, que es lo que queremos.
Contador del stream abierto.
Nos detendremos cuando lleguemos al máximo número de streams fijado,
o cuando no haya más streams, que será lo más normal. Para saber que no
existen más streams, deberemos comprobar que el puntero que le hemos pasado
a la función anterior para que identifique al stream es igual a NULL, que nos
indicará que está vacı́o.
A continuación comprobaremos que se han podido abrir los streams, ya sea
porque no existı́a ninguno dentro del fichero o porque los datos estén dañados,
de tal forma que no se puedan extraer dichos datos.
El número de streams a devolver no será la cuenta que llevábamos, sino uno
menos, ya que el programa también cuenta la última comprobación en la que
vemos que está vacı́o (cuando comprobamos que es igual a NULL).
Antes de finalizar el programa deberemos cerrar el fichero, usando la función
AVIFileRelease, a la cual deberemos pasarle el puntero que habı́amos obtenido
en la primera función que hemos comentado. También deberá abandonarse la
37
3.1. Vı́deo
librerı́a, ya que como se ha comentado en capı́tulos anteriores, deberemos llevar
cuidado de cerrar siempre las librerı́as después de ser abiertas. De esta forma,
como habı́amos abierto la librerı́a al principio de la función para poder hacer uso
de las funciones proporcionadas por dicha librerı́a, ahora deberemos cerrarla.
Para finalizar se devuelve el número de streams que hemos encontrado dentro
del fichero multimedia que habı́amos pasado a la función como parámetro.
3.1.4.
Ejemplo Básico del DirectX
Dado que hasta ahora se ha hecho una explicación muy teórica acerca del
DirectX y de todos sus componentes, nos dedicaremos en este punto a poner un
ejemplo práctico, sencillo y fácil de entender sobre cómo se podrı́a extraer una
fotograma utilizando la interfaz IMediaDet.
Ası́, se ha creado la función ExtraeFotograma, tay y como se muestra en la
figura 3.4, a la cual simplemente deberemos pasarle como parámetros el nombre
del vı́deo del que queremos extraer el fotograma, y una estructura BITMAPINFOHEADER, tı́pica de Windows, y que recogerá los datos del fotograma.
Detallaremos ahora todas las funciones de las que se ha hecho uso en este
ejemplo. Como vemos en la figura 3.4, se ha llamado en primer lugar al método
CoInitialize, para inicializar el COM, y en último lugar se ha llamado al CoUninitialize, para finalizarlo. Como hemos dicho con anterioridad, estos son dos puntos
fundamentales cuando trabajamos con el DirectX y el COM.
A continuación, inicializamos la interfaz IMediaDet, para poder después usar
los métodos que contiene. Ası́, después de convertir el nombre que se pasa como
parámetro, se llama al método put Filename, con el que indicamos el origen
de los datos. Una vez tenemos el vı́deo, vamos recorriendo sus streams hasta
que encontremos el de vı́deo, utilizando el método get OutputStreams para saber
el número total de streams, el método put CurrentStream para ir cambiando
el stream, y el método get StreamType para obtener el tipo de stream del que
se trata, que compararemos después con el tipo MEDIATYPE Video hasta que
encontremos el stream de vı́deo.
Con todo esto lo que hemos conseguido es tener activo el stream de vı́deo, si
es que existe dentro del archivo pasado como parámetro. Ası́ que ahora lo que nos
queda es averiguar el tamaño del buffer que debemos guardar en memoria para
almacenar al fotograma, y se llevará a cabo mediante el método GetBitmapBits,
al cual si le pasamos como tercer parámetro NULL, nos devolverá en el segundo
38
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
Figura 3.4: Código de ejemplo para extraer un fotograma.
parámetro el tamaño que buscábamos.
Con todo esto, simplemente nos queda guardar espacio en memoria para el
fotograma, y volver a llamar al método GetBitmapBits, ahora indicándole como
tercer parámetro el puntero a la dirección de memoria reservada. Ası́, después de
hacer las comprobaciones pertinentes, lo único que quedará será el insertar en el
39
3.2. Audio
parámetro de entrada el fotograma que hemos obtenido, y pasar como valor de
retorno un resultado positivo, si es que todo ha funcionado bien.
3.2.
Audio
El audio también es una parte importante en la realización de nuestro proyecto. Será necesario el poder independizar el audio de nuestro fichero de vı́deo, para
poder ası́ hacer el procesado correspondiente. Por ello, se deberán crear funciones
para poder obtener el audio y poder guardarlo en un fichero independiente.
Para ello, se usan diversas interfaces del DirectShow, tales como el IAudioMediaStream, IAudioStreamSample, IMemoryData y el IAudioData.
Todas ellas las encontramos en un apéndice del DirectShow, llamado Multimedia Streaming, en el que se ofrecen facilidades para la reproducción tanto de
secuencias de vı́deo como de audio. Con unos pequeños cambios, se ha conseguido
adaptar estas interfaces a nuestra finalidad, que era la de poder independizar el
stream de audio del stream de vı́deo, guardando dicho audio en un fichero aparte.
Empezaremos hablando acerca de la arquitectura del Multimedia Streaming,
ası́ como de sus ventajas y de su relación ı́ntima con el COM, para pasar a continuación en el punto 3.2.2 a ver cómo se utiliza esta tecnologı́a en la extracción
del audio de los ficheros multimedia.
En el punto 3.2.3 veremos con detalle las funciones creadas para el tratamiento
del audio, una vez separado del fichero multimedia y listo para su uso; y por
últimos, en el punto 3.2.4 veremos un práctico ejemplo de cómo extraer el audio
de un fichero multimedia, haciendo uso de las funciones creadas.
3.2.1.
Multimedia Streaming
Cuando los programadores usan el multimedia streaming en sus aplicaciones,
este reduce considerablemente la cantidad de programación especı́fica necesaria.
Tı́picamente, una aplicación que debe obtener datos multimedia desde un fichero
que debe conocer perfectamente el formato de los datos1 . La aplicación debe
manejar la conexión, trasferir los datos, convertir los datos que sea necesario
1
Al igual que hablamos de datos guardados en un fichero, se sobreentiende que se podrá trabajar igualmente con fuentes hardware, como por ejemplo un lector de CD
40
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
convertir, y representar dichos datos o almacenarlos en disco, conforme el caso.
A causa de que cada formato y cada dispositivo es diferente a cualquier otro, este
proceso es frecuentemente complejo y pesado. El multimedia streaming, por otra
parte, negocia automáticamente la transferencia y conversión de datos desde la
fuente de la aplicación. Las interfaces de streaming proveen un método uniforme
y previsible de acceso y control de los datos, que lo hace fácil para una aplicación
que deba reproducir los datos, a pesar de su fuente original o de su formato.
Jerarquı́a de objetos
El diagrama de la figura 3.5 muestra la jerarquı́a básica de objetos usada en
Multimedia Streaming.
Figura 3.5: Jerarquı́a de objetos en Multimedia Streaming.
Hay tres tipos de objeto básicos definidos en la arquitectura de Multimedia
Streaming:
1. Un stream multimedia, que soporta la interfaz IMultiMediaStream.
2. Streams de datos, que soportan la interfaz IMediaStream y contienen datos
especı́ficos (vı́deo, audio,...). Cada stream multimedia contiene uno o más
de estos streams de datos.
3. Streams de muestras, que soportan la interfaz IStreamSample y son creados
por un stream de datos. Estos objetos representan una unidad básica de
3.2. Audio
41
trabajo para el stream, y que serán en definitiva las muestras, es decir, que
si estamos con un stream de vı́deo, serán fotogramas.
Los objetos que soportan la interfaz IMultiMediaStream son los contenedores
básicos de los streams de datos multimedia. La interfaz IMultiMediaStream incluye métodos que enumeran los objetos del stream de datos; esos streams son
tı́picamente datos de vı́deo y audio, pero pueden incluir datos de cualquier formato, como por ejemplo texto (para los subtı́tulos), o referencias de tiempo SMPTE.
La interfaz IMultiMediaStream es un contenedor genérico, de forma que los programadores pueden crear otras versiones de la interfaz que soporten formatos de
datos especı́ficos. Los objetos que implemente la interfaz IAMMultiMediaStream,
por ejemplo, pueden enumerar y controlar streams de cualquier formato de datos
del DirectShow. A causa de que esos streams de datos individualmente tienen
un formato especı́fico, soportan al menos dos interfaces diferentes: una genérica
y otra especı́fica dependiente de los datos que contiene. Cada stream soporta la
interfaz IMediaStream, que proporciona métodos para obtener su formato y un
puntero al mismo stream. Cualquier interfaz derivada de la IMultiMediaStraem
también soporta la creación de streams de muestras, las unidades básicas de los
datos multimedia.
Una muestra multimedia es una referencia a un objeto que contiene los datos.
Por ejemplo, para un vı́deo, esta muestra será un fotograma. El contenido exacto
de la muestra varia, dependiendo del tipo de medio (audio, texto,...). A causa de
que una muestra es sólo una referencia al objeto de datos, varias muestras pueden
referirse al mismo objeto. La interfaz IStreamSample proporciona métodos que
obtienen y modifican caracterı́sticas de la muestra, tal como las posiciones de
comienzo y fin, el estado, y el stream asociado.
Uso del multimedia streaming
Las interfaces multimedia streaming simplifican considerablemente el proceso
de manipulación de los datos multimedia, ya que eliminan la dependencia que se
produce normalmente con las fuentes hardware o software; además, proporcionan
soporte para todos los formatos multimedia de Microsoft DirectX. Lo que se hace
es abstraer los datos a un nivel alto, de forma que nosotros sólo hablamos de
streams; las aplicaciones pueden incluso mover datos desde un stream a otro sin
conocer absolutamente nada sobre el formato de los datos que contiene dicho
stream.
Para crear un stream multimedia, simplemente deberı́amos seguir los siguientes
pasos:
42
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
1. En primer lugar, deberemos crear el objeto stream multimedia. El método
para crear e inicializar el stream es especı́fico de la arquitectura que usemos.
El DirectShow soporta la interfaz IAMMultiMediaStream, que se usa para
inicializar el stream, aunque también existen otras formas de crear el stream,
por ejemplo usando el IMultiMediaStream.
2. Después de haber inicializado el objeto stream multimedia, la aplicación
usará la función QueryInterface para obtener la interfaz IMultiMediaStream
necesaria para el objeto. Deberá usarse esta interfaz para determinar las
propiedades del stream y enumerar los streams por sı́ mismos. Por ejemplo, podremos obtener un stream especı́fico llamando al método IMultiMediaStream::GetMediaStream con un especı́fico ID. Los ID usados más
comúnmente son el MSPID PrimaryVideo y el MSPID PrimaryAudio.
3. Ahora deberemos llamar al método IUnknown::QueryInterface para obtener una interfaz especı́fica del tipo de stream multimedia; es decir, que si
queremos reproducir un stream de vı́deo, con esto obtendremos la interfaz IDirectDrawMediaStream, que se encargará de reproducir dicho vı́deo
en una superficie DirectDraw. Las interfaces especı́ficas de cada medio definen métodos adicionales necesarios para obtener todas las ventajas que
nos ofrece el formato con el que trabajemos.
4. Crearemos ahora una o más muestras del stream de datos. Cada stream
multimedia soporta el método IMediaStream::CreateSharedSample para la
creación de muestras. La muestra resultante soporta la interfaz IStreamSample, que proporciona control total sobre la muestra y sus caracterı́sticas. Tı́picamente, las muestras extraı́das soportan un método especı́fico
(dependiendo del formato) que es más potente que los métodos de la interfaz IStreamSample. Por ejemplo, la interfaz IDirectDrawMediaStream puede
crear muestras que ataquen directamente a una superficie DirectDraw. En
otras situaciones, sin embargo, nos interesará trabajar con las muestras sin
conocer nada acerca de su formato; para ello, tendrı́amos que usar el método
IMediaStream::CreateSharedSample.
5. Después de crear todas las muestras deseadas, empezarı́amos a reproducir
el stream, llamando al método IMultiMediaStream::SetState y pasándole
como parámetro el correspondiente al comienzo de la reproducción.
6. Por último, deberemos actualizar la muestra en la que nos encontremos,
mediante el método IStreamSample::Update.
43
3.2. Audio
3.2.2.
Extracción de audio
Con todo lo dicho hasta ahora, podemos ver que tenemos perfectamente separada la información de vı́deo de la información de audio, por lo que podemos
extraerlos por separado; es decir, que si queremos podremos extraer un fotograma,
y si lo que nos interesa es el audio podremos extraerlo y guardarlo en un formato
de sonido (como por ejemplo el wav, mp3, ...).
Esto es lo que se ha hecho en el proyecto, el poder extraer el audio de un
fichero multimedia y guardarlo como un archivo de sonido, en concreto en WAV.
Para ello, se ha recurrido al multimedia streaming, para poder extraer el stream
de audio, y a una clase llamada CWaveFile, que se encargará de guardar dicho
stream de audio en un fichero WAV. No nos adentraremos en profundidad en
la explicación de esta clase por no hacer demasiado pesada la lectura de esta
memoria, además de no considerar relevante dicha explicación en estos momentos.
Lo que se hace básicamente es abrir el fichero de vı́deo, buscar el stream de
audio, y cuando se ha encontrado, abrirlo y reproducirlo, mientras que la clase
CWaveFile ya se encarga de ir guardando dichos datos en el archivo de sonido.
Finalmente deberemos tener cuidado de cerrar todo los que habı́amos abierto: el
stream de audio, el fichero multimedia y el fichero de audio.
Para hacer más fácil la extracción del audio de los ficheros de vı́deo, lo que
se ha hecho es crear una función que directamente se encargue de todo. Dicha
función se ha llamado extraeAudio, y que podremos ver en el punto 3.3.2
3.2.3.
Funciones para el tratamiento del audio
Como ya hemos comentado anteriormente, estas funciones se escribieron en
un principio en el lenguaje de programación M, perteneciente a MATLAB, ya
que es mucho más sencillo y práctico el tratamiento del audio en el lenguaje M
que en el lenguaje C++.
Por ello, se ha tenido que hacer una adaptación de dichas funciones al entorno
en el que estamos trabajando continuamente, que es el entorno de C++. Para ello,
el programa MATLAB ofrece un compilador el cual nos realiza la transformación
pertinente, obteniendo como resultado librerı́as estáticas, dinámicas, e incluso el
código fuente C++. Para ver con detalle el funcionamiento de dicho compilador,
véase apéndice A.
Normalmente a estas funciones se le pasarán datos como strings, es decir,
44
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
cadenas de caracteres, ya que el programa MATLAB trata todos los parámetros
como texto, y por lo tanto los datos que le pasemos a la función deberán ser texto
también. Además, estas funciones no retornan ningún valor para comprobar que
se ha completado con éxito o ha ocurrido algún tipo de error. Por ello, deberemos
comprobar con antelación, antes de llamar a estas funciones, que los parámetros
que le pasemos sean ciertamente los que tenemos que pasar (por ejemplo, si el
nombre que le pasamos es el de un vı́deo, comprobar con antelación que dicho
vı́deo existe realmente).
Podremos ver una completa referencia del uso de estas funciones en el punto 3.3.2.
3.2.4.
Ejemplo: Reproducción de Audio con DirectX
En este punto veremos un ejemplo básico y sencillo acerca de cómo reproducir
un archivo de audio, utilizando el DirectX Audio (en concreto el DirectMusic).
Veremos paso a paso los puntos básicos en la reproducción, que serán el inicializar
una interfaz DirectMusic y después reproducir el archivo WAV.
En la figura 3.6 podemos ver el código que necesario para llevar a cabo esta
función. En este código, se reproduce un archivo llamado audio.wav situado en
el directorio raı́z. Veremos ahora paso a paso todas las funciones necesarias.
En primer lugar, y como siempre, deberemos inicializar el COM, mediante el
CoInitialize. Para finalizar, no deberemos olvidar el utilizar la función CoUnitialize, para liberar todas las interfaces que hemos estado utilizando.
A continuación, inicializaremos las dos interfaces que necesitamos: la interfaz IDirectMusicLoader y la interfaz IDirectMusicInterface. Para ello, y como se
explicó, se utiliza la función CoCreateInstance.
Una vez inicializadas las interfaces, el próximo paso será inicializar el sintetizador y los dispositivos que se utilizarán para reproducir el sonido. Deberemos
usar para ello el método InitAudio, con los parámetros pertinentes y que podemos
ver en el código de ejemplo.
Ahora simplemente deberemos cargar el archivo de audio, mediante el método
LoadObjectFromFile, al que deberemos pasarle como parámetro el nombre del
archivo que queremos abrir, pero también con caracteres anchos (WCHAR), tal
y como ocurrı́a en el punto 3.1.3.
45
3.2. Audio
Figura 3.6: Código de ejemplo del DirectX Audio.
Con todo esto ya tenemos el sonido almacenado en un buffer, dispuesto a
ser reproducido. Pero antes de reproducirlo, deberemos traspasar los datos al
sintetizador, que será el que finalmente se encargue de que se escuche el sonido.
Para ello deberemos utilizar el método Download, y a continuación el método
PlaySegmentEx, que será el que realmente reproduzca el sonido.
Ya para finalizar, deberemos detener la reproducción del sonido, mediante el
método Stop; cerrar el fichero que habı́amos abierto con anterioridad, mediante el
método CloseDown; y cerrar todas las interfaces que habı́amos abierto, mediante
el método Release correspondiente a cada una de las interfaces abiertas.
46
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
3.3.
Librerı́a creada
Uno de los objetivos del proyecto era el de crear una librerı́a donde se incluyeran funciones para el tratamiento de los ficheros multimedia. Por lo tanto, es
de especial interés ofrecer una referencia de todas y cada una de las funciones
que estarán disponibles en la librerı́a.
Hemos dividido las funciones, al igual que en otros capı́tulos, en los dos grandes
bloques del proyecto: audio y vı́deo.
La estructura utilizada para la explicación de las funciones es la siguiente:
Sinopsis: resumen de la función.
Formato: sintaxis a utilizar en la llamada a la función y breve explicación
de cada uno de los parámetros que intervienen.
include: Fichero a incluir para poder usar la función.
Descripción: implementación de la función.
3.3.1.
Vı́deo
En esta sección nos encontraremos con funciones para extraer fotogramas,
para sacar las caras de un fotograma en concreto, para posicionarse dentro del
vı́deo,...
Veremos a continuación todas las funciones que se creado, de forma que han
sido separadas según la función que realizan, viendo primero todas las funciones
que se encargan de la extracción de fotogramas, tratamiento de los distintos
formatos y por último las funciones necesarias para el tratamiento de los vı́deos
VOB.
getVideoInfo
Sinopsis: Función que nos proporciona información de un vı́deo.
Formato: int getVideoInfo (char *fileName, int *width, int *height, double
*fps);
• char *fileName: El nombre del vı́deo origen.
3.3. Librerı́a creada
47
• int *width: Puntero a una variable donde se guardará el ancho de los
fotogramas.
• int *height: Puntero a una variable donde se guardará el alto de los
fotogramas.
• double *fps: Puntero a una variable en la que se guardará el número
de fotogramas por segundo.
include: video.h
Descripción: La función nos proporcionará información acerca de los parámetros principales de un vı́deo, tales como la anchura y altura de sus fotogramas, y el número de fotogramas por segundo.
extractMediaDet
Sinopsis: Función que extrae y guarda un fotograma del vı́deo indicado
(en el caso de que sea MPEG1 o AVI); además extrae la cara dadas las
coordenadas y la guarda con el nombre indicado.
Formato: int extractMediaDet (char *fileName, char *output, char *face,
double second, int xc1, int yc1, int xc2, int yc2);
• char *fileName: El nombre del vı́deo origen.
• char *output: El nombre de la imagen donde se guardará el frame.
• char *face: El nombre de la imagen donde se guardará la cara.
• double second: El segundo exacto del fotograma del que queremos
extraer.
• int xc1: La coordenada x del primer ojo.
• int yc1: La coordenada y del primer ojo.
• int xc2: La coordenada x del segundo ojo.
• int yc2: La coordenada y del segundo ojo.
include: video.h
Descripción: La función extraerá el fotograma indicado en second del
vı́deo fileName, y lo guardará en el archivo output. Para ello, utilizará la
interfaz IMediaDet vista en el punto 3.1.3. En el archivo face guardará la
selección del fotograma indicada por las coordenadas xc1, yc1, xc2, yc2. El
valor de retorno será un entero, y será igual a cero si la función falla, o
cualquier otro valor si la función cumple su objetivo.
48
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
NOTA: Recordar que esta interfaz nos será válida cuando estemos en la
plataforma w98, y para los vı́deos MPEG-1 o AVI.
extractSampGrab
Sinopsis: Función que extrae y guarda un fotograma del vı́deo indicado
(siempre que sea MPEG-1 o AVI); además, extrae la cara dadas las coordenadas y la guarda con el nombre indicado.
Formato: HRESULT extractSampGrab (char *fileName, double second,
int xc1, int yc1, int xc2, int yc2, char *output, char *face);
• char *fileName: El nombre del vı́deo origen.
• double second: El segundo exacto del fotograma del que queremos
extraer.
• int xc1: La coordenada x del primer ojo.
• int yc1: La coordenada y del primer ojo.
• int xc2: La coordenada x del segundo ojo.
• int yc2: La coordenada y del segundo ojo.
• char *output: El nombre de la imagen donde se guardará el frame.
• char *face: El nombre de la imagen donde se guardará la cara.
include: video.h
Descripción: La función extraerá el fotograma indicado en second del
vı́deo fileName, y lo guardará en el archivo output. Para ello, utilizará la interfaz ISampleGrabber vista en el punto 3.1.3. En el archivo face guardará la
selección del fotograma indicada por las coordenadas xc1, yc1, xc2, yc2. El
valor de retorno será el tipo HRESULT, y será igual a S OK si la función
lleva a cabo su tarea, y el código de error resultante si la función falla.
NOTA: Cabe diferenciar esta función y la anterior, ya que básicamente
hacen lo mismo, pero cada una debe de ser ejecutada en el entorno correcto,
en el caso contrario no funcionará. Esta función funcionará correctamente en
todos los entornos, siempre que el vı́deo sea MPEG-1, pero consume muchos
más recursos y será preferible utilizar la función anterior en los casos en que
trabajemos en w98 y dejar esta función para cuando trabajemos en wXP
(ver punto 3.1.3).
3.3. Librerı́a creada
49
extractAvi
Sinopsis: Función que extrae y guarda un fotograma del vı́deo indicado,
siempre que sea un vı́deo MPEG2. Además, extrae la cara dadas las coordenadas.
Formato: int extractAvi(char *fileName, char *output, char *face, int
frame, int xc1, int yc1, int xc2, int yc2);
• char *fileName: El nombre del vı́deo origen.
• char *output: El nombre de la imagen donde se guardará el frame.
• char *face: El nombre de la imagen donde se guardará la cara.
• int frame: El número de fotograma a extraer.
• int xc1: La coordenada x del primer ojo.
• int yc1: La coordenada y del primer ojo.
• int xc2: La coordenada x del segundo ojo.
• int yc2: La coordenada y del segundo ojo.
include: video.h
Descripción: La función extraerá el fotograma indicado en frame del vı́deo
fileName, y lo guardará en el archivo output. Para ello, utilizará la librerı́a
AVIFile vista en el punto 3.1.3. En el archivo face se guardará la selección
del fotograma indicada por las coordenadas xc1, yc1, xc2, yc2. El valor de
retorno será un entero, y será igual a cero si la función falla, o cualquier
otro valor si la función cumple su objetivo. Esta función se utilizará para
extraer un único fotograma.
extractMPEG2
Sinopsis: Función que extrae y guarda un fotograma del vı́deo indicado,
siempre que sea un vı́deo MPEG2. Además, extrae la cara dadas las coordenadas.
Formato: long extractMPEG2 (char *fileName, long frame, long xc1, long
yc1, long xc2, long yc2, double duration, char *output, char *face);
• char *fileName: El nombre del vı́deo origen.
• long frame: El número de fotograma a extraer.
• int xc1: La coordenada x del primer ojo.
50
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
• int yc1: La coordenada y del primer ojo.
• int xc2: La coordenada x del segundo ojo.
• int yc2: La coordenada y del segundo ojo.
• double duration: La duración total del vı́deo, en segundos.
• char *output: El nombre de la imagen donde se guardará el frame.
• char *face: El nombre de la imagen donde se guardará la cara.
include: video.h
Descripción: La función extraerá el fotograma indicado en frame del vı́deo
fileName, y lo guardará en el archivo output. Para ello, utilizará la librerı́a
Mpeg2Lib vista en el punto 3.1.3. En el archivo face guardará la selección
del fotograma indicada por las coordenadas xc1, yc1, xc2, yc2. El valor de
retorno será un entero, y será igual a cero si la función falla, o cualquier
otro valor si la función cumple su objetivo. Esta función se utilizará para
extraer un único fotograma. Si lo que queremos es extraer un intervalo de
fotogramas utilizaremos la función SacaFrame, que veremos más adelante.
isVideoMPEG2
Sinopsis: Función muy práctica la cual nos dirá si el vı́deo que le indicamos
es MPEG2 o no.
Formato: bool isVideoMPEG2 (char *video);
• char *video: El nombre del vı́deo del cuál queremos obtener la información.
include: video.h
Descripción: La función comprobará el vı́deo de entrada, y devolverá verdadero si el vı́deo cumple el formato MPEG2, o por el contrario falso si
el vı́deo no lo cumple. Nos será de utilidad cuando tengamos un archivo
con extensión mpg y no sepamos exactamente si el formato es MPEG1 o
MPEG2.
3.3. Librerı́a creada
51
extractFramesMPEG2
Sinopsis: Función que nos extrae ciertos fotogramas de un vı́deo en formato MPEG2, válida para cuando vayamos a hacer el reconocimiento de un
personaje.
Formato: int extractFramesMPEG2 (char *fileName, double duration, char
*spath);
• char *fileName: El nombre del vı́deo origen.
• double duration: La duración total del vı́deo, en segundos.
• char *spath: El directorio donde queremos se guarden los archivos
temporales.
include: video.h
Descripción: Los fotogramas extraı́dos del vı́deo fileName serán uno de cada diez, empezando por el fotograma número treinta y desechando el último,
por si existen errores de inicialización y finalización del vı́deo. El valor de
retorno será un entero, y será igual a cero si la función falla, o cualquier otro
valor si la función cumple su objetivo. Esta función guardará los fotogramas
en el directorio temporal que le hemos indicado (spath), en formato tif, y
con el identificativo del fotograma al que corresponde en el nombre de la
imagen guardada. El formato de los fotogramas será de 352 pı́xels de ancho
por 240 pı́xels de alto.
decodermpeg
Sinopsis: Función que funciona de forma similar a la anterior, pero válida
sólo para vı́deos en formato MPEG1.
Formato: int decodermpeg (char *fileName, char *spath);
• char *fileName: El nombre del vı́deo origen.
• char *spath: El directorio donde queremos se guarden los archivos
temporales.
include: video.h
Descripción: Al igual que la función anterior, esta función nos extrae un
fotograma de cada diez del vı́deo fileName, y lo guarda en un formato ppm,
quedando indicado en el nombre del archivo el fotograma al que corresponde. Dichos archivos se guardarán en el directorio temporal indicado en
52
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
spath. El valor de retorno será un entero, y será igual a cero si la función
falla, o cualquier otro valor si la función cumple su objetivo.
getFrameMediaDet
Sinopsis: Función que nos extrae un fotograma determinado de un vı́deo
MPEG-1.
Formato: int getFrameMediaDet(char *fileName, ı́nt frame, imgdes *output);
• char *fileName: Nombre del vı́deo origen.
• int frame: Fotograma que queremos extraer.
• imgdes *output: Imagen de VICTOR donde se almacena el fotograma.
include: video.h
Descripción: Esta función, a partir de un vı́deo MPEG-1, y dentro de la
plataforma Windows 98, nos extrae un fotograma en concreto. Para ello,
utiliza la interfaz IMediaDet. El resultado será una imagen de VICTOR,
que podremos manipular como queramos, ya que se almacena en memoria.
El valor de retorno será igual a cero si la función falla, o cualquier otro valor
si se extrae el fotograma con éxito.
getFrameSampGrab
Sinopsis: Función que nos extrae un fotogramaen concreto de un vı́deo
MPEG-1.
Formato: int getFrameSampGrab(char *fileName, int frame, imgdes *output);
• char *fileName: Nombre del vı́deo origen.
• int frame: Fotograma que queremos extraer.
• imgdes *output: Imagen de VICTOR donde se almacena el fotograma.
include: video.h
3.3. Librerı́a creada
53
Descripción: Esta función, a partir de un vı́deo MPEG-1, y dentro de
la plataforma Windows XP, nos extrae un fotograma en concreto. Para
ello, utiliza la interfaz ISampleGrabber. El resultado será una imagen de
VICTOR, que podremos manipular como queramos, ya que se almacena en
memoria. El valor de retorno será el tipo HRESULT, y será igual a S OK si
se extrae con éxito el fotograma, o el código de error resultante si la función
falla.
getFrameAvi
Sinopsis: Función que nos extrae un fotograma determinado de un vı́deo
AVI.
Formato: int getFrameAvi(char *fileName, int frame, imgdes *output);
• char *fileName: Nombre del vı́deo origen.
• int frame: Número del fotograma que queremos extraer.
• imgdes *output: Imagen de VICTOR donde se almacena el fotograma.
include: video.h
Descripción: Esta función, a partir de un vı́deo AVI, nos extrae un fotograma en concreto. Para ello, utiliza la librerı́a AVIFile. El resultado será una
imagen de VICTOR, que podremos manipular como queramos, ya que se
almacena en memoria. El valor de retorno será igual a cero si la función
falla, o cualquier otro valor si se extrae el fotograma con éxito.
getFrameMPEG2
Sinopsis: Función que nos extrae un fotograma de un vı́deo MPEG-2 y
almacena el resultado en una imagen de VICTOR.
Formato: int getFrameMPEG2 (char *fileName, long frame, double duration, imgdes *output);
• char *fileName: Nombre del vı́deo origen.
• long frame: Número del fotograma que queremos extraer.
• double duration: Duración total del vı́deo, en segundos.
• imgdes *output: Imagen de VICTOR donde se almacena el fotograma.
54
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
include: video.h
Descripción: Esta función, a partir de un vı́deo MPEG-2 nos extrae un
fotograma en concreto. Para ello, utiliza la librerı́a Mpeg2Lib. Además del
número de fotograma a extraer, deberemos pasarle como parámetro la duración total del vı́deo. El resultado será una imagen de VICTOR, que podremos manipular como queramos, ya que se almacena en memoria. El valor
de retorno será igual a cero si la función falla, o cualquier otro valor si se
extrae el fotograma con éxito.
extractIntervalMPEG1
Sinopsis: Función que nos extrae un intervalo de fotogramas de un vı́deo
MPEG-1, guardándolos con el formato deseado.
Formato: HRESULT extractIntervalMPEG1(char *fileName, int init, int
end, char *output);
• char *fileName: Nombre del vı́deo origen.
• int init: Primer fotograma a extraer.
• int end: Último fotograma a extraer.
• char *output: Ruta completa y nombre que se le darán a los fotogramas.
• char *format: Extensión que tendrán los fotogramas.
include: video.h
Descripción: Esta función, a partir de un vı́deo MPEG-1, nos extrae
un conjunto de fotogramas, delimitados entre init y end, ambos incluidos. Los fotogramas se guardarán en la ruta y nombre indicados en output, seguidos de el número de fotograma al que corresponde, y con la
extensión indicada. Por ejemplo, un valor válido para output serı́a el de
”c : /tmp/f otogramas %05d.jpg”, con lo que los fotogramas se guardarı́an
con formato JPEG, y con cinco dı́gitos para identificar el número de fotograma (fotogramas00030.jpg, fotogramas00031.jpg,...). El valor de retorno
será el tipo HRESULT, y será igual a S OK si la función tiene éxito, o el
código de error resultante si la función falla.
3.3. Librerı́a creada
55
extractIntervalMPEG2
Sinopsis: Función que nos extrae un intervalo de fotogramas de un vı́deo
MPEG-2, guardándolos con el formato deseado.
Formato: int extractIntervalMPEG2(char *fileName, int init, int end, char
*output);
• char *fileName: Nombre del vı́deo origen.
• int init: Primer fotograma a extraer.
• int end: Último fotograma a extraer.
• char *output: Ruta completa y nombre que se le darán a los fotogramas.
• char *format: Extensión que tendrán los fotogramas.
include: video.h
Descripción: Esta función, a partir de un vı́deo MPEG-2, nos extrae un
conjunto de fotogramas, delimitados entre init y end, ambos incluidos. Para
ello, se utilizará la librerı́a Mpeg2Lib. Los fotogramas se guardarán en la
ruta y nombre indicados, seguidos de el número de fotograma al que corresponde, y con la extensión indicada. Por ejemplo, un valor válido para
output serı́a el de ”c : /tmp/f otogramas %05d.jpg”, con lo que los fotogramas se guardarı́an con formato JPEG, y con cinco dı́gitos para identificar
el número de fotograma (fotogramas00030.jpg, fotogramas00031.jpg,...). El
valor de retorno será igual a cero si la función falla, o cualquier otro valor
si se extrae el fotograma con éxito.
extractIntervalAVI
Sinopsis: Función que nos extrae un intervalo de fotogramas de un vı́deo
AVI, guardándolos con el formato deseado.
Formato: HRESULT extractIntervalAVI(char *fileName, int init, int end,
char *output);
• char *fileName: Nombre del vı́deo origen.
• int init: Primer fotograma a extraer.
• int end: Último fotograma a extraer.
• char *output: Ruta completa y nombre que se le darán a los fotogramas.
56
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
• char *format: Extensión que tendrán los fotogramas.
include: video.h
Descripción: Esta función, a partir de un vı́deo AVI, nos extrae un conjunto de fotogramas, delimitados entre init y end, ambos incluidos. Para
ello, se utilizará la librerı́a AVIFile. Los fotogramas se guardarán en la
ruta y nombre indicados, seguidos de el número de fotograma al que corresponde, y con la extensión indicada. Por ejemplo, un valor válido para
output serı́a el de ”c : /tmp/f otogramas %05d.jpg”, con lo que los fotogramas se guardarı́an con formato JPEG, y con cinco dı́gitos para identificar
el número de fotograma (fotogramas00030.jpg, fotogramas00031.jpg,...). El
valor de retorno será igual a cero si la función falla, o cualquier otro valor
si se extrae el fotograma con éxito.
AbreVideoMPEG1
Sinopsis: Función que nos abre un vı́deo y lo deja preparado para la
búsqueda y extracción de fotogramas.
Formato: HRESULT AbreVideoMPEG1 (char *fileName);
• char *fileName: Nombre del vı́deo que queremos abrir.
include: video.h
Descripción: Función que dado un vı́deo en formato MPEG1, inicializa las
interfaces necesarias y prepara el vı́deo fileName para la próxima extracción
de fotogramas de dicho vı́deo. El valor de retorno será del tipo HRESULT
y será igual a S OK si la función tiene éxito, o cualquier otro valor en caso
contrario.
SacaFrameMPEG1
Sinopsis: Función que nos extrae un fotograma de un vı́deo abierto previamente con la función AbreVideoMPEG2 y lo guarda en disco.
Formato: HRESULT SacaFrameMPEG1(double second, imgdes *img);
• double second: Segundo en el que se encuentra el fotograma.
• imgdes *img: Estructura de VICTOR donde se guardará el fotograma.
3.3. Librerı́a creada
57
include: video.h
Descripción: Función que nos extrae un fotograma de un vı́deo abierto
previamente con la función AbreVideoMPEG1. Averiguar el segundo en el
que se encuentra el fotograma será fácil utilizando la función getVideoInfo,
que nos devuelve el número de fotogramas por segundo con el que se reproduce el vı́deo. El valor de retorno será del tipo HRESULT y será igual a
S OK si la función tiene éxito, o cualquier otro valor en caso contrario.
AbreVideoAvi
Sinopsis: Función que nos abre un vı́deo y lo deja preparado para la
búsqueda y extracción de fotogramas.
Formato: PGETFRAME AbreVideoAvi(char *fileName, PAVISTREAM
*stream);
• char *fileName: Nombre del vı́deo que queremos abrir.
• PAVISTREAM *stream: Variable donde se almacenará el stream de
vı́deo con el que vamos a tratar.
include: video.h
Descripción: Función que dado un vı́deo en formato AVI, inicializa las interfaces necesarias y prepara el vı́deo fileName para la próxima extracción
de fotogramas de dicho vı́deo. El valor de retorno será del tipo PGETFRAME y será el que utilizaremos más tarde para la extracción de fotogramas, mediante la función SacaFrameAvi.
CierraVideoAvi
Sinopsis: Función que se encarga de cerrar cualquier vı́deo abierto con la
función AbreVideoAvi.
Formato: void CierraVideoAvi(PGETFRAME t, PAVISTREAM stream);
• PGETFRAME t: Variable devuelta en la llamada a la función AbreVideoAvi.
• PAVISTREAM *stream: Variable devuelta en la llamada a la función
AbreVideoAvi.
58
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
include: video.h
Descripción: A la función le pasaremos como parámetros los que nos ha
devuelto la función AbreVideoAvi, y se encargará de liberar todos los recursos guardados para la extracción de fotogramas.
SacaFrameAvi
Sinopsis: Función que nos extrae un fotograma de un vı́deo abierto previamente con la función AbreVideoAvi y lo almacena en una estructura de
VICTOR.
Formato: int SacaFrameAvi (PGETFRAME t,int numframe, imgdes *imagen);
• PGETFRAME t: Variable devuelta en la llamada a la función AbreVideoAvi.
• int numframe: Número de fotograma a extraer.
• imgdes *img: Estructura de VICTOR donde se guardará el fotograma.
include: video.h
Descripción: Función que nos extrae un fotograma de un vı́deo abierto
previamente con la función AbreVideoAvi. El valor de retorno será del tipo
int y será igual a cero si la función falla, o cualquier otro valor en caso
contrario.
AbreVideoMPEG2
Sinopsis: Función que nos abre un vı́deo y lo deja preparado para la
búsqueda y extracción de fotogramas. Esta función fallará si el vı́deo no
tiene formato MPEG2.
Formato: long AbreVideoMPEG2 (char *fileName);
• char *fileName: Nombre del vı́deo que queremos abrir.
include: video.h
3.3. Librerı́a creada
59
Descripción: Función que dado un vı́deo en formato MPEG2, inicializa la
librerı́a Mpeg2Lib y prepara el vı́deo fileName para la próxima extracción de
fotogramas de dicho vı́deo. El valor de retorno será del tipo long y será igual
a cero si la función falla, o tomará el valor del tamaño del vı́deo, valor de
utilidad para reproducir el vı́deo.
Esta es la primera de las funciones para el tratamiento de los vı́deos VOB,
y que nos serán de gran utilidad cuando queramos reproducir un vı́deo VOB en
nuestra aplicación, sin tener que utilizar el Windows Media Player. Esta función
y todas las siguientes hacen uso de la librerı́a Mpeg2Lib, vista en el punto 3.1.3.
CierraVideoMPEG2
Sinopsis: Función que se encarga de cerrar cualquier vı́deo abierto con la
función AbreVideoMPEG2.
Formato: void CierraVideoMPEG2 ();
include: video.h
Descripción: La función no tiene parámetros, ya que internamente se
mantiene una referencia del vı́deo que está abierto; y no tiene valor de retorno, ya que es imposible que falle. Por lo tanto, deberemos tener cuidado
de llamar a la función después de haber llamado a la función AbreVideoMPEG2. Además de cerrar el vı́deo, liberará la librerı́a Mpeg2Lib y todos los
recursos guardados.
SeekPositionMPEG2
Sinopsis: Función que se sitúa en el fotograma indicado.
Formato: int SeekPositionMPEG2 (long position);
• long position: Número de bytes a moverse en el vı́deo.
include: video.h
Descripción: Función que se moverá dentro del vı́deo para situarse en el
fotograma que indiquemos. Para ello, se mueve dentro del vı́deo una cierta
cantidad de bytes, la que le indiquemos en position; este valor está relacionado con el valor e retorno de la función AbreVideoMPEG2. No hace falta indicarle el nombre del vı́deo porque se mantiene una referencia interna
60
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
del vı́deo abierto. Como valor de retorno tendrá un entero, que valdrá cero
si la función fallo, o cualquier otro valor si la función cumple su objetivo.
Como se explicó con anterioridad, esta forma de moverse dentro del vı́deo
es muy rápida, y por lo tanto la respuesta será casi inmediata.
SacaFrameMPEG2
Sinopsis: Función que nos extrae un fotograma de un vı́deo abierto previamente con la función AbreVideoMPEG2 y lo guarda en disco.
Formato: long SacaFrameMPEG2 (char *output);
• char *output: Nombre con el que queremos que se guarde el fotograma.
include: video.h
Descripción: Función que nos extrae un fotograma de un vı́deo abierto
previamente con la función AbreVideoMPEG2, y que lo guarda en disco
con el nombre de output. El formato a utilizar para guardar el fotograma
será el que le indiquemos en output, y serán válidos los formatos jpg, tif o
ras. El valor de retorno será del tipo int, y será igual a cero si la función
falla, o devolverá el ancho del fotograma, que nos será de utilidad para su
posterior tratamiento.
extraeCara
Sinopsis: Función que nos extrae un recuadro de una imagen determinada,
determinado por ciertas coordenadas.
Formato: int extraeCara(char *fileName, double x1, double y1, double x2,
double y2, char *output, int &ancho, int &alto);
• char *fileName: El nombre de la imagen origen.
• double x1: La coordenada x del primer ojo.
• double y1: La coordenada y del primer ojo.
• double x2: La coordenada x del segundo ojo.
• double y2: La coordenada y del segundo ojo.
• char *output: El nombre de la imagen donde se guardará la cara.
3.3. Librerı́a creada
61
• int &ancho: Un puntero a un entero, donde se guardará el ancho del
fotograma.
• int &alto: Un puntero a un entero, donde se guardará el alto del fotograma.
include: video.h
Descripción: Función que nos extrae la cara de la imagen fileName, dándole
las coordenadas de dos puntos, normalmente de los ojos (x1, y1, x2, y2 ).
La cara se guardará con el nombre de output, con un tamaño de 50 pı́xels
de ancho por 70 pı́xels de alto, y con el formato indicado en output. Esta
función nos será de utilidad cuando trabajemos con los vı́deos VOB, de
los cuales sacamos un fotograma pero después deseamos obtener la cara,
aunque podrı́amos hacer uso de ella en cualquier tipo de vı́deo, ya que
depende sólo de la imagen que le pasemos como parámetro.
Los valores de las coordenadas que le pasemos como parámetros no serán
los reales, sino los relativos al VB. Es decir, que nosotros pasaremos como
coordenada la que saquemos del VB dividida por el ancho del objeto que
contiene el vı́deo en VB, y después esta función ya se encarga de rectificar
este cambio. Se hace de esta forma para tener que realizar operaciones
innecesarias.
3.3.2.
Audio
Tal y como hemos hecho con el vı́deo, detallaremos ahora cada una de las
funciones que se han creado para el audio.
extraeAudio
Sinopsis: Función que nos extrae el audio de un archivo de vı́deo, y lo
guarda en formato WAV.
Formato: int extraeAudio (char *nombre, char *audio);
• char *fileName: El nombre del fichero de vı́deo original, del cual se
extraerá el audio.
• char *audio: El nombre del fichero de audio de salida, en el cual se
guardará, en formato WAV, el audio buscado.
include: audio.h
62
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
Descripción: Función que nos permite, a partir de un archivo de vı́deo origen, extraer el audio y guardarlo en formato WAV. Como valor de retorno,
tendremos un cero si la función ha fallado, o por el contrario, si la función
se ha llevado a cabo satisfactoriamente, dicho valor será 1.
cepstrum
Sinopsis: Función que nos extraerá los coeficientes de un archivo de audio.
Formato: void cepstrum (char *audio, char *coeficientes);
• char *audio: Un string donde le indiquemos el nombre del archivo de
audio original, del que queremos extraer los parámetros.
• char *coeficientes: Un string indicando el nombre del fichero en que
queremos guardar dichos parámetros.
include: audio.h
Descripción: Función que se encarga de llevar a cabo el mel cepstrum, es
decir, la generación de los coeficientes de un vı́deo en concreto. El fichero
de salida (coeficientes), que contendrá los parámetros, tendrá la extensión
mcc. Como entrada, como vemos, no tenemos que pasarle el vı́deo del que
queremos extraer los parámetros, sino directamente el audio, por lo que
previamente a esta función se tendrá que hacer uso de la función extraeAudio
para extraer el audio de dicho vı́deo.
genera modelo
Sinopsis: Función que genera el modelo de audio de un personaje, a partir
de uno o varios archivos de coeficientes.
Formato: genera modelo coef1, coef2, coef3, ..., modelo
• coef1, coef2, ...: El nombre del fichero o ficheros de coeficientes, hasta
un máximo de 63 (recordar que estos archivos tienen extensión mcc).
• modelo: El nombre del archivo donde queremos se guarde el modelo
que se creará (este tendrá la extensión gmm).
include: audio.h
3.3. Librerı́a creada
63
Descripción: Esta función es la que se encarga de, a partir de los ficheros
de parámetros (o coeficientes) generados por la función anterior, nos crea
un fichero que contendrá el modelo del personaje. Por lo tanto, le podremos
pasar uno o varios ficheros de coeficientes, y en este punto se nos presenta
un problema, y será que el lenguaje C++ acepta funciones que no tengan
los parámetros definidos, pero en el punto de enlazar esta función con Visual
Basic resulta imposible, por lo que se buscó otra solución.
Esta solución consiste en crear un archivo ejecutable, que sı́ acepta varios
parámetros como entrada y se puede llamar fácilmente desde el Visual Basic.
Ası́, cuando queramos usar esta función, al llamarla se abrirá una ventana
de comandos, y se ejecutará dicha función, aunque el usuario prácticamente
no notará que se está ejecutando la función en una ventana de comandos,
ya que aparecerá minimizada y en cuanto termine se cerrará.
verifica modelo
Sinopsis: Función que comprueba si un fichero de coeficientes pertenece a
un determinado modelo.
Formato:int verifica modelo (char *modelo, char *coeficientes, char *salida);
• char *modelo: El nombre del fichero que contiene el modelo.
• char *coeficientes: El nombre del fichero que contiene los parámetros
que queremos verificar.
• char *salida: El nombre del fichero donde queremos que se guarde el
valor de salida.1
include: audio.h
Descripción: Función la cual a partir de modelo y un fichero de coeficientes, nos da un valor para comprobar si realmente dichos coeficientes
pertenecen al personaje o no. Posteriormente, si queremos utilizar dicho
valor, simplemente tendremos que abrir el fichero de salida de la función, y
leer la primera lı́nea, que será dicho valor.
1
El formato de este archivo es de texto.
64
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
Remuestreo
Sinopsis: Función que remuestrea el audio a una nueva frecuencia de 22050
Hz y lo convierte a mono. Además, nos cortará el audio por la posición
inicial y final indicada.
Formato: int remuestreo (char *fileName, char *Tin, char *Tout, char
*output);
• char *fileName: Nombre del fichero que contiene el audio original, en
formato WAV.
• char *Tin: Tiempo inicial, en segundos.
• char *Tout: Tiempo final, en segundos.
• char *output: Nombre del fichero destino, que también será WAV, y
que contendrá el audio remuestreado a la frecuencia deseada.
include: audio.h
Descripción:Función que nos remuestrea el audio a una frecuencia de
22050 Hz, que será la que nosotros utilizamos para la generación de coeficientes. Además, nos cortará el archivo de audio de salida por los puntos
que nosotros le indiquemos. Esta función se utiliza normalmente de forma interna, pero se ha decidido extraerla también ya que se considera que
puede servir de utilidad en algunos casos, como por ejemplo en casos que ya
tengamos el audio separado del vı́deo, pero no lo tengamos a la frecuencia
deseada.
resampleOnly
Sinopsis: Función que remuestrea el audio a una nueva frecuencia de 22050
Hz y lo convierte a mono.
Formato: void resampleOnly (char *fileName, char *output);
• char *fileName: Nombre del audio origen, muestreado a cualquier frecuencia.
• char *output: Nombre del fichero de audio destino, que contendrá el
audio mono y muestreado a 22050 Hz.
include: audio.h
3.3. Librerı́a creada
65
Descripción: Funciona básicamente como la función remuestreo, pero esta
función no cortará el audio, sino que realizará la conversión de frecuencia
a todo el archivo de audio. También convertirá el audio a mono, si es que
originalmente tenı́a más de un canal.
66
CAPı́TULO 3. MANEJO DE FUNCIONES MULTIMEDIA
Capı́tulo 4
Teorı́a del reconocimiento
La base de este proyecto es el reconocimiento de personajes, tanto a nivel visual como de audio. Por lo tanto, consideramos necesario el incluir los conocimientos teóricos fundamentales en los que se basan las operaciones de reconocimiento.
Ası́, y como a lo largo de todo el proyecto, hemos dividido este capı́tulo en
dos puntos, dedicando el primero de ellos al reconocimiento visual y el segundo
al reconocimiento de audio.
Quiero recalcar que no es objetivo del proyecto el estudiar las técnicas de
reconocimiento. Por ello, podremos encontrar más información acerca de este
tema en [1].
4.1.
Reconocimiento empleando información visual
Cuando se trata del reconocimiento de vı́deo, lo que realmente pretendemos
es, a partir de una secuencia de vı́deo, averiguar si la cara de la persona que
buscamos aparece realmente dentro de dicha secuencia. Para ello, generalmente
se debe realizar una búsqueda para encontrar las caras que puedan haber en cada
fotograma de la secuencia, para después llevar a cabo el reconocimiento de cada
una de las caras encontradas.
En la figura 4.1 podemos ver el diagrama de bloques que se ha implementado
en este proyecto. En dicho diagrama podemos observar que la entrada será la
secuencia en la cuál queremos encontrar a la persona buscada. A continuación
aparece el bloque de detección de caras, que será el que nos encuentre todas las
67
68
CAPı́TULO 4. TEORı́A DEL RECONOCIMIENTO
Figura 4.1: Diagrama de bloques de un sistema de reconocimiento visual.
caras existentes en la secuencia. La salida de dicho bloque se introduce al bloque
de reconocimiento de caras, cuya tarea será la de, a partir del modelo de la persona
que buscamos, separar todas las caras encontradas en la secuencia en dos tipos:
las que pertenecen al personaje buscado y las que no. Una vez reconocidas las
caras, pasaremos los resultados a un bloque que combinará todos los resultados
obtenidos en cada imagen, y pasaremos al bloque de decisión, el cual nos dirá si
realmente se ha encontrado al personaje en la secuencia, o en caso contrario que
no aparece en dicha secuencia.
Cabe comentar que previo al reconicimiento de caras, es necesaria una normalización para eliminar los cambios en la posición de la cara, en su expresión o
las diferentes condiciones de iluminación.
De entre todas las técnicas existentes para el reconocimiento de caras, la que
se ha utilizado en el proyecto es la de Análisis de Componente Principal (PCA),
ya que se ha demostrado que es una de las mejores técnicas existentes en el
momento, especialmente en las aplicaciones de identificación de caras.
La idea básica de la técnica PCA es reducir considerablemente las dimensiones
del espacio en el cuál se van a realizar los cálculos, realizando proyecciones del
original dentro de un subespacio de dimensión mucho menor. Después, se usarán
dichas proyecciones para representar el original. Este subespacio está representado
por un conjunto de vectores ortonormales, denominados eigenfaces.
De forma básica, el proceso de reconocimiento de una persona se llevará a
cabo mediante los siguientes pasos:
Obtención de una cierta cantidad de imágenes fijas que contienen el rostro
de la persona buscada.
4.1. Reconocimiento empleando información visual
69
Se proyectan las caras de test sobre el subespacio formado por esas eigenfaces.
Se reconstruye la cara del personaje a partir de las eigenfaces obtenidas
anteriormente.
Se calcula el error de reconocimiento, que será el que realmente nos servirá para
decidir si el personaje que aparece en la secuencia es la persona que buscábamos
o no.
En el proyecto se ha implementado una variante del modelo PCA original,
en el que el análisis trabaja sobre un grupo de diferentes vistas de la misma
persona a ser reconocida, obteniendo un grupo particular de eigenfaces para cada
personaje, llamadas self-eigenfaces.
La figura 4.2 muestra un ejemplo donde se obtiene un conjunto de selfeigenfaces a partir de tomas distintas del rostro de la misma persona. En dicha
figura podemos ver que se aumenta el conjunto de caras de entrenamiento añadiendo las imágenes espejo de cada una de ellas. Este paso, que se basa en la
simetrı́a de la cara, intenta mejorar la calidad de las self-eigenfaces, ya que añade
variaciones adicionales a las caras de entrenamiento.
Figura 4.2: Ejemplo de caras de entrenamiento y self-eigenfaces.
70
CAPı́TULO 4. TEORı́A DEL RECONOCIMIENTO
Pasaremos ahora a la etapa de test, en la cual cada imagen que tiene que ser
reconocida se proyecta sobre un grupo diferente de self-eigenfaces. Pongamos que
x es un imagen de test representada como un vector columna, y xn es la cara
después del proceso de normalización. Entonces, los coeficientes de proyección se
usan para aproximar la imagen de test original, y el error de reconstrucción se
usa para tomar la decisión final. Los coeficientes de proyección de la cara de test
usando las self-eigenfaces de la persona se obtiene haciendo uso de la siguiente
ecuación:
(4.1)
y m = VmT (xn − xm
µ)
donde xm
µ es la cara media de la persona m y Vm es una matriz que contiene
las Km eigenfaces principales vkm de la persona m. Una vez hayamos obtenido los
coeficientes de proyección, la cara de test original se podrá aproximar mediante
la siguiente ecuación:
xm
n =
Km
X
y m (k).vkm + xm
µ
(4.2)
k=1
Después de reconstruir la imagen de test, el error de reconstrucción ²m se
calculará de la siguiente forma:
²m =
v
u
1 u
u 1
t
255
MN
M
N
X
2
|xn (j) − xm
n (j)|
(4.3)
j=1
donde j es el ı́ndice de vector.
La idea básica de este modelo es que dada una cara de test, se conseguirá un
error de reconstrucción bajo cuando se utilice el grupo de self-eigenfaces del personaje correspondiente, o lo que es lo mismo, que habremos acertado en el reconocimiento.
En la figura 4.3 podremos ver un ejemplo de reconocimiento, en el cuál se ha
intentado reconocer a un personaje mediante cuatro tomas diferentes de su cara,
y utilizando las self-eigenfaces mostradas en la figura 4.2. Podemos observar que
se producen dos casos bien identificados:
En el primer caso, las caras de test A y B están situadas aproximadamente
en la misma posición que las imágenes de entrenamientos que veı́amos en
la figura 4.2. Podemos ver que el error de reconstrucción nos será útil para
el reconocimiento de caras. En este caso, la cara reconstruida An se parece
mucho a la cara normalizada An , y por lo tanto el error de reconstrucción es
pequeño. En el caso de la cara B podemos ver que la cara reconstruida B n no
se parece a la cara normalizada B n , y por lo tanto el error de reconstrucción
será más alto.
4.1. Reconocimiento empleando información visual
71
En el segundo caso, la posición de las caras normalizadas C n y Dn difiere
mucho de la posición de las caras en el entrenamiento (ver figura 4.2).
Por lo tanto, las caras reconstruidas C n y Dn , que se construyen a partir
de combinaciones lineales de las self-eigenfaces de la figura 4.2, son muy
diferentes de las caras normalizadas. Por ello, el error de reconstrucción es
mucho mayor que el obtenido en el caso B. Otro detalle será que en el caso
D el tamaño de la imagen no es el mismo que el de la cara normalizada,
que tendrá siempre el mismo tamaño, por lo que se añade una deformación
adicional que conlleva el que el error sea mucho mayor.
Figura 4.3: Ejemplo de reconocimiento de caras.
Para finalizar con este punto, mostraremos los detalles matemáticos del modelo PCA. Supongamos que X = x1 , x2 , ..., xM es un conjunto de vectores Ndimensionales. El valor medio (xµ ) y la covarianza (Σ) de los datos podrán obtenerse de la siguiente forma:
M
1 X
xm
(4.4)
xµ =
M m=1
y
M
1 X
ΣN xN =
[xm − xµ ][xm − xµ ]T
(4.5)
M m=1
72
CAPı́TULO 4. TEORı́A DEL RECONOCIMIENTO
donde ΣN xN es una matriz simétrica de N xN y caracteriza la dispersión del
conjunto de datos. Si suponemos ahora que gi = xi − xµ y que G = [g1 , g2 , ..., gM ],
podremos reescribir la ecuación de la matriz de varianza de la siguiente forma:
ΣN xN = GGT
(4.6)
Ası́, tendremos que un vector distinto de cero para el que se cumpla la siguiente
ecuación:
ΣN xN vk = λk vk
(4.7)
será un eigenvector de la matriz de covarianza, mientras que λk será su correspondiente eigenvalue. Si suponemos ahora que λ1 , λ2 , ..., λK son los eigenvalues mayores de ΣN xN , entonces la matriz:
VN xK = [v1 , v2 , ..., vK ]
(4.8)
contiene los K eigenvectores dominantes. Esos vectores abarcan un subespacio K dimensional al que nos referiremos como subespacio principal. Cuando el conjunto
de datos está formado por caras, esos eigenvectores se llaman eigenfaces.
Un vector de entrada x N -dimensional puede ser transformado linealmente
dentro de un vector K -dimensional y como sigue:
x=
K
X
yk vk + xµ
(4.9)
k=1
Se ha diseñado la PCA para minimizar el error cuadrático medio de reconstrucción, ², sobre el conjunto de datos de entrenamiento, donde:
²=
M
1 X
|xm − xm |2
M m=1
(4.10)
Sin embargo, en situaciones prácticas los eigenvectores de ΣN xN son difı́ciles
de obtener, debido a que la matriz es muy grande. No obstante, los eigenvectores
principales pueden estimarse usando una técnica similar a la SVD (Singular Value
Descomposition) y que pasamos a resumir a continuación.
Sea vj un eigenvector de ΣN xN y sea λj su eigenvalue correspondiente; entonces:
GGT vk = λk vk
(4.11)
4.2. Reconocimiento empleando información del audio
73
Si multiplicamos ambas partes por GT , y asumiendo que uk = GT vk y que
CM xM = GT G, podremos expresar la ecuación anterior de la siguiente forma:
CM xM uk = λk uk
(4.12)
A partir de aquı́, vemos que podemos obtener los eigenvalores de ΣN xN obteniendo los eigenvalores de CM xM . Por lo tanto, como es sabido que el número de
elementos de CM xM es mucho menor que el de ΣN xN , se simplifica considerablemente el problema computacional.
4.2.
Reconocimiento empleando información del
audio
En el audio existente en una secuencia de vı́deo también existe multitud de
información que puede ser explotada para el reconocimiento de personajes. Por lo
tanto, se hará uso del audio para complementar el reconocimiento visual o incluso
para llevar a cabo el reconocimiento cuando sólo tengamos un fichero de audio.
En la figura 4.4 podemos ver el diagrama de bloques básico para llevar a cabo
el reconocimiento mediante el audio. En dicho diagrama podemos observar que
el primer paso será una segmentación del audio de entrada, para obtener trozos
de audio homogéneos en los que sólo aparezca el personaje hablando, o en el que
haya música, ruido o incluso trozos de silencio. Ası́, estos trozos se pasarán a un
bloque que reconocerá si realmente dicho sonido es voz o no, para descartar todos
los bloques en los que no aparezca el personaje. Por último, se aplican todas las
técnicas de reconocimiento de audio para obtener la identidad del personaje que
está hablando.
En muchos casos, la segmentación del audio de entrada no será tan fácil como
se ha descrito, ya que es posible que el personaje esté hablando con una música
de fondo, que hablen dos o más personajes al mismo tiempo, o cualquier otro
problema que haga que la voz del personaje suene distinta. Para el proyecto se
ha asumido que sólo un personaje está hablando a la vez.
Además, en el proyecto también se ha asumido que el audio de entrada ya
es voz, por lo que dejaremos aparte los dos primeros bloques del diagrama, para
74
CAPı́TULO 4. TEORı́A DEL RECONOCIMIENTO
Figura 4.4: Diagrama de bloques en el reconocimiento de audio.
centrarnos en el tercero de los bloques, es decir, en el reconocimiento de voz y las
técnicas usadas para llevarlo a cabo.
Para llevar a cabo el reconocimiento, serán necesarios dos pasos:
Extracción de parámetros: A partir de señales de voz del personaje
a reconocer, llevaremos a cabo medidas para extraer los rasgos de dicho
personaje, que corresponderán a ciertos parámetros de la voz.
Comparación de parámetros: Una vez tengamos el modelo del personaje
a reconocer, podremos comparar las señales de audio que tengamos para
decidir si se ha reconocido al personaje o no.
Se ha demostrado que el espectro frecuencial de la voz es muy útil para el
reconocimiento, ya que dicho espectro refleja directamente el efecto del tracto
vocal del personaje, que es el principal factor psicológico que distingue a la voz
de las personas.
El conjunto de parámetros que utilizaremos para el reconocimiento de la voz
serán los Mel-Frequency Cepstrum Coefficients (MFCC ). Estos parámetros están
basados en la variación de los anchos de banda crı́ticos del oı́do humano con
la frecuencia, que tendrá filtros espaciados linealmente en bajas frecuencias y
logarı́tmicamente a altas frecuencias para capturar las principales caracterı́sticas
fonéticas de la voz. Esto se puede comprobar en la escala de frecuencias mel, que
usa un espaciado lineal por debajo de los 1000 Hz, y un espaciado logarı́tmico
por encima de los 1000 Hz.
En la figura 4.5 podemos ver un diagrama de bloques del proceso de extracción
de los parámetros MFCC. El único requerimiento es que la señal de entrada
esté muestreada a 22050 muestras/s, para que cubra todos los sonido que generan
los humanos.
Pasaremos ahora a detallar los bloques de la figura 4.5. En primer lugar nos
4.2. Reconocimiento empleando información del audio
75
Figura 4.5: Extracción de parámetros MFCC.
encontramos con el Windowing, que nos partirá la señal en trozos para poder ser
procesada posteriormente. A continuación cada uno de estos trozos pasará por el
bloque de FFT, que convertirá la señal al dominio frecuencial. Ya que la salida de
este bloque será compleja, se incluye a continuación un bloque que se quedará con
el valor absoluto de dicha señal compleja. Ahora, y tras un bloque de preénfasis,
se pasará la señal por un banco de filtros del tipo mel, cuyo aspecto puede verse
en la figura 4.6 1 . Para finalizar, simplemente nos quedará aplicar el Cepstrum,
que nos convertirá la señal de nuevo al dominio temporal; para ello, se aplica la
DCT (Discrete Cosine Transform), cuya salida serán los coeficientes MFCC.
Una vez obtenidos los parámetros correspondientes al personaje, pasaremos
ahora a la parte de reconocimiento. La técnica usada para el reconocimiento
está basada en el cociente de verosimilitud, de forma que dados los coeficientes del
segmento de voz a reconocer, se calcula su probabilidad empleando el modelo de la
persona buscada. Este modelo se construye empleando la mezcla de componentes
gaussianas (GMM, Gaussian Mixture Models). Se ha utilizado este modelo gracias
a las ventajas que presenta; la primera de esas ventajas es que tiene la capacidad
de representar densidades arbitrarias de los rasgos de los personajes. Otra ventaja
es que las decisiones finales de clasificación están basadas en probabilidades, y
no en distancias. Además, las componentes individuales gaussianas se interpretan
normalmente para representar las clases acústicas dependientes del personaje.
1
Vemos que los filtros tienen una respuesta en frecuencia triangular, y el espaciado depende
de una constante del modelo.
76
CAPı́TULO 4. TEORı́A DEL RECONOCIMIENTO
Figura 4.6: Banco de filtros mel.
Capı́tulo 5
Programación en Visual Basic y
Visual C++
Debido a los problemas surgidos con la programación y creación de librerı́as,
tanto estáticas como dinámicas, hemos considerado necesario dedicar un punto
a explicar las opciones existentes y las soluciones por las que se ha optado en
nuestro proyecto.
Tal y como se ha dicho en los capı́tulos anteriores, la creación de librerı́as es
necesaria y fundamental para la puesta en marcha de nuestro proyecto, ya que
son el punto clave para enlazar las dos plataformas de programación.
Además, hemos visto que deberemos adaptar las funciones creadas originalmente en MATLAB, para lo cuál también necesitamos el poder trabajar con
librerı́as dinámicas. Encontraremos más información de todo lo relacionado con
el MATLAB en el anexo A.
Ası́, dejando aparte el MATLAB, veremos en los siguientes puntos la creación
de librerı́as dinámicas (DLL) en Visual C++ (en adelante VC++), y a continuación podremos ver cómo se usan después esas librerı́as en Visual Basic (en
adelante VB) y las diferentes formas de usarlas en VC++. Para finalizar, se
explicará el uso de librerı́as estáticas (normalmente con extensión lib).
5.1.
Creación de DLLs en VC++
La aplicación con la que programamos en C++ (Microsoft Visual C++) ofrece
un amplio rango de posibilidades cuando creamos un nuevo proyecto. Todas ellas
tienen su función, pero en nuestro caso la que nos interesa es la opción de ”Win32
77
78
CAPı́TULO 5. PROGRAMACIÓN EN VISUAL BASIC Y VISUAL C++
Dynamic-Link Library”, como podemos ver en la figura 5.1.
Figura 5.1: Ventana de nuevo proyecto en Microsoft VC++.
Una vez hayamos creado satisfactoriamente nuestro proyecto, eligiendo la opción anterior, empezaremos a escribir nuestro código fuente. Para ello, escribiremos las funciones normalmente, con los parámetros que consideremos necesarios
y con el valor de retorno que deseemos. La única consideración que hay que tener
será la de añadir la palabra WINAPI entre el tipo de valor de retorno y el nombre
de la función. Es decir, que para una función que retorne un entero, y que tenga
como parámetros dos caracteres, tendremos que escribir el código siguiente:
int WINAPI funcion (char a, char b) {
//código
}
Aparte de todas las funciones que hayamos programado, deberemos añadir
otra que será la principal, al igual que ocurre cuando tenemos un proyecto que
crea un ejecutable, en el cuál deberá existir una función main. Esta función se
llamará DllMain, y tendrá tres parámetros:
5.1. Creación de DLLs en VC++
79
1. Un identificador del módulo de la DLL.
2. La razón por la que se ha llamado a la librerı́a.
3. Parámetro reservado.
Ası́, el aspecto que tomará dicha función será el siguiente:
BOOL WINAPI DllMain (
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpReserved) {
}
En el código de esta función, y dependiendo de la razón por la que se ha
llamado a la librerı́a, se introducirá un código u otro. Normalmente, y en todos
nuestros casos, no se introduce ningún tipo de código.
Y con esto ya tendrı́amos el archivo de código fuente terminado. Ahora deberemos añadir un segundo archivo, con extensión DEF y que se denominará archivo de definiciones. Aquı́ será donde le indiquemos al compilador las funciones que
queremos exportar. Para ello, simplemente pondremos en cada lı́nea el nombre
de la función que queramos exportar, seguido del sı́mbolo de @ y de un número
consecutivo que identificará a la función. Además de los nombres de las funciones,
se deberán añadir unos campos de cabecera. Para clarificar esta explicación, pondremos un ejemplo de un archivo DEF:
;Módulo DEF para EJEMPLO.DLL
LIBRARY EJEMPLO
DESCRIPTION ’Funciones de ejemplo’
EXPORTS
funcion1
@1
funcion2
@2
;Fin del módulo
80
CAPı́TULO 5. PROGRAMACIÓN EN VISUAL BASIC Y VISUAL C++
Podemos ver que en este código de ejemplo la librerı́a dinámica se hubiera llamado ejemplo.dll (la primera y la última lı́nea son comentarios, ya que empiezan
por ’;’), y hubiera exportado dos funciones, llamadas funcion1 y funcion2.
En la figura 5.2 podemos ver una ventana de entorno de trabajo del programa
Visual C++, en el cual se ha creado un proyecto llamado ejemplo, y en el que
podemos ver que existen dos ficheros principales: el fichero con extensión cpp,
que contendrá todas las declaraciones de funciones, y el fichero con extensión def,
que contendrá todas las definiciones de las funciones que se van a exportar, tal y
como se ha explicado anteriormente.
Figura 5.2: Ejemplo de proyecto en VC++.
Una vez llevados a cabo todos estos pasos satisfactoriamente, se le indicará a
la aplicación que compile el código, dando como resultado en el directorio en el
que se indique el fichero .dll que tenı́amos como objetivo.
5.2.
Llamada a DLLs en VB
Ahora, una vez tenemos las funciones que hemos desarrollado dentro de una
librerı́a dinámica dll, tendremos que enlazar dicha librerı́a con el VB, para poder
hacer uso de dichas funciones.
Para ello, dentro del programa crearemos un proyecto nuevo, y no elegiremos
ninguna opción especial, sino simplemente la opción de EXE estándar, tal y como
5.2. Llamada a DLLs en VB
81
se muestra en la figura 5.3.
Figura 5.3: Ventana de nuevo proyecto en Microsoft VB.
Con esto, se nos abrirá una nueva ventana de proyecto, donde podemos ir
añadiendo los objetos que deseamos para nuestra aplicación.
Para llamar a las funciones de la librerı́a, deberemos crear un nuevo módulo,
yendo al menú Proyecto y eligiendo el submenú de Agregar módulo. Los módulos
se utilizan para realizar declaraciones de funciones comunes al proyecto, o para
declarar variables globales.
En la figura 5.4 podemos ver un ejemplo de una ventana de proyecto dentro del
programa Visual Basic; en dicho proyecto se ha añadido un fichero de formulario,
donde crearemos nuestra aplicación gráfica, y un fichero de módulo, en el que
realizaremos las declaraciones de funciones externas.
Dentro de este módulo, haremos las llamadas a nuestras funciones. Para e-
82
CAPı́TULO 5. PROGRAMACIÓN EN VISUAL BASIC Y VISUAL C++
Figura 5.4: Ejemplo de proyecto en VB.
llo, deberemos empezar por la palabra clave Declare, seguida de Function o Sub,
según la función devuelva algún parámetro o no, respectivamente. A continuación
pondremos el nombre de la función, exactamente igual que el que habı́amos exportado anteriormente1 , seguido de la palabra clave Lib y el nombre de la librerı́a
donde se encuentra la función, entre comillas.
Ahora le llega el momento a los parámetros, declarándolos del mismo tipo que
habı́amos exportado, e indicando delante la palabra clave ByVal si pasamos el
parámetro por valor o ByRef, si lo pasamos por referencia.
Para finalizar, y si procede, indicaremos el tipo del valor de retorno, anteponiendo la palabra clave As.
Vemos a continuación como quedarı́a una declaración de ejemplo:
Declare Function f Ejemplo Lib ”ejemplo.dll” (ByVal p1 As Integer,
ByVal p2 As Double) As Boolean
En este ejemplo hemos declarado la función f Ejemplo, con dos parámetros:
un entero y un double. El valor de retorno será booleano, y si no ponemos una
ruta especı́fica en el nombre de la librerı́a, como es el caso, queda implı́cito que
dicha librerı́a existirá en el mismo directorio en el que se encuentre el fichero
ejecutable creado.
1
Es importante que el nombre sea exactamente el mismo, incluso con mayúsculas y minúsculas iguales.
5.3. Llamada a DLLs en VC++
83
En la figura 5.5 podemos ver un ejemplo de cómo se llamarı́a a la función que
hemos declarado anteriormente, llamada f Ejemplo, y a la que debemos pasar
dos parámetros y recoger su valor de retorno.
Figura 5.5: Ejemplo de código en VB.
Deberemos tener en cuenta que los tipos de variables no coinciden entre las
dos plataformas, sino que existen una serie de variaciones con algunos tipos. En
el apéndice B encontraremos una tabla con todas estas conversiones.
5.3.
Llamada a DLLs en VC++
Tendremos dos formas de llamar a funciones incluidas dentro de librerı́as
dinámicas: la forma directa, en la que se llamará directamente a la dll, y la forma
indirecta, en la que se utilizará un archivo lib y un archivo h.
5.3.1.
Forma directa
No será muy usual el utilizar la forma directa de llamar a las librerı́as dinámicas, ya que normalmente se utilizará la forma indirecta, pero existen casos en
los que es necesario el uso de esta forma directa, por lo que se ha considerado
84
CAPı́TULO 5. PROGRAMACIÓN EN VISUAL BASIC Y VISUAL C++
necesario el incluir una referencia sobre esta forma de llamar a las funciones de
una dll.
Para utilizar una función que se encuentre incluida dentro de una dll, tendremos que utilizar ciertos métodos que detallamos a continuación.
En primer lugar deberemos cargar la librerı́a dentro de nuestro proyecto, mediante la función LoadLibrary, a la cual deberemos pasarle como parámetro la
ruta completa donde se encuentre la librerı́a, y que nos devolverá un puntero a
una estructura que identifica a dicha librerı́a, del tipo HINSTANCE.
Deberemos hacer una declaración, mediante la palabra clave typedef, de los
parámetros que tiene la función y del valor de retorno que tiene, tal y como se
muestra en la figura 5.6. Como vemos, el tipo que le hemos asignado (LPFNDLLFUNC1) será el que más tarde identifique a esta función, y declararemos una
nueva variable de este tipo, que será la que recoja el puntero a dicha función.
Para cargar la función, haremos uso del método GetProcAddress, al cuál deberemos pasarle como parámetros el puntero que identifique a la librerı́a, y el
nombre de la función que habı́amos exportado, exactamente igual que lo habı́amos
definido en el archivo DEF (ver punto 5.1). El valor de retorno de este método
será el que hemos declarado con anterioridad.
Ahora ya podemos usar la función como si la hubiéramos declarado en el
mismo documento, utilizando los parámetros y valor de retorno que habı́amos
definido al principio con el typedef.
Para finalizar, deberemos liberar la librerı́a que habı́amos cargado, mediante
el método FreeLibrary, al cuál deberemos pasarle como parámetro el puntero a
la librerı́a.
5.3.2.
Forma indirecta
Esta será la forma que se utilizará normalmente, ya que es la más intuitiva y
la que más fácil resulta a la hora de la programación.
Esta forma se basa en que cuando se ha creado una dll, también se ha creado
un archivo lib y un archivo h, en los cuales se encuentran las declaraciones de las
funciones que se han exportado dentro de la librerı́a dinámica.
5.4. Uso de librerı́as estáticas
85
Figura 5.6: Código de ejemplo: utilizar una DLL en VC++.
Por lo tanto, cuando queramos utilizar dichas funciones, simplemente deberemos incluir al archivo lib dentro de los archivos de nuestro proyecto, y hacer una
referencia al archivo h 1 dentro del archivo cpp desde el que queramos hacer uso
de dichas funciones.
Una vez seguidos estos pasos, podremos hacer uso de cualquiera de las funciones que se hayan exportado dentro de la librerı́a dinámica.
5.4.
Uso de librerı́as estáticas
Pasaremos ahora a detallar el uso de librerı́as estáticas, mucho más fáciles de
utilizar en la programación y mucho más intuitivas.
En primer lugar, una breve reseña de cómo crear estas librerı́as estáticas. En
el menú de nuevo proyecto del VC++, elegiremos la opción de Win32 Static
Library, tal y como podemos ver en la figura 5.7.
1
Mediante la instrucción #include”archivo.h”.
86
CAPı́TULO 5. PROGRAMACIÓN EN VISUAL BASIC Y VISUAL C++
Esta vez no deberemos hacer nada en especial, sino simplemente crear un
archivo de cabecera, con extensión h, que contenga todas las funciones que queremos exportar. Una vez hayamos compilado el proyecto, obtendremos como salida
un archivo con extensión lib, que junto con el archivo de cabecera serán los que
tenemos que distribuir para usar las funciones creadas.
Figura 5.7: Ventana de nuevo proyecto en VC++.
Para hacer uso de las funciones exportadas en librerı́as estáticas tendremos
que utilizar el programa VC++, ya que el VB no da opciones para este tipo de
librerı́as.
Simplemente tendremos que añadir en nuestro archivo una definición, mediante la palabra clave include, seguida del nombre del archivo cabecera que
habı́amos creado con antelación. Un ejemplo serı́a el siguiente:
#include "libreria.h"
Además deberemos incluir a los archivos de nuestro proyecto la librerı́a que
queramos usar, ya que en caso contrario la aplicación no encontrará los datos a
los que se refiere el archivo de cabecera que le habı́amos indicado.
Capı́tulo 6
Entorno gráfico
Ya que una de las finalidades era crear un entorno gráfico ameno para el
usuario de estas funciones, dedicaremos este capı́tulo a mostrar la forma que
tiene dicho entorno, ası́ como la forma básica de usar dicho programa con todas
las facilidades que se le han añadido.
Deberemos diferenciar dos vertientes básicas del programa: reconocimiento y
detección. Cada una de ellas necesitará diferentes opciones y objetos para llevar a
cabo su función. Por ejemplo, en el caso de reconocimiento, deberemos tener una
ventana donde se reproduzca el vı́deo y donde podamos seleccionar los ojos, para
sacar la cara correspondiente y poder después obtener el modelo PCA de dicho
personaje. En el caso de detección, también necesitaremos una ventana donde
reproducir un vı́deo, pero de carácter meramente informativo, ya que después es
el mismo programa el que se encarga de extraer los fotogramas de dicho vı́deo,
obtener los candidatos y tomar una decisión sobre si el personaje que aparece en
el vı́deo se corresponde o no con el nombre que le indicamos.
Dentro de la parte de reconocimiento, se han creado dos programas distintos,
cada uno de los cuales lleva a cabo su función. Por lo tanto, tendremos en total
tres ejecutables, por lo que dividiremos este capı́tulo en tres puntos, dedicando
cada uno de ellos a cada uno de los ejecutables.
6.1.
FaceDemo
Este programa trata de ofrecer una demostración acerca del reconocimiento de
caras, sin contar para nada con el audio. Por ello, se basará en todas las funciones
descritas para el vı́deo, y ofrecerá un resultado basado sólo en el vı́deo, por lo
que no será tan fiable como si utilizamos ambas componentes.
87
88
CAPı́TULO 6. ENTORNO GRÁFICO
Cuando abrimos el programa, la ventana principal toma una forma como la
de la figura 6.1.
Figura 6.1: Ventana principal del programa FaceDemo.
Para empezar, deberemos abrir una secuencia de vı́deo (menú File ⇒Open).
Serán válidos todos los formatos de vı́deo, no importa que sean MPEG1, MPEG2
o VOB. Una vez tengamos abierto el vı́deo, se habilitará el botón de Extract and
Detect Faces, que llevará a cabo las tareas de extraer los fotogramas y detectar
las caras existentes en cada uno de ellos. Cuando termine, se mostrarán a la parte
derecha de la ventana los fotogramas con las caras detectadas, representadas por
un cuadro blanco. En la figura 6.2 podemos ver cómo quedarı́a el programa en este
punto, donde vemos a la parte derecha un fotograma en el que se han detectado
dos caras, de las cuales una es un fallo (ha detectado una cara donde en realidad
no existe, es una parte de cuello).
Una vez hayamos terminado con esa tarea, se habilitará la lista desplegable
situada debajo de los botones. En dicha lista elegiremos el personaje que queremos
comprobar si está en la secuencia o no. Una vez elegido el personaje, se habilitará el botón de Face Recognition, que llevará a cabo las tareas de reconocimiento
del personaje dentro de las caras detectadas. En la figura 6.3 podemos ver que
ha reconocido al personaje en una de las caras que habı́a detectado. Podemos
comprobarlo porque las caras que se detectan se pinta también con un cuadro,
89
6.1. FaceDemo
Figura 6.2: Programa FaceDemo tras detección.
pero negro.
Figura 6.3: Programa FaceDemo tras reconocimiento.
Además de comprobar que se ha reconocido el personaje, podemos ver la
fiabilidad que nos da ese reconocimiento. Para ello tenemos la barra existente
entre el vı́deo y los fotogramas representados. Se representará en ella el umbral
90
CAPı́TULO 6. ENTORNO GRÁFICO
que nosotros fijemos mediante una lı́nea azul y el identificativo de Threshold
(umbral). Como vemos, esta cara ha sido reconocida con bastante fiabilidad, ya
que el nivel de la barra supera con creces el umbral fijado.
Podremos fijar este umbral a nuestro gusto, aunque se ha comprobado que un
umbral igual a 0.08 obtiene resultados con una alta fiabilidad.
Podremos repetir el proceso con distintos personajes, comprobando las veces
que encuentra a dicho personaje en el vı́deo y las veces que se equivoca.
Aparte de estas funciones, se han añadido menús adicionales para el fácil y
práctico manejo de esta herramienta. Ası́, dentro del menú Persons, tendremos las
herramientas para añadir y quitar personas de la lista de personajes a reconocer.
Para añadir un personaje a la lista, deberemos ir al menú Persons ⇒Add Person,
y en la ventana que nos salga simplemente deberemos indicarle un archivo psn de
personajes, que habrá sido creado anteriormente con el programa de creación de
modelos. Para borrar un personaje, iremos al menú Persons ⇒Remove Person y
se abrirá una ventana como la de la figura 6.4, en la cual elegiremos el personaje
que deseamos quitar de la lista. Al aceptar nos pedirá confirmación, y si es positiva
nos dará la opción de borrar los archivos de modelos del personaje.
Figura 6.4: Ventana para eliminar personaje.
Dentro del menú Config, podremos elegir las opciones de visualización de los
fotogramas, es decir, que podremos elegir entre ver los fotogramas detectados, los
fotogramas detectados y reconocidos juntos,. . . Ası́, dentro de dicho menú, tendremos dos submenús: View Frames y View Next. El primero de ellos lo utilizaremos para, una vez reconocidas las caras, intercambiar la vista entre fotogramas
6.2. Recognition
91
con caras detectadas solo (opción Detected ), o fotogramas con caras detectadas
y reconocidas (opción Recognized ).
Como ayuda adicional, en la parte inferior izquierda de la ventana, en la barra
de estado, se irán mostrando todas las tareas que realiza el programa en cada
momento. Ası́, podremos tener una idea perfecta de las tareas necesarias para
cada función que queramos ejecutar.
6.2.
Recognition
En este programa veremos aplicadas las funciones de reconocimiento de personas, tanto a nivel de vı́deo como de audio. Por ello, este programa será mucho
más fiable que el anterior, si utilizamos los dos criterios de reconocimiento (audio
y vı́deo).
También podremos usar este programa para el reconocimiento de audio y de
vı́deo por separado, si por alguna razón es este nuestro cometido.
Cuando ejecutamos el programa, veremos en primer lugar la ventana de presentación, y tomará el aspecto tal y como puede verse en la figura 6.5. En ella
podemos ver los elementos principales de la aplicación.
Explicaremos ahora la forma de uso de esta aplicación. En primer lugar deberemos abrir una secuencia de vı́deo, mediante el menú File ⇒Open. Podremos
trabajar con cualquier tipo de formato de vı́deo, e incluso podremos trabajar con
archivos de sonido del tipo WAV.
Una vez abierto el archivo multimedia, deberemos elegir el criterio de reconocimiento a usar. Para ello, dentro del menú Options tendremos tres opciones
para elegir: sólo vı́deo, sólo audio, o ambos, vı́deo y audio. Por supuesto, si la
entrada es un archivo de vı́deo pero no tiene sonido, sólo estará disponible la
opción de sólo vı́deo; igualmente si el archivo de entrada es del tipo WAV, sólo
estará disponible la opción de sólo audio.
A continuación, deberá pulsarse el botón de Extract-Detect, que procederá a
la extracción de los parámetros del archivo de entrada. Si hemos elegido la opción
de sólo vı́deo, su función será extraer los fotogramas y detectar las caras dentro de
dichos fotogramas. Si hemos elegido la opción de sólo audio, su función será extraer el audio del vı́deo, adaptarlo para que pueda ser utilizado y extraer sus
parámetros. Si hemos elegido la opción de utilizar ambos, primero realizará las
funciones del vı́deo y a continuación las del audio.
92
CAPı́TULO 6. ENTORNO GRÁFICO
Figura 6.5: Ventana principal del programa Recognition.
Cuando terminen de ejecutarse las funciones anteriores, se habilitará la lista
desplegable situada debajo de los botones, donde podremos indicar el personaje
que queremos reconocer dentro del archivo multimedia de entrada. La elección
del personaje deberá ser coherente con la elección anterior de utilizar el audio o
el vı́deo, ya que deberán existir los modelos pertinentes. Es decir, que si hemos
elegido la opción de sólo vı́deo, deberán existir los archivos de PCA para el
personaje seleccionado.
Si se cumplen los requisitos anteriores, se habilitará el botón de Recognition,
que finalmente nos mostrará una pantalla indicándonos si el personaje ha sido reconocido dentro del vı́deo o no. Este reconocimiento dependerá mucho del
umbral que hayamos elegido. Para elegir el umbral, deberemos mover la barra
de desplazamiento nombrada como Select a threshold. Al mover dicho control,
se moverá el umbral situado en la barra vertical situada al lado del vı́deo, que
indicará el umbral seleccionado.
Si hemos elegido la opción de reconocimiento mediante vı́deo, además de indicarnos si el personaje se encuentra o no en el vı́deo de entrada, se nos mostrará la
cara que más se parece al personaje seleccionado. Podemos ver un ejemplo en la
figura 6.6.
93
6.2. Recognition
Figura 6.6: Programa Recognition tras reconocimiento válido.
Figura 6.7: Ventana para eliminar personaje.
Además, para facilitar el manejo del programa, se ha permitido la inserción
y extracción de personajes de la lista. Ası́, dentro del menú Persons podremos
elegir la opción de Add para añadir un personaje a la lista (seleccionando el
archivo psn correspondiente al personaje a añadir). Si lo que queremos es borrar
a un personaje, elegiremos la opción de Remove, con lo que se nos mostrará una
ventana como la de la figura 6.7, en la que podremos elegir el personaje a borrar
94
CAPı́TULO 6. ENTORNO GRÁFICO
de la lista; tras pedir una confirmación del borrado, se nos presentará además la
opción de borrar los archivos de modelos del personaje.
Como ayuda adicional, en la parte inferior izquierda de la ventana, en la barra
de estado, se irán mostrando todas las tareas que realiza el programa en cada
momento. Ası́, podremos tener una idea perfecta de las tareas necesarias para
cada función que queramos ejecutar.
6.3.
ModelGeneration
Este programa se creó para poder crear modelos de los personajes, para poder
después utilizarlos con los anteriores programas de reconocimiento. Podremos
crear tanto modelos de audio como de vı́deo. Si generamos los modelos de vı́deo,
se generarán los archivos correspondientes a las PCA, y si generamos los modelos
de audio, se generará el correspondiente archivo gmm.
Figura 6.8: Ventana principal del programa ModelGeneration.
Cuando ejecutamos el programa, nos aparecerá una ventana como la de la
figura 6.8, donde podemos ver las componentes principales de la aplicación: el
objeto Windows Media Player donde se reproducirá el vı́deo, el objeto donde se
6.3. ModelGeneration
95
irán mostrando las caras extraı́das del vı́deo (Selected Faces), y el objeto donde
se mostrarán los audios utilizados para generar el modelo de audio (Selected
Audios).
Para empezar a utilizar el programa, deberemos crear un nuevo personaje, o
abrir uno que ya hubiéramos guardado con anterioridad. Para ello, deberemos ir
al menú File ⇒New Person e introducir el nombre del personaje nuevo que deseamos crear; si lo que queremos es abrir un personaje ya utilizado anteriormente,
iremos al menú File ⇒Open Person y buscaremos el archivo del personaje, que
tendrá extensión psn.
Una vez tengamos abierto un personaje, podremos elegir entre crear su modelo
de vı́deo o de audio. Empezaremos con el de vı́deo, por lo que tendremos que ir
al menú Video y elegir el submenú Open Video..., que nos dará opciones para
abrir distintos formatos de vı́deo. Cuando tengamos abierto el vı́deo, deberemos
ir eligiendo fotogramas de los que queremos extraer la cara. Para extraer la cara,
simplemente deberemos de seleccionar los ojos. Para ello, pincharemos con el
botón primario del ratón hasta que hayamos acertado en el primer ojo, y para
confirmar pincharemos con el botón secundario. Repetiremos esta acción para el
segundo ojo con el resultado de que la cara aparecerá en el objeto de Selected
Faces, tal y como podemos ver en la figura 6.9. En dicha figura vemos como se
han marcado los dos ojos y el resultado ha sido la cara de la parte derecha. Como
podemos observar, se nos irá indicando el número de caras que se han extraı́do.
Repetiremos el proceso hasta que tengamos un número de caras razonable;
para que el modelo sea aceptable, deberemos tener al menos veinte caras extraı́das.
Como facilidad adicional, se ha añadido un menú de Zoom, donde podremos
cambiar el tamaño de la ventana de reproducción del vı́deo a la mitad (menú 50 % ),
o por el contrario reproducir el vı́deo a su tamaño original (menú 100 % ). Esta
función será útil cuando reproduzcamos vı́deos de gran tamaño, o tengamos una
configuración de pantalla con baja resolución.
Antes de proceder a crear el modelo, deberemos elegir las opciones pertinentes
para la generación del modelo. Para ello, deberemos ir al menú Video⇒Options...,
con lo que se nos abrirá una ventana como la de la figura 6.10. Los campos
existentes en dicha ventana significan lo siguiente:
Save...: Nos dará la opción de guardar los archivos intermedios o no. La
opción de Log Files nos permitirá guardar los archivos de depuración, y la
opción de Eigenfaces nos permitirá guardar como imágenes las eigenfaces
96
CAPı́TULO 6. ENTORNO GRÁFICO
Figura 6.9: Ejemplo de extracción de caras.
generadas.
Eigenface Ratio: Valor de la varianza de los valores obtenidos. Mediante
pruebas, se ha comprobado que el mejor valor es el de 0,9.
Components: Pesos que daremos a las componentes Y, U, V. Por defecto,
estos valores deberán tener su valor máximo, es decir, uno.
Una vez elegidas todas las opciones deseadas, iremos al menú Video⇒PCA
Generation, con lo que se empezarán a crear todos los archivos necesarios para
generar las PCA. Este proceso puede tardar varios minutos si el número de caras
es grande. Finalizado el proceso, obtendremos, dentro de la carpeta Results (dentro del directorio donde ejecutemos el programa), los correspondientes archivos
de modelo del personaje, con extensión pca. Tendremos tres archivos, uno para
cada componente (Y, U, V), y serán todos necesarios para su futuro uso en
los programas de reconocimiento. Además, si guardamos los cambios hechos en
el personaje (menú File⇒Save Person...), este modelo se guardará dentro del
directorio correspondiente al personaje.
Si en la ventana de opciones hemos elegido la generación de eigenfaces, tendremos ahora una opción para ver los resultados obtenidos. Iremos al menú Video⇒View
6.3. ModelGeneration
97
Figura 6.10: Opciones para la generación de modelos de vı́deo.
Results, y se nos abrirá una ventana como la de la figura 6.11, en la que podemos
ver los resultados correspondientes a la componente Y. Para poder ver las demás
componentes, simplemente deberemos seleccionar la componente que queremos en
el cuadro Components. Para que la componente que deseamos ver esté activada,
deberemos indicárselo al programa con anterioridad; para ello, iremos al menú de
Video⇒Result Options y elegiremos las componentes que deseamos visualizar.
Una vez vistas todas las opciones para la generación de modelos de vı́deo,
pasaremos ahora a los detalles de la generación de modelos de audio. Al igual que
con el vı́deo, necesitaremos crear un nuevo personaje o abrir uno ya existente,
mediante los menús File⇒New Person o File⇒Open Person, respectivamente. A
continuación, podremos insertar archivos de audio de dos formas diferentes.
Una de ellas será seleccionar un archivo de audio en formato WAV. Para
ello, iremos al menú Audio⇒Insert new audio, y en la ventana que nos aparezca
elegiremos el archivo WAV que deseemos añadir. Ası́, este fichero nos aparecerá en
la lista de Selected Audios, con un indicativo del lugar que ocupa en la lista (valor
meramente informativo, sin ningún significado adicional).
La otra opción para insertar audio será mediante la utilización del audio
de un archivo de vı́deo; es decir, que si tenemos a un personaje grabado en una
secuencia de vı́deo, en la cual tenemos imagen y audio, podremos aprovechar dicho
audio para generar el modelo. Para ello, una vez tengamos un vı́deo abierto, se
habilitará el menú Audio⇒Insert this audio, mediante el cual añadiremos el audio
98
CAPı́TULO 6. ENTORNO GRÁFICO
Figura 6.11: Visualización de los resultados de la PCA.
a la lista de Selected Audios. Cuando elegimos esta opción, podremos indicar el
momento inicial y el final que queremos que se utilice para el modelo. Para ello,
tenemos los botones de Tin y Tout, mediante los cuales podremos seleccionar
el instante inicial y el final, y que se mostrará más tarde en la lista de Selected
Audios, al lado del nombre del vı́deo abierto. En la figura 6.12 podemos ver un
ejemplo, donde se han añadido tres audios: el primero es un fichero WAV, el
segundo todo el audio de un archivo de vı́deo MPEG, y el tercero una parte del
vı́deo MPEG abierto en el momento.
Una vez seleccionados todos los audios que consideremos necesarios para la
creación del modelo, iremos al menú Audio⇒Model Generation, con lo que empezarán a llevarse a cabo todas las funciones necesarias que desembocarán en
la creación de un modelo de audio, de extensión gmm, y que se almacenará en
el directorio Results, situado en el directorio donde se haya ejecutado el programa. Además, si guardamos los cambios hechos en el personaje (menú File⇒Save
Person...), este modelo se guardará dentro del directorio correspondiente al personaje.
6.3. ModelGeneration
Figura 6.12: Ejemplo de la inserción de audios.
99
100
CAPı́TULO 6. ENTORNO GRÁFICO
Capı́tulo 7
Conclusiones
Como conclusiones apuntaremos que se han llevado a cabo con éxito todos los
objetivos planteados en este proyecto, ya que además de crear un entorno gráfico
amigable para poner en funcionamiento todas las funciones de reconocimiento, se
ha creado una librerı́a adicional con un conjunto de funciones de utilidad para el
tratamiento de archivos multimedia.
Con respecto a la tecnologı́a utilizada, hemos comprobado que el DirectX no
ha cumplido con todas las expectativas que se le habı́an atribuido en un principio,
basándonos en su documentación. Se ha comprobado que el uso del DirectX sólo
es provechoso en el tratamiento de vı́deos MPEG1. Por lo tanto, para la extracción
de fotogramas de los demás formatos se ha debido hacer uso de librerı́as externas,
como el MPEG2Lib en el caso de los vı́deos MPEG2 y VOB. Además se ha
comprobado que las funciones no son válidas en todas las versiones de Windows,
ni llevan a cabo las funciones que deberı́an ejecutar, difiriendo mucho de lo se
muestra en la documentación.
Con respecto a los lenguajes de programación usados, hemos comprobado
que poseen una gran potencia cuando se utilizan conjuntamente, ya que podremos crear un entorno gráfico completo con el VB y poder hacer uso de todas
las ventajas que posee el VC++. El uso de las DLL facilita en gran medida la
comunicación entre plataformas, ya que permite comunicar el MATLAB con el
Visual C++, y el Visual C++ con el Visual Basic, con el que realmente creamos
el entorno gráfico.
Con respecto a los resultados, decir que se ha intentado crear un entorno
gráfico cuyo uso fuera lo más fácil posible, con menús y botones de uso intuitivo. También se ha intentado que las librerı́as creadas contengan un conjunto de
funciones muy útiles en cuanto al tratamiento de ficheros multimedia, y además
que fueran de un uso muy intuitivo.
101
102
CAPı́TULO 7. CONCLUSIONES
Apéndice A
MATLAB
Cuando llegamos a la parte relacionada con el audio, todas las funciones
que nos extraen los parámetros, comprueban los modelos,... están programadas
r ya que es un lenguaje de programación en el que se pueden
en MATLAB °,
controlar mucho mejor los archivos de audio, matrices y todo lo necesario para
obtener y comprobar los modelos del audio de las personas.
Hasta este punto todo perfecto, pero el problema viene cuando tenemos que
integrarlo todo, ya que deberemos insertar estas funciones de reconocimiento de
audio en el proyecto global, en el que hemos trabajado con BASIC y C++.
En este momento aparece el compilador de MATLAB (mcc). Este compilador
nos permite pasar el código M, escrito en el lenguaje de programación de MATLAB, al lenguaje C o C++, que será el que realmente nos interese a nosotros
para crear librerı́as dinámicas, que después llamaremos desde la interfaz gráfica
del programa.
A.1.
Compilador de MATLAB
Los ficheros en C resultantes de la compilación pueden usarse en cualquiera
de los tipos de fichero ejecutables, como por ejemplo MEX, ejecutables (exe), o
librerı́as. A nosotros nos interesará el crear aplicaciones independientes (standalone applications), que serán usadas después para crear las DLLs necesarias.
Las principales razones para compilar los archivos M mediante el compilador
de MATLAB son las siguientes:
Para crear aplicaciones independientes o librerı́as compartidas en C (DLLs).
103
104
APÉNDICE A. MATLAB
Para ”esconder”el código que hemos programado en MATLAB.
Para aumentar la velocidad de ejecución de los programas creados.
En el primer punto lo que pretendemos es poder crear aplicaciones en MATLAB, que se aprovechen de las ventajas de las funciones matemáticas del MATLAB, pero sin el requerimiento de tener instalado este programa para poder
ejecutarlos. Las aplicaciones independientes son la forma más conveniente de
aprovechar la potencia del MATLAB y distribuir ejecutables a los usuarios.
Con respecto a la ocultación del código creado, nos será de gran utilidad esta
herramienta, ya que los ficheros M son ficheros de texto ASCII, que cualquiera
puede ver y modificar. Sin embargo, si compilamos ese código, lo que nos resulta
es un ejecutable que contiene datos binarios, por lo que es imposible la modificación de los algoritmos que hayamos creado en ese programa, y además también
será imposible la copia de dichos algoritmos.
El aumento de la velocidad de ejecución de los programas compilados frente a
sus equivalentes M se debe a que el código compilado normalmente se ejecuta más
rápidamente que el código interpretado, que es lo que hace el MATLAB. Además,
en C++ se puede liberar memoria en cualquier momento, si es que se sabe que
no va a usarse más; en MATLAB no podremos hacerlo por diversos motivos.
Además, los bucles se ejecutarán mucho más rápido en el código compilado que
en MATLAB.
A.2.
Generación de código
Aquı́ podremos ver los ficheros que se generan tras la compilación, es decir,
los ficheros cabecera, las funciones de interfaz, librerı́as en C o C++,...
Cuando se usa el compilador de MATLAB para compilar los ficheros M, se
generan los siguientes ficheros:
Código C o C++, dependiendo del lenguaje que hayamos especificado (con
la opción -L)
Fichero de cabecera
Fichero de datos (lib, dll, mex,...), dependiendo de la opción elegida (-W)
A.2. Generación de código
105
El código C o C++ que genera el compilador y el fichero cabecera son independientes del fichero ejecutable que vayamos a crear después, incluso de la
plataforma en la que vaya a ser usado dicho código. El fichero de datos será el
que proporcione el código necesario para soportar el tipo ejecutable de salida.
Por lo tanto, el fichero de datos es diferente para cada tipo de fichero ejecutable.
Como hemos dicho, el compilador de MATLAB nos ofrece una amplia variedad en los formatos de salida, pero el que nos interesa a nosotros y el que
hemos empleado en el proyecto es la librerı́a. Con esta opción, tendremos como
salida varios ficheros, entre ellos los ficheros C y los ficheros H, uno por cada
fichero M que hayamos introducido en la orden de compilación. En estos archivos
tendremos todo el código compilado, por si necesitamos llevar a cabo algún tipo
de modificación.
También tendremos como salida un archivo con estensión exports, el cual nos
indicará todas las funciones que tenemos disponible en la librerı́a que se ha creado,
y otro archivo con extensión mlib, que también nos aportará información acerca
de las funciones que se exportan.
Y por último, tendremos la librerı́a en sı́ misma, es decir, un archivo con
extensión lib, que será el que después usemos para llamar a las funciones que
tenı́amos en el archivo original M. Para ello, deberemos primero cargar la librerı́a
mediante unas funciones que ya nos aporta la compilación (generalmente la llamada a esta inicialización será el nombre de la función que tenı́amos en el archivo
M, al que añadiremos la palabra libInitialize, sin ningún parámetro y sin ningún
valor de retorno), por lo que deberemos incluir en el proyecto el fichero cabecera
principal que nos saque la compilación. A partir de entonces ya podremos usar
las funciones tal y como las usábamos en MATLAB. Por supuesto, deberemos
cerrar la librerı́a cuando terminemos de usarla; para ello, haremos lo mismo que
al inicializarla, pero sustituyendo la palabra libInitialize por libTerminate.
Pondremos un ejemplo práctico para que quede totalmente clara esta explicación. Supongamos que tenemos una función en MATLAB llamada prueba. Los
ficheros que usaremos son el pruebalib.lib y el pruebalib.h. Cuando vayamos a
inicializar la librerı́a, llamaremos a la función pruebalibInitialize(). Para usar la
función, lo haremos con el nombre mlfPrueba, con los argumentos oportunos. Por
último, para cerrar la librerı́a llamaremos a la función pruebalibTerminate().
106
APÉNDICE A. MATLAB
A.3.
Optimizaciones
El compilador de MATLAB también permite el aplicar optimizaciones en el
código fuente del fichero M, lo que desembocará en que el código generado en
C/C++ sea mucho más rápido que el código original M, o que el código creado
sin ningún tipo de optimización. En general, los distintos tipos de optimizaciones
son los siguientes:
Manipulación de arrays escalares o no escalares.
Contenido de arrays de una o dos dimensiones.
Contenido de bucles que empiecen y se incrementen con enteros.
Contenido de expresiones condicionales donde los operandos son enteros.
Únicamente no elegiremos ninguna optimización en el caso de que estemos
depurando nuestro código o en el caso en que se quiera mantener la legibilidad
de nuestro código.
Generalmente, todas las optimizaciones están activadas por defecto, por lo que
aunque no lo sepamos, estaremos usando dichas optimizaciones. Sin embargo,
para estar seguros de que realmente están activadas, simplemente deberemos
ejecutar la orden de MATLAB siguiente:
mcc -O list
y directamente se nos mostrará por pantalla todas las optimizaciones disponibles,
y si realmente están activadas o no.
Si alguna de las optimizaciones no está activada, deberemos activarla, y en el
caso de que no deseemos hacerlo deberemos saber muy bien que nuestro programa
no va a usar funciones que hagan uso de esas funciones (por ejemplo, si nuestro
programa no utiliza ningún tipo de bucle, aunque la optimización de los bucles no
esté activada no acarreará ningún tipo de problema). Como opinión personal, recomiendo que estén todas las optimizaciones activadas, ya que ası́ nos evitaremos
problemas en el código compilado, con respecto a la velocidad del programa.
Apéndice B
Tabla de conversiones entre VB y
VC++
El intercambio de datos entre el Visual C++ y el Visual Basic, con respecto a
las variables, no es inmediato. Existen una serie de cambios que deberemos tener
en cuenta. En la tabla se muestran los tipos de datos de C++ más comunes y su
equivalente en Visual Basic.
Tipos de datos del
lenguaje C++
Declarados en Visual
Basic como
Llamados con
ATOM
ByVal variable As Integer
Una expresión que da
como resultado un tipo
de datos Integer
BOOL
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
BYTE
ByVal variable As Byte
Una expresión que da
como resultado un tipo
de datos Byte
CHAR
ByVal variable As Byte
Una expresión que da
como resultado un tipo
de datos Byte
107
108
APÉNDICE B. TABLA DE CONVERSIONES ENTRE VB Y VC++
Tipos de datos del
lenguaje C++
Declarados en Visual
Basic como
Llamados con
COLORREF
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
DWORD
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
HWND, HDC, etc.
(controladores de Windows)
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
INT, UINT
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
LONG
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
LPARAM
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
LPDWORD
variable As Long
Una expresión que da
como resultado un tipo
de datos Long
LPINT, LPUINT
variable As Long
Una expresión que da
como resultado un tipo
de datos Long
109
Tipos de datos del
lenguaje C++
Declarados en Visual
Basic como
Llamados con
LPSTR, LPCSTR
ByVal
String
Una expresión que da
como resultado un tipo
de datos String
LPVOID
variable As Long
Cualquier variable (se
utiliza ByVal si se pasa
una cadena)
LRESULT
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
NULL
ByVal variable As Long
ByVal Nothing o vbNullString
SHORT
ByVal variable As Integer
Una expresión que da
como resultado un tipo
de datos Integer
VOID
Sub procedimiento
No aplicable
WORD
ByVal variable As Integer
Una expresión que da
como resultado un tipo
de datos Integer
WPARAM
ByVal variable As Long
Una expresión que da
como resultado un tipo
de datos Long
variable
As
110
APÉNDICE B. TABLA DE CONVERSIONES ENTRE VB Y VC++
Bibliografı́a
[1] Alberto Albiol Colomer. Face Detection for Pseudo-Semantic labeling in
Video Databases. Tesis Doctoral, Universidad Politécnica Valencia, 2003.
[2] DVD2AVI. http://arbor.ee.ntu.edu.tw/ jackei/dvd2avi.
[3] Joan L. Mitchell et al. MPEG video compression standard. Digital Multimedia standards series, New York, 1996.
[4] MPEG Simulation Software Group. http://www.mpeg.org/MPEG/MSSG.
[5] The MathWorks. MATLAB Compiler User’s Guide. Versión 2.1, Septiembre
2000.
[6] The MathWorks. MATLAB C++ Math Library User’s Guide. Versión 2.3,
Octubre 2001.
[7] Microsoft. Microsoft Development Network Library (MSDN).
[8] Microsoft. Microsoft DirectX 8.1 SDK (Software Development Kit).
[9] Mpeg2Lib. http://logicnet.dk/lib.
[10] Brian Siler & Jeff Spotts. Edición Especial Visual Basic 6. Prentice Hall,
Madrid, 1999.
[11] Beck Zaratian. Microsoft Visual C++ 6.0: Manual del programador.
McGraw-Hill / Interamericana de España, 1999.
111

Documentos relacionados