Informe - Escuela de Ingeniería Eléctrica

Anuncio
Universidad de Costa Rica
Facultad de Ingeniería
Escuela de Ingeniería Eléctrica
IE – 0502 Proyecto Eléctrico
Implementación de un sistema de captura de
video y detección de movimiento utilizando el
sistema Video4Linux
Por:
Andrés Díaz Soto
Ciudad Universitaria Rodrigo Facio
Junio del 2005
Implementación de un sistema de captura de
video y detección de movimiento utilizando el
sistema Video4Linux
Por:
Andrés Díaz Soto
Sometido a la Escuela de Ingeniería Eléctrica
de la Facultad de Ingeniería
de la Universidad de Costa Rica
como requisito parcial para optar por el grado de:
BACHILLER EN INGENIERÍA ELÉCTRICA
Aprobado por el tribunal:
Ing. Federico Ruiz Ugalde
Profesor Guía
Ing. Francisco Siles Canales
Profesor lector
Ing. Enrique Coen Alfaro
Profesor lector
II
DERECHOS DE PROPIEDAD INTELECTUAL
Linux es una marca registrada de Linus Torvalds.
Intel es una marca registrada de Intel Corporation.
ImageMagick es una marca registrada de ImageMagick Studio LLC.
USB (Universal Serial Bus Specification) es una marca registrada de Compaq Computer Corporation, Intel Corporation, NEC Corporation y otros.
Las demás marcas registradas son propiedad de sus respectivos dueños.
III
DEDICATORIA
A toda mi familia y a mis amigos, que han estado a mi lado a lo largo de estos años.
IV
ÍNDICE GENERAL
ÍNDICE DE FIGURAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . VIII
ÍNDICE DE CUADROS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
IX
NOMENCLATURA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
X
RESUMEN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XII
CAPÍTULO 1: Introducción . . . . .
1.1. Justificación . . . . . . . . . .
1.2. Objetivos . . . . . . . . . . .
1.2.1. Objetivo General . . .
1.2.2. Objetivos Específicos .
1.3. Metodología . . . . . . . . . .
1.4. Herramientas Utilizadas . . . .
1.4.1. Hardware . . . . . . .
1.4.2. Software . . . . . . .
. . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
2
2
2
2
3
3
3
CAPÍTULO 2: Desarrollo Teórico . . . . . . . . . . . . . . .
2.1. El sistema operativo GNU/Linux . . . . . . . . . . .
2.2. Acceso a dispositivos y funciones de entrada y salida
2.2.1. open() . . . . . . . . . . . . . . . . . . . . .
2.2.2. close() . . . . . . . . . . . . . . . . . . . . .
2.2.3. read() . . . . . . . . . . . . . . . . . . . . .
2.2.4. write() . . . . . . . . . . . . . . . . . . . . .
2.2.5. ioctl() . . . . . . . . . . . . . . . . . . . . .
2.3. Programación con múltiples hilos de procesamiento .
2.3.1. pthread_create() . . . . . . . . . . . . . . .
2.3.2. pthread_exit() . . . . . . . . . . . . . . . . .
2.3.3. pthread_join() . . . . . . . . . . . . . . . . .
2.3.4. pthead_mutex_init() . . . . . . . . . . . . .
2.3.5. pthread_mutex_lock() . . . . . . . . . . . .
2.3.6. pthread_mutex_unlock() . . . . . . . . . . .
2.3.7. pthread_mutex_destroy() . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
6
7
7
8
8
8
9
10
10
10
11
11
11
V
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2.4. El sistema Video4Linux . . . . . . . . . . . . . . . .
2.4.1. La interfaz de programación de Video4Linux
2.5. Algoritmos de detección de movimiento . . . . . . .
2.6. Reducción de ruido en las imágenes . . . . . . . . .
2.6.1. Filtro de media . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
12
12
16
18
18
CAPÍTULO 3: Descripción general del sistema de captura de video y detección
de movimiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.1. Estructura de la aplicación y aspectos generales . . . . . . . . . . . . . . . .
3.2. Estructura del servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.1. Inicialización y comunicación con el cliente . . . . . . . . . . . . . .
3.2.2. Captura de video . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2.3. Detección de movimiento . . . . . . . . . . . . . . . . . . . . . . .
3.2.4. Envío del video . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3. Estructura del cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3.1. Interfaz gráfica de usuario y mensajería TCP . . . . . . . . . . . . .
3.3.2. Recepción de los cuadros de video . . . . . . . . . . . . . . . . . . .
20
20
21
21
22
23
23
25
26
26
CAPÍTULO 4: Rutinas de captura de video . . . . . . . . . . . . . . . . . . . . . . . 29
4.1. Inicialización del sistema de video . . . . . . . . . . . . . . . . . . . . . . . 29
4.2. Ciclo de captura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
CAPÍTULO 5: Rutinas de comunicación con el cliente y transmisión de video
sobre la red . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1. Creación del socket TCP en el servidor . . . . . . . . . . . . . . . . . . . . .
5.2. Envío y recepción de datos . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.3. Envío del video capturado al cliente . . . . . . . . . . . . . . . . . . . . . .
5.3.1. Creación del socket UDP . . . . . . . . . . . . . . . . . . . . . . . .
5.3.2. Fragmentación y envío de los cuadros de video . . . . . . . . . . . .
36
36
38
41
41
42
CAPÍTULO 6: Rutinas de detección de movimiento y escritura a disco duro
6.1. Algoritmo de detección de movimiento . . . . . . . . . . . . . . . . .
6.2. Filtrado de las imágenes . . . . . . . . . . . . . . . . . . . . . . . . .
6.3. Determinación de eventos de movimiento . . . . . . . . . . . . . . . .
6.4. Rutinas de escritura a disco duro . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44
44
46
48
52
CAPÍTULO 7: Rutinas de comunicación y despliegue de video en el cliente .
7.1. Envío de mensajes al servidor . . . . . . . . . . . . . . . . . . . . . .
7.2. Recepción de los cuadros de video . . . . . . . . . . . . . . . . . . . .
7.3. Despliegue de la imagen . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
58
58
61
64
CAPÍTULO 8: Resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
8.1. Captura de video . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
8.2. Reducción de ruido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
VI
8.3. Determinación de eventos de movimiento . . . . . . . . . . . . . . . . . . . 67
CAPÍTULO 9: Conclusiones y Recomendaciones . . . . . . . . . . . . . . . . . . . . 71
9.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
9.2. Recomendaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
BIBLIOGRAFÍA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
APÉNDICES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
VII
ÍNDICE DE FIGURAS
Figura 3.1 Partes principales de la aplicación . . . . . . . . . . . . . . . . . . . . . . . . .
Figura 3.2 Diagrama de flujo del hilo de inicialización y comunicación con el cliente. . .
Figura 3.3 Diagrama de flujo del hilo de captura de video. . . . . . . . . . . . . . . . . . .
Figura 3.4 Diagrama de flujo del hilo de detección de movimiento. . . . . . . . . . . . . .
Figura 3.5 Diagrama de flujo del hilo de envío del video. . . . . . . . . . . . . . . . . . . .
Figura 3.6 Estructura de la aplicación cliente. . . . . . . . . . . . . . . . . . . . . . . . . .
Figura 3.7 Diagrama de flujo del hilo encargado de la interfaz gráfica y la mensajería
TCP en el cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Figura 3.8 Diagrama de flujo del hilo de recepción de video. . . . . . . . . . . . . . . . . .
20
22
23
24
25
26
27
28
Figura 4.1 Diagrama del algoritmo de captura de video utilizado. . . . . . . . . . . . . . . 32
Figura 6.1 Diagrama de la máquina de estados encargada de detectar los eventos de
movimiento. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Figura 8.1 Aplicación del filtro de media para reducir el ruido en las imágenes capturadas 67
Figura 8.2 Porcentaje de pixeles con diferencias durante un evento de movimiento . . . . 69
Figura 8.3 Respuesta del sistema ante un cambio rápido en la iluminación . . . . . . . . . 70
VIII
ÍNDICE DE CUADROS
Cuadro 2.1 Información contenida por la estructura video_capability.
Cuadro 2.2 Información contenida por la estructura video_buffer. . . .
Cuadro 2.3 información contenida por la estructura video_window. . .
Cuadro 2.4 Información contenida por la estructura video_channel.. .
Cuadro 2.5 Información contenida por la estructura video_picture. . .
Cuadro 2.6 Información contenida por la estructura video_tuner. . . .
Cuadro 2.7 Información contenida por la estructura video_mbuf. . . .
Cuadro 2.8 Información contenida por la estructura video_mmap.. . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
14
14
15
15
16
16
Cuadro 5.1 Mensajes implementados para la comunicación entre el cliente y el servidor . 39
Cuadro 6.1 Estados necesarios para el algoritmo de detección de eventos de movimiento
48
Cuadro 8.1 Cuadros por segundo capturados. . . . . . . . . . . . . . . . . . . . . . . . . . 66
Cuadro 8.2 Parámetros del sistema de detección de eventos de movimiento utilizados
durante la prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
IX
NOMENCLATURA
BSD: Berkeley Software Distribution. Familia de versiones de UNIX implementadas a partir de 1977. Muchas de las características de estos sistemas se han convertido en un
estándar para implementaciones posteriores.
buffer: Sector de memoria utilizado para el almacenamiento temporal de datos.
FSF: Free Software Foundation, Fundación del Software Libre.
GNU: Acrónimo recursivo: GNU is Not UNIX. Proyecto de FSF que busca crear un sistema
operativo completo utilizando únicamente software libre.
GTK: Gimp Toolkit. Conjunto de herramientas para la creación de interfaces de usuario
gráficas.
JPEG: Joint Picture Experts Group. Formato estandarizado de almacenamiento y compresión de imágenes.
libc: Biblioteca Estándar del lenguaje C del sistema operativo GNU/Linux.
LZO: Biblioteca de compresión de datos en tiempo real.
MPEG: Moving Picture Expert Group. Formato estandarizado de compresión de video.
POSIX: Portable Operating System Interface. Conjunto de estándares definidos por la IEEE
que definen una interfaz de programación para software diseñado para ser ejecutado en
variantes del sistema operativo UNIX.
pthreads: POSIX threads. Implementación de hilos múltiples de procesamiento que forma
parte del estándar POSIX.
socket: Mecanismo para la creación de conexiones virtuales entre procesos, los cuales pueden ser ejecutados en una misma máquina, o en computadoras distintas.
SYSV: UNIX System V. Versión de UNIX desarrollada en 1983 por AT&T. Esta versión dio
lugar a un estándar conocido como System V Interface Definition.
X
TCP: Transfer Control Protocol. Protocolo de transmisión de datos orientado a conexión,
que forma parte del conjunto de protocolos TCP/IP.
TCP/IP: Conjunto de protocolos desarrollados para la transmisión de datos sobre Internet.
UDP: User Datagram Protocol. Protocolo no orientado a conexión, que forma parte del
conjunto de protocolos TCP/IP.
UNIX: Sistema Operativo multitareas desarrollado en 1969 por Ken Thompson. Entre 1972
y 1974 fue implementado completamente en el lenguaje C, lo que lo convirtió en el primer sistema operativo portable a nivel de código fuente. Esto ha dado lugar a múltiples
versiones e implementaciones, entre ellas Linux.
USB: Universal Serial Bus, bus serial universal.
V4L: Video4Linux. Interfaz para el acceso a dispositivos de video del sistema operativo
GNU/Linux.
V4L2: Video4Linux versión 2.
XI
RESUMEN
Este proyecto tiene como objetivo desarrollar un sistema de adquisición de video que implemente algoritmos de detección de movimiento, con el fin de utilizarlo como parte de una
aplicación de vigilancia, utilizando el conjunto de controladores y rutinas de acceso a dispositivos de video conocido como Video4Linux disponibles en el sistema operativo GNU/Linux.
El sistema se implementó en el lenguaje de programación C, utilizando un modelo clienteservidor, con una aplicación principal encargada de llevar a cabo la adquisición y el procesamiento del video, y una aplicación cliente que se comunicaba con el servidor utilizando
el protocolo TCP/IP y permitía controlar el sistema y observar el video capturado en tiempo
real. Se utilizaron múltiples hilos de procesamiento, tanto en la aplicación cliente como en el
servidor, con el fin de separar las distintas partes del sistema y de optimizar el desempeño.
Se creó un sistema de adquisición de video que colocaba los cuadros capturados en memoria, a disposición de las otras partes del sistema . Posteriormente se desarrolló un conjunto
de rutinas que permitían detectar movimiento con base en las diferencias de intensidad entre dos cuadros consecutivos de la secuencia capturada. También se implementó un sistema
encargado de determinar la existencia de eventos de movimiento significativos, con el fin de
eliminar eventos falsos, y de grabar los eventos detectados al disco duro de la computadora
para su posterior revisión.
Además, se creó un sistema utilizando sockets de red para llevar a cabo la transmisión de
video y la comunicación entre el cliente y el servidor.
Se logró capturar video a la mayor velocidad permitida por el dispositivo. Además, se obtuvieron buenos resultados en las rutinas de detección de movimiento, las cuales funcionaron
de manera satisfactoria en las pruebas realizadas. El sistema final desarrollado resultó estable
y eficiente, adecuado para una amplia gama de aplicaciones.
XII
CAPÍTULO 1: Introducción
1.1. Justificación
En la actualidad, la mayoría de los sistemas de video están construidos utilizando sistemas
propietarios, los cuales, además de ser costosos, limitan en gran medida la escalabilidad y
la flexibilidad. Generalmente, estos productos utilizan protocolos cerrados, que dificultan e
incluso impiden la interoperabilidad con dispositivos de otras marcas. Esto limita las opciones
a la hora de una actualización y dificulta el mantenimiento del sistema.
El desarrollo de estos sistemas sobre plataformas abiertas brinda una mayor versatilidad,
dado que amplía la gama de opciones disponibles, además de que permite crear soluciones
que se adapten de una mejor forma a cada situación particular.
Hoy en día, una opción cada vez más utilizada consiste en utilizar sistemas de computo
para el almacenamiento y el procesamiento del video. Esto proporciona una mayor versatilidad, debido a que facilita realizar cambios y añadir elementos. Actualmente pueden encontrarse en el mercado varios sistemas de esta clase, tanto propietarios como abiertos, para
múltiples sistemas operativos.
Para este proyecto se ha elegido implementar un conjunto de aplicaciones de adquisición
y procesamiento de video sobre el sistema operativo GNU/Linux, utilizando el conjunto de
controladores y herramientas Video4Linux, presente en este sistema. Entre las razones principales para esta elección se encuentran la gran estabilidad de este sistema operativo, así como
sus características de seguridad. Además, existe una cantidad importante de dispositivos de
bajo costo compatibles con Video4Linux, lo que reduce la inversión en equipo y brinda mayor
flexibilidad. Por último, tanto la interfaz de acceso al hardware como el sistema operativo y
demás aplicaciones necesarias son completamente abiertas, con todas las ventajas expuestas
anteriormente.
Si bien es cierto la aplicación principal para este sistema es la vigilancia, es posible adaptarlo para su utilización en una amplia gama de aplicaciones, lo que convierte a este sistema
en una plataforma de de desarrollo para proyectos futuros.
1
2
1.2. Objetivos
1.2.1. Objetivo General
Desarrollar un sistema de adquisición y procesamiento de video que implemente algoritmos de detección de movimiento, utilizando el sistema Video4Linux.
1.2.2. Objetivos Específicos
Desarrollar un conjunto de rutinas que sean capaces de capturar video de uno o varios
dispositivos a la máxima velocidad posible.
Implementar un algoritmo que permita detectar movimiento y que sea capaz de registrar y capturar eventos para su posterior revisión.
Implementar una interfaz gráfica de usuario que permita visualizar en tiempo real la
imagen adquirida por cada una de las cámaras conectadas al sistema, así como los
eventos registrados mediante los algoritmos de detección de movimiento.
1.3. Metodología
La metodología utilizada para la realización del trabajo fue la siguiente:
Se definen los objetivos y los alcances del proyecto, con el fin de estructurar el plan de
trabajo a realizar.
Se realiza una investigación bibliográfica sobre GNU/Linux y la interfaz de programación de Video4Linux, con el fin de conocer las características del sistema. Además se
investiga sobre las principales técnicas y algoritmos para llevar a cabo la detección de
movimiento.
Se inicia el desarrollo de las rutinas de captura de video, capturando primero imágenes
fijas y posteriormente secuencias de imágenes. Se investiga como optimizar el proceso
de captura con el fin de obtener la mayor cantidad de cuadros por segundo posibles.
Una vez implementadas las rutinas de captura de video se realizan pruebas utilizando
varios dispositivos, con el fin de evaluar el desempeño obtenido.
3
Se decide utilizar el protocolo TCP/IP para llevar a cabo la comunicación entre la
interfaz gráfica de usuario y el sistema de captura y procesamiento. Se desarrolla un
sistema de mensajería, así como un método para enviar los cuadros de video para su
visualización.
Se implementa el algoritmo de detección de movimiento y se desarrolla un sistema de
almacenamiento de las imágenes con base en los eventos de movimiento.
Se realizan pruebas con el fin de evaluar el desempeño del sistema de detección de
movimiento y el almacenamiento a disco duro.
Paralelamente a cada uno de estos pasos, se lleva a cabo la documentación del trabajo
realizado y la redacción del informe final.
1.4. Herramientas Utilizadas
1.4.1. Hardware
Dispositivos de video
El desarrollo y las pruebas del sistema implementado se llevó a cabo utilizando dos dispositivos de adquisición de video compatibles con Video4Linux:
1. Cámara USB marca Genius, modelo VideoCAM Express V2.
2. Tarjeta de televisión y adquisición de video marca Haupage, modelo TVPhone 98.
1.4.2. Software
Lenguaje de programación y compilador
El desarrollo del sistema se llevó a cabo utilizando el lenguaje de programación C. El
compilador utilizado fue gcc1 , el cual forma parte del proyecto GNU. Se utilizaron las funciones de la biblioteca estándar C de GNU/Linux (libc), además de otras bibliotecas que
forman parte de este sistema operativo, tales como pthreads.
1
http://gcc.gnu.org/
4
Editor de texto
La edición del programa, así como la este informe, se llevó utilizando el editor de texto
Vim2 , tanto en su versión de consola como en su versión gráfica (gvim).
Sistema operativo
El desarrollo se llevó a cabo utilizando el sistema operativo GNU/Linux. La distribución
empleada fue Debian3 «Sid» (versión inestable).
Procesamiento tipográfico
Este documento fue preparado utilizando el sistema de procesamiento tipográfico LATEX4 .
2
http://www.vim.org/
http://www.debian.org/
4
http://www.latex-project.org/
3
CAPÍTULO 2: Desarrollo Teórico
2.1. El sistema operativo GNU/Linux
Linux es un kernel1 tipo UNIX, creado en 1991 por el finlandés Linus Torvalds. Linux
se caracteriza por ser una implementación libre del estándar POSIX, con extensiones SYSV
y BSD. Si bien es cierto, es compatible en muchos aspectos con UNIX, todo el código fue
escrito de nuevo y no contiene partes de ninguna versión o variante de UNIX existente. Linux
fue escrito inicialmente para funcionar en procesadores Intel 386 y compatibles, sin embargo
hoy en día ha sido portado a un gran número de plataformas, entre las que se encuentran
Alpha, PowerPC, Motorola 68K y Sparc.
Un aspecto que es importante resaltar es que Linux es solamente el kernel o núcleo del
sistema operativo, o sea, el que se encarga de realizar las tareas de más bajo nivel, como la
gestión del hardware, el acceso a memoria o el manejo de los sistemas de archivos. Para poder
utilizar el sistema es necesario contar con aplicaciones tales como un intérprete de comandos
(conocido como shell), compiladores, herramientas para el manejo de archivos, procesadores
de texto, entre otras. En el caso de Linux, la mayoría de estas aplicaciones forman parte del
proyecto GNU, iniciado por la Fundación del Software Libre (FSF), el tiene como fin crear
un sistema operativo completo libre. El conjunto de estas aplicaciones GNU y el kernel Linux
es conocido generalmente como GNU/Linux.
Desde el punto de vista técnico, algunas de las características principales de Linux son
las siguientes:
Mutitareas.
Multiusario.
Soporte para múltiples arquitecturas.
Soporte para sistemas multiprocesador.
Funciona en modo protegido en los procesadores Intel 386 y compatibles.
1
Núcleo de un sistema operativo. Componente de software responsable de proveer acceso seguro al hardware
de la máquina y a los procesos que están siendo ejecutados[9].
5
6
Posee protección de memoria entre procesos. Esto impide que un programa pueda provocar una falla en el sistema.
Soporte para memoria virtual con paginación en el disco duro, con el fin de proveer más
memoria al sistema en caso de ser necesaria (Esto se conoce como área de intercambio
o swap).
Soporte para una gran cantidad de sistemas de archivos, entre ellos ReiserFS, XFS,
Ext2, Ext3, MSDOS, FAT16, FAT32 y NTFS (lectura, el soporte para escritura es limitado y aún se encuentra en desarrollo).
Soporte para una gran cantidad de protocolos de red, entre ellos TCP, IPv4, IPv6, IPX,
AppleTalk y SMB.
2.2. Acceso a dispositivos y funciones de entrada y salida
En Linux, al igual que en todos los sistemas tipo UNIX, la mayoría de los dispositivos
pueden ser accedidos como si se tratara de archivos. Esto permite llevar a cabo todas las
operaciones de entrada y salida a través de un pequeño conjunto de llamadas al sistema, las
cuales se encuentran definidas en el estándar POSIX. A continuación se brinda una descripción general de las llamadas más utilizadas.
2.2.1. open()
La llamada al sistema open() se encarga de abrir un archivo y asignarlo a una variable
conocida como descriptor de archivo. El descriptor de archivo se utiliza para referirse al
archivo a lo largo del programa, y es utilizado como argumento para las otras funciones de
entrada y salida.
La sintaxis de open() es la siguiente:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags).
int open(const char *pathname, int flags, mode_t mode);
7
donde pathname corresponde al nombre del archivo, flags especifica las condiciones en que
se abrirá el archivo, y mode define los permisos que se utilizarán cuando se crea un nuevo archivo. Por ejemplo, para abrir el dispositivo /dev/ttyS0 (primer puerto serial) para lectura
se debe utilizar la llamada
int fd;
/* Descriptor de archivo que se
utilizará para referirse al dispositivo */
fd = open("/dev/ttyS0", O_RDONLY);
2.2.2. close()
La llamada close() se encarga de cerrar un archivo que ha sido abierto con anterioridad,
una vez que no se va a utilizar más. close() recibe como argumento el descriptor de archivo
correspondiente al archivo que se quiere cerrar, y retorna 0 en caso de éxito, o −1 si se
produjo algún error. La sintaxis de esta llamada es la siguiente:
#include <unistd.h>
int close(int fd);
2.2.3. read()
La llamada al sistema read() se utiliza para leer datos desde un archivo. la sintaxis es la
siguiente:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
donde fd es el descriptor de archivo, buf es un puntero al sector de memoria donde se almacenarán los datos y count corresponde a la cantidad de información —en bytes— que será leída.
El valor retornado por read es el número de bytes leídos, o −1 en caso de que se presente un
error.
8
2.2.4. write()
La llamada al sistema write() permite escribir datos a un archivo. La sintaxis de write()
es la siguiente:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
El parámetro fd es el descriptor de archivo, buf corresponde a un puntero dirigido al
sector de memoria en el cual se encuentran los datos que se desean escribir al archivo y count
es el número de bytes que se desean escribir. La llamada write() retorna el número de bytes
escritos, o −1 en caso de que ocurra algún error.
2.2.5. ioctl()
La llamada al sistema ioctl() es utilizada casi exclusivamente con los dispositivos. Esta
llamada se encarga de llevar a cabo operaciones sobre un archivo, o bien definir parámetros
asociados a un archivo en particular. Estas operaciones y parámetros dependen del archivo,
y corresponden generalmente a características del dispositivo. Por ejemplo, ioctl() se puede
utilizar para definir la frecuencia de muestreo, el número de canales o la cantidad de bits de
una tarjeta de sonido. La sintaxis de ioctl() es la siguiente:
#include <sys/ioctl.h>
int ioctl(int d, int request, ...)
donde d corresponde al descriptor de archivo y request denota la operación que se quiere
realizar. La llamada ioctl() también puede recibir otros argumentos requeridos por alguna
operación en particular.
2.3. Programación con múltiples hilos de procesamiento
La programación con múltiples hilos de procesamiento es un esquema que permite desarrollar aplicaciones que ejecuten varios flujos de procesamiento dentro de un mismo proceso.
De esta forma, cada uno de los flujos o hilos se ejecuta de forma independiente, sin interferir
9
con los otros. Además, cada uno de los hilos tiene acceso a los distintos recursos del proceso,
tales como las variables globales y el entorno.
Cada uno de los hilos de procesamiento es administrado por el planificador del sistema
de archivos. De esta forma se obtiene un esquema similar a programación multiproceso tradicional de Linux, con la ventaja de que es más sencillo compartir recursos entre los flujos
de procesamiento, sin necesidad de recurrir a recursos tales como colas (queues) o tuberías
(pipes).
Utilizando hilos múltiples de procesamiento es posible obtener ejecución paralela en sistemas multiprocesador, lo cual incrementa el desempeño de una aplicación de forma significativa. En el caso de los sistemas monoprocesador, se obtiene un pseudo-paralelismo debido
a la planificación llevada a cabo por el sistema de archivos, lo cual trae consigo también
mejoras en el desempeño, así como una simplificación del diseño de la aplicación, debido a
que es posible separar las tareas que deben ejecutarse simultáneamente y dejar que el sistema
operativo se encargue de su ejecución.
La implementación de hilos múltiples de procesamiento en Linux es conocida como
pthreads, o POSIX threads. Esta implementación es utilizada en la mayoría de los sistemas
UNIX actuales.
Algunas de las rutinas principales de esta biblioteca se describen a continuación.
2.3.1. pthread_create()
La función pthread_create() se encarga de crear un nuevo hilo de procesamiento y hacer
que el sistema operativo programe su ejecución. La sintaxis de esta función se muestra a
continuación:
#include <pthread.h>
int
pthread_create(pthread_t *thread, pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
donde thread es la variable que apunta al nuevo hilo creado, con los atributos attr. Este
nuevo hilo ejecutará la función start_routine la cual debe retornar un puntero nulo. Además,
esta función puede recibir un argumento arg, el cual debe convertirse a un puntero nulo antes
de ser enviado.
10
2.3.2. pthread_exit()
Esta función es llamada por las funciones ejecutadas dentro de un hilo de procesamiento
para indicar su finalización. La sintaxis de pthread_exit() se muestra a continuación:
#include <pthread.h>
void pthread_exit(void *retval);
La variable retval es el valor de retorno de la función. Esta función es equivalente a la
función exit() utilizada en las aplicaciones de un solo hilo.
2.3.3. pthread_join()
La rutina pthread_join() detiene la ejecución del hilo actual hasta que otro hilo finalice.
La sintaxis de esta función es la siguiente:
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
donde th es el hilo por el cual se esperará. El valor de retorno de este hilo (retornado por
la función pthread_exit()) es almacenado en la variable apuntada por thread_return.
2.3.4. pthead_mutex_init()
Esta función inicializa una variable mutex. Esta variable permite controlar el acceso a un
recurso compartido, evitando que dos hilos puedan manipularlo al mismo tiempo. La sintaxis
de esta función es la siguiente:
#include <pthread.h>
int
pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex_attr_t *mutexattr);
donde mutex es la nueva variable de control de acceso y mutexattr contiene los atributos
de esta nueva variable.
11
2.3.5. pthread_mutex_lock()
La función pthread_mutex_lock() coloca un bloqueo sobre una variable mutex. Esta operación es llevada a cabo por un hilo cuando desea acceder a alguno de los recursos compartidos.
Si la variable ya está bloqueada el recurso está siendo utilizado por otro hilo y la ejecución del
hilo actual se detendrá hasta que la variable sea desbloqueada. Una variante de esta función
es la rutina pthread_mutex_trylock(), la cual no detendrá la ejecución del hilo si la variable
se encuentra bloqueada.
La sintaxis de pthread_mutex_lock se muestra a continuación:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
donde mutex es la variable de control a bloquear.
2.3.6. pthread_mutex_unlock()
Esta rutina se encarga de desbloquear una variable mutex, con el fin de permitir que otros
hilos puedan tener acceso al recurso compartido. La sintaxis de esta función se muestra a
continuación:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
2.3.7. pthread_mutex_destroy()
La rutina pthread_mutex_destroy() se encarga de liberar los recursos ocupados por una
variable mutex, una vez que su utilización ha finalizado. La sintaxis de esta rutina es la siguiente:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
12
2.4. El sistema Video4Linux
Video4Linux es un sistema de acceso a dispositivos de video que agrupa a varios controladores bajo una interfaz de programación común. Esto permite desarrollar programas con
una interfaz de acceso al hardware única, que funcionan con una gran cantidad de dispositivos. Además, el programador puede concentrarse en los aspectos funcionales de su programa
sin preocuparse por las rutinas de bajo nivel del controlador.
Existe una amplia gama de dispositivos soportados por Video4Linux, incluyendo tarjetas
de captura de video, sintonizadores de televisión, cámaras USB y de puerto paralelo, además
de decodificadores de teletexto.
Video4Linux (V4L) fue introducido al final del ciclo de desarrollo del kernel 2.1.x. A
partir del kernel 2.5.x se introdujo Video4Linux 2 (V4L2). Esta nueva generación corrige
muchos de los problemas de diseño de la versión anterior, además de que presenta nuevas
características tales como funciones para el manejo de streaming de video y métodos optimizados de acceso de memoria. Si bien es cierto la recomendación es utilizar V4L2, aún
muchos controladores no soportan esta nueva especificación.
2.4.1. La interfaz de programación de Video4Linux
Los dispositivos de captura de video V4L son accesibles a través del archivo /dev/video[n],
donde [n] es un número entre 0 y 63 que corresponde al identificador del dispositivo y se asigna de forma sucesiva al registrar el dispositivo en el kernel. Una vez abierto este archivo es
posible acceder a la mayoría de las funciones del dispositivo utilizando la llamada al sistema
ioctl().
Los controles disponibles son los siguientes:
VIDIOCGCAP: Permite obtener la información básica del dispositivo, tal como el tipo de
dispositivo, el número de canales y la resolución máxima. Recibe como argumento
una estructura del tipo video_capability, en la cual se almacena la información retornada por la llamada. La información contenida por esta estructura se muestra en el
cuadro 2.1.
VIDIOCSFBUF: Permite fijar los parámetros para la escritura directa al framebuffer de
la tarjeta de video. No todos los dispositivos soportan esta característica. Además, el
13
Cuadro 2.1 Información contenida por la estructura video_capability.
Campo
name[32]
type
channels
audios
maxwidth
maxheight
minwidth
minheight
Descripción
Nombre del dispositivo
Tipo de dispositivo
Número de canales
Número de dispositivos de audio
Ancho máximo posible del cuadro capturado
Altura máxima posible del cuadro capturado
Ancho mínimo posible del cuadro capturado
Altura mínima posible del cuadro capturado
acceso a framebuffer no es posible en algunos sistemas. Esta llamada recibe como argumento una estructura del tipo video_buffer, la cual contiene los parámetros de acceso
al framebuffer. El contenido de esta estructura se muestra en el cuadro 2.2.
Cuadro 2.2 Información contenida por la estructura video_buffer.
Campo
void *base
int height
int width
int depth
int bytesperline
Descripción
Dirección física base del buffer
Altura del buffer
Ancho del buffer
Profundidad del buffer
Número de bytes entre el inicio de dos líneas adyacentes
VIDIOCGWIN: Obtiene la información del área de captura. Recibe como argumento una
estructura del tipo video_window, en la cual se escribe la información. El contenido de
esta estructura se describe en el cuadro 2.3.
VIDIOCSWIN: Permite definir la información del área de captura requerida. Recibe como
argumento una estructura video_window la cual debe contener los parámetros deseados.
14
Cuadro 2.3 información contenida por la estructura video_window.
Campo
x
y
width
height
chromakey
flags
clips
clipcount
Descripción
Coordenada x del área de captura
Coordenada y del área de captura
Ancho del área de captura
Altura del área de captura
Valor RGB32 del chroma
Banderas de captura adicionales
Lista de rectángulos que se requiere extraer (sólo VIDIOCSWIN)
Número de rectángulos que se desea extraer (sólo VIDIOCSWIN)
VIDIOCCAPTURE: Permite activar y desactivar la captura en modo overlay2 .
VDIOCGCHAN: Retorna la información de los canales del dispositivo. Almacena la información en la estructura del tipo video_channel que recibe como argumento. Los
contenidos se esta estructura se muestran en el cuadro 2.4.
Cuadro 2.4 Información contenida por la estructura video_channel.
Campo
Descripción
channel
Número del canal
name
Nombre del canal
tuners Número de sintonizadores en el canal
flags
Propiedades del sintonizador
type
Tipo de entrada
norm
Norma que utiliza el canal
VIDIOCSCHAN: Selecciona el canal del cual se va a capturar. Recibe un entero con el
número del canal como argumento.
VIDIOCGPICT: Retorna las características (brillo, saturación, etc.) de la imagen. Almacena
la información en la estructura de tipo video_picture que recibe como argumento. Los
componentes de esta estructura se muestran en el cuadro 2.5.
2
Transferencia directa desde la memoria del dispositivo de captura hasta la memoria de la tarjeta de video.
Este modo incrementa la velocidad y reduce el procesamiento necesario cuando sólo se requiere desplegar el
video capturado, sin efectuar ninguna operación intermedia.
15
Cuadro 2.5 Información contenida por la estructura video_picture.
Campo
brightness
hue
colour
contrast
whiteness
depth
palette
Descripción
Brillo
Tonalidad (sólo imágenes a color)
Balance de color (sólo imágenes a color)
Contraste de la imagen
Nivel de blancura (sólo imágenes en escala de grises)
Nivel de profundidad de la imagen
Paleta que debe usarse para la imagen
VIDIOCSPICT: Permite definir las características de la imagen que será capturada. Recibe
como argumento una estructura del tipo video_picture, la cual debe contener los nuevos
valores requeridos.
VIDIOCGTUNER: Obtiene las características del sintonizador. Escribe la información en
la estructura de tipo video_tuner que recibe como argumento. En el cuadro 2.6 puede
verse la información almacenada en esta estructura. No todos los canales ni todos los
dispositivos tienen sintonizador, sólo los que corresponden a captura de televisión o de
radio.
Cuadro 2.6 Información contenida por la estructura video_tuner.
Campo
tuner
name
rangelow
rangehigh
flags
mode
signal
Descripción
Número del sintonizador
Nombre canónico del sintonizador
Mínima frecuencia sintonizable
Máxima frecuencia sintonizable
Banderas que describen el sintonizador
Modo de la señal de video
Intensidad de la señal, si se conoce (entre 0 y 65535)
VIDIOCSTUNER: Permite seleccionar cual sintonizador se va a utilizar. Recibe un entero
con el número de sintonizador como argumento.
VIDIOCGFREQ: Obtiene la frecuencia en la cual se encuentra ajustado el sintonizador.
VIDIOCSFREQ: Permite definir una nueva frecuencia del sintonizador.
16
VIDIOCGMBUF: Permite obtener el tamaño de la memoria del dispositivo, así como la
cantidad de cuadros que puede contener. La llamada almacena esta información en la
estructura video_mbuf que recibe como argumento, la cual es descrita en el cuadro 2.7.
Cuadro 2.7 Información contenida por la estructura video_mbuf.
Campo
Descripción
size
Tamaño de la memoria del dispositivo
frames Número de cuadros que puede contener la memoria
offsets
Posición de cada cuadro dentro de la memoria
VIDIOCMCAPTURE: Inicia la captura hacia uno de los cuadros de la memoria de video.
Recibe como argumento una estructura del tipo video_mmap donde se especifican varios parámetros tales como el cuadro que se desea capturar, el tamaño del cuadro y el
formato de la imagen. La descripción de esta estructura se presenta en el cuadro 2.8.
Cuadro 2.8 Información contenida por la estructura video_mmap.
Campo
Descripción
frame Número de cuadro que se desea capturar
height
Altura del cuadro
width
Ancho del cuadro
format
Formato de la imagen
VIDIOCSYNC: Espera a que finalice la captura de un cuadro, el cual se especifica a través
de la estructura del tipo video_mmap que recibe como parámetro.
Además de estas llamadas, existen otras que se encargan de controlar las funciones de
audio asociadas con algunos de los dispositivos.
2.5. Algoritmos de detección de movimiento
El análisis del movimiento es una de las áreas de estudio más importantes en el campo del
procesamiento y análisis de imágenes, debido a que existe una gran cantidad de aplicaciones,
como por ejemplo el estudio del movimiento en seres vivos o el análisis del tráfico vehicular. En los últimos años se ha dado un auge en la investigación desarrollada en esta área,
17
principalmente debido a los avances en la tecnología informática y al desarrollo de nuevos y
eficientes algoritmos.
La tarea más básica dentro de esta disciplina consiste en determinar cuando se da movimiento en un escenario específico. Existen varias técnicas que permiten alcanzar este objetivo; la elección de una de ellas depende de los requerimientos específicos de la aplicación, así
como de los recursos disponibles.
La forma más intuitiva y sencilla de analizar el movimiento consiste en evaluar las diferencias entre dos imágenes de una secuencia. Estas diferencias pueden estudiarse analíticamente sustrayendo una imagen de la otra, de acuerdo con la ecuación 2.1.
O(x, y) = I1 (x, y) − I2 (x, y)
(2.1)
En este método se utilizan imágenes en escala de grises, debido a que sólo es necesario
evaluar los cambios en la intensidad de los pixeles. Al sustraer una imagen de la otra se
remueven de la imagen resultante todas las características que no han cambiado, y se resaltan
aquellas que sí. Si la iluminación y la geometría del escenario son consistentes entre ambas
imágenes, las únicas diferencias en los pixeles donde no ha ocurrido ningún cambio se deben
a variaciones estadísticas, provocadas por ruido electrónico o de la cámara[6].
De acuerdo con esto, el movimiento provoca cambios en los niveles de grises entre las
imágenes de una secuencia. En el caso ideal, cualquier cambio en uno o más pixeles puede
considerarse como movimiento. Sin embargo, en los sistemas reales existen muchos factores
que pueden producir estos cambios. Algunos de estos factores son los cambios en el nivel de
iluminación y el ruido, tal como se mencionó anteriormente. Si bien es posible utilizar técnicas de filtrado para reducir su influencia, no es posible eliminarla completamente. Además,
en muchas ocasiones, no todos los eventos de movimiento que se presentan en una escena
son de interés. Por ejemplo, una cortina movida por el viento probablemente no sea de interés cuando lo que se desea es detectar la presencia de una persona en movimiento en un
escenario. Debido a estas razones, se utilizan algunos valores de umbral cuando se lleva a
cabo la detección.
Sea D(x, y) la diferencia, pixel por pixel de dos imágenes, I1 (x, y) y I2 (x, y), definida de
acuerdo a la ecuación 2.2

 0 si | I (x, y) − I (x, y) |≤ ²
2
1
D(x, y) = 
1 si | I2 (x, y) − I1 (x, y) |> ²
(2.2)
18
donde ² es el valor de umbral de sensibilidad. Esto significa que la diferencia de intensidad en
un pixel tiene que estar por encima de cierto valor para que esta diferencia sea significativa.
Esto permite eliminar la influencia del ruido que consiste en cambios pequeños de intensidad.
Un evento de movimiento se da cuando la ecuación 2.3 es válida:
X
D(x, y) > δ
(2.3)
donde δ es el valor de umbral de evento. De esta forma, la cantidad de pixeles con diferencias
significativas debe estar por encima de este valor de umbral para que se considere que se
produjo movimiento.
Existen otros métodos más complejos para llevar a cabo la detección de movimiento, sin
embargo, el método descrito anteriormente proporciona buenos resultados cuando se desea
realizar la detección en un escenario estacionario, lo que lo hace apropiado para una gran
cantidad de aplicaciones. Además, este algoritmo es significativamente más rápido que los
otros, lo que es muy importante en una aplicación de tiempo real como esta.
2.6. Reducción de ruido en las imágenes
Como se explicó en la sección anterior, el ruido presente en las imágenes puede afectar
significativamente el desempeño de los algoritmos de detección de movimiento. Debido a
esto, se acostumbra aplicar algún filtro para reducir los niveles de ruido antes de llevar a cabo
la sustracción.
Existen muchas técnicas de filtrado que permiten reducir el ruido de una imagen. La
elección de una técnica en particular depende de los requerimientos particulares de cada aplicación. Sin embargo existe un filtro muy utilizado, debido a su efectividad y su simplicidad.
Este filtro es el filtro paso bajos, o filtro de media.
2.6.1. Filtro de media
El filtro de media asigna a cada pixel un promedio calculado con base en su propio valor
y el valor de los pixeles cercanos. Esto permite atenuar las transiciones rápidas de intensidad
que se dan de un pixel a otro; debido a esto, este filtro también es conocido como filtro paso
bajos, dado que solamente conserva las transiciones suaves (o frecuencias bajas). Debido a
que la mayoría del ruido aleatorio existente en las imágenes presenta estas características,
19
este filtro es bastante efectivo para reducirlo.
El funcionamiento del filtro de media puede expresarse matemáticamente de la siguiente
forma:
+m
X
∗
Px,y
=
Wi,j · Px+i,y+j
i,j=−m
+m
X
(2.4)
Wi,j
i,j=−m
donde P es la matriz de pixeles de la imagen, P ∗ es la matriz resultante una vez aplicado
el filtro y W es una matriz cuadrada de dimensión 2m + 1, la cual contiene los pesos que
se asignarán a cada uno de los pixeles del vecindario del pixel que está siendo calculado. El
tamaño de este vecindario es 3 × 3, 5 × 5 o 7 × 7, pero también podrían utilizarse vecindarios
más grandes si se desea obtener un filtrado mejor. También es posible utilizar regiones no
cuadradas en esta clase de filtrado[6].
La forma más sencilla de este filtro consiste en una matriz W 3 × 3 en la que todos los
pesos son 1:


W =



1 1 1 

1 1 1 

1 1 1
También es posible escoger valores diferentes para la matriz W , de manera que cada uno
de los pixeles tiene un peso diferente en el cálculo del promedio. Generalmente se trata de que
la matriz tenga una distribución similar a la curva Gausiana, con los pesos mayores asignados
al pixel que está siendo calculado y a los pixeles cercanos, y los menores a los pixeles más
alejados.
Este filtro presenta problemas en los bordes de la imagen, debido a que la región que
rodea a los pixeles no es simétrica. Aunque es posible utilizar un vecindario asimétrico en
estos casos, la mayoría de las ocasiones estos pixeles se deja sin procesar y se les asigna un
valor de cero (color negro).
CAPÍTULO 3: Descripción general del sistema de captura
de video y detección de movimiento
3.1. Estructura de la aplicación y aspectos generales
La aplicación desarrollada está dividida en dos partes principales: un servidor, que se
encarga de capturar del dispositivo de video y llevar a cabo las rutinas de detección de movimiento, y un cliente, a través del cual se realiza la visualización en tiempo real del video
capturado por el servidor, y que permite controlar algunos de los parámetros del servidor.
La comunicación entre ambas partes se lleva a cabo utilizando el conjunto de protocolos
TCP/IP, a través de una interfaz de red. Esto permite implementar una aplicación distribuida,
en la cual, la captura y el procesamiento puedan llevarse a cabo en una computadora y la
visualización y el control puedan ejecutarse de forma remota desde otro equipo.
Figura 3.1 Partes principales de la aplicación
Tanto el servidor como el cliente fueron desarrollados en lenguaje C, utilizando funciones de la biblioteca estándar de Linux (conocida como libc). La aplicación fue desarrollada
utilizado múltiples hilos de procesamiento utilizando la implementación POSIX (conocida
como pthreads). Esto permite ejecutar en paralelo varias de las rutinas, como es el caso de la
captura del video y de la detección de movimiento en el servidor.
20
21
3.2. Estructura del servidor
El servidor está dividido en cuatro partes, cada una de las cuales es ejecutada en un hilo
de procesamiento diferente. Estas partes son:
Inicialización y comunicación con el cliente: Es el primer hilo que se ejecuta y el que se
encarga de crear y administrar a los otros. Se encarga de inicializar el dispositivo de
video y de crear un socket TCP, el cual espera conexiones del cliente. A través de esta
rutina se controlan todos los procesos del servidor, de acuerdo con las instrucciones
enviadas por el cliente.
Captura de video: Se encarga de leer las imágenes del dispositivo de video y colocarlas
en un área de memoria, la cual es accesible por las otras rutinas del programa. Puede
iniciarse y detenerse bajo demanda, según las instrucciones enviadas por el cliente.
Detección de movimiento: Analiza la secuencia de imágenes obtenida por el hilo de captura
con el fin de detectar movimiento en el video. En caso de que esto ocurra se encarga
de almacenar el video en el disco duro, y notificar al cliente que se dio un evento de
movimiento. También es activado o desactivado bajo demanda por el cliente a través
del canal de instrucciones TCP.
Envío del video: Crea un socket UDP y envía el video para su despliegue en tiempo real en
el cliente. Es creado por el cliente una vez iniciada la captura y puede detenerse cuando
no se desee recibir más video.
3.2.1. Inicialización y comunicación con el cliente
Como se mencionó anteriormente, éste es el hilo principal del programa, debido a que se
encarga de administrar los distintos componentes de acuerdo con las instrucciones recibidas
del cliente. Este hilo se ejecuta al iniciar el programa, y todos los demás son creados desde
aquí.
Una vez creado, su primera función es la inicialización del dispositivo de video. Posteriormente, crea un socket TCP y lo registra para que espere conexiones en un puerto determinado.
Cuando se establece una conexión con el cliente ejecuta las instrucciones que este envía y una
vez finalizada la sesión espera a que se establezca de nuevo una conexión. En la figura 3.2 se
muestra el diagrama de flujo de esta rutina.
22
Figura 3.2 Diagrama de flujo del hilo de inicialización y comunicación con el
cliente.
3.2.2. Captura de video
Esta rutina se encarga de reservar un sector de memoria con el fin de proporcionar un
buffer circular para almacenar los cuadros capturados del dispositivo de video. Este buffer
permite a los otros hilos acceder los últimos cuadros capturados, con el fin de llevar a cabo el
procesamiento necesario para la detección de movimiento y de enviar los cuadros al cliente
para su despliegue en tiempo real.
Una vez hecho esto, se mapea la memoria del dispositivo de video y se inicia el ciclo que
copia secuencialmente cada uno de los cuadros de la memoria de video al buffer. Este ciclo
se repite hasta que se reciba una orden del cliente.
En la figura 3.3 se muestra el diagrama de flujo de la rutina de captura de video.
23
Figura 3.3 Diagrama de flujo del hilo de captura de video.
3.2.3. Detección de movimiento
El hilo de detección de movimiento se encarga de determinar cuando se produce algún
evento de movimiento en el escenario observado y de almacenar estos eventos en el disco duro. La detección se lleva a cabo analizando las diferencias que existen entre un cuadro y otro.
Si las diferencias se encuentran por encima de cierto valor y se mantienen por algún tiempo
se considera que se está dando un evento de movimiento y se graban los cuadros capturados
al disco duro. Una vez que el evento finaliza, las imágenes almacenadas se combinan en un
archivo de video y se notifica al usuario el registro del evento.
El diagrama de flujo de la rutina de detección de movimiento se muestra en la figura 3.4.
3.2.4. Envío del video
La rutina de envío de video se encarga de crear, bajo pedido del cliente, un socket UDP
para enviar los cuadros de video capturados para su visualización en tiempo real. Una vez
24
Figura 3.4 Diagrama de flujo del hilo de detección de movimiento.
creado el socket espera por un mensaje del cliente que le informe que está listo para recibir
el video. Una vez recibido este mensaje inicia un ciclo en el cual toma un cuadro del buffer,
lo fragmenta en paquetes de 4096 bytes con el fin de cumplir con los requerimientos de
capacidad del protocolo UDP y lo envía al cliente. Esto se repite hasta que el cliente solicite
que se detenga el Envío, lo que provoca que el ciclo finalice y que el hilo de procesamiento
se detenga. En la figura 3.5 se muestra el diagrama de flujo para esta rutina.
25
Figura 3.5 Diagrama de flujo del hilo de envío del video.
3.3. Estructura del cliente
La aplicación cliente consiste en una interfaz gráfica de usuario que permite visualizar
en tiempo real el video capturado por el servidor. Además, es posible manipular muchas de
las funciones del servidor, como por ejemplo iniciar o detener la captura de video. El cliente
además es notificado por el servidor cada vez que se registra un evento del movimiento.
La interfaz gráfica de usuario se implementó utilizando la biblioteca GTK 2.4 para crear
las ventanas y demás elementos visuales, así como para manejar la interacción con el usuario
y con el administrador de ventanas. Al igual que el servidor, este programa también se dividió en múltiples hilos de procesamiento, con el fin de ejecutar en forma paralela las rutinas
correspondientes a la recepción de los cuadros de video y lo referente a la visualización y la
interacción con el usuario. En la figura 3.6 pueden verse las partes principales del programa,
así como su interacción con el servidor.
26
Figura 3.6 Estructura de la aplicación cliente.
3.3.1. Interfaz gráfica de usuario y mensajería TCP
La rutina principal del programa cliente se encarga de crear e inicializar la interfaz gráfica, así como de establecer la comunicación con el servidor a través de un socket TCP. Una
vez iniciado el programa, este envía mensajes al servidor para que inicie la captura y la transmisión del video. Además solicita al servidor las características de los cuadros de video con
el fin de poder reconstruirlos adecuadamente a partir de los datos recibidos a través del socket
UDP. Una vez llevada a cabo la inicialización, la rutina principal lanza el hilo que se va a
encargar de recibir los paquetes UDP que contienen el video. Por último, la rutina configura y despliega los elementos de la interfaz gráfica y llama a la rutina de manejo de eventos
GTK, la cual se encarga de la interacción con el usuario y con el manejador de ventanas. Este
hilo de procesamiento (y el programa completo) finaliza cuando la ventana es destruida (el
usuario presiona el botón de cerrar de la ventana) o cuando el usuario selecciona en el menú
la acción correspondiente. En la figura 3.7 puede verse el diagrama de flujo de este hilo de
procesamiento.
3.3.2. Recepción de los cuadros de video
La recepción de los cuadros de video para su despliegue en tiempo real es llevada a
cabo por un hilo de procesamiento independiente que crea un socket UDP, envía un mensaje
al servidor indicando que está listo para recibir la información y posteriormente inicia un
ciclo en el cual recibe la ráfaga de paquetes que contiene los cuadros de video. Como se
indicó anteriormente, el servidor envía cada cuadro fragmentado en varios paquetes de 4096
bytes debido a los requerimientos del protocolo UDP, por lo que la rutina de recepción debe
reconstruir cada imagen. Para esto, se calcula el número de paquetes por cuadro con base en
las características del video capturado solicitadas al servidor a través del canal TCP. Además,
27
Figura 3.7 Diagrama de flujo del hilo encargado de la interfaz gráfica y la
mensajería TCP en el cliente
cada vez que el servidor finaliza el envío de un cuadro envía un paquete de sincronización
de 4 bytes, que permite a la rutina de recepción verificar si ha recibido el número correcto
de paquetes de datos. Si hay alguna inconsistencia, el cuadro es desechado y se espera el
siguiente.
Cuando un cuadro es recibido satisfactoriamente, este es colocado en una posición de
memoria que es leída periódicamente por el hilo encargado de la interfaz gráfica. De esta
forma, el usuario puede ver un flujo constante de video.
En la figura 3.8 se muestra el diagrama de flujo para este hilo de procesamiento.
28
Figura 3.8 Diagrama de flujo del hilo de recepción de video.
CAPÍTULO 4: Rutinas de captura de video
4.1. Inicialización del sistema de video
El primer paso necesario para acceder el dispositivo de video consiste en abrir el dispositivo para lectura y escritura, y asignarlo a un descriptor de archivo. Este descriptor de
archivo se utiliza durante el resto del programa para referirse al dispositivo y efectuar todas
las operaciones necesarias.
if((fd = open(video_dev, O_RDWR)) == -1) {
fprintf(stderr, "Error abriendo el dispositivo %s\n", video_dev);
return -1;
}
En el fragmento de código mostrado, fd es el descriptor de archivo asignado al dispositivo de video. La variable video_dev que se pasa como argumento a open() contiene una
cadena de carácteres con el nombre del dispositivo de video que se desea abrir (por ejemplo
/dev/video0).
Una vez abierto el dispositivo se utiliza la llamada al sistema ioctl() para obtener las de
las características del dispositivo, así como para definir algunos parámetros necesarios para
la captura.
/* Obtención de las capacidades de captura */
if(ioctl(fd, VIDIOCGCAP, &cap) == -1) {
fprintf(stderr, "Error al realizar VIDIOCGCAP\n");
return -1;
}
/* Información de los Buffers */
if((ioctl(fd, VIDIOCGMBUF, &buf_info)) == -1) {
printf("Error adquiriendo los buffers\n");
return -1;
}
29
30
La llamada a VIDIOCGCAP solicita al dispositivo las características del dispositivo, las
cuales son almacenadas en la variable cap —del tipo video_capability— que se pasa como
argumento a ioctl(). Estas características incluyen el nombre del dispositivo y el número de
canales, así como los tamaños máximos y mínimos posibles para el cuadro a capturar (ver
cuadro 2.1).
Posteriormente, la llamada a VIDIOCGMBUF se encarga de obtener la información de
la memoria del dispositivo. Esta información es almacenada en la variable buf_info, una estructura del tipo video_mmap, y es utilizada por la función mmap() para asignar este bloque
de memoria a una posición direccionable por el sistema operativo. VIDIOCGMBUF permite
obtener además la cantidad de cuadros que puede almacenar la memoria de video, que puede
ir desde un solo cuadro hasta 32 cuadros, dependiendo del dispositivo.
map = mmap(0, buf_info.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(map == NULL) {
printf("Error en el mapeo de la memoria\n");
return -1;
}
De esta forma, la memoria del dispositivo es accesible a través del puntero map. Esto
proporciona un método de captura que resulta más eficiente que efectuar llamadas a read()
sobre el dispositivo.
El último paso que se debe llevar a cabo antes de iniciar la captura consiste en definir
cual será el canal del cual se va a capturar la imagen. Esto se realiza mediante la llamada a
VIDIOCSCHAN, la cual toma como argumento un entero con el canal que se desea.
int set_chan(int fd, int chan) {
if(ioctl(fd, VIDIOCSCHAN, chan) == -1)
return -1;
else
return 0;
}
31
4.2. Ciclo de captura
Tal como se mencionó en el capítulo 3, el ciclo de captura es llevado a cabo por un hilo
de ejecución independiente. Esto es implementado utilizado la biblioteca pthreads, la cual
forma parte de la biblioteca estándar del lenguaje C para Linux.
pthread_t capture_thread;
...
pthread_create(&capture_thread, &attr, capture_loop, NULL);
De esta forma se crea un nuevo hilo que llama a la función capture_loop.
Una vez que inicia la ejecución de la función se reserva memoria para el buffer circular que va a contener los cuadros capturados. El tamaño del buffer depende del número de
cuadros que se requiera almacenar, así como del tamaño de cada cuadro.
framesize = video_buf.width * video_buf.height * 3;
...
main_buf = (char *)malloc(BUF_FRAME_NUM * framesize);
Para llevar a cabo la captura de un cuadro se deben llevar a cabo dos llamadas, mediante
ioctl(), al dispositivo de video. La primera llamada, conocida como VIDIOCMCAPTURE, se
encarga de iniciar la captura de uno de los cuadros de la memoria de video. La otra llamada,
VIDIOCSYNC, se encarga de sincronizar un cuadro capturado, y no retorna hasta que la
captura del cuadro finalice. De esta forma, es posible preparar uno de los cuadros mientras se
sincroniza otro, lo cual aumenta la eficiencia del proceso de captura. La forma más sencilla de
implementar este método consiste en utilizar dos cuadros y sincronizar uno de ellos mientras
el otro se prepara, sin embargo es posible llevar esto más allá y utilizar la totalidad de la
memoria de video. Tal como se mencionó en la sección anterior, el número de cuadros con
que se cuenta depende del dispositivo utilizado.
El algoritmo necesario para realizar la captura utilizando n cuadros se muestra en la
figura 4.1. Al realizar la lectura utilizando este método es posible obtener la máxima velocidad posible de captura permitida por el dispositivo.
32
Figura 4.1 Diagrama del algoritmo de captura de video utilizado.
33
Tal como se muestra en el diagrama de flujo de la figura 4.1, el primer paso consiste en
preparar los cuadros iniciando la captura en cada uno de ellos. El cuadro sobre el cual se va a
efectuar la acción se define utilizando el miembro frame, de la estructura del tipo video_mbuf.
for(i = 0; i < buf_info.frames; i++) {
video_buf.frame = i;
ioctl(vfd, VIDIOCMCAPTURE, &video_buf);
}
Una vez hecho esto, se entra en el ciclo de captura, el cual recorrerá todos los cuadros de
la memoria del dispositivo secuencialmente. En este caso, una vez que el cuadro se encuentra
disponible, es copiado a la siguiente posición disponible del buffer principal del programa,
con el fin de hacerlo disponible para el resto de las rutinas.
34
framecounter = 0;
...
while(capture) {
/* Si llegó al fin de la memoria del dispositivo */
if(i == buf_info.frames)
i = 0;
video_buf.frame = i;
ioctl(vfd, VIDIOCSYNC, &video_buf);
// Sincroniza
ioctl(vfd, VIDIOCMCAPTURE, &video_buf); // Prepara para la siguiente
// lectura
/* Si llegó al final del buffer vuelve a empezar desde el inicio */
if(framecounter == BUF_FRAME_NUM) {
framecounter = 0;
...
}
/* Copia el cuadro al buffer pricipal */
memcpy(main_buf + framecounter * framesize,
map + buf_info.offsets[i], framesize);
/* Incrementa ambos contadores */
i++;
framecounter++;
}
Este ciclo se va a ejecutar hasta que el valor de la variable global capture sea 0. Esto
le permite al hilo principal del programa iniciar o detener la captura de acuerdo con las
solicitudes del cliente.
Una vez que se sale del ciclo de captura, se libera la memoria utilizada por el buffer de
video y se sale del hilo de ejecución.
35
free(main_buf);
pthread_exit(NULL);
Cuando el hilo principal ordena que se detenga la captura, debe asegurarse que el hilo
sea destruido correctamente, con el fin de que se liberen todos los recursos correspondientes.
Esto lo hace mediante una llamada a la función pthread_join(). Esta función detiene el flujo
normal hasta que el hilo retorne.
if(capture) {
capture = 0;
pthread_join(capture_thread, NULL);
printf("Captura detenida\n");
}
CAPÍTULO 5: Rutinas de comunicación con el cliente y transmisión de video sobre la red
5.1. Creación del socket TCP en el servidor
Como se mencionó en el capítulo 3, el hilo de ejecución principal del programa, que
se encarga de crear y administrar los otros hilos de ejecución, crea un socket TCP, una vez
inicializado el dispositivo de video, con el fin de responder a las solicitudes del cliente. Esto
brinda la posibilidad de controlar remotamente las funciones realizadas por el sistema de
captura, y además monitorear su estado.
El código necesario para crear el socket y esperar por conexiones del cliente se muestra a
continuación:
/* Creación del socket */
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "Problemas creando el socket\n");
pthread_exit((void *)-1);
}
/* Almacenamiento de los parámetros de conexión en
la estructura sockaddr_in */
bzero((char *)&server, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "Error al enlazar el socket\n");
pthread_exit((void *)-1);
}
/* El socket espera conexiones */
listen(sd, 5);
36
37
/* Ciclo infinito de atención de conexiones */
while(1) {
/* Acepta la conexión entrante */
client_len = sizeof(client);
if((sd2 = accept(sd, (struct sockaddr *)&client, &client_len)) == -1) {
fprintf(stderr, "No se puede aceptar la conexion\n");
pthread_exit((void *)-1);
}
...
close(sd2);
}
En primer lugar se crea el socket utilizando la llamada al sistema socket(). La combinación
de los parámetros AF_INET y SOCK_STREAM indican al sistema operativo que se desea
utilizar el protocolo TCP. Esta llamada retorna un descriptor de socket, el cual se utilizará
para referirse al socket en el resto de la rutina.
A continuación se almacenan los parámetros de la conexión, tales como el puerto, el
protocolo y la dirección en la cual se esperaran las conexiones en la estructura sockaddr_in.
Esta estructura se pasa como argumento a la llamada bind(), la cual se encarga de enlazar el
socket. Esto provoca que el sistema operativo reserve el puerto para el proceso que lo solicitó,
evitando que otro proceso pueda utilizarlo.
Una vez llevado a cabo el enlace, se utiliza la llamada listen() para indicar al sistema que el
programa va a manejar todas las conexiones entrantes por el socket sd. El segundo argumento
de listen() corresponde al número de conexiones que el sistema operativo debe colocar en
cola, en caso de que el programa no pueda atenderlas. En este caso, se pueden colocar en
espera un máximo de 5 conexiones. Si existe una sexta solicitud, esta será rechazada por el
sistema operativo.
Posteriormente, el programa inicia un ciclo infinito para atender consecutivamente las
conexiones entrantes. La llamada accept() hace que el programa duerma hasta que haya una
solicitud de conexión. Cuando esta se presenta se crea un nuevo descriptor exclusivo para
atender esta conexión (esto debido a que el envío de los datos al cliente se lleva a cabo
a través de un puerto diferente, asignado de manera aleatoria por el sistema operativo). Una
38
vez finalizada la comunicación con el cliente, este descriptor es cerrado, y el programa vuelve
a dormir, hasta que se de una nueva conexión.
5.2. Envío y recepción de datos
El envío y la recepción de los datos se realizan utilizando las llamadas read() y write(),
debido a que el descriptor del socket puede verse como si fuera un descriptor de archivos.
De esta forma pueden enviarse y recibirse datos fácilmente, sin tener que recurrir a rutinas
de bajo nivel, y sin necesidad de conocer a profundidad el funcionamiento del protocolo
TCP/IP. El sistema operativo es el que se encarga de fragmentar la información en paquetes
y de colocar los encabezados necesarios para su transmisión.
Una vez establecida la conexión, el servidor envía un mensaje inicial al cliente, con el
fin de indicarle que está listo para recibir peticiones. En este caso, el mensaje enviado es
simplemente el nombre y la versión del programa:
char out_buf[BUF_SIZE];
...
/* Envía mensaje de bienvenida */
strcpy(out_buf,"Telescreen v0.3");
write(sd2, out_buf, BUF_SIZE);
Una vez enviado este mensaje, se inicia un ciclo infinito en el cual espera que el cliente
haga una petición y la procesa. En este ciclo se llama a read() con el fin de leer el mensaje
enviado por el cliente:
char *buf_ptr;
int bytes_left, n_size, n_read;
while(1) {
/* Para que el ciclo no ocupe todo el procesador */
usleep(100);
/* Lectura de los datos enviados por el cliente */
bytes_left = BUF_SIZE;
buf_ptr = in_buf;
39
while((n_read = read(sd2, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
/* Si no hay datos disponibles reinicia el ciclo */
if(n_read < 0)
continue;
...
Una vez que un mensaje es leído, este es identificado y procesado. Los mensajes implementados actualmente se muestran en el cuadro 5.1.
Cuadro 5.1 Mensajes implementados para la comunicación entre el cliente y el
servidor
Mensaje
CAPTURE
NOCAPTURE
STREAM
NOSTREAM
MOTION
NOMOTION
VIDEOPROP
CLOSE
Acción
Inicia el hilo de captura
Detiene la ejecución del hilo de captura
Inicia el hilo encargado de enviar el video capturado al cliente
Detiene el envío del video al cliente
Inicia el hilo encargado de llevar a cabo la detección de movimiento
Detiene la detección de movimiento en el video capturado
Solicita el envío de las propiedades del cuadro de video capturado
Cierra la conexión con el servidor
La mayoría de los mensajes se encargan de iniciar o detener los hilos de procesamiento de
las distintas partes que componen el sistema. A manera de ejemplo se muestra a continuación
el código para manejar los mensajes CAPTURE y NOCAPTURE:
if((strcmp(in_buf,"CAPTURE")) == 0) {
if(!capture) { // Inicio de la captura
capture = 1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&capture_thread, &attr, capture_loop, NULL);
}
40
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if((strcmp(in_buf, "NOCAPTURE")) == 0) {
if(capture) {
capture = 0;
pthread_join(capture_thread, NULL);
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
}
// Fin de la captura
En el caso de CAPTURE, se asigna 1 a la variable global capture, y se crea el hilo de
procesamiento que llama a la función capture_loop(). Posteriormente se envía el mensaje
«OK» al cliente, indicándole que el comando ha sido ejecutado satisfactoriamente. En lo que
respecta a NOCAPTURE, la variable capture se pone en 0 lo que provoca que la función
capture_loop() finalice. Por último se espera que el hilo de procesamiento finalice con la
función pthread_join() y se envía el mensaje de confirmación al cliente.
Los otros mensajes son implementados de forma similar, con excepción de VIDEOPROP.
En el caso de este mensaje, el servidor debe enviar los datos del cuadro capturado, con el
fin de que el programa cliente pueda desplegarlo. Estos datos son enviados en la estructura
video_prop:
/* Propiedades del cuadro enviado */
struct video_prop {
int width;
// Ancho
int height; // Altura
};
El código que atiende esta solicitud se muestra a continuación:
/* Envío de las propiedades del cuadro capturado */
} else if((strcmp(in_buf, "VIDEOPROP")) == 0) {
struct video_prop prop;
char *prop_ptr = ∝
prop.width = video_buf.width;
41
prop.height = video_buf.height;
write(sd2, prop_ptr, sizeof(struct video_prop));
printf("Propiedades de video enviadas\n");
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
}
5.3. Envío del video capturado al cliente
5.3.1. Creación del socket UDP
Como se mencionó en el capítulo 3, el envío de los cuadros de video capturados se lleva
a cabo utilizando el protocolo UDP. Debido a esto, el primer paso para enviar los cuadros
consiste en crear un socket que utilice este protocolo:
/* Creación del socket UDP */
if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
fprintf(stderr, "Problemas creando el socket\n");
pthread_exit((void *)-1);
}
/* Información de la conexión */
bzero((char *)&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "Problemas al enlazar el socket\n");
pthread_exit((void *)-1);
}
/* Espera que el cliente solicite los datos */
client_len = sizeof(client);
if((n = recvfrom(sd, buf, BUF_SIZE, 0, (struct sockaddr *)&client,
&client_len)) < 0) {
42
fprintf(stderr, "Problemas recibiendo datagramas del cliente\n");
pthread_exit((void *)-1);
}
El proceso de creación del socket UDP es muy similar a la del socket TCP. La principal
diferencia se da al utilizar la llamada socket(), a la cual se le pasa como parámetro la constante
SOCK_DGRAM.
Además, debido a que UDP es un protocolo no orientado a conexión, no se utilizan las
llamadas listen() ni accept(). Una vez enlazado el socket ya se pueden enviar o recibir datos.
Para llevar a cabo la comunicación es necesario únicamente utilizar las llamadas recvfrom()
para el envío y sendto() para la recepción. En este caso, el servidor espera que el cliente envíe
un paquete que le informe que esta listo para recibir el video.
5.3.2. Fragmentación y envío de los cuadros de video
Una vez creado el socket, el siguiente paso consiste en el envío secuencial de los cuadros
adquiridos por el hilo de captura. Debido a que el tamaño máximo de la carga útil de un paquete UDP es de aproximadamente 5200 bytes, cada cuadro de video debe fragmentarse en
varias porciones para su envío. La rutina de envío se encarga de calcular el número de fragmentos necesarios de acuerdo al tamaño del cuadro, y de enviar la secuencia de fragmentos.
Además, con el fin de facilitar la reconstrucción del cuadro por parte del cliente, se inserta un
paquete de sincronización entre cada cuadro. Esto permite que el cliente pueda determinar el
final de un cuadro y determinar si se perdieron fragmentos durante la transmisión.
El código necesario para enviar los cuadros de video se incluye a continuación:
while(stream) {
/* Envía el cuadro anterior al que está siendo capturado */
if(framecounter)
fc = framecounter - 1;
else
fc = BUF_FRAME_NUM;
if(prev_fc == fc)
continue;
43
prev_fc = fc;
int fp = main_buf + framesize * fc;
/* Se divide el frame en paquetes UDP de 4096 bytes */
int n_packets = framesize / 4096;
int i;
for(i = 0; i <= n_packets; i++) {
if(sendto(sd, fp + i*4096, 4096, 0, (struct sockaddr *)&client,
client_len) == -1)
printf("Error enviando el paquete #%d\n", i);
/* Para que no se pierdan paquetes en interfaces muy
rápidas (ej. loopback) */
usleep(10);
}
/* Envío del paquete de sincronización */
int sync = n_packets; // Valor arbitrario, lo que importa es el tamaño
if(sendto(sd, (char *)&sync, sizeof(int), 0, (struct sockaddr *)&client,
client_len) == -1)
printf("Error enviando paquete de sincronización\n");
counter++;
}
En este caso, el tamaño de cada paquete se establece en 4096 bytes. El paquete de sincronización es un entero, que tiene un tamaño de 4 bytes. De esta forma, el cliente puede
determinar cual es el paquete de sincronización con base en la cantidad de bytes recibidos.
CAPÍTULO 6: Rutinas de detección de movimiento y escritura a disco duro
6.1. Algoritmo de detección de movimiento
El algoritmo utilizado para llevar a cabo la detección de movimiento consiste en tomar un
cuadro, restarle el cuadro anterior y evaluar el resultado. Si la cantidad de pixeles diferentes
entre un cuadro y el otro está por encima de cierto valor de umbral, puede considerarse
que alguno de los elementos del cuadro cambió de posición, y por lo tanto se presentó un
evento de movimiento. Si bien es cierto este algoritmo es uno de los más sencillos, funciona
bastante bien para la aplicación implementada, dado se pretende realizar la detección en un
escenario estacionario, donde las únicas perturbaciones corresponden a los eventos que se
desean registrar.
Uno de los principales problemas de este algoritmo es que es muy susceptible al ruido que
se presenta durante la captura, principalmente con cámaras de baja calidad. Debido a esto es
necesario aplicar un filtro a las imágenes con el fin de reducir este ruido y obtener mejores
resultados. En la próxima sección se explica cual fue el filtro utilizado en este sistema, y
como se llevó a cabo su implementación.
El código que se encarga de realizar la detección de movimiento entre dos cuadros se
muestra a continuación:
/* Toma el cuadro anterior al cuadro que está siendo capturado */
fc = framecounter - 1;
if(fc < 0)
fc = 0;
if(fc == prevfc)
continue;
else
prevfc = fc;
...
/* Convierte el cuadro actual y el anterior a escala de grises */
44
45
grayscale(main_buf + fc * framesize, framesize, curr);
grayscale(main_buf + (fc?(fc - 1):0) * framesize, framesize, prev);
/* Aplica filtro a ambos cuadros */
mean_filter(curr, video_buf.width, video_buf.height);
mean_filter(prev, video_buf.width, video_buf.height);
/* Resta ambos cuadros y almacena la diferencia en dif */
int i;
for(i = 0; i < framesize / 3; i++) {
dif[i] = abs(curr[i] - prev[i]);
/* Si hay diferencia entre los pixeles incrementa el contador */
if(dif[i] > ith)
ndif++;
}
/* Calcula el porcentaje de pixeles diferentes */
pdif = (float)ndif/((float)framesize/3)*100;
if(pdif > threshold)
/* Hay movimiento */
...
La rutina toma los dos cuadros anteriores al que está siendo capturado y los convierte a
escala de grises. Posteriormente aplica un filtro a ambos cuadros con el fin de reducir el ruido
presente. Se trabaja en escala de grises con el fin de facilitar el procesamiento y aumentar
la velocidad. Además, para detectar los cambios de posición sólo es necesario evaluar los
cambios de intensidad de los pixeles, y no la diferencia de colores. La conversión a escala de
grises se realiza utilizando la siguiente ecuación:
I = 0, 30R + 0, 59G + 0, 11B
(6.1)
Donde I representa el valor de intensidad del pixel (escala de grises), R representa el
valor de rojo del pixel de la imagen original, G el valor de verde y B el valor de azul.
Una vez hecho esto, se realiza la resta de los cuadros, pixel por pixel. El resultado se
almacena en el arreglo dif. Si la diferencia entre dos pixeles es mayor que cierto nivel de
intensidad entonces, se considera el pixel como diferente y se incrementa la variable ndif, la
46
cual almacena la cantidad de pixeles diferentes. La razón por la cual no se considera cualquier
diferencia entre los pixeles es que existen muchas oscilaciones pequeñas de intensidad entre
dos cuadros, debido principalmente al ruido, que no son corregidas por el filtro.
Por último, una vez analizada toda la imagen, se calcula el porcentaje de pixeles diferentes
y se compara con el valor de umbral, almacenado en la variable threshold. Si este porcentaje
es mayor que el valor de umbral, se considera que hubo movimiento entre los cuadros. Se
utilizan valores porcentuales con el fin de simplificar la manipulación de las variables. De
esta forma no es necesario conocer la cantidad de pixeles que componen una imagen.
6.2. Filtrado de las imágenes
Como se mencionó anteriormente, el ruido afecta de manera significativa la detección de
movimiento. Debido a esto, antes de realizar la resta de los cuadros se aplica un filtro paso
bajos a las imágenes con el fin de reducir el nivel de ruido presente.
El filtro paso bajos se encarga de atenuar los cambios drásticos de intensidad que existan
entre un pixel y los pixeles cercanos. Esto se logra asignando al pixel el valor promedio de los
pixeles que lo rodean, tal como se explica en el capítulo 2. En este caso, el filtro implementado
es un filtro de media, que asigna a cada pixel el mismo peso, y utiliza la matriz de pesos que
se muestra a continuación:


W =



1/9 1/9 1/9 

1/9 1/9 1/9 

1/9 1/9 1/9
El código necesario para implementar el filtro se muestra a continuación:
void mean_filter(unsigned char *frame, int x, int y) {
int i, j;
unsigned char *new_frame;
/* Matriz de pesos */
float weights[9] = {0.11, 0.11, 0.11, 0.11, 0.11, 0.11,
0.11, 0.11, 0.11};
47
/* Almacenamiento temporal para la imagen procesada */
new_frame = malloc(x * y);
/* Aplica el filtro pixel por pixel
for(i = 1; i < (y - 1); i++)
for(j = 1; j < (x - 1); j++) {
new_frame[i*x+j] = weights[0] *
weights[1] *
weights[2] *
weights[3] *
weights[4] *
weights[5] *
weights[6] *
weights[7] *
weights[8] *
*/
frame[(i-1)*x+(j-1)] +
frame[(i-1)*x+j] +
frame[(i-1)*x+(j+1)] +
frame[i*x+(j-1)] +
frame[i*x+j] +
frame[i*x+(j+1)] +
frame[(i+1)*x+(j-1)] +
frame[(i+1)*x+j] +
frame[(i+1)*x+(j+1)];
}
/* Coloca pixeles negros en el borde de la imagen */
for(i = 0; i < y; i++) {
new_frame[i*x] = 0;
new_frame[i*x+(x-1)] = 0;
}
for(i = 0; i < x; i++) {
new_frame[i] = 0;
new_frame[(y-1)*x+i] = 0;
}
/* copia la imagen procesada sobre la imagen original
y libera la memoria temporal */
memcpy(frame, new_frame, x * y);
free(new_frame);
}
El filtro es aplicado a todos los pixeles, con excepción de los bordes, a los cuales se les
asigna un 0 (color negro). Aunque esto trae consigo pérdida de información, el área de los
bordes no es muy significativa en comparación con el área total de la imagen. Además, al
asignar el mismo valor en todas las imágenes, la rutina de detección de movimiento no se ve
afectada.
48
6.3. Determinación de eventos de movimiento
Una diferencia entre dos cuadros por encima del nivel de umbral no es suficiente criterio
para asegurar que se dio un evento de movimiento. Esta diferencia pudo ser provocada por un
cambio repentino en la iluminación de la escena o por ruido que no pudo ser eliminado por el
filtro. Es necesario que las diferencias entre los cuadros se mantenga por algún tiempo para
que puedan considerarse un evento de movimiento real. Debido a esto se diseñó un algoritmo
que determina cuando se presenta un evento real de movimiento.
El algoritmo define un tiempo inicial durante el cual deben mantenerse las diferencias
entre los cuadros para que se declare un evento de movimiento. Además se define un tiempo
al final del evento durante el cual no debe haber movimiento para que el evento finalice. Si se
presenta movimiento durante este periodo se reinicia el temporizador y el evento continua.
La implementación de este algoritmo se llevó a cabo mediante una máquina de estados,
cuyo diagrama se muestra en la figura 6.1. Los estados definidos para esta máquina se muestran en la tabla 6.1.
Cuadro 6.1 Estados necesarios para el algoritmo de detección de eventos de
movimiento
Estado
IDLE
MSTARTED
MNORMAL
MSTOPED
Significado
En espera, no se ha detectado movimiento
En el periodo de tiempo inicial
En un evento de movimiento
En el periodo de tiempo final
Los cuadros que deben almacenarse al disco duro son los que se capturan en los estados
MSTARTED, MNORMAL y MSTOPED. Además, a cada evento de movimiento se le añaden
al inicio varios cuadros capturados antes de que empiece el movimiento, con el fin de que el
usuario pueda ver como se encontraba el escenario antes de que se diera la perturbación.
El fragmento de código que implementa la máquina de estados se muestra a continuación.
Las partes que se encargan del almacenamiento han sido removidas, debido a que lo que
interesa en este caso es la estructura de la máquina de estados. Las rutinas de almacenamiento
serán explicadas posteriormente.
49
Figura 6.1 Diagrama de la máquina de estados encargada de detectar los eventos de movimiento.
50
/* Maquina de estados para almacenar los eventos
capturados al disco */
switch(estado) {
case IDLE:
/* Si hay movimiento */
if(pdif > threshold) {
estado = MSTARTED;
gettimeofday(&start_time, &tz);
}
break;
case MSTARTED:
gettimeofday(&temp_time, &tz);
/* Almacenamiento de los cuadros */
...
/* Si se cumplió el tiempo inicial */
if((temp_time.tv_sec - start_time.tv_sec)*1000 +
(temp_time.tv_usec - temp_time.tv_usec)/1000 >= mstart_time) {
if(pdif > threshold) { // Si hay movimiento
estado = MNORMAL;
printf("Evento de movimiento iniciado...\n");
/* Lanza el hilo de almacenamiento al disco duro */
...
} else {
// Si no hay movimiento
estado = IDLE;
/* Destruye los cuadros almacenados en memoria */
...
}
}
break;
case MNORMAL:
51
/* Almacenamiento del cuadro */
...
if(pdif < threshold) { // Si no hay movimiento
estado = MSTOPED;
gettimeofday(&stop_time, &tz);
}
break;
case MSTOPED:
/* Almacenamiento del cuadro */
...
if(pdif > threshold) // Si hay movimiento
estado = MNORMAL;
else { // Si no hay movimiento
gettimeofday(&temp_time, &tz);
/* Si se cumplió el tiempo final */
if((temp_time.tv_sec - stop_time.tv_sec)*1000 +
(temp_time.tv_usec - temp_time.tv_usec)/1000 >= mstop_time) {
...
printf("Evento de movimiento finalizado.\n");
}
}
break;
}
La temporización se lleva a cabo mediante la llamada al sistema gettimeofday(), que retorna la cantidad de segundos y de microsegundos desde epoch1 . Esta llamada proporciona
una mayor precisión que time(), la cual solamente retorna segundos.
1
00:00:00 UTC del 1 de enero de 1970. Esta fecha se utiliza como referencia para el sistema de manejo de
tiempo en la mayoría de los sistemas UNIX.
52
6.4. Rutinas de escritura a disco duro
El almacenamiento a disco duro de los eventos de movimiento se lleva a cabo almacenando cada cuadro individualmente, como una imagen en formato JPEG. Esta imagen se genera
utilizando la biblioteca de manipulación de imágenes imagemagick2 . Posteriormente se utiliza el programa mencoder3 para crear un archivo de video MPEG4 a partir de estas imágenes
individuales.
Debido al retardo que trae consigo la escritura a disco duro, los cuadros capturados son
colocados en un buffer temporal en la memoria, y no son escritos inmediatamente. La escritura es llevada a cabo por un hilo de procesamiento diferente, que se ejecuta de manera
paralela al hilo de detección de movimiento. Otra razón para utilizar almacenamiento temporal en memoria es que es necesario almacenar cuadros que no necesariamente van a formar
parte de un evento. Esto se da cuando el mecanismo de detección se encuentra en el estado
MSTARTED (ver sección anterior). En este caso, es posible que se detenga el movimiento
antes de que se cumpla el tiempo inicial, lo que cancelaría el evento de movimiento. Es más
sencillo y eficiente borrar estos cuadros de la memoria que del disco duro.
La estructura de almacenamiento utilizada para esta memoria temporal consiste de una
lista enlazada. Esto permite una mayor eficiencia, dado que permite agregar y eliminar elementos del de manera dinámica. En esta lista, cada elemento contiene un puntero a un cuadro
de video, y además un puntero al siguiente elemento de la lista. En el caso del elemento final,
este puntero se encuentra indefinido (apunta a NULL). Cada celda de la lista es implementada
mediante una estructura:
/* Lista enlazada para almacenar los cuadros */
typedef struct le {
unsigned char *frame;
// Puntero al cuadro
struct le *next;
// Puntero a la siguiente celda
} list;
Como se mencionó en la sección anterior, en cada evento de movimiento se almacenan,
además de los cuadros propios del evento, varios cuadros anteriores al inicio del movimiento.
Estos cuadros se toman de un buffer temporal y son colocados en la lista enlazada la primera
vez que se entra al estado MSTARTED.
2
3
http://www.imagemagick.org/
http://www.mplayerhq.hu/
53
/* Si no existe la lista enlazada */
if(start == NULL) {
/* Crea la primera celda de la lista */
start = (list *)malloc(sizeof(list));
start->frame = (char *)malloc(framesize);
memcpy(start->frame, nomotion + nmc * framesize, framesize);
start->next = NULL;
current = start;
/* Copia los cuadros previos a la lista */
int i;
for(i = nmc + 1; i < nmsize + nmc; i++) {
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
if(i >= nmsize)
memcpy(temp->frame, nomotion + (i - nmsize) * framesize, framesize);
else
memcpy(temp->frame, nomotion + i * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
}
}
La variable start es un puntero al primer elemento de la lista enlazada. Al inicializar este puntero se crea la primera celda, en la cual se almacena el cuadro más antiguo del buffer
nomotion. Este buffer es un arreglo circular de tamaño nmsize en el cual se almacenan los cuadros que están siendo procesados. El último cuadro almacenado se encuentra en la posición
nmc − 1, por lo que el cuadro más antiguo se encuentra en la posición nmc.
Posteriormente se almacenan el resto de los cuadros. Para cada uno de ellos se crea una
celda nueva utilizando la variable temporal temp. Posteriormente se hace que el puntero
current, el cual indicaba la última celda apunte hacia este nuevo elemento.
La rutina permanecerá en el estado MSTARTED hasta que se cumpla el tiempo inicial.
Durante este periodo, todas las imágenes capturadas deben agregarse a la lista:
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
54
memcpy(temp->frame, main_buf + fc * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
Al igual que antes, se crea la nueva celda utilizando el puntero temp, y luego se mueve current hacia este nuevo elemento. Este mismo procedimiento se utiliza en los estados
MNORMAL y MSTOPED para agregar los nuevos cuadros capturados a las lista.
Si se llega al estado MNORMAL, hay un evento de movimiento real, lo que significa que
los cuadros almacenados en la lista deben ser escritos a disco duro. Para esto se crea un nuevo
hilo de procesamiento que se encargará de realizar la escritura. Este hilo ejecuta la función
save_frames(), que recibe un puntero al primer elemento de la lista que se desea salvar como
argumento.
estado = MNORMAL;
/* Lanza el hilo de almacenamiento al disco duro */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&save_thread, &attr, save_frames, (void *)start);
pthread_attr_destroy(&attr);
La función save_frames() crea un directorio para almacenar las imágenes, el cual tiene
como nombre la fecha y la hora a la cual inicia el evento. Posteriormente la rutina recorre la
lista, empezando por el primer elemento, y guarda cada uno de los cuadros en el disco duro.
Una vez que un cuadro es almacenado, la memoria donde se encontraba es liberada y la celda
es destruida.
void *save_frames(void *list_start) {
char timestamp[30];
char filename[50];
list *start = (list *)list_start;
list *temp = NULL;
55
int i = 0;
/* Determina la hora y la fecha actual */
time_t tptr;
tptr = time(NULL);
struct tm *tsptr;
tsptr = localtime(&tptr);
strftime(timestamp, 30, "%Y%m%d-%H%M%S", tsptr);
/* Crea el directorio para almacenar las imágenes */
mkdir(timestamp, 0777);
/* Bloquea el mutex */
pthread_mutex_lock(&mutexsave);
while(1) {
/* Guarda el cuadro */
snprintf(filename, 50, "%s/%04d.jpg", timestamp, i);
save_image(start->frame, filename, video_buf.width,
video_buf.height, "BGR");
/* Si llegó al final de la lista */
while(start->next == NULL) {
/* Si no se van a agregar cuadros a la lista */
if(save_end) {
/* libera la memoria */
free(start->frame);
free(start);
/* Desbloquea el mutex */
pthread_mutex_unlock(&mutexsave);
/* Genera el archivo de video con mencoder */
printf("Generando archivo de video...\n");
char mencoder_line[150];
snprintf(mencoder_line, 150,
"mencoder \"mf://%s/*.jpg\" -mf fps=%f:w=%d:h=%d:type=jpeg
-ovc divx4 -o %s/%s.avi", timestamp, fps, video_buf.width,
video_buf.height, timestamp, timestamp);
system(mencoder_line);
56
/* Finaliza el hilo de ejecución */
pthread_exit(NULL);
}
/* Espera que se añada otro cuadro a la lista */
usleep(100);
continue;
}
/* mueve start y libera la memoria */
temp = start;
start = temp->next;
free(temp->frame);
free(temp);
i++;
}
}
Una de las cosas más importantes de esta función es la utilización de una variable mutex,
con el fin de controlar el acceso a los recursos compartidos. En este caso, el mutex se utiliza
para evitar que dos hilos de procesamiento guarden imágenes a la vez. Esto fue necesario
debido a que la biblioteca imagemagick presentó problemas cuando se daba esta situación. Al
utilizar el mutex, el primer hilo que se ejecute coloca un bloqueo sobre la variable utilizando
la función pthread_mutex_lock(). Si otro hilo trata de ejecutar esta función sobre el mutex
bloqueado será detenida hasta que el bloqueo sea eliminado. Una vez que la función termina
de escribir las imágenes, esta elimina el bloqueo con la función pthread_mutex_unlock(),
permitiendo que los otros hilos puedan llevar a cabo la escritura.
La escritura de las imágenes es llevada a cabo por la función save_image(). Como se
mencionó anteriormente, se utiliza la biblioteca imagemagick para llevar a cabo la creación y
la escritura de la imagen.
/* Crea una imagen y la salva al disco duro */
void save_image(unsigned char *data, char *filename, int width,
int height, char *colorspace) {
ExceptionInfo exception;
Image *image;
ImageInfo *image_info;
57
...
InitializeMagick((char *)NULL);
GetExceptionInfo(&exception);
image_info = CloneImageInfo((ImageInfo *) NULL);
image = ConstituteImage(width, height, colorspace,
CharPixel, data, &exception);
(void)strcpy(image->filename, filename);
WriteImage(image_info, image);
DestroyImage(image);
DestroyExceptionInfo(&exception);
DestroyImageInfo(image_info);
DestroyMagick();
}
CAPÍTULO 7: Rutinas de comunicación y despliegue de video en el cliente
7.1. Envío de mensajes al servidor
El envío de mensajes al servidor se lleva a cabo utilizando el protocolo TCP. Una vez
iniciado el programa cliente, este llama a la función open_conn(), la cual se encarga de crear
el socket y realizar la conexión al servidor, dejando al sistema listo para el envío de los
mensajes.
int open_conn(char *addr, int port) {
struct hostent *hp;
struct sockaddr_in server;
char *buf_ptr, in_buf[BUF_SIZE];
int bytes_left;
int n_read;
/* Creación del socket */
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "No se puede abrir el socket\n");
return -1;
}
/* Información de la conexión */
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(port);
if((hp = gethostbyname(addr)) == NULL) {
fprintf(stderr, "No se puede resolver el nombre del servidor\n");
return -1;
}
memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length);
/* Conexión con el servidor */
if(connect(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "No se puede establecer la conexión
58
59
con el servidor\n");
return -1;
}
...
return 0;
}
El código es similar al estudiado en el capítulo 5, sin embargo presenta algunas diferencias importantes. En primer lugar, es necesario crear una estructura sockaddr_in con la información del servidor. La dirección del servidor se obtiene utilizando la función gethostbyname(),
la cual recurre a las herramientas de resolución de nombres del sistema operativo.
Otra diferencia con respecto al servidor es que utilizan las llamadas bind() o listen(),
debido a que no es necesario esperar por conexiones. El cliente utiliza la llamada connect()
para establecer la conexión con el servidor. Una vez establecido el enlace, es posible enviar
o recibir mensajes, utilizando read() y write().
El envío de los mensajes es llevado a cabo por la función send_tcp_msg():
int send_tcp_msg(char *msg, void *retdata, int size) {
char *buf_ptr, in_buf[BUF_SIZE], out_buf[BUF_SIZE];
int bytes_left, n_read;
/* Envía el mensaje */
strcpy(out_buf, msg);
write(sd, out_buf, BUF_SIZE);
/* Recibe los datos, si hay algo que recibir */
if(retdata != NULL) {
bytes_left = size;
printf("bytes_left = %d\n", bytes_left);
buf_ptr = retdata;
while((n_read = read(sd, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
60
}
/* Recepción del mensaje de confirmación */
bytes_left = BUF_SIZE;
buf_ptr = in_buf;
while((n_read = read(sd, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
/* Verifica que el comando haya sido
ejecutado satisfactoriamente */
if(strcmp(in_buf, "OK") == 0)
return 0;
else
return -1;
}
Esta función envía el mensaje al servidor. Si el servidor debe retornar algo (como en el
caso de VIDEOPROP), los datos son leídos y almacenados en la variable retdata. Por último,
la función espera que el servidor envíe un mensaje de confirmación y con base en el contenido
de este mensaje determina si el comando fue ejecutado de manera satisfactoria.
Por ejemplo, a continuación se muestra el código necesario para enviar los mensajes
CAPTURE y VIDEOPROP:
/* Solicitud de inicio de captura */
if((msg_status = send_tcp_msg("CAPTURE", NULL, 0)) != 0) {
fprintf(stderr, "Problemas solicitando el inicio de la captura\n");
exit(EXIT_FAILURE);
}
/* Petición de envío de las propiedades de video */
if((msg_status = send_tcp_msg("VIDEOPROP", (void *)&prop,
sizeof(struct video_prop))) != 0) {
fprintf(stderr, "Error obteniendo propiedades de video\n");
exit(EXIT_FAILURE);
}
61
En el caso de CAPTURE, el comando no retornará datos, por lo tanto se envía NULL
como segundo argumento. En VIDEOPROP, el comando retornará una estructura, del tipo
video_prop, por lo tanto se le pasa a la función la variable prop para que almacene los datos,
así como el tamaño de esta variable.
7.2. Recepción de los cuadros de video
La recepción del video es realizada por un hilo de procesamiento diferente, el cual es
creado después de solicitar al servidor el envío de los datos.
/* Solicita al servidor el envío de los cuadros */
if((msg_status = send_tcp_msg("STREAM", NULL, 0)) != 0) {
fprintf(stderr, "Error obteniendo el frame\n");
exit(EXIT_FAILURE);
}
/* Datos de la conexión */
strmdata.addr = "localhost";
strmdata.port = 24001;
strmdata.width = prop.width;
strmdata.height = prop.height;
/* Crea el hilo de recepción de video */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&stream_thread, &attr, start_vid_stream,
(void *)&strmdata);
printf("Stream Iniciado\n");
Este hilo ejecuta la función start_vid_stream(), la cual recibe como argumento la variable
strmdata, una estructura del tipo stream_data:
/* Información de la conexión UDP */
struct stream_data {
char *addr; // Dirección del servidor
int port;
// Puerto de conexión
int width;
// Ancho del cuadro
62
int height;
};
// Altura del cuadro
Una vez dentro de start_vid_stream(), primer paso para leer estos datos consiste en crear
el socket correspondiente.
/* Creación del socket UDP */
if((sdu = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
fprintf(stderr, "Error creando el socket UDP\n");
exit(-1);
}
/* Información del servidor */
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(sdata->port);
if((hp = gethostbyname(sdata->addr)) == NULL) {
fprintf(stderr, "Error resolviendo la dirección del servidor\n");
exit(-1);
}
memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length);
/* Información del cliente */
memset((char *)&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(0);
client.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sdu, (struct sockaddr *)&client, sizeof(client)) == -1) {
fprintf(stderr, "Error enlazando el socket\n");
exit(-1);
}
63
Una vez creado el socket, el cliente envía un paquete al servidor indicándole que está listo
para recibir los datos:
server_len = sizeof(server);
strcpy(buf, "telescreen");
if(sendto(sdu, buf, BUF_SIZE, 0, (struct sockaddr *)&server,
server_len) == -1) {
fprintf(stderr, "Problemas enviando datagrama al servidor\n");
exit(-1);
}
El siguiente paso consiste en iniciar un ciclo en el cual se leerán los datos enviados por el
servidor. Tal como se mencionó en el capítulo 5, cada cuadro debe fragmentarse en segmentos
de 4096 bytes debido a las limitaciones del paquete UDP. El cliente debe leer cada uno de
estos paquetes y reconstruir el cuadro para su despliegue:
/* Número de paquetes */
int n_packets = framesize / 4096;
/* Cantidad de bytes sobrantes */
int leftover = framesize % 4096;
int pc = 0;
int p_size;
char buf2[4096];
/* Reserva memoria para los cuadros */
temp_frame = malloc(framesize);
frame = malloc(framesize);
/* Recibe los paquetes UDP con el cuadro */
while(1) {
p_size = recvfrom(sdu, buf2, 4096, 0, (struct sockaddr *)&server,
&server_len);
if(p_size < 0) { // Error en la recepción
fprintf(stderr, "Error recibiendo el paquete %d\n", pc);
continue;
} else if(p_size == sizeof(int)) { // Paquete de sincronización
64
if(pc == n_packets + 1) {
memcpy(frame, temp_frame, framesize);
} else {
fprintf(stderr, "Imagen con información incorrecta\n");
}
pc = 0;
} else if(p_size == 4096) { // Paquete de datos
if(pc == n_packets)
memcpy(temp_frame + pc*4096, buf2, leftover);
else
memcpy(temp_frame + pc*4096, buf2, 4096);
pc++;
}
}
La rutina lee los paquetes que recibe a través del socket utilizando la llamada recvfrom().
Si el tamaño del paquete es incorrecto lo descarta y lee el paquete siguiente. Cuando recibe
el paquete de sincronización verifica que número de paquetes recibidos sea correcto, o sea,
que la imagen este completa y la copia en la posición apuntada por la variable global frame.
Esta posición es leída por las rutinas de la interfaz gráfica para llevar a cabo el despliegue.
Si el número de paquetes es incorrecta, la imagen completa se descarta y se continúa con la
siguiente.
7.3. Despliegue de la imagen
Como se explicó en el capítulo 3, se utilizó la biblioteca GTK 2.4 para crear la interfaz
gráfica de usuario, así como para desplegar los cuadros de video recibidos del cliente.
Las imágenes se muestran utilizando el widget image. Este widget permite desplegar una
imagen dentro de una ventana, ya sea leída desde un archivo o contenida en un objeto GdkPixbuf. Estos objetos están diseñados para contener y administrar imágenes, y pueden ser
inicializados a partir de un arreglo de bytes que contenga los pixeles de la imagen.
En el cliente, la función refresh() se encarga de crear el GdkPixbuf a partir de la información contenida en frame. Posteriormente crea un widget image a partir de este objeto y lo
despliega.
65
/* Despliega la imagen recibida */
void refresh(void) {
if(frame != NULL) {
/* Crea el objeto GdkPixbuf */
video_pixbuf = gdk_pixbuf_new_from_data((const guchar *)frame,
GDK_COLORSPACE_RGB, FALSE, 8, prop.width, prop.height,
prop.width * 3, NULL, NULL);
/* Inicializa image a partir de video_pixbuf */
gtk_image_set_from_pixbuf(image, video_pixbuf);
/* Despliega image */
gtk_widget_show(image);
}
}
Esta rutina es llamada periódicamente por el ciclo de manejo de la interfaz gráfica. Esto
se logra registrándola mediante la función g_timeout_add():
g_timeout_add(10, refresh, NULL);
Esto le indica al ciclo principal de GTK que ejecute la rutina cada 10 milisegundos.
CAPÍTULO 8: Resultados
8.1. Captura de video
Se llevaron a cabo pruebas para determinar la velocidad de captura (tasa de cuadros por
segundo) máxima de la aplicación. Estas pruebas se realizaron utilizando dos dispositivos:
1. Cámara USB marca Genius, modelo VideoCAM Express V2, controlador V4L spca50x.
2. Tarjeta de adquisición de video marca Avermedia, modelo TVPhone 98, controlador
V4L bt848.
Los resultados obtenidos en las pruebas de velocidad, al realizar la captura al buffer principal de la aplicación se muestran en el cuadro 8.1
Cuadro 8.1 Cuadros por segundo capturados.
Dispositivo
1
2
Tasa (cuadros por segundo)
8,89
29.97
En ambos casos se alcanzó la tasa de captura máxima permitida por el dispositivo. La
cámara USB es una cámara de baja calidad, por lo tanto no es posible capturar a una velocidad
alta. Por otro lado, la tarjeta de captura permite una tasa máxima que va de acuerdo a los
estándares de video profesional (alrededor de 30 cuadros por segundo).
8.2. Reducción de ruido
Como se mencionó en la sección 6.2, se aplicó un filtro de media para atenuar el ruido
presente en las imágenes capturadas y reducir su efecto en las rutinas de detección de movimiento. En la figura 8.1 se muestra una imagen capturada con el dispositivo 1 antes y después
de aplicar el filtro de media para la reducción de ruido.
En los recuadros se muestran dos casos particulares de pixeles con ruido. Puede verse
como en ambos casos, este ruido ha sido atenuado de forma significativa, lo que permite
eliminarlo al aplicar el umbral de sensibilidad durante de determinación de la cantidad de
66
67
(a) Antes de aplicar el filtro
(b) Después de aplicar el filtro
Figura 8.1 Aplicación del filtro de media para reducir el ruido en las imágenes
capturadas
pixeles con cambios. También puede verse como la imagen ha sido suavizada al aplicar el
filtro. Esto es una característica que se presenta siempre que se aplica este tipo de filtros,
debido a que se atenúan las transiciones fuertes (como es el caso de los bordes de los objetos)
presentes en la imagen. Este suavizado no afecta el desempeño de la rutina de detección de
movimiento, debido a que el efecto se presenta por igual en todas las imágenes y no altera la
comparación.
8.3. Determinación de eventos de movimiento
Otra de las partes de la aplicación que fue sometida a pruebas fue la rutina de determinación de eventos de movimiento. Estas pruebas se llevaron a cabo en distintos escenarios,
con distintas condiciones de iluminación para evaluar la respuesta del sistema en un conjunto
amplio de situaciones. En todas las pruebas el objeto en movimiento fue una persona, debido
a que la aplicación principal de este programa es la vigilancia.
68
En todos los casos, fue posible detectar el movimiento y determinar de forma adecuada
la presencia del evento, una vez ajustados los distintos parámetros del sistema:
Umbral de sensibilidad
Umbral de evento
Tiempo inicial
Tiempo final
Al alterar estos valores es posible ajustar el sistema para que responda satisfactoriamente
en distintas situaciones, siempre que las condiciones de luz permitan que la cámara capture
una imagen nítida.
En la figura 8.2 se muestra un gráfico del porcentaje de pixeles con diferencia por cuadro
analizado, durante un evento de movimiento. Este evento consistió de una persona caminando a velocidad normal frente a la cámara. La distancia entre la persona y la cámara fue de
aproximadamente 3 m y el escenario presentaba una iluminación normal (luz de día). Los
parámetros del sistema de detección durante este evento se muestran en el cuadro 8.2.
Cuadro 8.2 Parámetros del sistema de detección de eventos de movimiento
utilizados durante la prueba
Parámetro
Umbral de sensibilidad
Umbral de evento
Tiempo inicial
Tiempo final
Valor
8
5%
50 ms
5s
El evento que se registró durante la prueba tuvo una duración de 8 segundos, incluyendo
el tiempo inicial y el final, así como los cuadros capturados antes que iniciara el evento. El
movimiento real se mantiene por aproximadamente 1,5 s.
Otra prueba que se realizó fue la respuesta del sistema ante un cambio rápido de iluminación. La figura 8.3 muestra la respuesta del sistema al encender y apagar la luz de una
habitación. Los parámetros utilizados durante esta prueba son los mismos que se utilizaron
en la prueba anterior (cuadro 8.2).
En este caso, el sistema no consideró estas diferencias como un evento de movimiento, lo
que muestra la importancia del tiempo inicial para eliminar las falsas alarmas que se pueden
producir por alteraciones de esta clase.
69
35
r
rrr
r
Porcentaje de pixeles con diferencias
30
25
r
r
20
15
r r
10
r
Umbral de evento
5
r
r r
rr
0 rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
0
50
100
150
200
Cuadro analizado
250
Figura 8.2 Porcentaje de pixeles con diferencias durante un evento de movimiento
70
60
r
Porcentaje de pixeles con diferencias
r
50
40
30
r
r
20
10
Umbral de evento
r
r
0 rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
0
50
100
150
200
250
Cuadro analizado
Figura 8.3 Respuesta del sistema ante un cambio rápido en la iluminación
CAPÍTULO 9: Conclusiones y Recomendaciones
9.1. Conclusiones
La utilización de múltiples hilos de ejecución (threads) permite que cada uno de los
procesos se dedique a una tarea específica. Cada hilo trabaja de forma paralela a los
otros, sin ser afectado significativamente por los demás, lo que trae consigo un mejor
desempeño y facilita el desarrollo de los sistemas. La utilización de múltiples hilos de
procesamiento permite además explotar al máximo sistemas de multiprocesamiento, en
los cuales es posible obtener un procesamiento paralelo real.
Se logró capturar video a la máxima tasa de cuadros por segundo al utilizar todos los
cuadros de la memoria del dispositivo de video. Esta técnica permite capturar uno de
los cuadros al mismo tiempo que los otros están siendo preparados, lo cual aumenta
significativamente la eficiencia en la captura.
Debido a la gran cantidad de ruido presente en las imágenes adquiridas, principalmente en el caso de las cámaras web fue necesario aplicar un filtro de media con el fin de
reducir esta fuente de error. El filtro funcionó adecuadamente, reduciendo significativamente el ruido aleatorio presente en los cuadros capturados.
La modificación de los valores de umbral durante el proceso de detección de movimiento permite ajustar la sensibilidad del sistema de una manera precisa, reduciendo
la cantidad de eventos falsos, y permitiendo adaptar el sistema a una gran cantidad de
escenarios.
Se obtuvieron buenos resultados al utilizar el algoritmo de determinación de eventos de
movimiento. La variación de los parámetros de tiempo permitió eliminar la influencia
de los cambios repentinos en la intensidad de luz y de otros fenómenos que podían
ocasionar el registro de un evento falso.
La utilización de un hilo de procesamiento independiente para realizar la escritura de
los cuadros capturados al disco duro redujo la influencia del retraso en la escritura a
este medio sobre el desempeño del sistema.
71
72
Se obtuvieron buenos resultados al utilizar el protocolo UDP para la transmisión hacia
el cliente de los cuadros de video capturados. Aunque hay una mayor pérdida de paquetes, este protocolo brinda una latencia menor, lo cual es fundamental en una aplicación
de transmisión en tiempo real.
La interfaz gráfica de usuario implementada permite visualizar en tiempo real el video
adquirido por el sistema. Esta interfaz puede ser modificada para incorporar funciones
adicionales y extender su capacidad.
Esta aplicación constituye una plataforma de desarrollo que puede ser utilizada como
componente de otros proyectos, debido a que sus características permiten adaptarla a
una gran cantidad de aplicaciones.
9.2. Recomendaciones
Es necesario realizar algún tipo de compresión en los datos que son enviados al cliente
sobre la red. En la actualidad los cuadros son enviados tal como son capturados, lo
cual hace que el volumen de datos sea muy grande. Esto hace que sea difícil utilizar
la característica de video en tiempo real cuando la red es lenta o tiene mucho tráfico.
Entre los métodos de compresión que se podrían utilizar se encuentran el sistema de
compresión de video MPEG4 o los algoritmos de compresión LZO.
Una forma de mejorar el desempeño durante el registro de los eventos de movimiento
sería crear el archivo de video que contiene el evento en la memoria utilizando funciones de alguna biblioteca de codificación, en vez de escribir cada uno de los cuadros
capturados y generar el archivo de video al final.
El diseño del programa podría ser modificado para que sea posible la inclusión de
otros filtros o rutinas de procesamiento, no sólo al realizar la detección de movimiento
sino también durante la captura del video. Esto permitiría adaptar el sistema para otras
aplicaciones, tales como rastreo de objetos o procesos de control de calidad.
BIBLIOGRAFÍA
[1] Baxes, G. «Digital Image Processing. Principles and Applications», primera edición,
John Wiley & Sons, Estados Unidos, 1994.
[2] Jähne, B. «Digital Image Processing. Concepts, Algorithms and Scientific Applications.», cuarta edición, Springer, Alemania, 1997.
[3] Johnson, M. «Linux Information Sheet», The Linux Documentation Project.
http://www.tldp.org/HOWTO/INFO-SHEET.html, 1998.
[4] Kernighan, B. y Ritchie, D. «The C Programming Language», segunda edición. Prentice Hall, Estados Unidos, 1988.
[5] Ondrik, M. et al. «Motion Detection Tecnologies», Canberra,
http://www.canberra.com/pdf/Literature/motion.pdf, 1998.
[6] Russ, J. «The Image Processing Handbook», CRC Press, Estados Unidos, 1992.
[7] Wall, M. et al. «Linux Programming Unleashed», primera edición, Sams, Estados
Unidos, 1999.
[8] León-García, A. y Widjaja, I. «Redes de Comunicación. Conceptos Fundamentales
y Arquitecturas Básicas», McGraw-Hill, España, 2002.
[9] Wikipedia. «Kernel (Computer Science)», Wikipedia,
http://en.wikipedia.org/wiki/kernel_(computer_science), julio, 2005.
[10] Wikipedia. «POSIX», Wikipedia,
http://en.wikipedia.org/wiki/POSIX, julio, 2005.
[11] — «Video4Linux API». http://linux.bytesex.org/v4l2/API.html, marzo, 2005.
[12] — «POSIX Threads Programming», http://www.llnl.gov/computing/tutorials/pthreads/,
junio, 2005.
73
APÉNDICE A: Código fuente de la aplicación servidor
global.h: Declaraciones y definiciones globales
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* global.h: Declaraciones y Definiciones globales
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
#include
<linux/videodev.h>
<stdio.h>
<pthread.h>
<assert.h>
<sys/types.h>
<sys/stat.h>
#ifndef GLOBAL_H
#define GLOBAL_H
/* Variables Globales */
unsigned char *main_buf; // Buffer principal
unsigned char *map; // Mapeo de la memoria del dispositivo
struct video_capability cap; // Caracaterísticas del dispositivo de video
struct video_mbuf buf_info; // Información de la memoria de video
struct video_mmap video_buf; // Características de los cuadros de la memoria de video
int vfd; // Descriptor de archivo del dispositivo de video
int capture; // Estado del hilo de captura
int stream; // Estado del hilo de envío de video
int motion; // Estado del hilo de detección de movimiento
int framecounter; // Contador de cuadro capturado
int framesize; // Tamaño del cuadro (3bytes/pixel)
float fps; // Cuadros por segundo
#endif /* GLOBAL_H */
main.c: Rutina principal del servidor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Telescreen v0.3
Programa de captura de video y detección de movimiento utilizando V4L
main.c: Implementación de la rutina principal
Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
74
75
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "global.h"
#include "video.h"
#include "sockets.h"
int main(int argc, char **argv) {
/* Inicialización de variables globales */
main_buf = NULL;
map = NULL;
vfd = -1;
capture = 0;
stream = 0;
motion = 0;
printf("Telescreen v0.3\n");
/* Inicializa el dispositivo de video */
vfd = init_video(VIDEO_DEV);
/* Verifica la existencia del dispositivo */
assert(vfd != -1);
/* Define el canal */
set_chan(vfd, CHAN);
/* Define propiedades de captura */
set_video_prop(cap.maxwidth, cap.maxheight, VIDEO_PALETTE_RGB24);
/* Inicia el servidor */
tcpsocket();
}
video.h: Declaraciones y definiciones para las rutinas de inicialización y
captura de video
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* video.h: Declaraciones y definiciones para las rutinas de inicialización y
*
captura de video
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
#include
#include
<stdlib.h>
<string.h>
<fcntl.h>
<sys/time.h>
<sys/ioctl.h>
<sys/mman.h>
"global.h"
76
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ifndef VIDEO_H
#define VIDEO_H
/* Definiciones */
#define VIDEO_DEV "/dev/video0"
#define CHAN 0
#define BUF_FRAME_NUM 50
// Dispositivo de video
// Canal
// Número de cuadros del buffer principal
/* Declaraciones de las funciones */
/* init_video: Inicialización del dispositivo de video */
int init_video(char *video_dev);
/* set_chan: Definición del canal de captura */
int set_chan(int fd, int chan);
/* set_video_prop: Definición de las propiedades de captura */
int set_video_prop(int width, int height, int format);
/* capture_loop: Ciclo de captura de video */
void *capture_loop(void *null);
#endif /* VIDEO_H */
video.c: Implementación de las rutinas de inicialización y captura de video
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* video.c: Implementación de las rutinas de inicialización y captura de video
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "video.h"
/* init_video: Inicialización del dispositivo de video */
int init_video(char *video_dev) {
int fd;
/* Apertura del dispositivo */
if((fd = open(video_dev, O_RDWR)) == -1) {
fprintf(stderr, "Error abriendo el dispositivo %s\n", video_dev);
return -1;
}
/* Obtención de las capacidades de captura */
if(ioctl(fd, VIDIOCGCAP, &cap) == -1) {
fprintf(stderr, "Error al realizar VIDIOCGCAP\n");
return -1;
}
/* Información de los Buffers */
if((ioctl(fd, VIDIOCGMBUF, &buf_info)) == -1) {
printf("Error adquiriendo los buffers\n");
return -1;
}
77
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
printf("Tamanno del buffer del dispositivo: %d\n", buf_info.size);
map = mmap(0, buf_info.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(map == NULL) {
printf("Error en el mapeo de la memoria\n");
return -1;
}
return fd;
}
/* set_chan: Definición del canal de captura */
int set_chan(int fd, int chan) {
if(ioctl(fd, VIDIOCSCHAN, chan) == -1)
return -1;
else
return 0;
}
/* set_video_prop: Definición de las propiedades de captura */
int set_video_prop(int width, int height, int format) {
video_buf.width = width;
video_buf.height = height;
video_buf.format = format;
return 0;
}
/* capture_loop: Ciclo de captura de video */
void *capture_loop(void *null) {
struct timeval time1, time2;
struct timezone tz;
int i = 0;
framesize = video_buf.width * video_buf.height * 3;
/* Reserva de memoria para el buffer */
printf("Tamanno del buffer principal: %d\n", framesize * BUF_FRAME_NUM);
main_buf = (char *)malloc(BUF_FRAME_NUM * framesize);
framecounter = 0;
gettimeofday(&time1, &tz);
/* Ciclo de captura - Usando todos los buffers */
/* Prepara todos los cuadros para captura */
for(i = 0; i < buf_info.frames; i++) {
video_buf.frame = i;
ioctl(vfd, VIDIOCMCAPTURE, &video_buf);
}
i = 0;
/* Para calcular la tasa de captura (fps) */
gettimeofday(&time1, &tz);
/* Loop infinito de captura */
while(capture) {
/* Si llegó al fin de la memoria del dispositivo */
if(i == buf_info.frames)
i = 0;
video_buf.frame = i;
ioctl(vfd, VIDIOCSYNC, &video_buf);
ioctl(vfd, VIDIOCMCAPTURE, &video_buf);
// Sincroniza
// Prepara para la siguiente lectura
/* Si llegó al final del buffer vuelve a empezar desde el incio */
if(framecounter == BUF_FRAME_NUM) {
framecounter = 0;
gettimeofday(&time2, &tz);
/* Calcula la tasa de captura */
fps = BUF_FRAME_NUM / ((float)(time2.tv_sec-time1.tv_sec) + (float)(time2.tv_usec-time1.tv_usec) / 1000000.0);
printf("Tasa de captura: %f fps\n", fps);
gettimeofday(&time1, &tz);
78
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
}
/* Copia el cuadro al buffer principal */
memcpy(main_buf + framecounter * framesize, map + buf_info.offsets[i], framesize);
/* Incrementa ambos contadores */
i++;
framecounter++;
}
pthread_exit(NULL);
}
motion.h: Declaraciones y definiciones para las rutinas de detección de
movimiento y escritura a disco duro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* motion.h: Declaraciones y definiciones para las rutinas de detección de movimiento y
*
escritura a disco duro
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
#include
"global.h"
<string.h>
<stdlib.h>
<unistd.h>
<sys/time.h>
<magick/api.h>
#ifndef MOTION_H
#define MOTION_H
/* Valores de umbral
Estos valores varían según las condiciones del escenario */
#define THRESHOLD 5 // Umbral de evento
#define ITH 8
// Umbral de sensibilidad
#define NMSIZE 20
/* Estados de captura */
#define
#define
#define
#define
IDLE 0 // No se ha detecctado movimiento
MSTARTED 1 // Tiempo inicial
MNORMAL 2 // Evento de movimiento
MSTOPED 3 // Tiempo final
/* Lista enlazada para almacenar los cuadros */
typedef struct le {
unsigned char *frame;
// Puntero al cuadro
struct le *next; // Puntero a la siguiente celda
} list;
/* Declaraciones de las funciones */
/* motion_detect: Rutina de detección de movimiento y determinación de eventos */
79
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
void *motion_detect(void *null);
/* mean_filter: Filtro de media para la reducción de ruido */
void mean_filter(unsigned char *frame, int x, int y);
/* grayscale: Conversión de la imagen a escala de grises (intensidad) */
void grayscale(unsigned char *frame, int size, char *new_frame);
/* save_frames: Almacenamiento de las imágenes a disco duro y generación del archivo de video */
void *save_frames(void *list_start);
/* save_image: Almacenamiento de una imágen a disco duro utilizando ImageMagick */
void save_image(unsigned char *data, char *filename, int width, int height, char *colorspace);
#endif /* MOTION_H */
motion.c: Implementación de las rutinas de detección de movimiento y
escritura a disco duro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* motion.c: Implementación las rutinas de detección de movimiento y
*
escritura a disco duro
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "motion.h"
/* Variables globales */
static int nmsize;
static pthread_mutex_t mutexsave;
static int save_end;
void *motion_detect(void *null) {
int fc = -1, prevfc = -1;
unsigned char *dif, *curr, *prev;
int ndif;
int save = 1;
float threshold = THRESHOLD;
int ith = ITH;
float pdif;
unsigned char *nomotion;
nmsize = NMSIZE;
int nmc = 0;
int estado = IDLE;
int mstart_time, mstop_time, seqn;
struct timeval start_time, temp_time, stop_time;
struct timezone tz;
pthread_t save_thread;
pthread_attr_t attr;
80
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
list *start = NULL, *current = NULL, *temp = NULL;
save_end = 0;
seqn = nmsize;
mstart_time = 50;
mstop_time = 5000;
dif = malloc(framesize / 3);
curr = malloc(framesize / 3);
prev = malloc(framesize / 3);
nomotion = malloc(nmsize * framesize);
FILE *stats = fopen("stats", "w");
int sframe = 0;
FILE *estados = fopen("estados", "w");
pthread_mutex_init(&mutexsave, NULL);
while(motion) {
/* Toma el cuadro anterior al que está siendo caoturado */
fc = framecounter - 1;
if(fc < 0)
fc = 0;
if(fc == prevfc)
continue;
else
prevfc = fc;
ndif = 0;
memcpy(nomotion + nmc * framesize, main_buf + fc * framesize, framesize);
nmc++;
if(nmc == nmsize)
nmc = 0;
/* Convierte el cuadro actual y el anterior a escala de grises */
grayscale(main_buf + fc * framesize, framesize, curr);
grayscale(main_buf + (fc?(fc - 1):0) * framesize, framesize, prev);
//DEBUG
if(save) {
save_image(curr, "current_gray.jpg", video_buf.width, video_buf.height, "I");
save_image(prev, "previous_gray.jpg", video_buf.width, video_buf.height, "I");
}
/* Aplica filtro a ambos cuadros */
mean_filter(curr, video_buf.width, video_buf.height);
mean_filter(prev, video_buf.width, video_buf.height);
//DEBUG
if(save) {
save_image(curr, "current.jpg", video_buf.width, video_buf.height, "I");
save_image(prev, "previous.jpg", video_buf.width, video_buf.height, "I");
save = 0;
}
/* Resta ambos cuadros y almacena la diferencia en dif */
int i;
for(i = 0; i < framesize / 3; i++) {
dif[i] = abs(curr[i] - prev[i]);
if(dif[i] > ith)
ndif++;
}
/* Calcula el porcentaje de pixeles diferentes */
pdif = (float)ndif/((float)framesize/3)*100;
fprintf(stats, "%d\t%f\n", sframe, pdif);
sframe++;
/* Maquina de estados para almacenar los eventos capturados al disco */
switch(estado) {
case IDLE:
fprintf(estados, "Estado: IDLE, fc = %d\n", fc);
if(pdif > threshold) {
estado = MSTARTED;
gettimeofday(&start_time, &tz);
}
81
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
break;
case MSTARTED:
fprintf(estados, "Estado: MSTARTED, fc = %d, ", fc);
gettimeofday(&temp_time, &tz);
/* Almacenamiento de los cuadros */
if(start == NULL) { // Si no existe la lista enlazada
fprintf(estados, "start = NULL");
/* Crea la primera celda de la lista */
start = (list *)malloc(sizeof(list));
start->frame = (char *)malloc(framesize);
memcpy(start->frame, nomotion + nmc * framesize, framesize);
start->next = NULL;
current = start;
/* Copia los cuadros previos a la lista */
int i;
for(i = nmc + 1; i < nmsize + nmc; i++) {
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
if(i >= nmsize)
memcpy(temp->frame, nomotion + (i - nmsize) * framesize, framesize);
else
memcpy(temp->frame, nomotion + i * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
}
} else {
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
memcpy(temp->frame, main_buf + fc * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
}
/* Si se cumplió el tiempo inicial */
if((temp_time.tv_sec - start_time.tv_sec)*1000 + (temp_time.tv_usec - temp_time.tv_usec)/1000 >= mstart_time) {
if(pdif > threshold) { // Si hay movimiento
estado = MNORMAL;
printf("Evento de movimiento iniciado...\n");
/* Lanza el hilo de almacenamiento al disco duro */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&save_thread, &attr, save_frames, (void *)start);
pthread_attr_destroy(&attr);
} else {
// Si no hay movimiento
estado = IDLE;
seqn = nmsize;
/* Destruye los cuadros almacenados en memoria */
while(start != NULL) {
temp = start;
start = temp->next;
free(temp->frame);
free(temp);
}
temp = NULL;
current = NULL;
}
}
fprintf(estados, "\n");
break;
case MNORMAL:
fprintf(estados, "Estado: MNORMAL, fc = %d\n", fc);
/* Almacenamiento del cuadro */
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
memcpy(temp->frame, main_buf + fc * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
if(pdif < threshold) {
// Si no hay movimiento
estado = MSTOPED;
gettimeofday(&stop_time, &tz);
}
break;
case MSTOPED:
fprintf(estados, "Estado: MSTOPED, fc = %d\n", fc);
/* Almacenamiento del cuadro */
temp = (list *)malloc(sizeof(list));
temp->frame = (char *)malloc(framesize);
memcpy(temp->frame, main_buf + fc * framesize, framesize);
temp->next = NULL;
current->next = temp;
current = temp;
if(pdif > threshold) // Si hay moviento
82
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
estado = MNORMAL;
else { // Si no hay movimiento
gettimeofday(&temp_time, &tz);
/* Si se cumplió el tiempo final */
if((temp_time.tv_sec - stop_time.tv_sec)*1000 + (temp_time.tv_usec - temp_time.tv_usec)/1000 >= mstop_time) {
estado = IDLE;
/* Inicializa los punteros de la lista */
start = NULL;
current = NULL;
temp = NULL;
save_end = 1;
printf("Evento de movimiento finalizado.\n");
}
}
break;
}
}
/* Libera memoria */
free(dif);
free(curr);
free(prev);
fclose(stats);
fclose(estados);
pthread_mutex_destroy(&mutexsave);
pthread_exit(NULL);
}
/* mean_filter: Filtro de media para la reducción de ruido */
void mean_filter(unsigned char *frame, int x, int y) {
int i, j;
unsigned char *new_frame;
/* Matriz de pesos */
float weights[9] = {0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11};
/* Almacenamiento temporal para la imagen procesada */
new_frame = malloc(x * y);
/* Aplica el filtro pixel por pixel
for(i = 1; i < (y - 1); i++)
for(j = 1; j < (x - 1); j++) {
new_frame[i*x+j] = weights[0] *
weights[1] *
weights[2] *
weights[3] *
weights[4] *
weights[5] *
weights[6] *
weights[7] *
weights[8] *
*/
frame[(i-1)*x+(j-1)] +
frame[(i-1)*x+j] +
frame[(i-1)*x+(j+1)] +
frame[i*x+(j-1)] +
frame[i*x+j] +
frame[i*x+(j+1)] +
frame[(i+1)*x+(j-1)] +
frame[(i+1)*x+j] +
frame[(i+1)*x+(j+1)];
}
/* Coloca pixeles negros en el borde de la imagen */
for(i = 0; i < y; i++) {
new_frame[i*x] = 0;
new_frame[i*x+(x-1)] = 0;
}
for(i = 0; i < x; i++) {
new_frame[i] = 0;
new_frame[(y-1)*x+i] = 0;
}
/* Copia la imagen procesada sobre la imagen actual y libera la memoria temporal */
memcpy(frame, new_frame, x * y);
free(new_frame);
}
/* grayscale: Conversión de la imagen a escala de grises (intensidad) */
void grayscale(unsigned char *frame, int size, char *new_frame) {
int i, j;
float temp;
for(i = 0, j = 0; i < size; i += 3, j++) {
temp = 0.11*frame[i] + 0.59*frame[i+1] + 0.3*frame[i+2];
if(temp >= 255)
new_frame[j] = 254;
else
new_frame[j] = temp;
83
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
}
}
/* save_frames: Almacenamiento de las imágenes a disco duro y generación del archivo de video */
void *save_frames(void *list_start) {
char timestamp[30];
char filename[50];
list *start = (list *)list_start;
list *temp = NULL;
int i = 0;
/* Determina la hora y la fecha actual */
time_t tptr;
tptr = time(NULL);
struct tm *tsptr;
tsptr = localtime(&tptr);
strftime(timestamp, 30, "%Y%m%d-%H%M%S", tsptr);
/* Crea el direcctorio para almacenar las imágenes */
mkdir(timestamp, 0777);
/* Bloquea el mutex */
pthread_mutex_lock(&mutexsave);
while(1) {
/* Guarda el cuadro */
snprintf(filename, 50, "%s/%04d.jpg", timestamp, i);
save_image(start->frame, filename, video_buf.width, video_buf.height, "BGR");
/* Si llegó al final de la lista */
while(start->next == NULL) {
/* Si no se van a agregar cuadros a la lista */
if(save_end) {
/* Libera la memoria */
free(start->frame);
free(start);
/* Desbloquea el mutex */
pthread_mutex_unlock(&mutexsave);
/* Genera el archivo de video con memcoder */
printf("Generando archivo de video...\n");
char mencoder_line[150];
snprintf(mencoder_line, 150,
"mencoder \"mf://%s/*.jpg\" -mf fps=%f:w=%d:h=%d:type=jpeg -ovc divx4 -o %s/%s.avi",
timestamp, fps, video_buf.width, video_buf.height, timestamp, timestamp);
system(mencoder_line);
/* Finaliza el hilo de ejecución */
pthread_exit(NULL);
}
/* Espera a que se añada otro cuadro a la lista */
usleep(100);
continue;
}
/* mueve start y libera la memoria */
temp = start;
start = temp->next;
free(temp->frame);
free(temp);
i++;
}
}
/* save_image: Almacenamiento de una imágen a disco duro utilizando ImageMagick */
void save_image(unsigned char *data, char *filename, int width, int height, char *colorspace) {
ExceptionInfo exception;
Image *image;
ImageInfo *image_info;
/* Bug? de imagemagick: 255 -> 0 si la imagen está en valores de intensidad*/
if(strcmp(colorspace, "I") == 0) {
int i;
for(i = 0; i < width * height; i++)
if(data[i] == 255)
data[i] = 254;
}
InitializeMagick((char *)NULL);
84
395
396
397
398
399
400
401
402
403
404
405
406
407
408
GetExceptionInfo(&exception);
image_info = CloneImageInfo((ImageInfo *) NULL);
image = ConstituteImage(width, height, colorspace, CharPixel, data, &exception);
(void)strcpy(image->filename, filename);
WriteImage(image_info, image);
DestroyImage(image);
DestroyExceptionInfo(&exception);
DestroyImageInfo(image_info);
DestroyMagick();
}
sockets.h: Declaraciones y definiciones para las rutinas de comunicación
con el cliente y transmisión de video
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* sockets.h: Declaraciones y definiciones para las rutinas de comunicación con
*
el cliente y transmisión de video
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
#include
#include
<sys/socket.h>
<netinet/in.h>
<string.h>
<unistd.h>
"global.h"
"video.h"
"motion.h"
#ifndef SOCKETS_H
#define SOCKETS_H
/* Definiciones */
#define PORT 24001
// Puerto del servidor
#define BUF_SIZE 256 // Tamaño del buffer de datos
/* Propiedades del cuadro enviado */
struct video_prop {
int width; // Ancho
int height; // Altura
};
/* Declaración de funciones */
/* tcpsocket: Servidor TCP para el hilo principal */
void tcpsocket(void);
/* udpsocket: Envío de los cuadros de video al cliente */
void *udpsocket(void *);
#endif /* SOCKETS_H */
85
sockets.c: Implementación de las rutinas de comunicación con el cliente
y transmisión de video
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* sockets.c: Implementación de las rutinas de comunicación con
*
el cliente y transmisión de video
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "sockets.h"
/* tcpsocket: Servidor TCP para el hilo principal */
void tcpsocket(void) {
int sd, sd2, client_len;
struct sockaddr_in server, client;
char *buf_ptr, out_buf[BUF_SIZE], in_buf[BUF_SIZE];
pthread_t capture_thread, udp_thread, motion_thread;
pthread_attr_t attr;
/* Creación del socket */
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "Problemas creando el socket\n");
pthread_exit((void *)-1);
}
/* Almacenamiento de los parámetros de conexión en la estructura sockaddr_in */
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "Error al enlazar el socket\n");
pthread_exit((void *)-1);
}
/* El socket espera conexiones */
listen(sd, 5);
/* Ciclo infinito de atención de conexiones */
while(1) {
/* Acepta la conexión entrante */
client_len = sizeof(client);
if((sd2 = accept(sd, (struct sockaddr *)&client, &client_len)) == -1) {
fprintf(stderr, "No se puede aceptar la conexion\n");
pthread_exit((void *)-1);
}
/* Envía mensaje de bienvenida */
strcpy(out_buf,"Telescreen v0.1");
write(sd2, out_buf, BUF_SIZE);
int n_read, bytes_left;
while(1) {
/* Para que el ciclo no ocupe todo el procesador */
usleep(100);
/* Lectura de los datos enviados por el cliente */
bytes_left = BUF_SIZE;
buf_ptr = in_buf;
86
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
while((n_read = read(sd2, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
if(n_read < 0)
continue;
if((strcmp(in_buf,"CAPTURE")) == 0) { // Inicio de la captura
if(!capture) {
capture = 1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&capture_thread, &attr, capture_loop, NULL);
printf("Captura Iniciada\n");
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if((strcmp(in_buf, "NOCAPTURE")) == 0) { // Fin de la captura
if(capture) {
capture = 0;
pthread_join(capture_thread, NULL);
printf("Captura detenida\n");
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if((strcmp(in_buf, "VIDEOPROP")) == 0) { // Envío de las propiedades del video capturado
struct video_prop prop;
char *prop_ptr = (char *)∝
prop.width = video_buf.width;
prop.height = video_buf.height;
write(sd2, prop_ptr, sizeof(struct video_prop));
printf("Propiedades de video enviadas\n");
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if(strcmp(in_buf, "STREAM") == 0) { // Inicio del envío del video al cliente
if(!stream) {
stream = 1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&udp_thread, &attr, udpsocket, NULL);
printf("Transmisión del video iniciada\n");
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if(strcmp(in_buf, "NOSTREAM") == 0) { // Fin del envío del video
if(stream) {
stream = 0;
pthread_join(udp_thread, NULL);
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if(strcmp(in_buf, "MOTION") == 0) { // Inicio de la detección de movimiento
if(!motion) {
motion = 1;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&motion_thread, &attr, motion_detect, NULL);
printf("Detección de movimiento iniciada\n");
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if(strcmp(in_buf, "NOMOTION") == 0) { // Fin de la detección de movimiento
if(motion) {
motion = 0;
pthread_join(motion_thread, NULL);
}
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
} else if(strcmp(in_buf, "CLOSE") == 0) { // Finalización de la conexión
printf("Conexión finalizada\n");
strcpy(out_buf, "OK");
write(sd2, out_buf, BUF_SIZE);
break;
} else { // Comando desconocido
printf("Comando desconocido\n");
strcpy(out_buf, "UNKNOWN");
write(sd2, out_buf, BUF_SIZE);
}
}
printf("Conexión con el cliente finalizada.\n");
close(sd2);
}
87
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
close(sd);
}
/* udpsocket: Envío de los cuadros de video al cliente */
void *udpsocket(void *null) {
int sd, client_len;
char buf[BUF_SIZE];
struct sockaddr_in server, client;
int fc = 0, prev_fc = -1;
int counter = 0;
/* Creación del socket UDP */
if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
fprintf(stderr, "Problemas creando el socket\n");
pthread_exit((void *)-1);
}
/* Información de la conexión */
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "Problemas al enlazar el socket\n");
pthread_exit((void *)-1);
}
int n;
/* Espera que el cliente solicite los datos */
client_len = sizeof(client);
if((n = recvfrom(sd, buf, BUF_SIZE, 0, (struct sockaddr *)&client, &client_len)) < 0) {
fprintf(stderr, "Problemas recibiendo datagramas del ciente\n");
pthread_exit((void *)-1);
}
while(stream) {
/* Envía el cuadro anterior al que está siendo capturado */
if(framecounter)
fc = framecounter - 1;
else
fc = BUF_FRAME_NUM;
if(prev_fc == fc)
continue;
prev_fc = fc;
unsigned char *fp = main_buf + framesize * fc;
/* Se divide el frame en paquetes UDP de 4096 bytes */
int n_packets = framesize / 4096;
int i;
for(i = 0; i <= n_packets; i++) {
if(sendto(sd, (void *)(fp + i*4096), 4096, 0, (struct sockaddr *)&client, client_len) == -1)
printf("Error enviando el paquete #%d\n", i);
/* Para que no se pierdan paquetes en interfaces muy rápidas (ej. loopback) */
usleep(10);
}
/* Envío del paquete de sincronización */
int sync = n_packets; // Valor arbitrario, lo que importa es el tamaño
if(sendto(sd, (char *)&sync, sizeof(int), 0, (struct sockaddr *)&client, client_len) == -1)
printf("Error enviando paquete de sincronización\n");
counter++;
}
close(sd);
pthread_exit(NULL);
}
APÉNDICE B: Código fuente de la aplicación cliente
main.c: Rutina principal del cliente
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* main.c: Rutina principal del cliente
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
static
static
static
extern
<stdio.h>
<stdlib.h>
<gtk/gtk.h>
<pthread.h>
"net.h"
GtkWidget *image;
GdkPixbuf *video_pixbuf;
struct video_prop prop;
unsigned char *frame;
/* refresh: Despliega la imagen recibida */
void refresh(void) {
printf("En refresh()\n");
if(frame != NULL) {
/* Crea el objeto GdkPixbuf */
video_pixbuf = gdk_pixbuf_new_from_data((const guchar *)frame, GDK_COLORSPACE_RGB, FALSE,
8, prop.width, prop.height, prop.width * 3, NULL, NULL);
/* Inicializa image a partir de video_pixbuf */
gtk_image_set_from_pixbuf(image, video_pixbuf);
/* Despliega image */
gtk_widget_show(image);
}
}
int main(int argc, char *argv[]) {
GtkWidget *window;
int msg_status = -1;
char *frame = NULL;
struct stream_data strmdata;
pthread_t stream_thread;
pthread_attr_t attr;
int trc;
/* Inicialización GTK */
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
/* Manejo de señales GTK */
g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(gtk_widget_destroy), NULL);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
/* Establecimiento de la conexión */
if(open_conn("localhost", 24001) != 0) {
fprintf(stderr, "Error iniciando la conexión con el servidor.\n");
exit(EXIT_FAILURE);
}
88
89
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* Solicitud de inicio de captura */
if((msg_status = send_tcp_msg("CAPTURE", NULL, 0)) != 0) {
fprintf(stderr, "Problemas solicitando el inicio de la captura\n");
exit(EXIT_FAILURE);
}
/* Petición de envío de las propiedades de video */
if((msg_status = send_tcp_msg("VIDEOPROP", (void *)&prop, sizeof(struct video_prop))) != 0) {
fprintf(stderr, "Error obteniendo propiedades de video\n");
exit(EXIT_FAILURE);
}
sleep(5);
/* Solicita el inicio de la detección de movimiento */
if((msg_status = send_tcp_msg("MOTION", NULL, 0)) != 0) {
fprintf(stderr, "Error iniciando detección de movimiento\n");
exit(EXIT_FAILURE);
}
/* Solicita al servidor el envío de los cuadros */
if((msg_status = send_tcp_msg("STREAM", NULL, 0)) != 0) {
fprintf(stderr, "Error obteniendo el frame\n");
exit(EXIT_FAILURE);
}
/* Datos de la conexión UDP */
strmdata.addr = "localhost";
strmdata.port = 24001;
strmdata.width = prop.width;
strmdata.height = prop.height;
/* Crea el hilo de recepción de video */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&stream_thread, &attr, start_vid_stream, (void *)&strmdata);
printf("Stream Iniciado\n");
/* Inicialización widgets GTK */
image = gtk_image_new();
gtk_container_add(GTK_CONTAINER(window), image);
gtk_widget_show(window);
gtk_widget_show(image);
/* refresh se ejecuta cada 10 ms */
g_timeout_add(10, refresh, NULL);
/* Ciclo principal GTK */
gtk_main();
/* Al finalizar el programa */
if((msg_status = send_tcp_msg("NOSTREAM", NULL, 0)) != 0) {
fprintf(stderr, "Error iniciando detección de movimiento\n");
exit(EXIT_FAILURE);
}
if((msg_status = send_tcp_msg("NOMOTION", NULL, 0)) != 0) {
fprintf(stderr, "Error iniciando detección de movimiento\n");
exit(EXIT_FAILURE);
}
if((msg_status = send_tcp_msg("NOCAPTURE", NULL, 0)) != 0) {
fprintf(stderr, "Error iniciando detección de movimiento\n");
exit(EXIT_FAILURE);
}
if((msg_status = send_tcp_msg("CLOSE", NULL, 0)) != 0) {
fprintf(stderr, "Error cerrando la conexión\n");
exit(EXIT_FAILURE);
}
close_conn();
return 0;
}
90
net.h: Declaraciones y definiciones de las rutinas para la comunicación y
la recepción del video en el cliente
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* net.h: Declaraciones y definiciones para la comunicación y la recepción
*
de video en el cliente
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<netdb.h>
<fcntl.h>
<unistd.h>
<stdlib.h>
<string.h>
#ifndef NET_H
#define NET_H
/* Definiciones */
#define BUF_SIZE 256 // Tamaño del buffer para el envío de datos
/* Propiedades del cuadro recibido */
struct video_prop {
int width;
// Ancho
int height; // Altura
};
/* Información de la conexión UDP */
struct stream_data {
char *addr; // Dirección del servidor
int port;
// Puerto de conexión
int width;
// Ancho del cuadro
int height; // Altura del cuadro
};
/* Declaración de funciones */
/* open_conn: Inicia la conexión con el servidor */
int open_conn(char *addr, int port);
/* close_conn: Finaliza la conexión con el servidor */
int close_conn(void);
/* send_tcp_msg: Envía mensaje al servidor */
int send_tcp_msg(char *msg, void *retdata, int size);
/* start_vid_stream: Inicia recepción de los cuadros de video */
void *start_vid_stream(void *strmdata);
#endif /* NET_H */
91
net.c: Implementación de las rutinas para la comunicación y la recepción
del video en el cliente
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*
* Telescreen v0.3
* Programa de captura de video y detección de movimiento utilizando V4L
* net.c: Implementación de las rutinas para la comunicación y la recepción
*
de video en el cliente
* Copyright (C) 2005 Andrés Díaz Soto <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "net.h"
static int sd;
unsigned char *frame;
/* open_conn: Inicia la conexión con el servidor */
int open_conn(char *addr, int port) {
struct hostent *hp;
struct sockaddr_in server;
char *buf_ptr, in_buf[BUF_SIZE];
int bytes_left;
int n_read;
/* Creación del socket */
if((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
fprintf(stderr, "No se puede abrir el socket\n");
return -1;
}
/* Información de la conexión */
memset((char *)&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_port = htons(port);
if((hp = gethostbyname(addr)) == NULL) {
fprintf(stderr, "No se puede resolver el nombre del servidor\n");
return -1;
}
memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length);
/* Conexión con el servidor */
if(connect(sd, (struct sockaddr *)&server, sizeof(server)) == -1) {
fprintf(stderr, "No se puede establecer la conexión con el servidor\n");
return -1;
}
printf("Conexión establecida con %s\n", hp->h_name);
/* Recepción del mensaje de bienvenida */
bytes_left = BUF_SIZE;
buf_ptr = in_buf;
while((n_read = read(sd, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
return 0;
}
/* close_conn: Finaliza la conexión con el servidor */
int close_conn(void) {
close(sd);
printf("Conexión finalizada.\n");
92
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
return 0;
}
/* send_tcp_msg: Envía mensaje al servidor */
int send_tcp_msg(char *msg, void *retdata, int size) {
char *buf_ptr, in_buf[BUF_SIZE], out_buf[BUF_SIZE];
int bytes_left, n_read;
/* Envía el mensaje */
strcpy(out_buf, msg);
write(sd, out_buf, BUF_SIZE);
/* Recepción de datos */
if(retdata != NULL) {
bytes_left = size;
buf_ptr = retdata;
while((n_read = read(sd, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
}
/* Recepción del mensaje de confirmación */
bytes_left = BUF_SIZE;
buf_ptr = in_buf;
while((n_read = read(sd, buf_ptr, bytes_left)) > 0) {
buf_ptr += n_read;
bytes_left -= n_read;
}
/* Verifica que el comando haya sido ejecutado satisfactoriamente */
if(strcmp(in_buf, "OK") == 0)
return 0;
else
return -1;
}
/* start_vid_stream: Inicia recepción de los cuadros de video */
void *start_vid_stream(void *strmdata) {
struct sockaddr_in server, client;
struct hostent *hp;
int sdu, server_len, n;
char buf[BUF_SIZE];
unsigned char *temp_frame;
struct stream_data *sdata = (struct stream_data *)strmdata;
int framesize = sdata->width * sdata->height * 3;
/* Creación del socket UDP */
if((sdu = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
fprintf(stderr, "Error creando el socket UDP\n");
exit(-1);
}
/* Información del servidor */
memset((char *)&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(sdata->port);
if((hp = gethostbyname(sdata->addr)) == NULL) {
fprintf(stderr, "Error resolviendo la dirección del servidor\n");
exit(-1);
}
memcpy((char *)&server.sin_addr.s_addr, hp->h_addr, hp->h_length);
/* Información del cliente */
memset((char *)&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(0);
client.sin_addr.s_addr = htonl(INADDR_ANY);
/* Enlace del socket */
if(bind(sdu, (struct sockaddr *)&client, sizeof(client)) == -1) {
fprintf(stderr, "Error enlazando el socket\n");
exit(-1);
}
sleep(1); /* Espera a que el UDP en el servidor esté listo */
server_len = sizeof(server);
strcpy(buf, "2+2=5");
if(sendto(sdu, buf, BUF_SIZE, 0, (struct sockaddr *)&server, server_len) == -1) {
fprintf(stderr, "Problemas enviando datagrama al servidor\n");
93
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
exit(-1);
}
/* Número de paquetes */
int n_packets = framesize / 4096;
/* Cantidad de bytes sobrantes */
int leftover = framesize % 4096;
int pc = 0;
int p_size;
char buf2[4096];
/* Reserva memoria para los cuadros */
temp_frame = malloc(framesize);
frame = malloc(framesize);
/* Recibe los paquetes UDP con el cuadro */
while(1) {
p_size = recvfrom(sdu, buf2, 4096, 0, (struct sockaddr *)&server, &server_len);
if(p_size < 0) { // Si hay error en la recepción
fprintf(stderr, "Error recibiendo el paquete %d\n", pc);
continue;
} else if(p_size == sizeof(int)) { // Paquete de sincronización
if(pc == n_packets + 1) {
memcpy(frame, temp_frame, framesize);
} else {
fprintf(stderr, "Imagen con información incorrecta\n");
}
pc = 0;
} else if(p_size == 4096) { // Paquete con datos
if(pc == n_packets)
memcpy(temp_frame + pc*4096, buf2, leftover);
else
memcpy(temp_frame + pc*4096, buf2, 4096);
pc++;
}
}
/* Libera memoria */
free(temp_frame);
free(frame);
close(sdu);
pthread_exit(NULL);
}
Descargar