Para dotar a nuestro agente de ojos debemos poder acceder a las cámaras del dispositivo. Para ellos nuestra web va a necesitar un elemento vídeo y otro canvas. Enlazamos el vídeo con la webcam del dispositivo y cada cierto tiempo tomamos una captura que copiaremos sobre el canvas para poder acceder a los pixels y aplicar nuestros algoritmos de visión por computador.
Empecemos por incluir una etiqueta vídeo y una canvas en nuestra web:
Las funciones que nos da HTML5 para acceder al vídeo se basan en la API navigator.mediaDevices. En versiones antiguas de los navegadores pierdes encontrarte el problema de que los nombres no están unificados y cada uno lo llamaba de una manera. Actualmente ya lo están. Algo parecido nos pasa con la API window.URL que vamos a usar para poder conectar la webcam a nuestro elemento vídeo. Para que no haya problemas entre navegadores usaremos:
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
Nos hace falta acceder a cada uno de los elementos. El vídeo, el canvas y el contexto del canvas del cual leeremos la imagen capturada del vídeo. Para ello usaremos el id del tag.
video = document.getElementById(videoId); canvas = document.getElementById(canvasId); context = canvas.getContext('2d');
Para enlazar la webcam con el elemento vídeo antes hay que saber que webcam es ya que un dispositivo puede tener varias, por ejemplo un móvil tiene frontal y trasera. Para saber que dispositivos hay puedes usar la API navigator.mediaDevices.enumerateDevices . Una vez seleccionado el dispositivo que quieras usar se emplea el metodo getUserMedia() pasandole una estructura del tipo MediaStreamConstraints donde describes los requisitos que necesitas que tenga el dispositivo. En nuestro caso va ser mas simple vamos a contemplar solo la cámara frontal, la trasera y la por defecto.
//Cámara frontal device.video = {facingMode: "user", deviceId: ""}; //Cámara trasera device.video = {facingMode: "environment", deviceId: ""}; //Cámara por defecto (frontal en los móviles) device.video = {deviceId: "default"};
También podemos desear ajustar la resolución:
device.video.width = 320; device.video.height = 240;
y el framerate.
this.configuration.framerate = 25;
Hay que decir que realmente estas condiciones no obligan que devolver un dispositivo que las cumpla, solo aconsejan que lo sea, así que no puedes confiar que la resolución sea la deseada y debes de verificarlos.
navigator.mediaDevices.getUserMedia(device) .then( function(stream) { ...} ).catch(...)
Una vez conectada a la webcam hemos de conectar esta con el elemento vídeo de la web para ello usamos window.URL.createObjectURL :
navigator.mediaDevices.getUserMedia(device) .then( function(stream) { var src = window.URL.createObjectURL(stream); video.src = src; } ).catch( function(e) { console.log("Video error: "+e); } );
Y por ultimo cada cierto tiempo hemos de capturar el frame que se esta mostrando en el video y volcarlo al canvas, desde donde podremos acceder a todos sus pixeles. El proceso es muy sencillo, tan sencillo como copiarla al canvas usando el método drawimage
canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.context.drawImage(video, 0, 0, this.video.videoWidth, this.video.videoHeight);
Lo mismo con videoToCanvas
Para hacer todo esto más sencillo se ha creado la librería videoToCanvas
//id del tag canvas y video var v2c = new VideoToCanvas("canvas", "video"); //por defecto es 320x240 v2c.configuration.width=640; v2c.configuration.height=480; v2c.webcam();
Para realizar la captura al canvas es tan simple como:
v2c.snap();
Ademas cuenta con gran cantidad de funciones para controlar la reproducción del vídeo de la cámara o para cargar en su lugar un vídeo (útil para pruebas).
Acceder a los pixels de un canvas
Con esto tenemos ya el primer paso dado. Capturar la imagen. Antes de empezar a manipular los datos de un canvas tenemos que ver cómo trabajar con ellos.
Para recuperar los datos del canvas usamos la función getImageData del contexto
var imageData = canvas.context.getImageData(0, 0, width, height); var data = imageData.data;
getImageData nos permite recuperar solo parte de la imagen indicando las coordenadas X e Y de la esquina superior izquierda del rectángulo de la imagen a recuperar y el ancho y el alto. En caso de que con las medidas especificadas recuperemos parte de de fuera de la imagen se devolverán pixels negros transparentes
Usando videoToCanvas tienes dos métodos getImageData(x, y, width, height) y getBoxes(rows, cols). el primero actúa igual que el getimageData del canvas.context y devuelve un ImageData. El segundo divide la imagen en rows*cols partes y devuelve un array de ImageData. Este método es útil cuando se va a trabajar en paralelo sobre distintas partes de la imagen.
En el ImageData devuelto encontramos las siguientes propiedades:
- ImageData.height Alto de la imagen
- ImageData.width Ancho de la imagen
- ImageData.data Un array de bytes que contiene los pixels de la imagen en formato RGBA
Los píxels de la imagen se recuperan en formato RGBA. Que significa que cada píxel ocupa 4 bytes. Los 3 primeros se dedican a los componentes de color: rojo, verde y azul. El cuarto al nivel de transparencia, también llamado alpha. De esa forma data[0] corresponde al valor de la componente roja del primer píxel, data[1] a la verde, data[2] a la azul y data[3] a la alpha. Después repetimos esa misma distribución con data[4], data[5], data[6] y data[7]. Y así una y otra vez durante toda el array.
Los valores de cada pixel varían entre 0 y 255 e indican la intensidad de ese color en el pixel. Una ventaja del tipo de datos Uint8ClampedArray es que no hace falta comprobar los límites al asignarle un valor, todo valor menor que 0 se convierte a 0 y todo valor mayor de 255 se convierte a 255. Su mayor problema es que es un tipo bastante lento para operar con él así que vamos a tratar de reducir el número de operaciones sobre el mismo.