Una de las grandes ventajas de OpenCV, especialmente de las versiones superiores a las 3.0, es su interoperabilidad con infinidad de arquitecturas de deep learning ya entrenadas, y enfocadas en aplicaciones concretas de computer vision.
A pesar de que hemos redactado un número importante de tutoriales sobre deep learning en OpenCV, aún no sabemos del todo bien cómo funciona el método cv2.dnn.blobFromImage(), el cual es un requisito fundamental para poder usar redes neuronales en esta librería.
Pues, ¡adivina!
Hoy se acaba el misterio, ya que dedicaremos el resto de este artículo a entender en profundidad lo que hace cv2.dnn.blobFromImage(), y por qué es tan importante.
¡Sigue leyendo!
Al final de este artículo habrás aprendido:
Comencemos ?.
¿Para Qué Sirve cv2.dnn.blobFromImage()?
Este método tan frecuentemente visto en nuestros tutoriales de deep learning con OpenCV, en esencia, lo que hace es lo siguiente:
Esta pequeña lista de operaciones que cv2.dnn.bloblFromImage() ejecuta sobre una imagen se conoce como preprocesamiento.
En pocas palabras, cv2.dnn.blobFromImage() lo que hace es poner la imagen en el estado ideal para que la red pueda realizar predicciones sobre ella. De forma más concreta, a esta imagen (o grupo de imágenes procesadas), las denominamos blob.
Sustracción de la Media
¿Recuerdas que muchas veces hemos dicho que la iluminación es uno de los factores que más afecta nuestros algoritmos de computer vision?
Bueno, este hecho se mantiene en deep learning.
¿También recuerdas que el valor o magnitud de un píxel no es más que una medida de la luminosidad o intensidad de color en ese punto?
¿Y que a medida que el valor de un píxel se aproxima a 255 (en el espacio RGB), mayor es su intensidad, y mientras más se aproxima a 0, menor es su brillo?
Si te acuerdas, fabuloso, estoy orgulloso de ti. Si no, no importa porque ya te refresqué la memoria ?.
Teniendo en cuenta estos datos, la sustracción de la media se utiliza para reducir el rango de valores de los píxeles y, en consecuencia, disminuir el impacto de los cambios de iluminación en las imágenes que componen nuestro dataset.
¿Cómo hacemos esto? Así:
- 1Calculamos el valor promedio de cada canal de color tomando en cuenta todas las imágenes de nuestro dataset.
- 2El resultado será una terna (µr, µg, µb), correspondiente a la media de los canales rojo, verde y azul, respectivamente.
- 3Restamos de cada píxel, el valor de la media correspondiente a cada canal. Es decir, la fórmula para el nuevo valor de un píxel es: Pr = Pr - µr, Pg = Pg - µg, Pb = Pb - µb.
Facilito, ¿no?
Escalado
En ocasiones queremos escalar los valores de los píxeles a un rango específico (típicamente entre 0 y 1).
En esas circunstancias, lo que podemos hacer es computar la desviación estándar (σ) del dataset, y dividir el resultado de la fórmula de la sustracción de la media entre σ.
Entonces, ¿cómo quedarían las fórmulas?
Así, mira:
¿Todo claro hasta acá? Genial, continuemos.
¡ATENTO!
Es importante mencionar que no hay una forma “estándar” de preprocesar imágenes. Dependiendo de la arquitectura, es posible que se aplique únicamente sustracción de media, o que también se escalen los datos.
De hecho, está dentro de las posibilidades que los autores de un determinado paper hayan decidido obviar cualquier tipo de preprocesamiento.
¡Es fundamental que leas las publicaciones de las arquitecturas que vayas a usar en OpenCV para saber qué técnicas de preprocesamiento aplicar!
Creación del Entorno de Desarrollo con Anaconda
Como siempre, empecemos viendo la estructura del proyecto que nos ocupa:
. ├── datasmarts │ └── blobfromimage.py ├── env.yml ├── images │ ├── boots.jpeg │ ├── car.jpg │ └── polaroid.jpg └── resources ├── bvlc_googlenet.caffemodel ├── bvlc_googlenet.prototxt └── synset_words.txt 3 directories, 8 files
Para crear el ambiente de Anaconda, corre este comando:
conda env create -f opencv-blobfromimage
Esta instrucción habrá creado un ambiente llamado opencv-blobfromimage, configurado de la siguiente manera:
Puedes activar el ambiente así:
conda activate opencv-blobfromimage
Cool, prosigamos.
Demostración de cv2.dnn.blobFromImage() en OpenCV
Abre el archivo datasmarts/blobfromimage.py e inserta estas líneas para importar las dependencias del programa:
Para poder etiquetar correctamente las imágenes de prueba con las predicciones de GoogLeNet, tenemos que cargar en memoria las categorías de ImageNet, el conjunto de datos en el que se entrenó la red:
Por completitud, he aquí una muestra del contenido de synset_words.txt:
El siguiente paso es cargar GoogLeNet, usando la ya familiar función cv2.dnn.readNetFromCaffe(), a la que debemos pasarle los correspondientes archivos prototxt y caffemodel:
Cargamos las rutas de las imágenes de prueba:
Iteramos sobre cada imagen. El primer paso consiste en cargar la imagen en memoria, y redimensionarla para que tenga un tamaño de 224x224x3, que es el volumen que espera GoogLeNet:
En el siguiente extracto procesamos la imagen usando cv2.dnn.blobFromImage, la estrella de este post. He aquí una breve descripción de cada parámetro, en orden de aparición, de izquierda a derecha:
Pasamos el blob (la imagen procesada) por la red para obtener las predicciones:
Extraemos el índice de la clase más probable:
Etiquetamos la imagen con la clase más probable y la mostramos:
Por último, limpiamos y liberamos recursos:
Para ejecutar el programa, corre este comando:
python datasmarts/blobfromimage.py
En la consola veremos la dimensión de cada blob procesado:
Blob #1: (1, 3, 224, 224)
Blob #2: (1, 3, 224, 224)
Blob #3: (1, 3, 224, 224)
Un blob, entonces, es un “batch” o lote compuesto de una sola imagen (de ahí el 1 en la tupla), de 224x224x3.
¡ATENTO!
Si quisiéramos procesar varias imágenes de una sola vez, llamaríamos a la función cv2.dnn.blobFromImages(), que recibe exactamente los mismos parámetros que su versión en singular, cv2.dnn.blobFromImage().
En pantalla veremos las imágenes de ejemplo etiquetadas con la categoría que GoogLeNet considera más probable en cada caso:
Si bien todos los resultados son técnicamente acertados, claramente un Toyota Corolla no es un carro deportivo ?.
Resumen
El día de hoy aprendimos exactamente qué hace la función cv2.dnn.blobFromImage() (y su contraparte para varias imágenes, cv2.dnn.blobFromImages()): preprocesa una imagen de la manera exacta que una red neuronal la espera para poder realizar predicciones sobre ella.
¿De qué clase de preprocesamiento hablamos?
Típicamente de:
¿Por qué tenemos que hacer esto?
Diferentes arquitecturas, ideadas por diversos equipos de trabajo, requieren distintas condiciones iniciales. Algunas redes funcionan sin ninguna clase de procesamiento previo, mientras que otros modelos son más estables cuando los píxeles de una imagen están en un rango numérico corto.
Es esta variedad la que nos obliga a leer las publicaciones de las redes que queramos usar en OpenCV, de manera que podamos sacarles el máximo provecho posible. El primer paso en estos casos es, precisamente, procesar adecuadamente las imágenes, cosa que se nos facilita enormemente gracias a la existencia de cv2.dnn.blobFromImage().
¿Qué te pareció el post de hoy? Espero que haya sido de utilidad para ti.
Como de costumbre, puedes descargar el código en el formulario de abajo:
¡Adiós!