Una LUT o “Look Up Table” (ver apartado “Lookup tables in image processing” aquí) es un procedimiento por el cual podemos variar las características (pseudocolorear) de una imagen, sea estática o dinámica como en el caso de una webcam. En definitiva se retocan sus píxeles uno a uno. Se trata de recorrerlos todos, obtener su valor y, precisamente en base a la LUT que diseñemos, retocarlo de manera que pase a ser diferente. Las LUTs se utilizan en todos los programas de edición y retoque fotográfico e incluso en algunas cámaras digitales para aplicar filtros a nuestras imágenes: pasarla de color a blanco y negro, pasarla a tono sepia, variar su brillo, etc. También se han empleado para “colorear” películas que originalmente se filmaron en blanco y negro o para aplicar tonalidades de colores distintas en mapas del tiempo y en función de la temperatura (rojo para el calor, verde para zonas neutras y azul para el frío).
En nuestro caso 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) Aplicará una LUT sobre ellas que además variará en función de la tecla que hayamos pulsado (0, 1, 2, 3 o 4). En la figura 1 podemos entenderlo mejor.
![webcam](http://mosaic.uoc.edu/wp-content/uploads/2013/04/webcam-450x320.png)
Figura 1. Dependiendo de qué tecla pulsemos obtendremos diferentes efectos en nuestro streaming de video. De izquierda a derecha se muestran los efectos: original, inversión de rojos y ruido blanco aleatorio.
Las opciones de que disponemos son:
- Pulsando la tecla 0: volver a la imagen de video original.
- Pulsando la tecla 1: LUT que enfatiza los tonos rojos y “cyan”.
- Pulsando la tecla 2: LUT que enfatiza los tonos verdes extremos y lilas.
- Pulsando la tecla 3: LUT que enfatiza los tonos azules y amarillos.
- Pulsando la tecla 4: generamos una imagen en movimiento de ruido blanco aleatorio (como cuando en un televisor no tenemos un canal correctamente sintonizado).
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;
// We define a mode variable to control
// the video rendering from 5 possibilities
// mode can be 0,1,2,3 or 4
int mode = 0;
// Our setup function to be executed once we begin
void setup() {
// The sizw 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);
}
Código 1. Inicialización de la carga de video en nuestro programa.
Por una parte creamos una variable para controlar cual de las dos LUTs queremos aplicar (mode). Además importamos 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).
El acceso a cada uno de los píxeles de las imágenes sucesivas así como la aplicación de las distintas LUTs 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 get the actual frame and copy it into an array (frameArray)
int[] frameArray = myStream.image();
// We will need to access the RGB properties of every pixel in frameArray
float r,g,b;
// We draw the pixels for the current frame to the screen
loadPixels();
// We loop all over our frameArray
for (int i = 0; i < width*height; i++){
// For every pixel, we get its RGB components
r = red(frameArray[i]);
g = green(frameArray[i]);
b = blue(frameArray[i]);
// Now it's time to apply our special effects menu
// 5 different modes (0,1,2,3 and 4)
// 0 = original image
// 1 = inverting the red component
// 2 = inverting the green component
// 3 = inverting the blue component
// 4 = a random image! unrelated to the one coming from the webcam
if (mode == 1) r = 255 - r;
else if (mode == 2) g = 255 - g;
else if (mode == 3) b = 255 - b;
else if (mode == 4){
r = random(255);
g = random(255);
b = random(255);
}
// We save our pseudocolored image
pixels[i] = color(r,g,b);
}
// We update the image to appear in the canvas
updatePixels();
// Some white text to inform user about switching between modes
String s = "Toggle rendering modes by pressing 0,1,2,3 or 4";
fill(255);
text(s, 10, 370, 200, 40);
}
Código 2. Aplicación de las LUTs 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 cargamos los píxeles de la imagen a memoria para poderlos acceder (loadPixels()). 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 PImage. De todas formas en nuestro caso iteraremos a lo largo de todo el array frameArray y aplicaremos la misma condición a cada píxel. Iteramos vía una estructura de control de bucle tipo FOR que recorre todo el largo del array. Su dimensión iguala a la resolución de la imagen, es decir su ancho por su alto (width*height). Una vez dentro del bucle podemos considerar que estamos iterando píxel a píxel. Obtenemos sus componentes de color RGB (r = red(frameArray[i]), g = green(frameArray[i]) y b = blue(frameArray[i])) y dependiendo del modo activo (mode) aplicamos una u otra LUT, gracias a un condicional de tipo IF:
- Si se pulsó la tecla 0: no hacemos nada dado que deseamos mantener o volver a la imagen de video original.
- Si se pulsó la tecla 1: invertimos el canal rojo (r = 255 – r) para nuestra LUT que enfatiza los tonos rojos y “cyan”.
- Si se pulsó la tecla 2: invertimos el canal verde (g = 255 – g) para nuestra LUT que enfatiza los tonos verdes extremos y lilas.
- Si se pulsó la tecla 3: invertimos el canal azul (g = 255 – g) para nuestra LUT que enfatiza los tonos azules y amarillos.
- Si se pulsó la tecla 4: generamos componentes aleatorias para nuestro píxel gracias a la función random.
Hecho esto salvamos el “nuevo” píxel a partir de un color que generamos con las tres componentes (pixels[i] = color(r,g,b)). Una vez se recorre toda la imagen, terminamos el bucle y actualizamos todos los píxeles a la imagen para después mostrar un texto informativo en color blanco (fill(255)) y gracias a la función text().
Por último y como observamos en el código 3, hacemos dos cosas:
- 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()).
- Mediante el evento/callback de teclas de Processing (keyPressed()), detectamos aquella que presiona el usuario y actualizamos la variable mode en consecuencia.
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();
}
// We toggle between modes depending on the pressed key
void keyPressed(){
if (key == '0') mode = 0;
if (key == '1') mode = 1;
if (key == '2') mode = 2;
if (key == '3') mode = 3;
if (key == '4') mode = 4;
}
Código 3. Método de parada de la librería y gestión de teclado.
// 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;
// We define a mode variable to control
// the video rendering from 5 possibilities
// mode can be 0,1,2,3 or 4
int mode = 0;
// Our setup function to be executed once we begin
void setup() {
// The sizw 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);
}
// the infinite loop
void draw() {
// We ask our myStream object to provide with a new frame
myStream.update();
// We get the actual frame and copy it into an array (frameArray)
int[] frameArray = myStream.image();
// We will need to access the RGB properties of every pixel in frameArray
float r,g,b;
// We draw the pixels for the current frame to the screen
loadPixels();
// We loop all over our frameArray
for (int i = 0; i < width*height; i++){
// For every pixel, we get its RGB components
r = red(frameArray[i]);
g = green(frameArray[i]);
b = blue(frameArray[i]);
// Now it's time to apply our special effects menu
// 5 different modes (0,1,2,3 and 4)
// 0 = original image
// 1 = inverting the red component
// 2 = inverting the green component
// 3 = inverting the blue component
// 4 = a random image! unrelated to the one coming from the webcam
if (mode == 1) r = 255 - r;
else if (mode == 2) g = 255 - g;
else if (mode == 3) b = 255 - b;
else if (mode == 4){
r = random(255);
g = random(255);
b = random(255);
}
// We save our pseudocolored image
pixels[i] = color(r,g,b);
}
// We update the image to appear in the canvas
updatePixels();
// Some white text to inform user about switching between modes
String s = "Toggle rendering modes by pressing 0,1,2,3 or 4";
fill(255);
text(s, 10, 370, 200, 40);
}
// The Myron library demands for a specific method to stop
public void stop() {
myStream.stop();
super.stop();
}
// We toggle between modes depending on the pressed key
void keyPressed(){
if (key == '0') mode = 0;
if (key == '1') mode = 1;
if (key == '2') mode = 2;
if (key == '3') mode = 3;
if (key == '4') mode = 4;
}
Código 4. Programa completo de aplicación de LUTs en nuestras imágenes de vídeo.
Deja un comentario