Detectando movimiento con OpenCV y Processing

Introducción

Una de las características que quería aprender a implementar con OpenCV era la detección de movimiento, esto me permitirá implementar formas de interacción interesantes (y bastante simples de generar) entre el usuario y el sketch a través de la cámara web.

En pocas palabras, la técnica para detectar movimiento que se describe a continuación se basa en generar una imagen monocromática con los píxeles que han cambiado entre dos cuadros del video.  Para hacer esto se siguen los pasos mostrados a continuación.

  1. Obtener una nueva imagen de la cámara web
  2. Invertir la imagen horizontalmente (opcional)
  3. Calcular la diferencia entre la imagen recién obtenida y la almacenada en memoria (recordada)
  4. Procesar la imagen para facilitar su manejo: convertirla a escala de grises, suavizar la imagen y reducir el ruido eliminando los valores demasiado altos o bajos.
  5. Mostrar la imagen de movimiento (opcional)
  6. Recordar la imagen actual para ser procesada nuevamente en la siguiente iteración

opencv.read();

opencv.flip(OpenCV.FLIP_HORIZONTAL);

opencv.absDiff();

opencv.convert(OpenCV.GRAY);

opencv.blur(OpenCV.BLUR, 3);

opencv.threshold(20);

image(opencv.image(), 0, 0);

opencv.remember(OpenCV.SOURCE, OpenCV.FLIP_HORIZONTAL); 

Para verificar si ha sucedido movimiento en una determinada área de la imagen se debe verificar si los píxeles que pertenecen a esa área se encuentran blancos (si hubo) o no.  Esta información se obtiene utilizando el método get(int x, int y) del objeto PImage el cual retorna el color del píxel seleccionado.  Por facilidad se recomienda que se obtenga el brillo (brightness(color)) de este píxel para su comparación.

El siguiente código revisa un área cuadrada de píxeles en búsqueda de movimiento en esa zona.

for(int px=x; px<x+size; px++)  
    for(int py=y; py<y+size; py++)     
        if (px < width && px > 0 && py < height && py > 0)       
            if (brightness(mImage.get(px, py)) > 127)
                count ++;

Screenshot

Imagen de movmiento sobre el objetivo (modo buffer)

Imagen de movmiento sobre el objetivo (modo buffer)

 

Enlaces

Poniendo sombreros a las personas con OpenCV y Processing

Introducción

Como una versión un poco mas elaborada de la publicación anterior he preparado este sketch que toma el flujo de video proveniente de la cámara web,  encuentra en él los rostros de las personas y les pone un sombrero.  Se incluyen diferentes sombreros que pueden cambiarse mediante el teclado.

q – terminar el sketch
a – mostrar/ocultar el rectángulo rojo alrededor de cada cara encontrada
s – mostrar/ocultar la máscara sobre cada cara encontrada
z/x – alternar entre las imágenes de máscaras disponibles

Screenshots

Demostración con varias personas en la misma imagen

Demostración con varias personas en la misma imagen

Demostración con la foto de un rostro

Demostración con la foto de un rostro

Agradecimientos para Martha, Diego y Jennifer Connerlly por participar como modelos para las fotos del artículo.

Acerca de la ubicación de los rostros

Hasta el momento el perfil que mejores resultados me ha dado para ubicar los rostros de las personas ha sido OpenCV.CASCADE_FRONTALFACE_ALT_TREE (haarcascade_frontalface_alt_tree.xml).  Este encuentra los rostros con el menor número de errores (identificación equivocada de un objeto inanimado como si fuera un rostro), sin embargo parece tener poca tolerancia a las variaciones de posición del rostro de las personas, si ellas agachan o rotan un poco la cara probablemente ya no serán renocidas.  Un trabajo a futuro consistirá en encontrar como mejorar estos resultados de la ubicación de rostros.

Estos son los perfiles de reconocimiento disponibles a través de OpenCV según la instalación que se realizó de la librería.

Los siguientes perfiles se encuentran disponibles como constantes asociadas a la clase OpenCV de Processing.

  public static final String CASCADE_FRONTALFACE_ALT_TREE
  public static final String CASCADE_FRONTALFACE_ALT
  public static final String CASCADE_FRONTALFACE_ALT2
  public static final String CASCADE_FRONTALFACE_DEFAULT
  public static final String CASCADE_PROFILEFACE
  public static final String CASCADE_FULLBODY
  public static final String CASCADE_LOWERBODY
  public static final String CASCADE_UPPERBODY

A continuación se listan todos los archivos XML de los perfiles de reconocimiento que también pueden ser utilizados directamente en Processing.

  haarcascade_eye_tree_eyeglasses.xml   
  haarcascade_frontalface_default.xml  
  haarcascade_mcs_eyepair_small.xml  
  haarcascade_mcs_upperbody.xml
  haarcascade_eye.xml                   
  haarcascade_fullbody.xml             
  haarcascade_mcs_lefteye.xml        
  haarcascade_profileface.xml
  haarcascade_frontalface_alt2.xml      
  haarcascade_lefteye_2splits.xml      
  haarcascade_mcs_mouth.xml          
  haarcascade_righteye_2splits.xml
  haarcascade_frontalface_alt_tree.xml  
  haarcascade_lowerbody.xml            
  haarcascade_mcs_nose.xml           
  haarcascade_upperbody.xml
  haarcascade_frontalface_alt.xml       
  haarcascade_mcs_eyepair_big.xml      
  haarcascade_mcs_righteye.xml 

Enlaces

Ubicando caras en una webcam con OpenCV y Processing

Introducción

En este sketch se aprovecha la agilidad para desarrollar en Processing con la facilidad que provee OpenCV para ubicar los rostros de las personas en el flujo de video proveniente de una cámara web.  En este caso el sketch mostrará una cara sonriente cuando se encuentra acompañado por una persona y una cara triste cuando está solo.

Screenshots

Sketch feliz

Sketch feliz


Sketch triste

Sketch triste

Enlaces

Instalación de la librería de OpenCV en Processing bajo GNU/Linux Mint 12

Introducción

En el presente artículo se describirán los pasos necesarios para instalar el software de OpenCV y su correspondiente librería para desarrollar sketches con él utilizando Processing.

Durante el desarrollo de la instalación se utilizó GNU/Linux Mint 12 sin emgbargo el procedimiento debe ser compatible con Ubuntu.

Aviso! La versión de OpenCV que se encuentra en los repositorios del sistema operativo (2.1.0-7build1) es un poco antigua, sin embargo se encontraron problemas para compilar la parte nativa de la librería con una versión mas reciente.  Por este motivo se decidió utilizar la versión disponible y experimentar posteriormente una posible actualización.

Procedimiento

Instalar OpenCV

$ sudo aptitude install libcv-dev libhighgui-dev libcvaux-dev opencv-doc

Instalar la librería de Processing

Nota!  Se recomienda descargar la última versión disponible de la página web del desarrollador.  En este caso se utilizó opencv_01.zip.

Crear el directorio donde se almacenarán las librerías de Processing del usuario.

$ mkdir -p ~/sketchbook/libraries ; cd ~/sketchbook/libraries

Descargar la librería y los ejemplos de la misma.

$ wget http://ubaa.net/shared/processing/opencv/download/opencv_01.zip
$ wget http://ubaa.net/shared/processing/opencv/download/opencv_examples.zip

Descomprimir los paquetes recién descargados

$ unzip opencv_01.zip
$ rm opencv_01.zip
$ unzip opencv_examples.zip
$ rm opencv_examples.zip

Ubicar correctamente el directorio con los ejemplos.

$ mv OpenCV\ examples/ OpenCV/examples

Recompilar el módulo nativo de la librería

La librería incluye una librería del sistema operativo (libOpenCV.so) enlazada dinámicamente con OpenCV.  La incluída con la distribución de la librería recién instalada fue compilada con versiones anteriores de OpenCV motivo por el cual no se cumplen sus dependencias y en el momento de compilar los sketches se obtiene el siguiente mensaje de error.

!!! required library not found : /home/jimezam/sketchbook/libraries/OpenCV/library/libOpenCV.so: libcxcore.so.1: cannot open shared object file: No such file or directory
Verify that the java.library.path property is correctly set and 'libcxcore.so', 'libcv.so', 'libcvaux.so', 'libml.so', and 'libhighgui.so' are placed (or linked) in one of your system shared libraries folder

Exception in thread "Animation Thread" java.lang.UnsatisfiedLinkError: hypermedia.video.OpenCV.capture(III)V
    at hypermedia.video.OpenCV.capture(Native Method)
    at hypermedia.video.OpenCV.capture(OpenCV.java:945)
    at sketch_jan12b.setup(sketch_jan12b.java:35)
    at processing.core.PApplet.handleDraw(Unknown Source)
    at processing.core.PApplet.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:662)

Por este motivo es necesario recompilar el módulo nativo con las versiones actuales.  Para hacer esto se deben seguir los siguientes pasos.

$ cd ~/sketchbook/libraries/OpenCV/library/

Crear una copia de seguridad del módulo nativo a recompilarse.

$ mv libOpenCV.so libOpenCV.so.OLD

Generar la cabecera del módulo nativo de Java (JNI).

$ javah -classpath OpenCV.jar -jni hypermedia.video.OpenCV

Recompilar el módulo nativo.

$ g++ -shared ../source/cpp/OpenCV.cpp -o libOpenCV.so -I/usr/include/opencv/ -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -I. -lcv -lhighgui -fPIC

Finalmente se obtienen los siguientes archivos.

$ ls -l

-rw-rw-r– 1 jimezam jimezam  10748 2012-01-12 14:43 hypermedia_video_OpenCV.h
-rwxr-xr-x 1 jimezam jimezam  80088 2009-05-27 13:15 libOpenCV.jnilib
-rwxrwxr-x 1 jimezam jimezam  41953 2012-01-12 16:16 libOpenCV.so
-rwxr-xr-x 1 jimezam jimezam  42861 2009-05-27 13:15 libOpenCV.so.OLD
-rwxr—– 1 jimezam jimezam 515184 2009-05-27 13:15 OpenCV.dll
-rw-r–r– 1 jimezam jimezam  12116 2009-05-27 13:15 OpenCV.jar

Instalar los perfiles de reconocimiento

Los perfiles de reconocimiento son archivos XML donde se estructura el conocimiento que le permite a la librería reconocer rostros sin la necesidad de mayor entrenamiento.  La versión actual de los paquetes de OpenCV en los repositorios instala estos perfiles en una ubicación que no permite que sean utilizados fácilmente desde Processing.  Los siguientes pasos corrígen esta situación.

$ sudo cp -R /usr/share/doc/opencv-doc/examples/haarcascades /usr/share/opencv/
$ sudo gunzip /usr/share/opencv/haarcascades/*.gz

Sketch de prueba

Una vez instalada la librería se podrán desarrollar sketches con ella, a continuación se presenta el sketch mas sencillo posible que muestra la imagen proveniente de la cámara web utilizando OpenCV.

import hypermedia.video.*;
OpenCV opencv;
void setup()
{
  size(640, 480);
  opencv = new OpenCV(this);
  opencv.capture(width,height);
}
void draw()
{
    opencv.read();
    background(opencv.image());
}

Enlaces

Utilizar la cámara infraroja (IR) del Kinect utilizando Processing

Introducción

Así como inicialmente se accedió a las imágenes RGB (convencional) y de profundidad de las cámaras del Kinect, también es posible acceder a la información proporcionada por la cámara infraroja con la misma facilidad.

Screenshot

Imagen de profundidad | Imagen infraroja

Imagen de profundidad | Imagen infraroja


Enlaces

Búsqueda del punto mas cercano con el Kinect utilizando Processing y Simple-OpenNI

Introducción

El siguiente paso lógico consiste en identificar cual es el punto mas cercano al Kinect, presumiblemente el usuario que interactúa con el sketch, para utilizar este valor como coordenada de entrada.

Su implementación se reduce simplemente a recorrer el mapa de profundidad de alta resolución (context.depthMap()) que provee la cámara de profundidad y obtener de allí la coordenada cuyo valor sea menor pero mayor a cero ya que este corresponde a las áreas no medidas.  Para mayores detalles acerca de la implementación referirse a la función int[] findClosestPoint(int, int).

Para hacerlo un poco mas interesante se agregaron dos barras de desplazamiento en la parte inferior que sirven para limitar la profundidad de los objetos que van a ser realmente tenidos en cuenta en la determinación del punto mas cercano, esto con el fin de aislar otras zonas de acuerdo a su distancia que no quieran ser tenidas en cuenta y puedan estar produciendo interferencia.  Hacia la izquierda de las barras de desplazamiento la distancia es menor (mas cerca del Kinect) y hacia la derecha la distancia crece (se aleja del Kinect).

Basado en esta característica se agregó la funcionalidad de aislar las porciones de la imagen de los objetos que no se encuentran ubicados en el rango de profundidad válido.  Para conmutar entre imagen completa e imagen parcial sólo es necesario presionar la letra 't' (toggle) en el sketch.

Screenshot

Punto mas cercano al Kinect filtrado por un rango de distancia

Punto mas cercano al Kinect filtrado por un rango de distancia

Separación de la imagen mostrando sólo los objetos ubicados en la distancia válida

Separación de la imagen mostrando sólo los objetos ubicados en la distancia válida

Enlaces

Prueba de las cámaras del Kinect utilizando Processing

Introducción

Este sketch es una demostración simple del uso de las cámaras del Kinect utilizando Processing y la librería Simple-OpenNI.  Se incluyen las siguientes características.

  • Mostrar las imágenes provenientes de la cámara de profundidad
  • Mostrar las imágenes provenientes de la cámara RGB (video convencional)
  • Mostrar la información de color/brillo de un punto específico de una imagen
  • Calcular la distancia física entre la cámara y el punto elegido en el mundo real.

Requisitos

  1. Librería OpenNI
    http://blog.jorgeivanmeza.com/2011/12/instalacion-openni-sensor-kinect-y-nite-en-gnulinux-ubuntu-11-10-desde-fuentes/
  2. Librería Simple-OpenNI para Processing
    http://blog.jorgeivanmeza.com/2012/01/construccion-de-la-libreria-simple-openni-para-processing-bajo-ubuntu-de-32-bits/
  3. Processing
    http://www.processing.org/

Screenshot

Demostración imágenes de profundidad y RGB

Demostración imágenes de profundidad y RGB

Al hacer clic izquierdo sobre cualquiera de las imágenes se obtiene información del color/brillo del pixel seleccionado y en el caso de la imagen de profundidad se obtiene además el cálculo de la distancia física entre la cámara y el punto elegido en el mundo real.

Bright: r = 175
Distance: 2443 mm/96.18111 inches

Bright: r = 81
Distance: 2609 mm/102.71654 inches

Color: r = 49; g = 66; blue = 51

Color: r = 73; g = 53; blue = 11

Enlaces

Construcción de la librería Simple-OpenNI para Processing bajo Ubuntu de 32 bits

Introducción

Simple-OpenNI es una librería de Processing que actúa como un recubrimiento (wrapper) para utilizar fácilmente OpenNI desde este lenguaje de programación.

La distribución binaria de esta librería puede ser descargada directamente desde el sitio web del proyecto.  En el presente artículo se describirán los pasos necesarios para construír esta librería a partir de su distribución de fuentes lo cual resulta interesante para garantizar compatibilidad con las versiones de las librerías nativas instaladas y mantener la última versión disponible, así como utilizar arquitecturas cuyas distribuciones binarias no se encuentren disponibles.

Prerequisitos

Para poder construír la librería bajo GNU/Linux Ubuntu es necesario contar con los siguientes requisitos previamente instalados.

  1. OpenNI y NITE
  2. Java Development Kit.
    $ sudo aptitude install openjdk-6-jdk openjdk-6-jre openjdk-6-jre-headless openjdk-6-jre-lib
    Activar la versión recién instalada: /usr/lib/jvm/java-6-openjdk/jre/bin/java
    $ sudo update-alternatives –config java
  3. CMake
    $ sudo aptitude install cmake
  4. Swig >= v2.0.2
    $ sudo aptitude install swig
  5. Eigen >= v3.0
    $ sudo aptitude install libeigen3-dev
  6. Boost >= v1.46 (use the static build)
    $ sudo aptitude install libboost-all-dev

Procedimiento

Obtener la última versión del código fuente de la librería.

$ svn checkout http://simple-openni.googlecode.com/svn/trunk/ simple-openni-read-only

$ cd simple-openni-read-only/SimpleOpenNI/

Ajustar el script de construcción modificando la invocación a cmake de la siguiente manera.  (Ajustar las rutas que se consideren convenientes, en especial a DP5_JAR)

$ vi buildLinux32.sh

(actualizar)

cmake -DOPEN_NI_INCLUDE=/usr/include/ni/ \
      -DXN_NITE_INCLUDE=/usr/include/nite/ \
      -DXN_NITE_LIB=/usr/lib/ \
      -DEIGEN3D_INCLUDE=/usr/include/eigen3/ \
      -DP5_JAR=~/Processing/2.0a4/lib/core.jar \
      -JAVA_INCLUDE_PATH=/usr/lib/jvm/java-6-openjdk/include/ \
      -JAVA_INCLUDE_PATH2=/usr/lib/jvm/java-6-openjdk/include/linux \
      ..

Realizar la construcción de la librería.

$ ./buildLinux32.sh

Instalar la librería recién construída.

$ ./installLinux.sh

Verificar la instalación de la librería

El procedimiento anterior construye e instala la librería de Simple-OpenNI bajo ~/sketchbook/libraries/SimpleOpenNI/ dejándola lista para ser utilizada con Processing.

$ tree -d ~/sketchbook/libraries/SimpleOpenNI/

/home/jimezam/sketchbook/libraries/SimpleOpenNI/
├── documentation
│   ├── resources
│   └── SimpleOpenNI
├── examples
│   ├── eclipse
│   ├── Nite
│   │   ├── CircleCtrl
│   │   ├── Hands
│   │   └── Slider2d
│   └── OpenNI
│       ├── AlternativeViewpoint3d
│       ├── DepthImage
│       ├── DepthImageXml
│       │   └── data
│       ├── DepthInfrared
│       ├── DepthMap3d
│       ├── Hands3d
│       ├── MultiCam
│       ├── RecorderPlay
│       ├── Scene
│       ├── SceneDepth
│       ├── Threaded
│       ├── User
│       ├── User3d
│       ├── User3dCallback
│       ├── UserSaveCalib
│       └── UserScene3d
└── library

Enlaces

Instalación de NodeJS en GNU/Linux Ubuntu/Mint

Introducción

NodeJS es  una plataforma de desarrollo de alta escalabilidad para aplicaciones en red basada en un único hilo de ejecución, una arquitectura orientada a eventos y un manejo asíncrono de E/S.  Esta plataforma se programa utilizando Javascript del lado del servidor.

En el presente artículo se describen los pasos realizados para su instalación siguiendo dos estrategias diferentes.

Instalación

Utilizando el gestor de paquetes

En este caso la instalación es mas rápida y sencilla ya que el gestor de paquetes, apt/aptitude en este caso, se hace cargo de la descarga e instalación del software y sus dependencias.

Utilizando los repositorios por defecto

Los repositorios incluídos por defecto con el sistema operativo permiten instalar a NodeJS sin embargo no en su versión mas reciente sino un par de números atrasada.

Versión de NodeJS disponible en los repositorios de Ubuntu

Versión de NodeJS disponible en los repositorios de Ubuntu

Para realizar su instalación se debe ejeuctar el siguiente comando.

$ sudo aptitude install nodejs

Utilizando los repositorios del proyecto

Este método permite actualizar los repositorios del sistema operativo con el repositorio oficial del proyecto de donde se podrán descargar versiones mas recientes.  Para hacer esto se deben ejecutar los siguientes comandos.

$ sudo apt-get install python-software-properties
$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update

Versión de NodeJS disponible en los repositorios del proyecto

Versión de NodeJS disponible en los repositorios del proyecto

Una vez incluído el nuevo repositorio se procede a instalar el paquete normalmente.

$ sudo aptitude install nodejs

Desde el código fuente

Este método permite compilar una distribución de NodeJS para la versión específica del sistema operativo que se está utilizando.  Esto requiere que se cuente con el soporte requerido de las herramientas de desarrollo y dependencias necesarias para construír el proyecto.

Descargar el código fuente de la última versión disponible (la 0.6.7 en este caso) desde la siguiente ubicación.

http://nodejs.org/#download

$ wget http://nodejs.org/dist/v0.6.7/node-v0.6.7.tar.gz

$ tar zxvf node-v0.6.7.tar.gz $ cd node-v0.6.7/

$ ./configure --prefix=/home/jimezam/nodejs/0.6.7

Aviso!  Si desea instalar NodeJS en una ubicación central haciéndolo disponible para todos los usuarios del sistema operativo, omita el parámetro –prefix de la ejecución anterior, de lo contrario ajuste su ruta según la ubicación específica donde desee instalarlo.

Checking for program g++ or c++          : /usr/bin/g++
Checking for program cpp                 : /usr/bin/cpp
Checking for program ar                  : /usr/bin/ar
Checking for program ranlib              : /usr/bin/ranlib
Checking for g++                         : ok  
Checking for program gcc or cc           : /usr/bin/gcc
Checking for program ar                  : /usr/bin/ar
Checking for program ranlib              : /usr/bin/ranlib
Checking for gcc                         : ok  
Checking for library dl                  : yes
Checking for openssl                     : yes
Checking for library util                : yes
Checking for library rt                  : yes
Checking for fdatasync(2) with c++       : yes
'configure' finished successfully (0.776s)

El siguiente paso consiste en construír la distribución compilando su código fuente.

$ make

Y finalmente se debe instalar la distribución recién construída en la ubicación elegida.

$ make install

Aviso!  Si se eligió instalar NodeJS en una ubicación central, la ejecución del comando anterior deberá ser hecha por el usuario root de la siguiente manera: sudo make install.

Actualizar el PATH para que incluya la ubicación de las herramientas de NodeJS recién instaladas.  Realizar esta modificación en el .bash_profile/.bashrc del usuario o /etc/profile para tener un alcance global y garantizar que esta modificación persista cada vez que se inicia la máquina.

$ PATH=/home/jimezam/nodejs/0.6.7/bin:$PATH

Si el ajuste fue exitoso la aplicación node ya podrá ser accedida directamente desde la línea de comando.

$ which node

/home/jimezam/nodejs/0.6.7/bin/node

Prueba de funcionamiento: Hola Mundo NodeJS!

Crear el siguiente archivo de código Javascript para crear un servicio extremadamente simple que responda "Hola Mundo" ante las peticiones web de los clientes a través de navegadores.

$ vi prueba.js

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hola Mundo NodeJS!\n');
}).listen(7777, '127.0.0.1');
console.log('Servidor ejecutándose en http://127.0.0.1:7777');

Para iniciar el servicio se debe invocar el comando node de la siguiente manera.

$ node prueba.js

Servidor ejecutándose en http://127.0.0.1:7777

Desde un navegador web acceder a la dirección mencionada.

Hola Mundo consultándose desde NodeJS

Hola Mundo consultándose desde NodeJS

 

Enlaces

The Falling Things (reloaded) con Processing

Introducción

Aprovechando los últimos días de vacaciones decidí rehacer el sketch de TheFallingThings que había creado hace un tiempo.  Esta nueva versión es muy similar en su funcionalidad pero tiene un código mejor orientado a objetos y mas limpio, de igual manera que su antecesora fue implementada utilizando el lenguaje de programación Processing.

El objetivo del sketch sigue siendo el mismo, atrapar con el paddle las cosas que caen desde el cielo.  El usuario puede controlar el paddle utilizando el teclado (flechas izquierda y derecha) o el ratón.

Las variables del sketch pueden ser controladas durante su ejecución: cantidad de cosas que caen (teclas +/-) y la velocidad de las cosas que caen (flechas arriba y abajo).  Otras teclas útiles son r (reset) para reiniciar el sketch, p (pause) para pausarlo y q (quit) para salír de él.

Screenshot

The Falling Things v0.1

The Falling Things v0.1

 

Enlaces