¡Buenos días, tardes o noches, queridos estudiantes!. ¡No importa cuándo estéis leyendo esto!. ¡Pasad!. ¡Pasad!. ¡Me alegro mucho de volver a veros por esta humilde web!.
En el día de hoy, vamos a continuar con esta humilde guía de iniciación a la Visión por Computador con Processing y OpenCV comentando (muy ligeramente) dos de las operaciones que posiblemente veáis más a menudo en este tipo de programas, y que se realizan como pasos previos de acondicionamiento de la imagen en programas más complejos. Me estoy refiriendo a las operaciones de DILATACIÓN y EROSIÓN.
Para redactar este artículo me he basado en este artículo, de la documentación de OpenCV, que está muy completo y bien explicado, aunque en inglés y posiblemente en términos mucho más técnicos y densos de lo que yo tengo intención de hacer hoy con vosotros. A lo largo de nuestro trabajo, vamos a experimentar con la misma imagen que en dicho artículo, que es la que tenéis debajo del párrafo. También podéis descargarla aquí. El nombre del archivo es iCaligrafia.png (recordadlo o cambiadlo por vuestra cuenta si así lo queréis).
Operaciones de dilatación en OpenCV
¿Ya estáis listos?. ¡Muy bien!. En primer lugar, y como siempre, vamos a adjuntar la imagen iCaligrafia.png a la carpeta del Sketch mediante el menú Sketch->Añadir archivo... Si en algún momento queréis guardar el resultado de estas prácticas, y por el motivo que sea guardáis el código con distintos nombres, aseguraos siempre que antes de ejecutar un nuevo sketch hayáis adjuntado esta imagen a vuestra carpeta de trabajo.
El primer código no utilizará OpenCV. Como os he mostrado en otros artículos, empezaremos creando un objeto PImage que llamaremos letra, le asignaremos el archivo y lo imprimiremos en mitad de la pantalla. ¡Vamos allá!
//Declaramos un objeto PImage
PImage letra;
void setup(){
//El tamaño lo dejo a vuestra elección
size(800,600);
//Cargamos la imagen en el objeto letra
letra=loadImage("iCaligrafia.png");
}
void draw(){
//Dibujamos el objeto letra en mitad de la pantalla
image(letra,width/2,height/2);
}
Si así lo hiciéreis, el dios de la Informática os lo premiare con esta bella imagen:
Sencillo, ¿no es cierto?. ¡Ahora vamos a procesar esta imagen con la operación dilate() de openCV. ¡Vamos allá!. Según la documentación que os he indicado más arriba, tanto la dilatación como la erosión son operaciones basadas en la forma que aplican un elemento de estructuración orientado a eliminar ruido, por un lado, y separar partes de la imagen por luminosidad. A grandes rasgos, la dilatación consiste precisamente en dilatar las zonas mayoritarias de la imagen. Por ejemplo, si la aplicamos a nuestra i caligrafiada, podremos apreciar que nos sale un trazo mucho más grueso, dado que ha ganado espacio al dilatarse. ¡Dentro código para que lo veáis!.
//Declaramos dos objetos PIMage y el opencv encargado de procesar la imagen
PImage letra,letraDilatada;
OpenCV opencv;
void setup(){
//Asignamos un tamaño de ventana compatible con las dos imágenes
size(300,150);
letra=loadImage("iCaligrafia.png");
//El objeto opencv se inicia en base al objeto PImage original
//(necesitamos una imagen sobre la que empezar)
opencv=new OpenCV(this,letra);
}
void draw(){
//Cargamos la imagen en opencv y aplicamos la operación
opencv.loadImage(letra);
opencv.dilate();
//Asignamos el resultado al segundo objeto PImage
letraDilatada=opencv.getSnapshot();
//Colocamos ambas imágenes juntas para apreciar el contraste
image(letra,0,0);
image(letraDilatada,150,0);
}
Y el resultado sería más o menos así:
¿Se pueden repetir estas operaciones con el objeto de aumentar su efecto?. ¡Claro que sí!. ¡Permitid que os muestre otro ejemplo! (Y ya de paso afinamos un poquito el tamaño de ventana y la colocación de imágenes):
import gab.opencv.*;
//El nuevo objeto PImage recogerá el efecto acumulado de dilatación
PImage letra,letraDilatada,letraDilatada2;
OpenCV opencv;
void setup(){
size(224,300);
letra=loadImage("iCaligrafia.png");
opencv=new OpenCV(this,letra);
}
void draw(){
opencv.loadImage(letra);
opencv.dilate();
letraDilatada=opencv.getSnapshot();
image(letra,0,0);
image(letraDilatada,112,0);
//Procedemos ahora a repetir dos veces la operación de dilatación
opencv.dilate();
opencv.dilate();
//El objeto letraDilatada2 recogerá dicho efecto
letraDilatada2=opencv.getSnapshot();
image(letraDilatada2,56,150);
}
Y aquí (por supuesto) el efecto logrado:
Creo que no puedo añadir nada a la imagen. Y dicho esto, pregunta: ¿qué efecto tendrá la operación de erosión?. ¡Exacto!. ¡Justo lo contrario!.
Operaciones de erosión en OpenCV
La operación de erosión realizará el proceso contrario: jugando con algoritmos de saturación y contraste, identificará formas cerradas y procederá a adelgazarlas erosionando sus bordes. Para no haceros perder más tiempo (me imagino que ya váis cogiéndole el tranquillo al asunto), pasaremos directamente al código (los objetos PImage letraErosionada y letraErosionada2 recogerán, respectivamente, el resultado de realizar la operación erode() a la imagen una y tres veces
import gab.opencv.*;
PImage letra,letraErosionada,letraErosionada2;
OpenCV opencv;
void setup(){
size(224,300);
letra=loadImage("iCaligrafia.png");
opencv=new OpenCV(this,letra);
}
void draw(){
opencv.loadImage(letra);
opencv.erode();
letraErosionada=opencv.getSnapshot();
image(letra,0,0);
image(letraErosionada,112,0);
opencv.erode();
opencv.erode();
letraErosionada2=opencv.getSnapshot();
image(letraErosionada2,56,150);
}
¿Resultado?. ¡Dentro imagen!
Como podéis ver, llega un momento en que la imagen original se difuminaría demasiado, pero creo que he logrado el objetivo de que entendáis cómo funciona esta operación
Otras operaciones: gray, threshold, invert.
Mi intención para este (mini) cursillo de OpenCV y Processing es, poco a poco, ir pasando por cada una de las principales operaciones de acondicionamiento de imagen que esta utilísima librería nos facilita, a medida que vayamos proponiendo distintos ejercicios. Sin embargo, puede ser interesante no cerrar este artículo sin ver, al menos por encima, alguna otra operación de preprocesado. Empezaremos por invert(), que lo que hace es precisamente invertir las tonalidades. Creo que lo más rápido sigue siendo mostraros el código para que lo ejecutéis:
import gab.opencv.*;
PImage letra,letraInvertida;
OpenCV opencv;
void setup(){
size(224,300);
letra=loadImage("iCaligrafia.png");
opencv=new OpenCV(this,letra);
}
void draw(){
opencv.loadImage(letra);
opencv.invert();
letraInvertida=opencv.getSnapshot();
image(letra,0,0);
image(letraInvertida,112,0);
}
Y de nuevo el resultado:
Para acabar, y para no ser pesado, vamos a explicar dos operaciones más que os pueden resultar útiles en OpenCV: la función gray() (que como ya os podéis imaginar, se limita a transformar cualquier imagen a tonos de gris) y la función threshold() (que en inglés significa umbral y lo que va a hacer es imprimir sólo aquellos píxeles cuya luminosidad pasen del valor que hayamos establecido como frontera).
Aquí os dejo el último código para que lo probéis y modifiquéis a vuestro gusto. Creo que de los comentarios podréis extraer la información que necesitéis, pero si no es así, hacédmelo saber.
//Importamos librerías
import processing.video.*;
import gab.opencv.*;
//Declaramos objetos video(webcam) y opencv (gestión de imágenes)
Capture video;
OpenCV opencv;
//Vamos a realizar tres transformaciones: gray, threshold(50) y threshold(150)
PImage gris,umbral,umbral2;
//Configuración
void setup(){
size(800,600);
video=new Capture(this,640,480);
opencv=new OpenCV(this,640,480);
//Iniciamos video
video.start();
}
//En esta función cíclica, opencv carga la imagen del objeto video y lo pasa por pantalla
void draw(){
//La primera imagen es el video en crudo (tomado del objeto capture video)
image(video,0,0);
//Primera transformación a gris
opencv.loadImage(video);
opencv.gray();
gris=opencv.getSnapshot();
//Segunda transformación (hay que refrescar el objeto opencv) a umbral débil
opencv.loadImage(video);
opencv.threshold(50);
umbral=opencv.getSnapshot();
//Tercera transformación (refrescamos opencv y aplicamos umbral fuerte)
opencv.loadImage(video);
opencv.threshold(150);
umbral2=opencv.getSnapshot();
//El comando resize nos permite cambiar la resolución de cada imagen
gris.resize(400,300);
umbral.resize(400,300);
umbral2.resize(400,300);
//Colocamos las tres imágenes retocadas
image(gris,400,0);
image(umbral,400,300);
image(umbral2,0,300);
}
//Esta función se ejecuta automáticamente a cada nueva lectura de la webcam
void captureEvent(Capture c) {
c.read();
}
Os paso a continuación la última imagen de esta lección. ¡No os riáis, que la he tomado con propósitos pedagógicos!.
¡Ups!. ¡Hora de comer!. Seguiremos en otra ocasión!.
¡Ya me llaman para ayudar a poner la mesa!. Y es que son ya las tres pasadas. ¡Me he divertido mucho explicándoos estas cosas!. Espero de corazón que os resulten de cierta utilidad. Para cualquier comentario, duda o aclaración, ya sabéis que ahora mismo mis canales más activos son los comentarios de la web y mi perfil de Twitter. ¡Nos vemos en cualquier otro momento!. Mientras tanto, ¡sed felices!. ¡Siempre creciendo!. ¡Siempre aprendiendo!. ¡Cultura maker!.