Realizaremos un programa Processing que gracias a la librería Myron a) Nos permitirá disponer de streaming de video en tiempo real vía nuestra webcam, b) Podrá acceder a cada una de las imágenes del streaming y c) Sustituirá los píxeles de la imagen por círculos, discretizándola aún más si cabe. Todo en tiempo real. Algo parecido, dependiendo del radio de éstos, al efecto de “Pointillism” o Puntillismo. En la figura 1 podemos observarlo.
Figura 1. Diferentes resoluciones de discretización de nuestras imágenes de vídeo.
Es importante tener en cuenta que para asegurar el correcto funcionamiento de la librería Myron de acceso a webcam y reproducción de video, tenemos que colocar los ficheros .DLL que la conforman dentro del directorio Processing de nuestro ordenador. Se trata de los ficheros DSVL.dll y myron_ezcam.dll.
Empecemos por aprender cómo se carga video, gracias al código 1.
// We will access our webcam
// thanks to the Myron library
// available at http://webcamxtra.sourceforge.net/
// Therefore we import it
import JMyron.*;
// We create a JMyron object to manage the video streaming
// that comes from our webcam
JMyron myStream;
// Our setup function to be executed once we begin
void setup(){
// The size of our canvas equals the optimal resolution
// of our webcam (check out yours to avoid flickering!)
size(640, 400);
// We initialize the myStream object
// and we start streaming from our webcam
myStream = new JMyron();
myStream.start(width, height);
// No strokes for our little circular objects
noStroke();
}
Código 1. Inicialización de la carga de video en nuestro programa.
Empezamos importando la librería Myron para poder acceder a sus funciones mediante la sentencia import JMyron.* y creamos un objeto de tipo JMyron que dará soporte a nuestro streaming de video por parte de la webcam (myStream). La función de inicialización setup() crea una ventana de 640 x 400 píxeles porque esa es la resolución de trabajo óptima para la webcam que se ha utilizado para programar este ejemplo por mi parte. Es importante que para vuestro caso concreto os informéis sobre la resolución óptima de vuestro periférico y actuéis en consecuencia. En caso contrario podríais estar trabajando con resoluciones forzadas o no admitidas que inestabilizarían el programa y podrían causar problemas de “flickering” (parpadeo) como se nos comenta aquí.
Inicializamos nuestro objeto de video myStream y a continuación avisamos al sistema de que puede iniciarse la adquisición de video por parte de la webcam (myStream.start(width, height)) a una resolución idéntica a la definida para nuestra ventana (el video la ocupará por entero). Por último desactivamos el color para los bordes (noStroke()).
El acceso a cada uno de los píxeles de las imágenes sucesivas así como la aplicación del efecto de discretización se observa dentro de la función draw(), en el código 2.
// the infinite loop
void draw(){
// We ask our myStream object to provide with a new frame
myStream.update();
// We define some variables.
// Note that we store the current webcam image inside an array called frameArray
int[] frameArray = myStream.image();
int pixelLocation;
float r,g,b;
int ellipseMagnitude = 15;
// We loop all over the pixels inside the captured image
for (int y=0; y < height; y+=15){
for (int x=0; x < width; x+=15){
// Evaluating the 2D coordinates of the current pixel
pixelLocation = x + y*width;
// Retrieving the RGB color of the current pixel
r = red(frameArray[pixelLocation]);
g = green(frameArray[pixelLocation]);
b = blue(frameArray[pixelLocation]);
// We will substitute the current pixel
// by a circle (same color)
// ellipseMagnitude controls the magnitude of the circle
fill(r,g,b,150);
ellipse(x,y,ellipseMagnitude,ellipseMagnitude);
}
}
}
Código 2. Aplicación del efecto de discretización a nuestras imágenes de video.
Iniciamos actualizando el streaming de video, es decir “pidiendo” una nueva imagen (myStream.update()) que almacenamos convenientemente en un array de enteros que declaramos a tal efecto (int[] frameArray = myStream.image()). Además, definimos tres variables reales, es decir que contendrán datos numéricos con decimales, para almacenar las componentes RGB de cada uno de los píxeles que modifiquemos (float r,g,b) y el diámetro que deseamos para nuestros círculos (ellipseMagnitude). Es importante tener en cuenta que cuando Processing carga una imagen no la almacena bidimensionalmente, es decir teniendo en cuenta el ancho por el alto, sino que lo hace unidimensionalmente. Se trata en definitiva de un array muy largo donde cada posición almacena el color de un píxel. Lee las sucesivas filas de la imagen y las va concatenando, tal y como podéis observar en el apartado “Pixels, pixels, and more pixels” de este tutorial. Eso implica que para acceder a un píxel en concreto de nuestra imagen, tenemos que calcular su localización dentro del objeto. En este ejemplo necesitamos asociar un círculo a la ubicación física de cada pixel y por tanto tendremos esto en cuenta. Para un píxel que tenga coordenadas XY en la imagen, su ubicación dentro del objeto se calculará como X + Y x Ancho de la Imagen. Y ese valor lo almacenaremos en la variable entera pixelLocation que definimos a tal efecto.
Hay que recorrer todos los píxeles de la imagen y hay tantos como la resolución de ésta (ancho x alto). Lo haremos con dos estructuras de control de tipo bucle FOR, una dentro de otra, de manera que gracias a la primera iteraremos por columnas (width) y gracias a la segunda por filas (height). Dentro del bucle ya podemos suponer que nos encontramos apuntando a un píxel concreto con coordenadas XY. Calculamos su ubicación dentro del objeto (pixelLocation) y obtenemos los valores de sus tres componentes de color RGB (funciones red, green y blue) para asociarlas a tres variables de nueva creación (r, g y b). Nótese que éstas son de tipo real y por tanto con decimales (float) dado que las componentes de color se retornan en ese formato.
Llegados a este punto sólo nos queda fijar un círculo del mismo color que el píxel que se encuentra debajo y que actuará como su centro. Activamos el color de nuestro círculo gracias a la función fill(r,g,b,150) y lo pintamos. El círculo tiene una cierta transparencia, como puede observarse en el cuarto parámetro de la función fill(), que hace referencia al canal alfa.
Por último y como observamos en el código 3, creamos una función pública que es obligatoria para el buen funcionamiento de la librería Myron (public void stop()). Ésta se ejecutará al salir del programa y en ella se detienen tanto la trama de vídeo que se está generando vía nuestro objeto (myStream.stop()) como el proceso en sí (super.stop()).
A continuación se muestran los códigos 3, al que nos referíamos en el último párrafo, así como el código 4 que contiene todo el programa funcional.
// The Myron library demands for a specific method to stop
public void stop() {
myStream.stop();
super.stop();
}
Código 3. Método de parada de la librería.
// We will access our webcam
// thanks to the Myron library
// available at http://webcamxtra.sourceforge.net/
// Therefore we import it
import JMyron.*;
// We create a JMyron object to manage the video streaming
// that comes from our webcam
JMyron myStream;
// Our setup function to be executed once we begin
void setup(){
// The size of our canvas equals the optimal resolution
// of our webcam (check out yours to avoid flickering!)
size(640, 400);
// We initialize the myStream object
// and we start streaming from our webcam
myStream = new JMyron();
myStream.start(width, height);
// No strokes for our little circular objects
noStroke();
}
// the infinite loop
void draw(){
// We ask our myStream object to provide with a new frame
myStream.update();
// We define some variables.
// Note that we store the current webcam image inside an array called frameArray
int[] frameArray = myStream.image();
int pixelLocation;
float r,g,b;
int ellipseMagnitude = 15;
// We loop all over the pixels inside the captured image
for (int y=0; y < height; y+=15){
for (int x=0; x < width; x+=15){
// Evaluating the 2D coordinates of the current pixel
pixelLocation = x + y*width;
// Retrieving the RGB color of the current pixel
r = red(frameArray[pixelLocation]);
g = green(frameArray[pixelLocation]);
b = blue(frameArray[pixelLocation]);
// We will substitute the current pixel
// by a circle (same color)
// ellipseMagnitude controls the magnitude of the circle
fill(r,g,b,150);
ellipse(x,y,ellipseMagnitude,ellipseMagnitude);
}
}
}
// The Myron library demands for a specific method to stop
public void stop() {
myStream.stop();
super.stop();
}
Código 4. Programa completo de aplicación de discretización a nuestras imágenes de vídeo.
Hola, disculpe, usted para la correcta instalacion de la libreria Jmyron, ya que he aplicado los .dll en la carpeta System32 de mi windows y en la carpeta libraries de Mis Documentos, y aun me da el error de que no esta instalada correctamente
Al parecer funciona en versiones de 64 bits.
Creo que la librería Jmyron para emisión con webcam desde la web se ha quedado un poco obsoleta. Yo he desarrollado varios proyectos de streaming de vídeo con webcams y creo que ahora mismo lo mejor sería utilizar la librería Webrtc que está bastante de moda. ¿Qué opináis?
Saludos,
Isabel Cámaras
He de añadir respecto al comentario anterior que también hay que tener muy en cuenta los recursos de servidor. Ahora mismo el flash está consumiendo como 3 veces más recursos que webrtc, entonces es otro punto a valorar también.
Saludos,
Isabel Cámaras
Lo Mejor para streaming por webcam es el WebRTC. El WebRTC, es un proyecto de código abierto desarrollado y liberado por Google para la integración de texto, llamadas de voz, chat de vídeo y uso compartido de archivos P2P sin plugins directamente desde el navegador. Ademas el WebRTC en sus sistemas de mensajería reduce los consumos del servidor. Todo ello conlleva a la socialización de los diversos estándares que hacen posible
la comunicación en tiempo real, recursos que actualmente son proporcionados por el software libre como WebRTC, Node.js, WebSocket, JavaScript, CSS3 y HTML5. Saludos!