opencv4arts: Dessine ma ville, Vincent

OpenCV est une bibliothèque avec une histoire de développement continu depuis 20 ans. L'âge où vous commencez à creuser en vous-même, à la recherche d'une destination. Y a-t-il des projets basés sur cela qui ont rendu la vie de quelqu'un meilleure, plus heureuse? Pouvez-vous le faire vous-même? En cherchant des réponses et en voulant découvrir des modules OpenCV jusque-là inconnus, je veux construire des applications qui "font magnifiquement" - pour qu'au début il y ait "wow" et ensuite seulement vous dites "oh oui, c'est la vision par ordinateur".


Le droit du premier article était une expérience avec le transfert de styles d'artistes mondiaux en photographie. À partir de l'article, vous apprendrez ce qui est au cœur de la procédure et à propos de la version relativement récente OpenCV.js - JavaScript de la bibliothèque OpenCV.



Transfert de style


Les opposants à l'apprentissage automatique me pardonneront, mais le principal élément de l'article d'aujourd'hui sera un réseau convolutionnel profond. Parce que ça marche. Il n'y a aucun moyen de former des réseaux de neurones dans OpenCV, mais vous pouvez exécuter des modèles existants. Nous utiliserons le réseau CycleGAN pré- formé . Les auteurs, pour lesquels ils sont très reconnaissants, offrent des réseaux de téléchargement entièrement gratuits qui convertissent des images de pommes en oranges, de chevaux en zèbres, des images satellites en cartes, des photos d'hiver en photos d'été, et bien plus encore. De plus, la procédure de formation réseau vous permet d'avoir deux modèles de générateur fonctionnant dans les deux sens à la fois. Autrement dit, en enseignant la transformation de l'hiver en été, vous obtiendrez un modèle pour peindre des paysages d'hiver dans des photographies d'été. Une offre unique impossible à refuser.


Dans notre exemple, nous prenons des modèles qui transforment des photos en peintures d'artistes. A savoir, Vincent Van Gogh, Claude Monet, Paul Cézanne ou dans le genre entier des estampes japonaises Ukiyo-e. Autrement dit, nous aurons quatre réseaux distincts à notre disposition. Il convient de noter que pour la formation de chacun a été utilisé non pas une image de l'artiste, mais une multitude entière, ainsi les auteurs ont essayé de former le réseau neuronal non pas pour changer le style d'une œuvre, mais pour ainsi dire pour adopter le style d'écriture.


Opencv.js


OpenCV est une bibliothèque développée en C ++, tandis que pour la plupart de ses fonctionnalités, il existe la possibilité de créer des wrappers automatiques qui appellent des méthodes natives. Officiellement, les wrappers pour les langages Python et Java sont pris en charge. De plus, il existe des solutions personnalisées pour Go , PHP . Si vous avez de l'expérience avec d'autres langues, ce serait formidable de savoir dans quelles langues et grâce aux efforts de qui.


OpenCV.js est un projet qui a acquis le droit à la vie grâce au programme Google Summer of Code en 2017. Soit dit en passant, une fois que le module d'apprentissage en profondeur OpenCV lui-même a été créé et considérablement amélioré dans son cadre. Contrairement à d'autres langages, OpenCV.js pour le moment n'est pas un wrapper de méthodes natives en JavaScript, mais une compilation complète utilisant Emscripten utilisant LLVM et Clang. Il vous permet de créer un fichier à partir de votre application ou bibliothèque C et C ++ .js qui peut être exécuté, par exemple, dans un navigateur.


Pour un exemple,


 #include <iostream> int main(int argc, char** argv) { std::cout << "Hello, world!" << std::endl; return 0; } 

Compilation en asm.js


 emcc main.cpp -s WASM=0 -o main.js 

Et chargez:


 <!DOCTYPE html> <html> <head> <script src="main.js" type="text/javascript"></script> </head> </html> 

Vous pouvez connecter OpenCV.js à votre projet comme suit (build nocturne):


 <script src="https://docs.opencv.org/master/opencv.js" type="text/javascript"></script> 

Une bibliothèque supplémentaire pour lire des images, travaillant avec l'appareil photo et d'autres choses, qui est écrite manuellement en JavaScript, peut également être utile:


 <script src="https://docs.opencv.org/master/utils.js" type="text/javascript"></script> 

Télécharger des images


Les images dans OpenCV.js peuvent être lues à partir d'éléments tels que canvas ou img . Cela signifie que le téléchargement direct des fichiers image sur eux reste la tâche de l'utilisateur. Pour plus de commodité, la fonction auxiliaire addFileInputHandler charge automatiquement l'image dans l'élément de canvas souhaité lorsqu'une image est sélectionnée à partir du disque en cliquant sur un bouton.


 var utils = new Utils(''); utils.addFileInputHandler('fileInput', 'canvasInput'); var img = cv.imread('canvasInput'); 


 <input type="file" id="fileInput" name="file" accept="image/*" /> <canvas id="canvasInput" ></canvas> 

Le point important est que img sera une image RGBA à 4 canaux, qui diffère du cv::imread habituel de cv::imread , qui crée une image BGR. Cela doit être pris en compte, par exemple, lors du portage d'algorithmes à partir d'autres langues.


Avec le rendu, tout est simple - appelez simplement imshow avec l' id canvas souhaité (attend RGB ou RGBA).


 cv.imshow("canvasOutput", img); 

Algorithme


L'ensemble de l'algorithme de traitement d'image est le lancement d'un réseau neuronal. Supposons que ce qui se passe à l'intérieur reste magique, il suffit de préparer la bonne entrée et d'interpréter correctement la prédiction (sortie réseau).


Le réseau considéré dans cet exemple reçoit un tenseur à quatre dimensions avec des valeurs float dans l'intervalle [-1, 1] . Chacune des dimensions, par ordre de vitesse de changement, est l'indice de l'image, les canaux, la hauteur et la largeur. Ce style est appelé NCHW, et le tenseur lui-même est appelé un gros objet binaire blob. La tâche de prétraitement consiste à convertir une image OpenCV, dont les intensités sont entrelacées, ont un intervalle de valeurs [0, 255] type caractère unsigned char dans un blob NCHW avec une plage de valeurs [-1, 1] .



un morceau du Kremlin de Nijni Novgorod (comme une personne le voit)



vue entrelacée (comment OpenCV stocke)



vue en plan (ce dont le réseau a besoin)


En tant que post-traitement, il sera nécessaire d'effectuer les transformations inverses: le réseau renvoie un blob NCHW avec des valeurs dans l'intervalle [-1, 1] , qui doit être reconditionné dans l'image, normalisé à [0, 255] et converti en caractère unsigned char .


Ainsi, en prenant en compte toutes les fonctionnalités de lecture et d'écriture d'images OpenCV.js, les étapes suivantes prennent forme:


 imread -> RGBA -> BGR [0, 255] -> NCHW [-1, 1] -> [] [] -> NCHW [-1, 1] -> RGB [0, 255] -> imshow 

En regardant le pipeline résultant, des questions se posent, pourquoi le réseau ne peut pas fonctionner immédiatement sur RGBA entrelacé et renvoyer RGB entrelacé? Pourquoi des transformations supplémentaires sont-elles nécessaires pour la permutation et la normalisation des pixels? La réponse est qu'un réseau neuronal est un objet mathématique qui effectue des transformations sur les données d'entrée d'une certaine distribution. Dans notre cas, elle a été formée pour recevoir des données sous cette forme, donc pour obtenir les résultats souhaités, il est nécessaire de reproduire le prétraitement que les auteurs ont utilisé dans la formation.


Implémentation


Le réseau de neurones que nous allons exécuter est stocké sous la forme d'un fichier binaire, qui doit d'abord être chargé dans le système de fichiers local.


 var net; var url = 'style_vangogh.t7'; utils.createFileFromUrl('style_vangogh.t7', url, () => { net = cv.readNet('style_vangogh.t7'); }); 

Par ailleurs, l' url est un lien complet vers le fichier. Dans ce cas, nous téléchargeons simplement le fichier à côté de la page HTML actuelle, mais vous pouvez le remplacer par la source d'origine (dans ce cas, le temps de téléchargement peut être plus long).


Lecture d'une image à partir du canvas et conversion de RGBA en 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); 

Création d'un blob 4D dans lequel la fonction blobFromImage convertit en un float données float l'aide de constantes de normalisation. Ensuite - lancez le réseau.


 var blob = cv.blobFromImage(imgBGR, 1.0 / 127.5, //  {width: imgBGR.cols, height: imgBGR.rows}, //  [127.5, 127.5, 127.5, 0]); //   net.setInput(blob); var out = net.forward(); 

Le résultat est reconverti en l'image du type souhaité et l'intervalle de valeurs [0, 255]


 //     [-1, 1]  [0, 255] var outNorm = new cv.Mat(); out.convertTo(outNorm, cv.CV_8U, 127.5, 127.5); //  interleaved   planar  var outHeight = out.matSize[2]; var outWidth = out.matSize[3]; var planeSize = outHeight * outWidth; var data = outNorm.data; var b = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(0, planeSize)); var g = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(planeSize, 2 * planeSize)); var r = cv.matFromArray(outHeight, outWidth, cv.CV_8UC1, data.slice(2 * planeSize, 3 * planeSize)); var vec = new cv.MatVector(); vec.push_back(r); vec.push_back(g); vec.push_back(b); var rgb = new cv.Mat(); cv.merge(vec, rgb); //   cv.imshow("canvasOutput", rgb); 

À l'heure actuelle, OpenCV.js est en cours de construction en mode semi-automatique. En ce sens que tous les modules et méthodes ne reçoivent pas les signatures correspondantes en JavaScript. Par exemple, pour un module dnn, la liste des fonctions valides est définie comme suit:


 dnn = {'dnn_Net': ['setInput', 'forward'], '': ['readNetFromCaffe', 'readNetFromTensorflow', 'readNetFromTorch', 'readNetFromDarknet', 'readNetFromONNX', 'readNet', 'blobFromImage']} 

La dernière conversion, divisant le blob en trois canaux, puis les mélangeant dans une image, en fait, peut être effectuée à l' imagesFromBlob méthode imagesFromBlob , qui n'a tout simplement pas encore été ajoutée à la liste ci-dessus. Ce sera peut-être votre première contribution au développement d'OpenCV? ;)


Conclusion


En guise de démonstration, j'ai préparé une page sur GitHub où vous pouvez tester le code résultant: https://dkurtaev.imtqy.com/opencv4arts (Attention! Télécharger un réseau d'environ 22 Mo, économisez votre trafic. Il est également recommandé de recharger la page pour chaque nouvelle image, sinon la qualité le traitement ultérieur est en quelque sorte fortement déformé). Soyez prêt pour un long processus de traitement ou essayez de redimensionner l'image, qui sera le résultat, un curseur.


En travaillant sur l'article et en choisissant l'image même qui deviendra son visage, j'ai accidentellement trouvé une photo de mon amie, qui représente le Kremlin de notre ville et tout s'est réuni - est venu avec le nom de l'article et a seulement senti que cela devrait être comme ça. Je vous suggère d'essayer l'application sur des photos de votre endroit préféré et, peut-être, d'en dire quelque chose d'intéressant dans les commentaires ou dans une lettre personnelle.


De moi - un fait amusant. La plupart des habitants de Nijni Novgorod et de la région de Nijni Novgorod utilisent le mot «sortir» dans le sens du mot «s'insérer» (trouver une place libre). Par exemple, la question «Allons-nous nettoyer votre voiture?» signifie "Avons-nous suffisamment d'espace dans votre voiture?", mais pas "Pouvons-nous nettoyer votre voiture?". Lorsque des étudiants d'autres régions viennent chez nous pour des stages d'été, nous aimons le dire - beaucoup sont sincèrement surpris.


Liens utiles


Source: https://habr.com/ru/post/fr437600/


All Articles