Tutoriel OpenCV : Détection d'objets en temps réel à l'aide de MSER dans iOS
Publié: 2022-03-11Au cours des dernières années, les performances moyennes des téléphones mobiles ont considérablement augmenté. Que ce soit pour la puissance du processeur ou la capacité de la RAM, il est désormais plus facile d'effectuer des tâches gourmandes en calcul sur du matériel mobile. Bien que ces technologies mobiles vont dans la bonne direction, il reste encore beaucoup à faire sur les plateformes mobiles, notamment avec l'avènement de la réalité augmentée, de la réalité virtuelle et de l'intelligence artificielle.
Un défi majeur en vision par ordinateur est de détecter les objets d'intérêt dans les images. L'œil et le cerveau humains font un travail exceptionnel, et reproduire cela dans les machines est encore un rêve. Au cours des dernières décennies, des approches ont été développées pour imiter cela dans les machines, et cela s'améliore.
Dans ce didacticiel, nous allons explorer un algorithme utilisé pour détecter les blobs dans les images. Nous utiliserons également l'algorithme de la bibliothèque open source OpenCV pour implémenter un prototype d'application iPhone qui utilise la caméra arrière pour acquérir des images et détecter les objets qu'elles contiennent.
Tutoriel OpenCV
OpenCV est une bibliothèque open source qui fournit des implémentations des principaux algorithmes de vision par ordinateur et d'apprentissage automatique. Si vous souhaitez implémenter une application pour détecter les visages, jouer aux cartes sur une table de poker, ou même une simple application pour ajouter des effets à une image arbitraire, alors OpenCV est un excellent choix.
OpenCV est écrit en C/C++ et possède des bibliothèques wrapper pour toutes les principales plates-formes. Cela le rend particulièrement facile à utiliser dans l'environnement iOS. Pour l'utiliser dans une application Objective-C iOS, téléchargez le framework OpenCV iOS depuis le site officiel. S'il vous plaît, assurez-vous que vous utilisez la version 2.4.11 d'OpenCV pour iOS (que cet article suppose que vous utilisez), car la dernière version, 3.0, a quelques changements de compatibilité dans la façon dont les fichiers d'en-tête sont organisés. Des informations détaillées sur la façon de l'installer sont documentées sur son site Web.
MSER
MSER, abréviation de Maximally Stable Extremal Regions, est l'une des nombreuses méthodes disponibles pour la détection de blob dans les images. En termes simples, l'algorithme identifie des ensembles contigus de pixels dont les intensités de pixel de limite extérieure sont supérieures (d'un seuil donné) aux intensités de pixel de limite intérieure. De telles régions sont dites stables au maximum si elles ne changent pas beaucoup sur une quantité variable d'intensités.
Bien qu'un certain nombre d'autres algorithmes de détection de blob existent, MSER a été choisi ici car il a une complexité d'exécution assez légère de O(n log(log(n))) où n est le nombre total de pixels sur l'image. L'algorithme est également robuste au flou et à l'échelle, ce qui est avantageux lorsqu'il s'agit de traiter des images acquises via des sources en temps réel, telles que l'appareil photo d'un téléphone portable.
Pour les besoins de ce tutoriel, nous allons concevoir l'application pour détecter le logo de Toptal. Le symbole a des angles vifs, ce qui peut amener à réfléchir à l'efficacité des algorithmes de détection des angles pour détecter le logo de Toptal. Après tout, un tel algorithme est à la fois simple à utiliser et à comprendre. Bien que les méthodes basées sur les coins puissent avoir un taux de réussite élevé lorsqu'il s'agit de détecter des objets distincts de l'arrière-plan (tels que des objets noirs sur fond blanc), il serait difficile d'obtenir une détection en temps réel du logo de Toptal sur le monde réel. images, où l'algorithme détecterait constamment des centaines de coins.
Stratégie
Pour chaque cadre d'image que l'application acquiert via la caméra, il est d'abord converti en niveaux de gris. Les images en niveaux de gris n'ont qu'un seul canal de couleur, mais le logo sera néanmoins visible. Cela permet à l'algorithme de traiter plus facilement l'image et réduit considérablement la quantité de données que l'algorithme doit traiter pour peu ou pas de gain supplémentaire.
Ensuite, nous utiliserons l'algorithme d'implémentation d'OpenCV pour extraire tous les MSER. Ensuite, chaque MSER sera normalisé en transformant son rectangle de délimitation minimum en un carré. Cette étape est importante car le logo peut être acquis sous différents angles et distances, ce qui augmentera la tolérance à la distorsion de perspective.
De plus, un certain nombre de propriétés sont calculées pour chaque MSER :
- Nombre de trous
- Rapport de la surface de MSER à la surface de sa coque convexe
- Rapport de l'aire de MSER à l'aire de son rectangle d'aire minimale
- Rapport de la longueur du squelette MSER à la surface du MSER
- Rapport de la surface de MSER à la surface de son plus grand contour
Afin de détecter le logo de Toptal dans une image, les propriétés de tous les MSER sont comparées aux propriétés du logo Toptal déjà apprises. Pour les besoins de ce didacticiel, les différences maximales autorisées pour chaque propriété ont été choisies de manière empirique.
Enfin, la région la plus similaire est choisie comme résultat.
Application iOS
L'utilisation d'OpenCV à partir d'iOS est facile. Si vous ne l'avez pas encore fait, voici un bref aperçu des étapes impliquées dans la configuration de Xcode pour créer une application iOS et y utiliser OpenCV :
Créez un nouveau nom de projet "SuperCool Logo Detector". En tant que langage, laissez Objective-C sélectionné.
Ajoutez un nouveau fichier d'en-tête de préfixe (.pch) et nommez-le PrefixHeader.pch
Allez dans le projet "SuperCool Logo Detector" Build Target et dans l'onglet Build Settings, recherchez le paramètre "Prefix Headers". Vous pouvez le trouver dans la section Langage LLVM ou utiliser la fonction de recherche.
Ajouter "PrefixHeader.pch" au paramètre d'en-têtes de préfixe
À ce stade, si vous n'avez pas installé OpenCV pour iOS 2.4.11, faites-le maintenant.
Faites glisser et déposez le framework téléchargé dans le projet. Cochez "Frameworks et bibliothèques liés" dans vos paramètres cibles. (Il devrait être ajouté automatiquement, mais mieux vaut être sûr.)
En outre, liez les frameworks suivants :
- Fondation AV
- ActifsBibliothèque
- CoreMedia
Ouvrez « PrefixHeader.pch » et ajoutez les 3 lignes suivantes :
#ifdef __cplusplus #include <opencv2/opencv.hpp> #endif”
Modifiez les extensions des fichiers de code créés automatiquement de « .m » à « .mm ». OpenCV est écrit en C++ et avec *.mm, vous dites que vous utiliserez Objective-C++.
Importez "opencv2/highgui/cap_ios.h" dans ViewController.h et modifiez ViewController pour qu'il soit conforme au protocole CvVideoCameraDelegate :
#import <opencv2/highgui/cap_ios.h>
Ouvrez Main.storyboard et placez un UIImageView sur le contrôleur de vue initial.
Créez une sortie vers ViewController.mm nommée "imageView"
Créez une variable "CvVideoCamera *caméra ;" dans ViewController.h ou ViewController.mm, et initialisez-le avec une référence à la caméra arrière :
camera = [[CvVideoCamera alloc] initWithParentView: _imageView]; camera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; camera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; camera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; camera.defaultFPS = 30; camera.grayscaleMode = NO; camera.delegate = self;
Si vous construisez le projet maintenant, Xcode vous avertira que vous n'avez pas implémenté la méthode "processImage" de CvVideoCameraDelegate. Pour l'instant, et par souci de simplicité, nous allons simplement acquérir les images de la caméra et les superposer avec un simple texte :
- Ajoutez une seule ligne à "viewDidAppear":
[camera start];
Maintenant, si vous exécutez l'application, elle vous demandera l'autorisation d'accéder à la caméra. Et puis vous devriez voir la vidéo de la caméra.
Dans la méthode « processImage » ajoutez les deux lignes suivantes :
const char* str = [@"Toptal" cStringUsingEncoding: NSUTF8StringEncoding]; cv::putText(image, str, cv::Point(100, 100), CV_FONT_HERSHEY_PLAIN, 2.0, cv::Scalar(0, 0, 255));
C'est à peu près tout. Vous avez maintenant une application très simple qui dessine le texte "Toptal" sur les images de l'appareil photo. Nous pouvons maintenant créer notre application de détection de logo cible à partir de cette application plus simple. Par souci de brièveté, dans cet article, nous ne discuterons que d'une poignée de segments de code qui sont essentiels pour comprendre le fonctionnement global de l'application. Le code sur GitHub contient une bonne quantité de commentaires pour expliquer ce que fait chaque segment.
L'application n'ayant qu'un seul but, détecter le logo de Toptal, dès son lancement, les fonctionnalités MSER sont extraites de l'image modèle donnée et les valeurs sont stockées en mémoire :
cv::Mat logo = [ImageUtils cvMatFromUIImage: templateImage]; //get gray image cv::Mat gray; cvtColor(logo, gray, CV_BGRA2GRAY); //mser with maximum area is std::vector<cv::Point> maxMser = [ImageUtils maxMser: &gray]; //get 4 vertices of the maxMSER minrect cv::RotatedRect rect = cv::minAreaRect(maxMser); cv::Point2f points[4]; rect.points(points); //normalize image cv::Mat M = [GeometryUtil getPerspectiveMatrix: points toSize: rect.size]; cv::Mat normalizedImage = [GeometryUtil normalizeImage: &gray withTranformationMatrix: &M withSize: rect.size.width]; //get maxMser from normalized image std::vector<cv::Point> normalizedMser = [ImageUtils maxMser: &normalizedImage]; //remember the template self.logoTemplate = [[MSERManager sharedInstance] extractFeature: &normalizedMser]; //store the feature [self storeTemplate];
L'application n'a qu'un seul écran avec un bouton Start/Stop, et toutes les informations nécessaires, comme le FPS et le nombre de MSER détectés, sont dessinées automatiquement sur l'image. Tant que l'application n'est pas arrêtée, pour chaque image de la caméra, la méthode processImage suivante est invoquée :
-(void)processImage:(cv::Mat &)image { cv::Mat gray; cvtColor(image, gray, CV_BGRA2GRAY); std::vector<std::vector<cv::Point>> msers; [[MSERManager sharedInstance] detectRegions: gray intoVector: msers]; if (msers.size() == 0) { return; }; std::vector<cv::Point> *bestMser = nil; double bestPoint = 10.0; std::for_each(msers.begin(), msers.end(), [&] (std::vector<cv::Point> &mser) { MSERFeature *feature = [[MSERManager sharedInstance] extractFeature: &mser]; if(feature != nil) { if([[MLManager sharedInstance] isToptalLogo: feature] ) { double tmp = [[MLManager sharedInstance] distance: feature ]; if ( bestPoint > tmp ) { bestPoint = tmp; bestMser = &mser; } } } }); if (bestMser) { NSLog(@"minDist: %f", bestPoint); cv::Rect bound = cv::boundingRect(*bestMser); cv::rectangle(image, bound, GREEN, 3); } else { cv::rectangle(image, cv::Rect(0, 0, W, H), RED, 3); } // Omitted debug code [FPS draw: image]; }
Cette méthode, en substance, crée une copie en niveaux de gris de l'image d'origine. Il identifie tous les MSER et extrait leurs caractéristiques pertinentes, note chaque MSER pour sa similarité avec le modèle et sélectionne le meilleur. Enfin, il dessine une limite verte autour du meilleur MSER et superpose l'image avec des méta-informations.
Vous trouverez ci-dessous les définitions de quelques classes importantes et leurs méthodes dans cette application. Leurs objectifs sont décrits dans les commentaires.
GeometryUtil.h
/* This static class provides perspective transformation function */ @interface GeometryUtil : NSObject /* Return perspective transformation matrix for given points to square with origin [0,0] and with size (size.width, size.width) */ + (cv::Mat) getPerspectiveMatrix: (cv::Point2f[]) points toSize: (cv::Size2f) size; /* Returns new perspecivly transformed image with given size */ + (cv::Mat) normalizeImage: (cv::Mat *) image withTranformationMatrix: (cv::Mat *) M withSize: (float) size; @end
MSERManager.h
/* Singelton class providing function related to msers */ @interface MSERManager : NSObject + (MSERManager *) sharedInstance; /* Extracts all msers into provided vector */ - (void) detectRegions: (cv::Mat &) gray intoVector: (std::vector<std::vector<cv::Point>> &) vector; /* Extracts feature from the mser. For some MSERs feature can be NULL !!! */ - (MSERFeature *) extractFeature: (std::vector<cv::Point> *) mser; @end
MLManager.h
/* This singleton class wraps object recognition function */ @interface MLManager : NSObject + (MLManager *) sharedInstance; /* Stores feature from the biggest MSER in the templateImage */ - (void) learn: (UIImage *) templateImage; /* Sum of the differences between logo feature and given feature */ - (double) distance: (MSERFeature *) feature; /* Returns true if the given feature is similar to the one learned from the template */ - (BOOL) isToptalLogo: (MSERFeature *) feature; @end
Une fois que tout est câblé, avec cette application, vous devriez pouvoir utiliser la caméra de votre appareil iOS pour détecter le logo de Toptal sous différents angles et orientations.
Conclusion
Dans cet article, nous avons montré à quel point il est facile de détecter des objets simples à partir d'une image à l'aide d'OpenCV. L'intégralité du code est disponible sur GitHub. N'hésitez pas à bifurquer et à envoyer des demandes push, car les contributions sont les bienvenues.
Comme c'est le cas pour tout problème d'apprentissage automatique, le taux de réussite de la détection du logo dans cette application peut être augmenté en utilisant un ensemble différent de fonctionnalités et une méthode différente pour la classification des objets. Cependant, j'espère que cet article vous aidera à démarrer avec la détection d'objets à l'aide de MSER et les applications des techniques de vision par ordinateur, en général.
Lectures complémentaires
- J. Matas, O. Chum, M. Urban et T. Pajdla. "Stéréo de base large et robuste à partir de régions extrêmes extrêmement stables."
- Neumann, Lucas ; Matas, Jiri (2011). "Une méthode de localisation et de reconnaissance de texte dans des images du monde réel"