Detección de objetos usando OpenCV y Swift
Publicado: 2022-03-11Swift ha estado con nosotros por un tiempo y, a través de sus iteraciones, nos ha brindado todas las características de un lenguaje de programación moderno orientado a objetos. Estos incluyen opcionales, genéricos, tuplas, estructuras que admiten métodos, extensiones y protocolos, y muchos más. Pero si su aplicación se basa en una biblioteca escrita con C++, entonces ya no puede contar con Swift. Por suerte, Objective-C++ está aquí para ayudarnos.
Desde su presentación, Swift ha tenido una gran interoperabilidad con Objective-C y usaremos Objective-C para conectar Swift con C++. Objective-C++ no es más que Objective-C con la capacidad de vincularse con el código C++, y con eso, en esta publicación de blog, crearemos una aplicación simple que reconocerá el logotipo de Toptal dentro de la imagen usando OpenCV. Cuando detectemos ese logotipo, abriremos la página de inicio de Toptal.
Como se indica en la página web de OpenCV:
"OpenCV fue diseñado para la eficiencia computacional y con un fuerte enfoque en las aplicaciones en tiempo real. Escrita en C/C++ optimizado, la biblioteca puede aprovechar el procesamiento de varios núcleos".
Esta es una gran solución si desea un desarrollo rápido y una visión por computadora confiable.
Creación del contenedor OpenCV Objective-C++
En este tutorial, diseñaremos la aplicación que coincidirá con un logotipo de Toptal dentro de una imagen y abrirá la página web de Toptal. Para comenzar, cree un nuevo proyecto Xcode y configure CocoaPods usando pod init
. Agregue OpenCV a Podfile pod 'OpenCV
y ejecute pod install
en Terminal. ¡Asegúrate de descomentar use_frameworks
! declaración dentro de Podfile.
Ahora, cuando tenemos OpenCV dentro del proyecto Xcode, tenemos que conectarlo con Swift. Aquí hay un resumen rápido de los pasos involucrados:
Paso 1: Cree una nueva clase de Objective-C OpenCVWrapper
. Cuando Xcode le pregunta " ¿Le gustaría configurar un encabezado de puente de Objective-C?" Elija " Crear encabezado de puente ". El encabezado puente es el lugar donde importa las clases de Objective-C, y luego son visibles dentro de Swift.
Paso 2: Para usar C++ dentro de Objective-C, tenemos que cambiar la extensión del archivo de OpenCVWrapper.m
a OpenCVWrapper.mm
. Puede hacerlo simplemente cambiando el nombre del archivo dentro del navegador de proyectos de Xcode. Agregar .mm
como extensión cambiará el tipo de archivo de Objective-C a Objective-C++.
Paso 3: Importe OpenCV en OpenCVWrapper.mm
usando la siguiente importación. Es importante escribir la importación dada arriba #import "OpenCVWrapper.h"
porque de esta manera evitamos un conflicto BOOL conocido. OpenCV contiene una enumeración que tiene el valor NO
, lo que provoca un conflicto con el valor Objective-C BOOL NO
. Si no necesita clases que usen dicha enumeración, esta es la forma más sencilla. De lo contrario, debe anular la definición de BOOL antes de importar OpenCV.
#ifdef __cplusplus #import <opencv2/opencv.hpp> #import <opencv2/imgcodecs/ios.h> #import <opencv2/videoio/cap_ios.h> #endif
Paso 4: agregue #import "OpenCVWrapper.h"
a su encabezado puente.
Abra OpenCVWrapper.mm
y cree una interfaz privada donde declararemos propiedades privadas:
@interface OpenCVWrapper() <CvVideoCameraDelegate> @property (strong, nonatomic) CvVideoCamera *videoCamera; @property (assign, nonatomic) cv::Mat logoSample; @end
Para crear CvVideoCamera
, tenemos que pasarle UIImageView
, y lo haremos a través de nuestro inicializador de designador.
- (instancetype)initWithParentView:(UIImageView *)parentView delegate:(id<OpenCVWrapperDelegate>)delegate { if (self = [super init]) { self.delegate = delegate; parentView.contentMode = UIViewContentModeScaleAspectFill; self.videoCamera = [[CvVideoCamera alloc] initWithParentView:parentView]; self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh; self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; self.videoCamera.defaultFPS = 30; self.videoCamera.grayscaleMode = [NSNumber numberWithInt:0].boolValue; self.videoCamera.delegate = self; // Convert UIImage to Mat and store greyscale version UIImage *templateImage = [UIImage imageNamed:@"toptal"]; cv::Mat templateMat; UIImageToMat(templateImage, templateMat); cv::Mat grayscaleMat; cv::cvtColor(templateMat, grayscaleMat, CV_RGB2GRAY); self.logoSample = grayscaleMat; [self.videoCamera start]; } return self; }
Dentro de él, configuramos CvVideoCamera
que representa el video dentro de parentView
dado y, a través del delegado, nos envía la imagen cv::Mat
para su análisis.

processImage:
método es del protocolo CvVideoCameraDelegate
, y dentro de él, haremos la coincidencia de plantillas.
- (void)processImage:(cv::Mat&)image { cv::Mat gimg; // Convert incoming img to greyscale to match template cv::cvtColor(image, gimg, CV_BGR2GRAY); // Get matching cv::Mat res(image.rows-self.logoSample.rows+1, self.logoSample.cols-self.logoSample.cols+1, CV_32FC1); cv::matchTemplate(gimg, self.logoSample, res, CV_TM_CCOEFF_NORMED); cv::threshold(res, res, 0.5, 1., CV_THRESH_TOZERO); double minval, maxval, threshold = 0.9; cv::Point minloc, maxloc; cv::minMaxLoc(res, &minval, &maxval, &minloc, &maxloc); // Call delegate if match is good enough if (maxval >= threshold) { // Draw a rectangle for confirmation cv::rectangle(image, maxloc, cv::Point(maxloc.x + self.logoSample.cols, maxloc.y + self.logoSample.rows), CV_RGB(0,255,0), 2); cv::floodFill(res, maxloc, cv::Scalar(0), 0, cv::Scalar(.1), cv::Scalar(1.)); [self.delegate openCVWrapperDidMatchImage:self]; } }
Primero, convertimos la imagen dada a una imagen en escala de grises porque dentro del método init convertimos nuestra imagen coincidente de la plantilla del logotipo de Toptal a escala de grises. El siguiente paso es buscar coincidencias con un cierto umbral que nos ayudará con la escala y los ángulos. Tenemos que verificar los ángulos porque puede capturar una foto en varios ángulos en los que todavía queremos detectar el logotipo. Después de alcanzar el umbral dado, invocaremos al delegado y abriremos la página web de Toptal.
No olvide agregar NSCameraUsageDescription
a su Info.plist, de lo contrario, su aplicación fallará justo después de llamar a [self.videoCamera start];
.
Ahora finalmente rápido
Hasta ahora nos estábamos enfocando en Objective-C++ porque toda la lógica se realiza dentro de él. Nuestro código Swift será bastante simple:
- Dentro de
ViewController.swift
, creeUIImageView
dondeCvVideoCamera
representará el contenido. - Cree una instancia de
OpenCVWrapper
y pásele la instancia deUIImageView
. - Implementar el protocolo
OpenCVWrapperDelegate
para abrir la página web de Toptal cuando detectemos el logo.
class ViewController: UIViewController { var wrapper: OpenCVWrapper! @IBOutlet var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. wrapper = OpenCVWrapper.init(parentView: imageView, delegate: self) } } extension ViewController: OpenCVWrapperDelegate { //MARK: - OpenCVWrapperDelegate func openCVWrapperDidMatchImage(_ wrapper: OpenCVWrapper) { UIApplication.shared.open(URL.init(string: "https://toptal.com")!, options: [:], completionHandler: nil) } }
OpenCV Swift en acción
En este artículo, mostramos cómo puede integrar el código C++ con Swift y crear clases contenedoras que están aquí para unir el código C++ con Swift. Ahora, cuando detectamos el logotipo de Toptal a través de la cámara, abrimos la página de inicio de Toptal.
Para futuras actualizaciones, es posible que desee ejecutar la coincidencia de plantillas de CV en un hilo de fondo. De esta manera, no bloqueará el hilo principal y la interfaz de usuario seguirá respondiendo. Debido a que este es un ejemplo simple de OpenCV, es posible que la coincidencia de plantillas no sea muy exitosa, pero el propósito de este artículo fue mostrarle cómo puede comenzar a usarlo.
Si todavía está interesado en aprender OpenCV y sus usos más complejos en iOS, le recomiendo la detección de objetos en tiempo real usando MSER en iOS , que lo guía a través de la detección de imágenes usando la cámara trasera del iPhone.