JavaScript

Juan Mellado, 16 Diciembre, 2011 - 18:10

Buscando información acerca de métodos de tracking (seguimiento) de objetos en tiempo real, me he encontrado que hay toda una teoría desarrollada para la detección de regiones dentro de una imagen que parezcan corresponder a piel humana. Hay muchas técnicas, pero las más sencillas parecen ser curiosamente las que mejor funcionan. Y básicamente consisten en convertir los pixeles originales desde RGB a otro espacio donde sea más fácil estudiarlos.

Al final he acabado montado mi pequeño "laboratorio" en JavaScript para comprobar una de estas teorías, concretamente la explicada en este paper que realiza el estudio de los pixels en el espacio de color YCbCr. Para ello he escrito un conversor de RGB a YCbCr utilizando las siguientes fórmulas propuestas en el documento:

Y = 16 + 65.481 * R + 128.553 * G + 24.966 * B
Cb = 128 - 37.797 * R - 74.203 * G + 112 * B
Cr = 128 + 112 * R - 93.786 * G - 18.214 * B

Las fórmulas admiten un valor RGB dentro del rango [0, 1] y generan su equivalente YCbCr en el rango [0, 255]. La idea clave es que dentro de dicho espacio el color correspondiente a la piel humana se encuentra dentro del rango [80, 120] para la componente Cb y [133, 173] para la componente Cr. La componente Y simplemente es ignorada. ¡Veamos si es cierto!

Al seleccionar una imagen del desplegable de abajo se muestra tal cual arriba a la izquierda. En los cuadros inferiores se dibujan los histogramas calculados para las componentes Cb (en azul) y Cr (en rojo). Las zonas destacadas en amarillo son los rangos que se corresponden teóricamente a la piel humana. Y la imagen superior derecha es el resultado de filtrar la imagen original dejando pasar sólo los pixels que se encuentran dentro de los rangos que corresponden a la piel humana.

Después de probar con una serie de imágenes he dejado tres fotografías de caras a modo de referencia. La de Lucy Liu corresponde a un caso bastante claro donde está bastante diferenciado del fondo y el algoritmo hace un buen trabajo. La de Nicole Kidman se complica un poco, ya que el pelo tiene una tonalidad tan clara que no se distingue de la piel. Y por último la de Whoopi Goldberg, que se corresponde al caso en que el algoritmo se confunde debido al color que tiene la ropa que lleva puesta.

El programa permite cambiar los rangos haciendo click sobre los histogramas para desplazar los valores mínimos y máximos. Por ejemplo, para la foto de Whoopi, si se aumenta un poco el mínimo del histograma de la componente Cb (azul) entonces el algoritmo ya no detecta la ropa como piel. De igual forma, jugando un poco con los rangos es posible eliminar la mayor parte del pelo de la foto de la Kidman. Reconozco que el control con el ratón es un poco complicado, la idea es que se modifica el límite más cercano a donde se hace el click.

Otra posibilidad que ofrece el programa es seleccionar una región dentro de la imagen original para ver los histogramas de esa región en concreto. Por defecto está seleccionada la imagen entera, pero haciendo click sobre ella se puede definir una región rectangular. La idea es poder ver como se distribuyen los valores para una región en concreto con más detalle para tener un mejor criterio a la hora de modificar los rangos. Lo ideal es seleccionar una región que incluya sólo piel, con brillos y sombras, y ajustar los mínimos y máximos de los histogramas obtenidos. Pulsando escape se restaura la composición original.

Resumiendo un poco, está claro que el criterio es válido en términos generales, pero hay mucha probabilidad de falsos positivos en función de la condiciones del entorno en que se pruebe. Los pixels aislados no representan un problema, ya que normalmente se eliminarán aplicando una operación morfológica del tipo erode. Y de igual forma los huecos se rellenarán con una operación dilate.

Para mejorar el proceso de detección en general hay que aplicar más criterios, y así lo hacen generalmente las técnicas propuestas, normalmente el estudio de la imagen en algún espacio de color, como el YCbCr o el HSV por ejemplo, es sólo la primera parte del proceso. Algunas técnicas más elaboradas utilizan una imagen de referencia, a modo de calibración del sistema, con la que se construyen unos umbrales de referencia. Otros sistemas, sobre todo cuando se trata de procesar vídeo en vez de imágenes estáticas, ajustan los umbrales del los histogramas de forma dinámica en función de la variación que se va produciendo en las imágenes que se van sucediendo.

En otro orden de cosas, buscando más información por la red, he encontrado fórmulas de conversión de RGB a YCbCr más sencillas que las propuestas en el paper. Por ejemplo las siguientes:

Y = 0.299 * R + 0.587 * G + 0.114 * B
Cb = (B - Y) * 0.564 + 128
Cr = (R - Y) * 0.713 + 128

Estas fórmulas tienen además la ventaja de que los valores de entrada RGB están dentro del rango [0, 255] en vez de [0, 1].

Juan Mellado, 12 Diciembre, 2011 - 16:25

Los defectos de convexidad son importantes en la medida que se corresponden con ciertos puntos que caracterizan un determinado contorno. El ejemplo más clásico es el de una mano. Si se calcula la envolvente convexa que engloba el contorno de una mano y sobre ella se marcan los puntos que se separan más de dicha envolvente, para cada segmento de la misma, estos puntos se corresponderán con el nacimiento de los dedos, es decir, donde dejan la palma. Estos puntos son los defectos de convexidad, y permiten determinar de una forma muy sencilla algunas características concretas, como por ejemplo el número de dedos que tiene levantados la mano.

Siguiendo la implementación de la función cvConvexityDefects de OpenCV he creado un pequeño programa en JavaScript que calcula los defectos de convexidad. Para hacerlo un poco más atractivo visualmente le he dado forma de un pequeño generador automático de constelaciones.

El programa genera varios puntos al azar asegurándose de que forman un polígono simple, o dicho de otra forma, que no se cruzan sus líneas. A continuación calcula la envolvente convexa. Y por último los defectos de convexidad.

La zona azul claro corresponde al área cubierta por la envolvente convexa. Los puntos blancos con doble círculo son los que se encuentran sobre el borde de la envolvente, el resto está dentro de ella. Los puntos amarillos son los defectos de convexidad, es decir, los puntos más alejados para cada segmento de la envolvente.

Juan Mellado, 7 Diciembre, 2011 - 15:42

Un experimento rápido y un poco tonto con js-aruco usando las sombras como marcadores de realidad aumentada.


Demo online:
www.inmensia.com/files/aruco/debug/debug.html

Juan Mellado, 5 Diciembre, 2011 - 20:15

Otra pieza del rompecabezas que estoy intentando montar. Esta vez una simple conversión de RGB a HSV en JavaScript. Hay mucha información acerca de como hacerlo en Internet, nada nuevo bajo el sol. He creado un pequeño programa que toma una imagen RGB y la convierte a HSV. Para poder ver los resultados he aplicado una pequeña transformación para devolverlos a RGB (no a los valores originales, claro).

Para los que sólo les interese el código, pongo la función de conversión. Admite como entrada los componentes RGB en el rango [0..1] y devuelve un objeto con los componentes HSV. H un ángulo dentro del rango [0..360], y S y V un porcentaje dentro del rango [0..1].

function rgb2hsv(r, g, b){
  var v = Math.max(r, g, b),
      delta = v - Math.min(r, g, b),
      s = v === 0? 0: delta / v,
      h = 0;
   
  if (0 !== delta){
 
    if (v === r){
      h = 60 * (g - b) / delta;
    }else if (v === g){
      h = 120 + (60 * (b - r) / delta);
    }else{
      h = 240 + (60 * (r - g) / delta);
    }
   
    if (h < 0){
      h += 360;
    }
  }
   
  return {h: h, s: s, v: v};
}
Temas: JavaScript
Juan Mellado, 2 Diciembre, 2011 - 11:28

Google ha lanzado la versión alpha de Google APIs Client Library for JavaScript. O lo que es lo mismo, la posibilidad de acceder a través de JavaScript a todos los servicios de Google (Google+, Calendar, Translate, URL Shortener, Maps, ...). Ya existía en versión beta para otros lenguajes como PHP, Python, Java y .NET. Esta nueva versión para JavaScript se une al resto de versiones que se encuentran en fase alpha como las de Ruby, Objective C, GWT y Go.

He hecho una pequeña prueba ejecutando el API de Google+, lanzando consultas para obtener todas las novedades públicas aparecidas recientemente en mi cuenta de Google+ con el siguiente código:

<script src="https://apis.google.com/js/client.js?onload=init">
...
function init(){
  gapi.client.setApiKey('YOUR_API_KEY');

  gapi.client.load('plus', 'v1', function(){

      var request = gapi.client.plus.activities.list({
          'userId': '101388150953455772115',
          'collection': 'public'
        });

      request.execute( function(response){
        //Aquí se recibe la respuesta
        ...
        });
    });
}

Como se observa, hay que cargar el script de cliente (https://apis.google.com/js/client.js) indicando la función a la que queramos que se llame cuando termine de cargarse el script (?onload=init). Una vez cargado el script hay que establecer la clave personal de acceso al API (gapi.client.setApiKey) que podemos conseguir gratuitamente en la APIs Console. Y a continuación invocar al API que se quiera (gapi.client.load), como el de Google+ por ejemplo (plus), indicando la función que se quiere ejecutar, como la de consulta de actividad por ejemplo (gapi.client.plus.activities.list), y donde queremos recibir la respuesta (request.execute).

Para los que quieran probar sin tener que escribir código, las APIs se pueden consultar y ejecutar de forma online a través del APIs Explorer. Una aplicación muy útil para consultar parámetros y examinar resultados.

Utilizando mi ID de usuario en Google+ (101388150953455772115) he hecho una consulta para obtener mis últimas novedades públicas (public) y he obtenido el siguiente objeto respuesta:

Google API Client - JavaScript

Como se observa, hay un ID, un título, una fecha, una serie de urls y un array de objetos. Ese array es el que contiene las últimas novedades aparecidas en mi cuenta. Veamos el primero:

Google API Client - JavaScript

Nuevamente un id, un título, fechas y urls. Con esta información básica ya se podría montar un pequeño listado de novedades en una página web con los títulos y fecha, e incluso actualizarlo periódicamente desde los propios clientes en JavaScript. Pero aún podemos añadir más detalles examinando los campos actor y object:

Google API Client - JavaScript

Google API Client - JavaScript

El actor es el usuario que generó la actividad, y tenemos su id, nombre, url, e incluso un enlace a la imagen de su avatar.

El object es lo publicado, con su contenido completo, el número de veces que se ha compartido, el número de usuarios que han pulsado el botón "+1" sobre él y los comentarios que ha generado. Incluyendo además una lista de adjuntos, que es lo compartido (enlace, foto, vídeo, ...):

Google API Client - JavaScript

Un último punto que no hay que olvidar, es que el número de llamadas que se pueden realizar con un mismo ID está limitado por API y día. Cada API tiene un límite que Google denomina "de cortesía". Cuando en un día se supera dicho límite deja de funcionar hasta el día siguiente.