Reconnaissance du contour du corps en temps réel. Détection d'objets à l'aide des fonctionnalités d'OpenCV : FREAK. Détection de plusieurs objets. Contours - reconnaissance d'objets

L'idée principale est de prendre en compte les relations statistiques entre la localisation des points anthropométriques du visage. Dans chaque image de visage, les points sont numérotés dans le même ordre. En fonction de leur position relative, les visages sont comparés.

Pour comparer des visages, vous pouvez utiliser la même position du visage par rapport à la caméra. Plus préférable pour cela.

Capturez le flux vidéo de la caméra et mettez en surbrillance le visage

#inclure en utilisant l'espace de noms cv ; int main() ( // Charger Face cascade (fichier .xml) CascadeClassifier face_cascade; face_cascade.load("haarcascade_frontalface_alt2.xml"); Mat img; VideoCapture cap(0); while (true) ( ​​​​cap >> img; // cvtColor(img, img, CV_BGR2GRAY); // Détecter les visages std :: vector visages; face_cascade.detectMultiScale(img, visages, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Taille(30, 30)); // Dessine des cercles sur les visages détectés pour (int i = 0; i< faces.size(); i++) { Point center(faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5); ellipse(img, center, Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0); } imshow("Detected Face", img); waitKey(1); } return 0; }

Les fichiers cascade se trouvent dans le répertoire c:\opencv\build\etc\... Placez la cascade souhaitée dans le répertoire du projet, au même endroit que le fichier source main.cpp.

Sélection de points spéciaux du visage

L'application est basée sur le code C++ pour OpenCV Facemark

#inclure #inclure #inclure #inclure #inclure #inclure #include "drawLandmarks.hpp" en utilisant l'espace de noms std ; en utilisant l'espace de noms cv ; en utilisant l'espace de noms cv::face ; int main(int argc, char** argv) ( // Charger le détecteur de visage CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml"); // Créer une instance de Facemark Ptr facemark = FacemarkLBF::create(); // Charger le détecteur de points de repère facemark->loadModel("lbfmodel.yaml"); // Configurer la webcam pour la capture vidéo VideoCapture cam(0); // Variable pour stocker une image vidéo et son image Mat en niveaux de gris, gray; // Lire une image while (cam.read(frame)) ( // Rechercher un vecteur de visage visages; // Convertit le cadre en niveaux de gris car // faceDetector nécessite une image en niveaux de gris. cvtColor(cadre, gris, COLOR_BGR2GRAY); // Détecter les visages faceDetector.detectMultiScale(gris, visages); // Variable pour les points de repère. // Les repères d'une face sont un vecteur de points // Il peut y avoir plusieurs faces dans l'image. Par conséquent, nous // utilisons un vecteur de vecteur de points. vecteur< vector>des points de repère ; // Exécuter le détecteur de points de repère bool success = facemark->< faces.size(); i++) { cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3); } for (int i = 0; i < landmarks.size(); i++) { drawLandmarks(frame, landmarks[i]); /*for (size_t j = 0; j < landmarks[i].size(); j++) circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2);*/ } } // Display results imshow("Facial Landmark Detection", frame); // Exit loop if ESC is pressed if (waitKey(1) == 27) break; } return 0; }

Dans le projet d'application, au même endroit que le fichier main.cpp, posté les fichiers haarcascade_frontalface_alt2.xml, drawLandmarks.hpp Et lbfmodel.yaml, qui sont référencés dans le code. Les fichiers cascade se trouvent dans le répertoire c:\opencv\build\etc\… Fichiers drawLandmarks.hpp Et lbfmodel.yaml est dans l'archive Facemark_LBF.rar.

Après avoir inséré le code, des erreurs sont apparues du fait qu'OpenCV 3.4.3-vc14-vc15 ne dispose pas d'un certain nombre de bibliothèques nécessaires au fonctionnement de l'application. J'ai compilé ma bibliothèque (télécharger opencv_new.zip) et l'ai installée à la racine du lecteur C (C:\opencv-new).

Maintenant, tous les réglages effectués doivent être effectués pour opencv-new :

Effectuer les réglages sous Windows. Je vais dans la fenêtre « Modifier la variable d'environnement » (Boutons Windows->Utilitaires->Panneau de configuration -> Système et sécurité -> Système -> Paramètres système avancés -> Variables d'environnement -> Chemin -> Modifier). Dans cette fenêtre je crée une variable C:\ opencv-nouveau\x64\vc14\bin. Redémarrage de Windows.

Dans les propriétés du projet Je crée également un lien vers la bibliothèque opencv_new (au lieu d'opencv). Dans la fenêtre « Pages de propriétés », j'effectue les actions suivantes :

  • C/C++ -> Général -> Répertoires d'inclusion supplémentaires -> C:\ opencv-nouveau\inclure
  • Linker -> Général -> Répertoires de bibliothèques supplémentaires -> C:\ opencv-nouveau\x64\vc14\lib
  • Linker -> Entrée -> Dépendances supplémentaires -> opencv_core400.lib ; opencv_face400.lib; opencv_videoio400.lib; opencv_objdetect400.lib; opencv_imgproc400.lib; opencv_highgui400.lib

Au démarrage, le programme génère une erreur si les paramètres du projet sont Debug. Pour Release, le lancement est réussi.


Sélection des fonctionnalités de filtrage d'images et de reconnaissance faciale

Le cadre de points du visage est affiché différemment en fonction de facteurs objectifs et subjectifs.

Facteurs objectifs - la position du visage par rapport à la caméra.

Facteurs subjectifs - éclairage inégal ou faible, distorsion du visage due aux émotions, plissement des yeux, etc. Dans ces cas, le cadre des points peut être incorrect, les points peuvent même être arrachés du visage :

Lors de la capture vidéo, de telles images passent parfois. Ils doivent être filtrés – tant lors de la formation que lors de la reconnaissance.

Certains des points sont les plus stables et informatifs. Ils sont rigidement fixés au visage, quelle que soit sa position par rapport à la caméra. De plus, ils caractérisent bien les spécificités du visage. Ces points peuvent être utilisés comme base pour modéliser le système de fonctionnalités.

Pour comparer des visages, vous pouvez utiliser un cadre de points 2D de la même position du visage. Quelle position du visage par rapport à la caméra est la plus informative ? Évidemment, c'est frontal. Ce n’est pas pour rien qu’en médecine légale, on prend des photos de face et de profil. Pour l’instant, nous nous limiterons au visage complet.

Toutes les caractéristiques (distances) doivent être sans dimension (normalisées), c'est-à-dire liées à une certaine taille (distance). Je suppose que la taille la plus appropriée pour cela est la distance entre le milieu des coins des yeux. Pourquoi, par exemple, pas les coins externes des yeux, qui sont en fait définis dans le tableau des repères ? Le fait est que les coins des yeux s'écartent (se rapprochent) lorsqu'ils réagissent à un changement de couleur, expriment une surprise, clignent des yeux, etc. La distance entre les centres des yeux neutralise ces fluctuations et est donc préférable.

Quel signe prendra-t-on comme base en première approximation ? Je suppose la distance entre le haut de l’arête du nez et le bas du menton. À en juger par la photo, ce signe peut différer considérablement selon les individus.

Ainsi, avant de former des caractéristiques d'apprentissage et de comparaison, il est nécessaire de filtrer les images ponctuelles des visages obtenues par capture vidéo, qui, pour des raisons subjectives ou objectives, ne constituent pas une image frontale correcte du visage (visage complet).

Nous ne laissons que les cadres de points qui répondent aux critères suivants :

  • La ligne droite qui passe par les points extrêmes des yeux (ligne des yeux) est perpendiculaire à la ligne droite qui passe par les points extrêmes du nez (ligne du nez).
  • La ligne des yeux est parallèle à la ligne droite qui passe par les pointes des commissures de la bouche (ligne de la bouche).
  • La symétrie des points ci-dessus par rapport à la ligne du nez est conservée.
  • Les coins des yeux (externes et internes) sont sur la même ligne droite.

Un exemple d'images frontales qui répondent à tous les critères :

Exemple d'images filtrées :

Essayez de déterminer par vous-même quelles caractéristiques les images ne transmettent pas.

Comment sont formalisées les fonctionnalités qui assurent le filtrage et la reconnaissance faciale ? Ils reposent principalement sur les conditions de détermination des distances entre points, les conditions de parallélisme et de perpendiculaire. La tâche de formaliser de telles fonctionnalités est abordée dans le sujet.

Algorithme de reconnaissance faciale utilisant un cadre de points 2D

Les coordonnées des points du cadre du visage sont initialement spécifiées dans un système de coordonnées lié au point supérieur gauche de la fenêtre. Dans ce cas, l’axe Y est dirigé vers le bas.

Pour faciliter l'identification des caractéristiques, nous utilisons un système de coordonnées personnalisé (UCS), dont l'axe X passe par le segment situé entre le milieu des yeux, et l'axe Y est perpendiculaire à ce segment passant par son milieu vers le haut. Les coordonnées UCS (de -1 à +1) sont normalisées – corrélées à la distance entre les milieux des yeux.

Le PSC offre commodité et simplicité dans la détermination des caractéristiques. Par exemple, la position du visage en vue frontale est déterminée par le signe de symétrie des points correspondants des yeux par rapport à la ligne du nez. Cette caractéristique est formalisée par la coïncidence de la ligne du nez avec l'axe Y, soit X1=X2=0, où X1 et X2 sont les coordonnées des points extrêmes du nez (27 et 30) dans l'UCS.

Déterminer par rapport à la fenêtre SC

Coordonnées des milieux des yeux gauche et droit (Gauche et Droite) :

XL = (X45 + X42) /2 ; YL = (Y45 + Y42) /2 ; XR = (X39 + X36) /2 ; AN = (Y39 + Y36) /2 ;

Début du PSC :

X0 =(XL + XR)/2 ; Y0 = (YL + ANN)/2 ;

Distances entre les milieux des yeux le long des axes X et Y :

DX = XR-XL ; DY = ANN - YL ;

La distance réelle L entre les milieux des yeux (selon le théorème de Pythagore) :

L = carré (DX** 2 + DY**2)

Fonctions trigonométriques de l'angle de rotation du SCU :

On passe des coordonnées dans le système de coordonnées de la fenêtre aux coordonnées dans le SCU, en utilisant les paramètres X0,Y0, L, sin AL, cos AL :

X_User_0 = 2 (X_Window - X0) / L ;

Y_User_0 = - 2 (Y_Window - Y0) / L ;

X_Utilisateur= X_Utilisateur_0 * cos_AL - Y_Utilisateur_0 * sin_AL ;

Y_Utilisateur= X_User_0 * sin_AL + Y_User_0 * cos_AL ;

Implémentation du filtrage d'images vérifier séquentiellement les signes :

1. Un signe de circularité des lignes du nez et des yeux, ainsi que de symétrie des coins des yeux. La ligne du nez est déterminée par les points 27 et 30 (voir figure). Les deux critères sont satisfaits si les coordonnées de ces points dans le SCU sont X1 = X2= 0 (c'est-à-dire que la ligne du nez coïncide avec l'axe Y).

2. Signe de parallélisme entre la ligne des yeux et la ligne de la bouche. La ligne de la bouche est déterminée par les points 48 et 54 (voir figure). Le signe est satisfait si Y1-Y2=0 dans le SCU.

3. Signe de symétrie des coins de la bouche. La ligne de la bouche est déterminée par les points 48 et 54 (voir figure). Le test est satisfait si dans le SCU X1+X2 =0

4. Panneau « Les coins des yeux sont sur la même ligne droite ». Les lignes sont définies par des paires de points : (36 et 45), ainsi que (39 et 42). Puisque le test de la caractéristique 1 a déjà été réussi, il suffit de déterminer le signe Y2-Y1 =0 dans l'UCS uniquement pour les points 36 et 39.

Il ne peut y avoir d'égalité absolue à zéro, les caractéristiques sont donc comparées à une petite valeur acceptable.

Programme de comparaison de visages basé sur une caractéristique

La distance entre les pointes de l'arête du nez et le menton est prise comme signe (Repères points 27 et 8, voir figure). Le signe, normalisé, est déterminé dans l'UCS par le rapport : (Y1 - Y2)/L, où L est la distance entre les centres des yeux. Lors de la formation du programme, le signe d'une personne spécifique est déterminé par un numéro affiché à côté de la personne suivie (cette partie du code est commentée dans le programme). Lors de la reconnaissance, la valeur de la caractéristique est comparée à la caractéristique spécifique saisie dans le programme pour chaque personne. Si le résultat de la comparaison est positif, son identifiant apparaît à côté de la personne.

Le programme me reconnaît également grâce à une photo sur laquelle j'ai 15 ans de moins, et même avec une moustache. La différence sur la photo est significative, tout le monde ne la saisira pas. Mais on ne peut pas tromper un programme informatique.

Tâches de test :

  1. Faites connaissance avec le programme.
  2. Déterminez la signification de l'attribut pour votre visage et plusieurs de vos collègues.
  3. Testez le programme pour identifier les individus (les vôtres et ceux de vos collègues).

#inclure #inclure #inclure #inclure #inclure #inclure #include "drawLandmarks.hpp" en utilisant l'espace de noms std ; en utilisant l'espace de noms cv ; en utilisant l'espace de noms cv::face ; int main(int argc, char** argv) ( // Charger le détecteur de visage CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml"); // Créer une instance de Facemark Ptr facemark = FacemarkLBF::create(); // Charger le détecteur de points de repère facemark->loadModel("lbfmodel.yaml"); // Configurer la webcam pour la capture vidéo VideoCapture cam(0); // Variable pour stocker une image vidéo et son image Mat en niveaux de gris, gray; // Lire une image while (cam.read(frame)) ( // Rechercher un vecteur de visage visages; // Convertit le cadre en niveaux de gris car // faceDetector nécessite une image en niveaux de gris. cvtColor(cadre, gris, COLOR_BGR2GRAY); // Détecter les visages faceDetector.detectMultiScale(gris, visages); // Variable pour les points de repère. // Les repères d'une face sont un vecteur de points // Il peut y avoir plusieurs faces dans l'image. Par conséquent, nous // utilisons un vecteur de vecteur de points. vecteur< vector>des points de repère ; // Exécuter le détecteur de points de repère bool success = facemark->fit(frame, faces, monuments); if (succès) ( // En cas de succès, affiche les repères sur le visage pour (size_t i = 0; i< faces.size(); i++) { cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3); } for (int i = 0; i < landmarks.size(); i++) { //if((i >=30)&&(je<= 35)) drawLandmarks(frame, landmarks[i]); for (size_t j = 0; j < landmarks[i].size(); j++) { circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2); } line(frame, Point(landmarks[i].x, landmarks[i].y), Point(landmarks[i].x, landmarks[i].y), Scalar(0, 0, 255), 2); float XL = (landmarks[i].x + landmarks[i].x) / 2; float YL = (landmarks[i].y + landmarks[i].y) / 2; float XR = (landmarks[i].x + landmarks[i].x) / 2; float YR = (landmarks[i].y + landmarks[i].y) / 2; line(frame, Point(XL, YL), Point(XR, YR), Scalar(0, 0, 255), 2); float DX = XR - XL; float DY = YR - YL; float L = sqrt(DX * DX + DY * DY); float X1 = (landmarks[i].x); float Y1 = (landmarks[i].y); float X2 = (landmarks[i].x); float Y2 = (landmarks[i].y); float DX1 = abs(X1 - X2); float DY1 = abs(Y1 - Y2); float L1 = sqrt(DX1 * DX1 + DY1 * DY1); float X0 = (XL + XR) / 2; float Y0 = (YL + YR) / 2; float sin_AL = DY / L; float cos_AL = DX / L; float X_User_0 = (landmarks[i].x - X0) / L; float Y_User_0 = -(landmarks[i].y - Y0) / L; float X_User27 = X_User_0 * cos_AL - Y_User_0 * sin_AL; float Y_User27 = X_User_0 * sin_AL + Y_User_0 * cos_AL; X_User_0 = (landmarks[i].x - X0) / L; Y_User_0 = -(landmarks[i].y - Y0) / L; float X_User30 = X_User_0 * cos_AL - Y_User_0 * sin_AL; float Y_User30 = X_User_0 * sin_AL + Y_User_0 * cos_AL; if (abs(X_User27 - X_User30) <= 0.1) { //putText(frame, std::to_string(abs(L1 / L)), Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); if (abs((L1 / L) - 1.6) < 0.1) { putText(frame, "Roman", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } if (abs((L1 / L) - 1.9) < 0.1) { putText(frame, "Pasha", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } if (abs((L1 / L) - 2.1) < 0.1) { putText(frame, "Svirnesvkiy", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } } putText(frame, "Incorrect", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } } // Display results imshow("Facial Landmark Detection", frame); // Exit loop if ESC is pressed if (waitKey(1) == 27) break; } return 0; }

Cet article utilise l'interface C++, FREAK et la détection d'objets multiples. La fiabilité de la détection d'objets avec FREAK est inférieure à celle de SURF, mais son fonctionnement est beaucoup plus rapide, ce qui permet d'utiliser l'algorithme sur des systèmes mobiles et embarqués. Un exemple de travail est présenté sur la figure :

Regardons le code source qui nous permet d'y parvenir. Le code est donné dans son intégralité pour ceux qui souhaitent l'insérer rapidement dans leur projet.
#inclure #inclure #inclure #inclure #inclure #inclure #inclure #inclure keypointsImageTemple, keypointsImage ; Descripteurs de tapisImageTemple, descripteursImage ; std :: vecteur allumettes; // Initialisation de la classe du détecteur de fonctionnalités, 1000 est la valeur seuil pour filtrer // les fonctionnalités insignifiantes SurfFeatureDetector detector(1000); // Classe pour les fonctionnalités FREAK. Vous pouvez configurer les modes de comparaison de fonctionnalités : // Extracteur FREAK (true, true, 22, 4, std::vector correspondant; // Détection double t = (double)getTickCount(); detector.detect(ImageTemple, keypointsImageTemple); detector.detect(Image, keypointsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std :: cout<< "detection time [s]: " << t/1.0 << std::endl; // Извлечение особенностей t = (double)getTickCount(); extractor.compute(ImageTemple, keypointsImageTemple, descriptorsImageTemple); extractor.compute(Image, keypointsImage, descriptorsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "extraction time [s]: " << t << std::endl; // Сравнение t = (double)getTickCount(); matcher.match(descriptorsImageTemple, descriptorsImage, matches); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "matching time [s]: " << t << std::endl; // Отобразить на изображении Mat imgMatch; drawMatches(ImageTemple, keypointsImageTemple, Image, keypointsImage, matches, imgMatch); imwrite("matches.jpeg", imgMatch); std::vectorObj; std :: vecteur Scène; pour (int je = 0; je< matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vector Scene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Tracez des lignes entre les coins (l'objet mappé dans la scène - image_2) line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255 , 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); imwrite("matches3.jpeg", imgMatch); renvoie 0 ; )

Pour toutes les fonctionnalités d'OpenCV, vous devez initialiser la classe SurfFeatureDetector. La première action après diverses initialisations consiste à détecter les caractéristiques du détecteur pour l'image de référence et l'image de scène. Après cela, pour chaque image, en fonction des résultats du détecteur, les caractéristiques FREAK sont calculées : extractor.compute.
La comparaison de similarité des fonctionnalités est effectuée à l'aide de matcher.match.
Ensuite, il y a un cycle avec la formation de points à partir de caractéristiques pour les deux images. Sur la base des points, l'homographie de l'image findHomography est calculée. La position et la rotation d'un objet sont calculées à l'aide de la fonction perspectiveTransform. Eh bien, alors – sortie vers l’image.
Image de référence :

Photo de la scène :


Le résultat est présenté en premier.
Cependant, cela soulève la question de savoir comment calculer le seuil de fonctionnalité optimal : détecteur SurfFeatureDetector(1000);. La réponse est expérimentale. Vous pouvez obtenir des informations sur ce problème.
Supposons que nous ayons plusieurs objets dans l'image :


Le résultat du programme sera le suivant :


Bien entendu, cette situation n’est pas satisfaisante. Afin de détecter tous les objets, il est nécessaire de diviser l’image en plusieurs parties. Cependant, il convient de rappeler ici que si l'image est divisée en blocs qui ne se chevauchent pas (par exemple, une image de 100x100 est divisée en 4 blocs de 50x50), alors une situation peut survenir lorsque l'objet sera partiellement situé dans plusieurs blocs et ne sera pas détecté. Pour éviter cela, vous devez créer des blocs qui se croisent, ce qui ralentira quelque peu le travail, mais améliorera la qualité (par exemple, divisez une image de 100x100 en 9 blocs de 50x50 comme indiqué dans l'exemple). Un exemple de programme détectant de nombreux objets est ci-dessous :
#inclure #inclure #inclure #inclure #inclure #inclure #inclure #inclure en utilisant l'espace de noms cv ; int main(int argc, char** argv) ( if(argc != 3) return 1; Mat ImageTemple = imread(argv, CV_LOAD_IMAGE_GRAYSCALE); if(!ImageTemple.data) return 2; // Erreur Mat Image = imread( argv, CV_LOAD_IMAGE_GRAYSCALE); if(!Image.data) renvoie 3; // Erreur std::vector points clésImageTemple ; Descripteurs de tapisImageTemple ; std :: vecteur allumettes; // Initialisation de la classe du détecteur de fonctionnalités, 1000 est la valeur seuil pour filtrer // les fonctionnalités insignifiantes SurfFeatureDetector detector(1000); detector.detect(ImageTemple, keypointsImageTemple); int maxy = 3 ; int maxx = 3 ; Mat Draw_mat = imread(argv, 1); pour (int y = 0; y< maxy; y++) for(int x = 0; x < maxx; x++) { // Класс для FREAK особенностей. Можно настраивать режимы сравнения особенностей: // FREAK extractor(true, true, 22, 4, std::vector()); Extracteur FREAK ; // Utilisé pour déterminer les correspondances de fonctionnalités - Mesure de Hamming BruteForceMatcher correspondant; std :: vecteur points clésImage ; Descripteurs de tapisImage ; CvRect Rect = cvRect(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)), 2 * (Image.cols / (maxx + 1)), 2 * ( Image.rows/(maxy + 1))); Tapis ImageROI(Image, Rect); detector.detect(ImageROI, keypointsImage); extractor.compute (ImageTemple, points clésImageTemple, descripteursImageTemple); extractor.compute (ImageROI, keypointsImage, descriptorsImage); matcher.match(descriptorsImageTemple, descriptorsImage, matches); // Supprime les valeurs trop divergentes pour (int i = 0; i< matches.size(); i++) { if(matches[i].distance >150) ( matches.erase (matches.begin() + i); ) ) std::vecteur Obj; std :: vecteur Scène; pour (int je = 0; je< matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vectorobj_corners(4); obj_corners = cvPoint(0,0); obj_corners = cvPoint(ImageTemple.cols, 0); obj_corners = cvPoint(ImageTemple.cols, ImageTemple.rows); obj_corners = cvPoint(0, ImageTemple.rows); std :: vecteur Scene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Tracez des lignes entre les coins (l'objet mappé dans la scène - image_2) line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar(0, 255, 0), 4) ; line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalaire(0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalaire(0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalaire(0, 255, 0), 4); ) imwrite("draw_mat.jpeg", Draw_mat); renvoie 0 ; )

Le résultat des travaux est le suivant :


On voit que tous les objets ont été détectés. De plus, certains deux fois (du fait qu'ils étaient divisés en deux blocs).

Les sources d’informations les plus importantes sur le monde extérieur pour un robot sont ses capteurs optiques et ses caméras. Après avoir reçu l’image, il est nécessaire de la traiter pour analyser la situation ou prendre une décision. Comme j'ai dit auparavant, vision par ordinateur combine de nombreuses méthodes de travail avec des images. Lorsque le robot fonctionne, on suppose que les informations vidéo des caméras sont traitées par un programme exécuté sur le contrôleur. Pour éviter d'écrire du code à partir de zéro, vous pouvez utiliser des solutions logicielles prêtes à l'emploi. Actuellement, il existe de nombreuses bibliothèques de vision par ordinateur prêtes à l'emploi :

  • Bibliothèque d'imagerie Matrox
  • Bibliothèque Camélia
  • Ouvrir eVision
  • HALCON
  • libCVD
  • OuvrirCV
  • etc…
Ces SDK peuvent varier considérablement en termes de fonctionnalités, de conditions de licence et de langages de programmation utilisés. Nous nous attarderons plus en détail sur OuvrirCV. Il est gratuit à des fins éducatives et commerciales. Écrit en C/C++ optimisé, prend en charge les interfaces C, C++, Python et Java et comprend des implémentations de plus de 2 500 algorithmes. En plus des fonctions classiques de traitement d'image (filtrage, flou, transformations géométriques, etc...), ce SDK permet de résoudre des problèmes plus complexes, parmi lesquels détecter un objet dans une photographie et le « reconnaître ». Il faut comprendre que les tâches de détection et de reconnaissance peuvent être complètement différentes :
  • recherche et reconnaissance d'un objet spécifique,
  • rechercher des objets de même catégorie (sans reconnaissance),
  • uniquement la reconnaissance d'objets (image prête à l'emploi avec).
Pour détecter les fonctionnalités d'une image et rechercher une correspondance, OpenCV dispose des méthodes suivantes :
  • Histogramme des gradients orientés (HOG) - peut être utilisé pour la détection des piétons
  • Algorithme Viola-Jones - utilisé pour rechercher des visages
  • Algorithme de détection de caractéristiques SIFT (Scale Invariant Feature Transform)
  • Algorithme de détection de fonctionnalités SURF (Speeded Up Robust Features)
Par exemple, SIFT détecte des ensembles de points pouvant être utilisés pour identifier un objet. En plus des méthodes ci-dessus, OpenCV dispose également d'autres algorithmes de détection et de reconnaissance, ainsi que d'un ensemble d'algorithmes liés à l'apprentissage automatique, tels que la méthode des k-voisins les plus proches, les réseaux de neurones, les machines à vecteurs de support, etc... De manière générale, OpenCV fournit des outils suffisants pour résoudre la grande majorité des problèmes de vision par ordinateur. Si l'algorithme n'est pas inclus dans le SDK, il peut généralement être programmé sans problème. De plus, il existe de nombreuses versions propriétaires d'algorithmes écrits par les utilisateurs et basés sur OpenCV. Il convient également de noter qu’OpenCV s’est beaucoup développé ces dernières années et est devenu un poids quelque peu lourd. À cet égard, différents groupes de passionnés créent des bibliothèques « légères » basées sur OpenCV. Exemples : SimpleCV, Liuliu ccv, tinycv… Sites utiles
  1. http://opencv.org/ - Site principal du projet
  2. http://opencv.willowgarage.com/wiki/ - Ancien site Web du projet avec documentation pour les anciennes versions

Une bibliothèque open source de vision par ordinateur et d’apprentissage automatique. Il comprend plus de 2 500 algorithmes, parmi lesquels des algorithmes classiques et modernes pour la vision par ordinateur et l'apprentissage automatique. Cette bibliothèque possède des interfaces dans différents langages, dont Python (nous l'utilisons dans cet article), Java, C++ et Matlab.

Installation

Les instructions d'installation sous Windows peuvent être consultées et sous Linux -.

Importer et visualiser une image

importer une image cv2 = cv2.imread("./path/to/image.extension") cv2.imshow("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()

Remarque Lors de la lecture en utilisant la méthode ci-dessus, l'image est dans l'espace colorimétrique non pas RVB (comme tout le monde en a l'habitude), mais BGR. Ce n'est peut-être pas si important au début, mais dès que vous commencez à travailler avec la couleur, il vaut la peine de connaître cette fonctionnalité. Il y a 2 solutions :

  1. Échangez le 1er canal (R - rouge) avec le 3ème canal (B - bleu), et la couleur rouge sera alors (0,0,255) et non (255,0,0).
  2. Changez l'espace colorimétrique en RVB : rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    Et puis dans le code on ne travaille plus avec image , mais avec rgb_image .

Remarque Pour fermer la fenêtre dans laquelle l'image est affichée, appuyez sur n'importe quelle touche. Si vous utilisez le bouton Fermer la fenêtre, vous risquez de rencontrer des blocages.

Tout au long de l'article, le code suivant sera utilisé pour afficher les images :

Importer cv2 def viewImage(image, nom_de_fenêtre) : cv2.namedWindow(nom_de_fenêtre, cv2.WINDOW_NORMAL) cv2.imshow(nom_de_fenêtre, image) cv2.waitKey(0) cv2.destroyAllWindows()

Recadrage

Toutou après cadrage

Importer cv2 recadré = image viewImage(recadrée, "Doggie après recadrage")

Où l'image est l'image.

Changement de taille

Après redimensionnement de 20 %

Importer cv2 scale_percent = 20 # Pourcentage de la taille d'origine width = int(img.shape * scale_percent / 100) height = int(img.shape * scale_percent / 100) dim = (largeur, hauteur) resize = cv2.resize(img, dim , interpolation = cv2.INTER_AREA) viewImage(resized, "Après redimensionnement de 20%")

Cette fonction prend en compte le rapport hauteur/largeur de l'image originale. D'autres fonctions de redimensionnement d'image peuvent être vues.

Tourner

Toutou après avoir tourné à 180 degrés

Importer cv2 (h, w, d) = image.shape center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, 180, 1.0) pivoté = cv2.warpAffine(image, M, (w , h)) viewImage(rotated, "Doggie après rotation de 180 degrés")

image.shape renvoie la hauteur, la largeur et les canaux. M - matrice de rotation - fait pivoter l'image de 180 degrés autour du centre. -ve est l'angle de rotation de l'image dans le sens des aiguilles d'une montre, et +ve, respectivement, dans le sens inverse des aiguilles d'une montre.

Conversion en niveaux de gris et noir et blanc par seuil

Toutou en niveaux de gris

Toutou noir et blanc

Importer cv2 gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ret, seuil_image = cv2.threshold(im, 127, 255, 0) viewImage(gray_image, "Chien en niveaux de gris") viewImage(threshold_image, "Toutou noir et blanc" )

gray_image est une version monocanal de l'image.

La fonction de seuil renvoie une image dans laquelle tous les pixels plus sombres (inférieurs à) 127 sont remplacés par 0, et tous les pixels plus clairs (supérieurs à) 127 sont remplacés par 255.

Pour plus de clarté, un autre exemple :

Ret, seuil = cv2.threshold(im, 150, 200, 10)

Ici, tout ce qui est plus sombre que 150 est remplacé par 10, et tout ce qui est plus clair est remplacé par 200.

Les autres fonctions de seuil sont décrites.

Flou/Lisse

Toutou flou

Importer cv2 flou = cv2.GaussianBlur(image, (51, 51), 0) viewImage(flou, "Blurred Doggie")

La fonction GaussianBlur prend 3 paramètres :

  1. Image originale.
  2. Un tuple de 2 nombres impairs positifs. Plus les chiffres sont élevés, plus le pouvoir de lissage est important.
  3. sigmax Et sigmaY. Si ces paramètres restent égaux à 0, alors leur valeur sera calculée automatiquement.

Dessiner des rectangles

Dessinez un rectangle autour du visage du chien

Importer la sortie cv2 = image.copy() cv2.rectangle(output, (2600, 800), (4100, 2400), (0, 255, 255), 10) viewImage(output, "Dessinez un rectangle autour du visage du chien" )

Cette fonction prend 5 paramètres :

  1. L'image elle-même.
  2. Coordonnée du coin supérieur gauche (x1, y1) .
  3. Coordonnée du coin inférieur droit (x2, y2) .
  4. Couleur du rectangle (GBR/RGB selon le modèle de couleur sélectionné).
  5. L'épaisseur de ligne du rectangle.

Tracer des lignes

2 toutous séparés par une ligne

Importer la sortie cv2 = image.copy() cv2.line(output, (60, 20), (400, 200), (0, 0, 255), 5) viewImage(output, "2 toutous séparés par une ligne")

La fonction line prend 5 paramètres :

  1. L'image elle-même sur laquelle la ligne est tracée.
  2. Coordonnée du premier point (x1, y1) .
  3. Coordonnée du deuxième point (x2, y2) .
  4. Couleur de ligne (GBR/RGB selon le modèle de couleur sélectionné).
  5. Épaisseur de ligne.

Texte sur l'image

Image avec texte

Importer la sortie cv2 = image.copy() cv2.putText(output, "Nous<3 Dogs", (1500, 3600),cv2.FONT_HERSHEY_SIMPLEX, 15, (30, 105, 210), 40) viewImage(output, "Изображение с текстом")

La fonction putText prend 7 paramètres :

  1. Image directe.
  2. Texte pour l'image.
  3. La coordonnée du coin inférieur gauche du début du texte (x, y).
  4. Personnes détectées : 2

    Importer cv2 image_path = "./path/to/photo.extension" face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade .detectMultiScale(gray, scaleFactor= 1.1, minNeighbors= 5, minSize=(10, 10)) faces_detected = "Visages détectés : " + format(len(faces)) print(faces_detected) # Dessinez des carrés autour des visages pour (x, y , w, h) en visages : cv2.rectangle(image, (x, y), (x+w, y+h), (255, 255, 0), 2) viewImage(image, faces_detected)

    detectMultiScale est une fonction générale pour la reconnaissance de visages et d'objets. Pour que la fonction recherche spécifiquement des visages, on lui passe la cascade correspondante.

    La fonction detectMultiScale prend 4 paramètres :

    1. L'image traitée est en niveaux de gris.
    2. paramètre scaleFactor. Certains visages peuvent être plus grands que d’autres parce qu’ils sont plus proches que d’autres. Ce paramètre compense la perspective.
    3. L'algorithme de reconnaissance utilise une fenêtre glissante lors de la reconnaissance d'objets. Le paramètre minNeighbors détermine le nombre d'objets autour du visage. Autrement dit, plus la valeur de ce paramètre est élevée, plus l'algorithme a besoin d'objets similaires pour pouvoir identifier l'objet actuel comme un visage. Une valeur trop petite augmentera le nombre de faux positifs, tandis qu’une valeur trop grande rendra l’algorithme plus exigeant.
    4. minSize est la taille directe de ces zones.

    Contours - reconnaissance d'objets

    La reconnaissance d'objets est effectuée à l'aide de la segmentation d'images couleur. Il existe deux fonctions pour cela : cv2.findContours et cv2.drawContours.

    Cet article détaille la détection d'objets à l'aide de la segmentation des couleurs. Tout ce dont vous avez besoin est là.

    Enregistrer une image

    importer une image cv2 = cv2.imread("./import/path.extension") cv2.imwrite("./export/path.extension", image)

    Conclusion

    OpenCV est une excellente bibliothèque avec des algorithmes légers qui peuvent être utilisés dans le rendu 3D, l'édition avancée d'images et de vidéos, le suivi et l'identification d'objets et de personnes dans une vidéo, la recherche d'images identiques à partir d'un ensemble et bien plus encore.

    Cette bibliothèque est très importante pour ceux qui développent des projets liés à l'apprentissage automatique dans le domaine des images.

Bonjour! J'ai été confronté à la tâche de mettre en œuvre la reconnaissance des panneaux routiers à partir d'un flux vidéo. Comme je n'ai jamais rencontré de problèmes de ce genre auparavant, le processus de mise en œuvre présuppose naturellement un long « fumage » préalable des forums et une moquerie impitoyable des exemples d'autrui. Par conséquent, j'ai décidé de rassembler tout ce que je lis au même endroit pour les générations futures et aussi, pendant l'histoire, de poser quelques questions à Habr.

Préludes.

Ainsi, après avoir étudié tous les outils pouvant être utilisés pour mettre en œuvre la tâche, j'ai opté pour l'environnement de développement Microsoft Visual Studio© 2010, en utilisant la merveilleuse bibliothèque OpenCV.

Le processus de travail avec OpenCV lui-même implique des danses préliminaires avec un tambourin, dont il existe des descriptions assez détaillées :

Le deuxième acte de danse avec un tambourin.

Du coup, je me suis orienté vers les cascades de formations. Après avoir « fumé » dans ce sens, j'ai réalisé que j'avais besoin de deux outils : createampes et haartraining. Mais je n’avais pas leur exe et ils ont refusé de compiler. À cette époque, j'avais OpenCV version 2.4.4, configuré à l'aide de , et dans l'article que j'ai lu pour la première fois sur l'utilisation de Cmake lors de l'installation. Finalement, j'ai décidé de télécharger la version 2.3.1 et de réinstaller la bibliothèque. Après quoi j'ai réussi à lancer les outils nécessaires via la ligne de commande et la question s'est posée de savoir comment travailler avec eux. Tous les i sont parsemés d'articles qui montrent les paramètres avec lesquels vous devez exécuter createampes et haartraining avec une description détaillée de ces paramètres.

Codez à partir de zéro.

Après avoir finalement abandonné l'ancienne méthode, le code a été réécrit pour remplacer les cascades entraînées.

Code 2.0

#include "stdafx.h" #include #inclure #inclure en utilisant l'espace de noms cv ; int main(int argc, char** argv) ( Mat frame, gray; string object_cascade = "haarustupi.xml"; CascadeClassifier haar(object_cascade); VideoCapture cap(0); nomméeWindow("Video", 1); vector objets; while (true) ( ​​​​cap >> frame; cvtColor(frame, gray, CV_BGR2GRAY); haar.detectMultiScale(gris, objets, 1.9, 10, 0, Size(50, 50)); pour (vecteur ::const_iterator r = objets.begin(); r != objets.end(); r++) rectangle(frame, r->tl(), r->br(), Scalar(0, 0, 255)); imshow("Vidéo", cadre); si (waitKey(33) >= 0) pause ; ) retour (EXIT_SUCCESS); )

Nous aménageons l'environnement de la même manière que dans le projet précédent.

La répétition est le père de l'apprentissage.

Il ne reste plus qu'à entraîner les cascades.)
C'est là que le plaisir commence. Après quoi, j'ai décidé d'écrire sur toutes ces épreuves sur Habr et de demander conseil.
J'ai préparé 500 images mesurant 1600x1200. et une image avec un panneau mesurant 80x80. Une seule image suffira car nous détectons un objet spécifique et non une grande variété de visages.

Ainsi, après avoir préparé les images et créé le fichier neg.dat avec la structure

Négatif/n (1).jpg négatif/n (2).jpg négatif/n (3).jpg négatif/n (4).jpg ... négatif/n (500).jpg

exécutez le fichier opencv_createsamples.exe via CMD avec les paramètres suivants

C:OpenCV2.3.1buildcommonx86opencv_createsamples.exe -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -img C:OpenCV2.3.1buildcommonx86ustupi.jpg -num 500 -w 50 -h 50 -bgcolor 0 -bgthresh 0 -show

le paramètre -show affiche les images positives créées, mais elles, contrairement à celles indiquées dans d'autres articles
des photos, ça ressemble à ça

petit

Autrement dit, l'utilitaire recadre l'image bg à la taille de l'image positive. Changer les paramètres -w et -h ne donne aucun résultat et l'arrière-plan est toujours quasiment invisible. Si quelqu'un sait ce qui se passe ici, partagez ses réflexions.. La taille des images négatives a été réduite à 800 x 600 – le même résultat.

C: OpenCV2.3.1Buildcommonx86opencv_haartraining.exe -data C: Opencv2.3.1buildcommonx86haarustupi -Vec C: Opencv2.3.1buildcommonx86positive.Vect -BG C: Opencv2.3.1buildcommonx86neg.dat -npos 500 -NNEG 500 -NSTAGES 6 -NSPLITS 2 -W 20 -h 24 -mem 1536 -mode ALL -nonsym -minhitrate 0,999 -maxfalsealarm 0,5

après quoi vous recevrez le fichier XML tant attendu, qui pourra être chargé dans le code source du programme.
Du coup, la cascade est légèrement entraînée et, avec un grand nombre de faux positifs, réagit à l'image du panneau de passage que j'adore.
Mais je ne parviens pas à obtenir des résultats précis, me semble-t-il, car l’arrière-plan des images positives est coupé. Et les images ne ressemblent tout simplement pas à celles des manuels. Mais il existe toujours la possibilité d'augmenter le nombre d'étapes de formation et, après avoir chargé votre ordinateur toute la journée, d'attendre que la cascade soit plus « éduquée ». C'est ce que je prévois de faire en attendant que d'autres idées arrivent.

Épilogue

C'est ainsi que s'est avéré mon premier article HelloHabr. J'attends avec impatience vos commentaires sur le style de présentation du matériel. Et bien sûr, des conseils sur le sujet.
J'espère qu'après les conseils reçus, il y aura quelque chose pour continuer l'histoire.