Tutorial de OpenCV: Detección de objetos en tiempo real usando MSER en iOS
Publicado: 2022-03-11En los últimos años, el rendimiento promedio de los teléfonos móviles ha aumentado significativamente. Ya sea por la potencia de la CPU o la capacidad de RAM, ahora es más fácil realizar tareas de cómputo pesado en hardware móvil. Aunque estas tecnologías móviles van en la dirección correcta, aún queda mucho por hacer en las plataformas móviles, especialmente con la llegada de la realidad aumentada, la realidad virtual y la inteligencia artificial.
Un desafío importante en la visión por computadora es detectar objetos de interés en las imágenes. El ojo y el cerebro humanos hacen un trabajo excepcional, y replicar esto en máquinas sigue siendo un sueño. En las últimas décadas, se han desarrollado enfoques para imitar esto en las máquinas, y está mejorando.
En este tutorial, exploraremos un algoritmo utilizado para detectar manchas en imágenes. También usaremos el algoritmo, de la biblioteca de código abierto, OpenCV, para implementar una aplicación prototipo de iPhone que usa la cámara trasera para adquirir imágenes y detectar objetos en ellas.
Tutorial de OpenCV
OpenCV es una biblioteca de código abierto que proporciona implementaciones de los principales algoritmos de visión artificial y aprendizaje automático. Si desea implementar una aplicación para detectar rostros, jugar a las cartas en una mesa de póquer o incluso una aplicación simple para agregar efectos a una imagen arbitraria, entonces OpenCV es una excelente opción.
OpenCV está escrito en C/C++ y tiene bibliotecas contenedoras para todas las plataformas principales. Esto hace que sea especialmente fácil de usar dentro del entorno iOS. Para usarlo dentro de una aplicación iOS de Objective-C, descargue OpenCV iOS Framework desde el sitio web oficial. Por favor, asegúrese de estar usando la versión 2.4.11 de OpenCV para iOS (que este artículo asume que está usando), ya que la última versión, 3.0, tiene algunos cambios que rompen la compatibilidad en la forma en que se organizan los archivos de encabezado. La información detallada sobre cómo instalarlo está documentada en su sitio web.
MSER
MSER, abreviatura de regiones extremas máximamente estables, es uno de los muchos métodos disponibles para la detección de manchas dentro de las imágenes. En palabras simples, el algoritmo identifica conjuntos contiguos de píxeles cuyas intensidades de píxeles del límite exterior son más altas (por un umbral dado) que las intensidades de píxeles del límite interior. Se dice que tales regiones son máximamente estables si no cambian mucho en una cantidad variable de intensidades.
Aunque existe una serie de otros algoritmos de detección de manchas, se eligió MSER aquí porque tiene una complejidad de tiempo de ejecución bastante ligera de O(n log(log(n))) donde n es el número total de píxeles en la imagen. El algoritmo también es robusto para difuminar y escalar, lo que resulta ventajoso cuando se trata de procesar imágenes adquiridas a través de fuentes en tiempo real, como la cámara de un teléfono móvil.
A los efectos de este tutorial, diseñaremos la aplicación para detectar el logotipo de Toptal. El símbolo tiene esquinas afiladas, y eso puede llevar a pensar en cuán efectivos pueden ser los algoritmos de detección de esquinas para detectar el logotipo de Toptal. Después de todo, dicho algoritmo es simple de usar y comprender. Aunque los métodos basados en esquinas pueden tener una alta tasa de éxito cuando se trata de detectar objetos que están claramente separados del fondo (como objetos negros sobre fondos blancos), sería difícil lograr la detección en tiempo real del logotipo de Toptal en el mundo real. imágenes, donde el algoritmo estaría constantemente detectando cientos de esquinas.
Estrategia
Por cada cuadro de imagen que la aplicación adquiere a través de la cámara, primero se convierte a escala de grises. Las imágenes en escala de grises solo tienen un canal de color, pero el logotipo será visible, no obstante. Esto facilita que el algoritmo se ocupe de la imagen y reduce significativamente la cantidad de datos que el algoritmo tiene que procesar con poca o ninguna ganancia adicional.
A continuación, utilizaremos la implementación del algoritmo de OpenCV para extraer todos los MSER. A continuación, cada MSER se normalizará transformando su rectángulo delimitador mínimo en un cuadrado. Este paso es importante porque el logotipo puede adquirirse desde diferentes ángulos y distancias y esto aumentará la tolerancia a la distorsión de la perspectiva.
Además, se calculan una serie de propiedades para cada MSER:
- Número de agujeros
- Relación entre el área de MSER y el área de su casco convexo
- Relación entre el área de MSER y el área de su rectángulo de área mínima
- Relación entre la longitud del esqueleto MSER y el área del MSER
- Relación del área de MSER al área de su mayor contorno
Para detectar el logotipo de Toptal en una imagen, las propiedades de todos los MSER se comparan con las propiedades del logotipo de Toptal ya aprendidas. A los efectos de este tutorial, se eligieron empíricamente las diferencias máximas permitidas para cada propiedad.
Finalmente, se elige como resultado la región más similar.
Aplicación iOS
Usar OpenCV desde iOS es fácil. Si aún no lo ha hecho, aquí hay un resumen rápido de los pasos necesarios para configurar Xcode para crear una aplicación iOS y usar OpenCV en ella:
Cree un nuevo nombre de proyecto "SuperCool Logo Detector". Como idioma, deje Objective-C seleccionado.
Agregue un nuevo archivo de encabezado de prefijo (.pch) y asígnele el nombre PrefixHeader.pch
Vaya al objetivo de compilación del proyecto "SuperCool Logo Detector" y en la pestaña Configuración de compilación, busque la configuración "Encabezados de prefijo". Puede encontrarlo en la sección Idioma de LLVM o utilizar la función de búsqueda.
Agregue "PrefixHeader.pch" a la configuración de encabezados de prefijo
En este punto, si no ha instalado OpenCV para iOS 2.4.11, hágalo ahora.
Arrastre y suelte el marco descargado en el proyecto. Marque "Marcos y bibliotecas vinculados" en su configuración de destino. (Debería agregarse automáticamente, pero es mejor estar seguro).
Además, vincule los siguientes marcos:
- Fundación AV
- Biblioteca de activos
- CoreMedia
Abra "PrefixHeader.pch" y agregue las siguientes 3 líneas:
#ifdef __cplusplus #include <opencv2/opencv.hpp> #endif”
Cambie las extensiones de los archivos de código creados automáticamente de “ .m” a “ .mm”. OpenCV está escrito en C++ y con *.mm está diciendo que usará Objective-C++.
Importe "opencv2/highgui/cap_ios.h" en ViewController.h y cambie ViewController para que se ajuste al protocolo CvVideoCameraDelegate:
#import <opencv2/highgui/cap_ios.h>
Abra Main.storyboard y coloque un UIImageView en el controlador de vista inicial.
Haga una salida a ViewController.mm llamada "imageView"
Crea una variable “CvVideoCamera *cámara;” en ViewController.h o ViewController.mm, e inicialícelo con una referencia a la cámara trasera:
camera = [[CvVideoCamera alloc] initWithParentView: _imageView]; camera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; camera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; camera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; camera.defaultFPS = 30; camera.grayscaleMode = NO; camera.delegate = self;
Si crea el proyecto ahora, Xcode le advertirá que no implementó el método "processImage" de CvVideoCameraDelegate. Por ahora, y en aras de la simplicidad, simplemente adquiriremos las imágenes de la cámara y las superpondremos con un texto simple:
- Agregue una sola línea a "viewDidAppear":
[camera start];
Ahora, si ejecuta la aplicación, le pedirá permiso para acceder a la cámara. Y luego deberías ver el video de la cámara.
En el método "processImage" agregue las siguientes dos líneas:
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));
Eso es básicamente todo. Ahora tiene una aplicación muy simple que dibuja el texto "Toptal" en las imágenes de la cámara. Ahora podemos construir nuestra aplicación de detección de logotipo de destino a partir de esta más simple. Para abreviar, en este artículo analizaremos solo algunos segmentos de código que son fundamentales para comprender cómo funciona la aplicación en general. El código en GitHub tiene una buena cantidad de comentarios para explicar lo que hace cada segmento.
Dado que la aplicación tiene un solo propósito, detectar el logotipo de Toptal, tan pronto como se inicia, las características de MSER se extraen de la imagen de la plantilla dada y los valores se almacenan en la memoria:
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];
La aplicación tiene solo una pantalla con un botón Iniciar/Detener, y toda la información necesaria, como FPS y el número de MSER detectados, se dibujan automáticamente en la imagen. Siempre que la aplicación no se detenga, para cada cuadro de imagen en la cámara, se invoca el siguiente método processImage:
-(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]; }
Este método, en esencia, crea una copia en escala de grises de la imagen original. Identifica todos los MSER y extrae sus características relevantes, califica cada MSER por su similitud con la plantilla y elige el mejor. Finalmente, dibuja un límite verde alrededor del mejor MSER y superpone la imagen con metainformación.
A continuación se encuentran las definiciones de algunas clases importantes y sus métodos en esta aplicación. Sus propósitos se describen en los comentarios.
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
Después de que todo esté conectado, con esta aplicación, debería poder usar la cámara de su dispositivo iOS para detectar el logotipo de Toptal desde diferentes ángulos y orientaciones.
Conclusión
En este artículo hemos mostrado lo fácil que es detectar objetos simples a partir de una imagen utilizando OpenCV. El código completo está disponible en GitHub. Siéntase libre de bifurcar y enviar solicitudes push, ya que las contribuciones son bienvenidas.
Como ocurre con cualquier problema de aprendizaje automático, la tasa de éxito de la detección de logotipos en esta aplicación se puede aumentar mediante el uso de un conjunto diferente de funciones y un método diferente para la clasificación de objetos. Sin embargo, espero que este artículo lo ayude a comenzar con la detección de objetos usando MSER y las aplicaciones de las técnicas de visión por computadora, en general.
Otras lecturas
- J. Matas, O. Chum, M. Urban y T. Pajdla. “Estéreo robusto de línea de base ancha de regiones extremas máximamente estables”.
- Neumann, Lucas; Matas, Jiri (2011). “Un método para la localización y el reconocimiento de texto en imágenes del mundo real”