OpenCV es una biblioteca con una historia de desarrollo continuo en 20 años. La edad cuando comienzas a cavar en ti mismo, buscando un destino. ¿Hay algún proyecto basado en él que haya mejorado la vida de alguien, alguien más feliz? ¿Puedes hacerlo tú mismo? En busca de respuestas y un deseo de descubrir módulos OpenCV previamente desconocidos, quiero crear aplicaciones que "funcionen maravillosamente", de modo que al principio haya "wow" y solo entonces diga "oh sí, es visión por computadora".
El derecho del primer artículo fue un experimento con la transferencia de estilos de artistas mundiales en fotografía. Del artículo aprenderá cuál es el corazón del procedimiento y sobre la relativamente nueva versión de OpenCV.js - JavaScript de la biblioteca OpenCV.

Transferencia de estilo
Los opositores al aprendizaje automático me perdonarán, pero el componente principal del artículo de hoy será una red convolucional profunda. Porque funciona No hay forma de entrenar redes neuronales en OpenCV, pero puede ejecutar modelos existentes. Utilizaremos la red CycleGAN previamente capacitada . Los autores, por los cuales están muy agradecidos, ofrecen redes completamente gratuitas para descargar que convierten imágenes de manzanas en naranjas, caballos en cebras, imágenes satelitales en mapas, fotos de invierno en fotos de verano y mucho más. Además, el procedimiento de capacitación en red le permite tener dos modelos de generador trabajando en ambas direcciones a la vez. Es decir, al enseñar la transformación del invierno en verano, obtendrá un modelo para pintar paisajes de invierno en fotografías de verano. Una oferta única que es imposible de rechazar.
En nuestro ejemplo, tomamos modelos que convierten fotos en pinturas de artistas. A saber, Vincent Van Gogh, Claude Monet, Paul Cezanne o en todo el género de estampas japonesas Ukiyo-e. Es decir, tendremos cuatro redes separadas a nuestra disposición. Vale la pena señalar que para el entrenamiento de cada uno se utilizó no una imagen del artista, sino una multitud completa, por lo que los autores trataron de entrenar la red neuronal para no cambiar el estilo de una obra, sino, por así decirlo, adoptar el estilo de escritura.
Opencv.js
OpenCV es una biblioteca desarrollada en C ++, mientras que para la mayor parte de su funcionalidad existe la posibilidad de crear envoltorios automáticos que llaman métodos nativos. Oficialmente, se admiten envoltorios para los lenguajes Python y Java. Además, hay soluciones personalizadas para Go , PHP . Si tiene experiencia en el uso de otros idiomas, sería bueno saber en qué y gracias a qué esfuerzos.
OpenCV.js es un proyecto que se ha ganado el derecho a la vida gracias al programa Google Summer of Code en 2017. Por cierto, una vez que el módulo de aprendizaje profundo OpenCV fue creado y mejorado significativamente en su marco. A diferencia de otros lenguajes, OpenCV.js en este momento no es un contenedor de métodos nativos en JavaScript, sino una compilación completa usando Emscripten usando LLVM y Clang. Le permite crear un archivo desde su aplicación C y C ++ o biblioteca .js
que se puede ejecutar, por ejemplo, en un navegador.
Por ejemplo,
#include <iostream> int main(int argc, char** argv) { std::cout << "Hello, world!" << std::endl; return 0; }
Compilando en asm.js
emcc main.cpp -s WASM=0 -o main.js
Y carga:
<!DOCTYPE html> <html> <head> <script src="main.js" type="text/javascript"></script> </head> </html>

Puede conectar OpenCV.js a su proyecto de la siguiente manera (compilación nocturna):
<script src="https://docs.opencv.org/master/opencv.js" type="text/javascript"></script>
También puede ser útil una biblioteca adicional para leer imágenes, trabajar con la cámara y otras cosas, que está escrita manualmente en JavaScript:
<script src="https://docs.opencv.org/master/utils.js" type="text/javascript"></script>
Subir imágenes
Las imágenes en OpenCV.js se pueden leer desde elementos como canvas
o img
. Esto significa que descargar archivos de imagen directamente a ellos sigue siendo tarea del usuario. Por conveniencia, la función auxiliar addFileInputHandler
cargará automáticamente la imagen en el elemento de canvas
deseado cuando se selecciona una imagen del disco con solo hacer clic en un botón.
var utils = new Utils(''); utils.addFileInputHandler('fileInput', 'canvasInput'); var img = cv.imread('canvasInput');
donde
<input type="file" id="fileInput" name="file" accept="image/*" /> <canvas id="canvasInput" ></canvas>
El punto importante es que img
será una imagen RGBA de 4 canales, que es diferente del cv::imread
habitual cv::imread
, que crea una imagen BGR. Esto debe tenerse en cuenta, por ejemplo, al portar algoritmos de otros idiomas.
Con el renderizado, todo es simple: solo llame a imshow
con la id
canvas
deseado (espera RGB o RGBA).
cv.imshow("canvasOutput", img);
Algoritmo
Todo el algoritmo de procesamiento de imágenes es el lanzamiento de una red neuronal. Supongamos que lo que sucede dentro sigue siendo mágico, solo necesitamos preparar la entrada correcta e interpretar la predicción correctamente (salida de red).
La red considerada en este ejemplo recibe un tensor de cuatro dimensiones con valores float
en el intervalo [-1, 1]
. Cada una de las dimensiones, en orden de velocidad de cambio, es el índice de la imagen, los canales, la altura y el ancho. Este estilo se llama NCHW, y el tensor en sí se llama blob, objeto binario grande. La tarea de preprocesamiento es convertir una imagen OpenCV, cuyas intensidades están intercaladas, tienen un intervalo de valores [0, 255]
tipo unsigned char
en un blob NCHW con un rango de valores [-1, 1]
.

un pedazo del Kremlin de Nizhny Novgorod (como ve una persona)

vista intercalada (cómo se almacena OpenCV)

vista plana (lo que necesita la red)
Como postprocesamiento, será necesario realizar las transformaciones inversas: la red devuelve un blob NCHW con valores en el intervalo [-1, 1]
, que deben volverse a empaquetar en la imagen, normalizar a [0, 255]
y convertir a caracteres unsigned char
.
Por lo tanto, teniendo en cuenta todas las características de lectura y escritura de imágenes OpenCV.js, los siguientes pasos están tomando forma:
imread -> RGBA -> BGR [0, 255] -> NCHW [-1, 1] -> [] [] -> NCHW [-1, 1] -> RGB [0, 255] -> imshow
Al observar la tubería resultante, surgen preguntas, ¿por qué la red no puede funcionar inmediatamente en RGBA intercalado y devolver RGB intercalado? ¿Por qué se necesitan transformaciones adicionales para la permutación y normalización de píxeles? La respuesta es que una red neuronal es un objeto matemático que realiza transformaciones en los datos de entrada de una determinada distribución. En nuestro caso, fue entrenada para recibir datos de esta forma, por lo tanto, para obtener los resultados deseados, es necesario reproducir el preprocesamiento que los autores utilizaron en la capacitación.
Implementación
La red neuronal que ejecutaremos se almacena en forma de un archivo binario, que primero debe cargarse en el sistema de archivos local.
var net; var url = 'style_vangogh.t7'; utils.createFileFromUrl('style_vangogh.t7', url, () => { net = cv.readNet('style_vangogh.t7'); });
Por cierto, url
es un enlace completo al archivo. En este caso, solo cargamos el archivo junto a la página HTML actual, pero puede reemplazarlo con la fuente original (en este caso, el tiempo de descarga puede ser más largo).
Lectura de una imagen del canvas
y conversión de RGBA a BGR:
var imgRGBA = cv.imread('canvasInput'); var imgBGR = new cv.Mat(imgRGBA.rows, imgRGBA.cols, cv.CV_8UC3); cv.cvtColor(imgRGBA, imgBGR, cv.COLOR_RGBA2BGR);
Crear un blob 4D donde la función blobFromImage
convierte en un float
datos float
utilizando constantes de normalización. Luego, inicie la red.
var blob = cv.blobFromImage(imgBGR, 1.0 / 127.5,
El resultado se convierte de nuevo a la imagen del tipo deseado y el intervalo de valores [0, 255]
Por el momento, OpenCV.js se está construyendo en modo semiautomático. En el sentido de que no todos los módulos y métodos de ellos reciben las firmas correspondientes en JavaScript. Por ejemplo, para un módulo dnn, la lista de funciones válidas se define de la siguiente manera:
dnn = {'dnn_Net': ['setInput', 'forward'], '': ['readNetFromCaffe', 'readNetFromTensorflow', 'readNetFromTorch', 'readNetFromDarknet', 'readNetFromONNX', 'readNet', 'blobFromImage']}
La última conversión, dividiendo el blob en tres canales y luego mezclándolos en una imagen, de hecho, se puede hacer con un método imagesFromBlob
, que aún no se ha agregado a la lista anterior. ¿Quizás esta sea su primera contribución al desarrollo de OpenCV? ;)
Conclusión
Como demostración, preparé una página en GitHub donde puede probar el código resultante: https://dkurtaev.imtqy.com/opencv4arts (¡Precaución! Descargar una red de aproximadamente 22 MB, guardar su tráfico. También se recomienda volver a cargar la página para cada nueva imagen, de lo contrario, la calidad el procesamiento posterior de alguna manera está fuertemente distorsionado). Esté preparado para un proceso de procesamiento largo o intente cambiar el tamaño de la imagen, que será el resultado, un control deslizante.
Mientras trabajaba en el artículo y elegía la imagen que se convertiría en su rostro, accidentalmente encontré una foto de mi amiga, que representa el Kremlin de nuestra ciudad y todo se unió, se me ocurrió el nombre del artículo y solo entonces sentí que debería ser así. Le sugiero que pruebe la aplicación en fotos de su lugar favorito y, tal vez, diga algo interesante al respecto en los comentarios o en una carta personal.
De mí, un hecho divertido. La mayoría de los residentes de Nizhny Novgorod y la región de Nizhny Novgorod usan la palabra "salir" en el sentido de la palabra "encajar" (encontrar un lugar libre). Por ejemplo, la pregunta "¿Limpiaremos su automóvil?" significa "¿Tenemos suficiente espacio en su automóvil?", pero no "¿Podemos limpiar su automóvil?". Cuando los estudiantes de otras áreas acuden a nosotros para realizar pasantías de verano, nos encanta contar este hecho: muchos se sorprenden sinceramente.
Enlaces utiles