Обнаружение объектов с использованием OpenCV и Swift
Опубликовано: 2022-03-11Swift уже давно с нами, и благодаря своим итерациям он принес нам все возможности современного объектно-ориентированного языка программирования. К ним относятся необязательные параметры, дженерики, кортежи, структуры, поддерживающие методы, расширения и протоколы, и многое другое. Но если ваше приложение опирается на библиотеку, написанную с использованием C++, вы больше не можете рассчитывать на Swift. К счастью, Objective-C++ здесь, чтобы помочь нам.
С момента своего появления у Swift была отличная совместимость с Objective-C, и мы будем использовать Objective-C для соединения Swift с C++. Objective-C++ — это не что иное, как Objective-C с возможностью связывания с кодом C++, и, используя это, в этом сообщении блога мы создадим простое приложение, которое будет распознавать логотип Toptal внутри изображения с помощью OpenCV. Когда мы обнаружим этот логотип, мы откроем домашнюю страницу Toptal.
Как указано на веб-странице OpenCV:
«OpenCV был разработан для повышения эффективности вычислений и уделения особого внимания приложениям реального времени. Написанная на оптимизированном языке C/C++, библиотека может использовать преимущества многоядерной обработки».
Это отличное решение, если вам нужна быстрая разработка и надежное компьютерное зрение.
Создание оболочки OpenCV Objective-C++
В этом уроке мы разработаем приложение, которое будет соответствовать логотипу Toptal внутри изображения и открывать веб-страницу Toptal. Для начала создайте новый проект Xcode и настройте CocoaPods с помощью pod init
. Добавьте OpenCV в модуль Podfile pod 'OpenCV
и запустите pod install
в терминале. Обязательно раскомментируйте use_frameworks
! заявление внутри Podfile.
Теперь, когда у нас есть OpenCV внутри проекта Xcode, мы должны подключить его к Swift. Вот краткое описание необходимых шагов:
Шаг 1: Создайте новый класс OpenCVWrapper
для Objective-C. Когда Xcode спрашивает вас: « Хотите ли вы настроить соединительный заголовок Objective-C? » выберите « Создать соединительный заголовок ». Связующий заголовок — это место, куда вы импортируете классы Objective-C, после чего они видны внутри Swift.
Шаг 2: Чтобы использовать C++ внутри Objective-C, мы должны изменить расширение файла с OpenCVWrapper.m
на OpenCVWrapper.mm
. Вы можете сделать это, просто переименовав файл в навигаторе проекта Xcode. Добавление .mm
в качестве расширения изменит тип файла с Objective-C на Objective-C++.
Шаг 3: Импортируйте OpenCV в OpenCVWrapper.mm
используя следующий импорт. Важно написать данный импорт выше #import "OpenCVWrapper.h"
потому что таким образом мы избегаем известного конфликта BOOL. OpenCV содержит перечисление со значением NO
, которое вызывает конфликт со значением Objective-C BOOL NO
. Если вам не нужны классы, использующие такое перечисление, то это самый простой способ. В противном случае вам придется отменить определение BOOL перед импортом OpenCV.
#ifdef __cplusplus #import <opencv2/opencv.hpp> #import <opencv2/imgcodecs/ios.h> #import <opencv2/videoio/cap_ios.h> #endif
Шаг 4: Добавьте #import "OpenCVWrapper.h"
в заголовок моста.
Откройте OpenCVWrapper.mm
и создайте приватный интерфейс, в котором мы объявим приватные свойства:
@interface OpenCVWrapper() <CvVideoCameraDelegate> @property (strong, nonatomic) CvVideoCamera *videoCamera; @property (assign, nonatomic) cv::Mat logoSample; @end
Чтобы создать CvVideoCamera
, мы должны передать UIImageView
, и мы сделаем это через наш инициализатор указателя.
- (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; }
Внутри него мы настраиваем CvVideoCamera
, которая рендерит видео внутри данного parentView
и через делегат отправляет нам изображение cv::Mat
для анализа.

processImage:
метод из протокола CvVideoCameraDelegate
, и внутри него мы будем выполнять сопоставление с шаблоном.
- (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]; } }
Во-первых, мы преобразуем данное изображение в изображение в градациях серого, потому что внутри метода init мы преобразовали наш шаблон логотипа Toptal, соответствующий изображению, в градации серого. Следующим шагом будет проверка совпадений с определенным порогом, который поможет нам с масштабированием и углами. Мы должны проверить углы, потому что вы можете сделать снимок под разными углами, в которых мы все еще хотим обнаружить логотип. После достижения заданного порога мы вызовем делегата и откроем веб-страницу Toptal.
Не забудьте добавить NSCameraUsageDescription
в свой Info.plist, иначе ваше приложение вылетит сразу после вызова [self.videoCamera start];
.
Теперь, наконец, Свифт
До сих пор мы фокусировались на Objective-C++, потому что вся логика выполняется внутри него. Наш код Swift будет довольно простым:
- Внутри
ViewController.swift
создайтеUIImageView
, гдеCvVideoCamera
будет отображать контент. - Создайте экземпляр
OpenCVWrapper
и передайте ему экземплярUIImageView
. - Реализовать протокол
OpenCVWrapperDelegate
, чтобы открывать веб-страницу Toptal при обнаружении логотипа.
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 в действии
В этой статье мы показали, как вы можете интегрировать код C++ со Swift и создавать классы-оболочки, предназначенные для соединения кода C++ со Swift. Теперь, когда мы обнаруживаем логотип Toptal через камеру, мы открываем домашнюю страницу Toptal.
Для будущих обновлений вы можете запустить сопоставление шаблонов резюме в фоновом потоке. Таким образом, вы не будете блокировать основной поток, а пользовательский интерфейс останется отзывчивым. Поскольку это простой пример OpenCV, сопоставление с шаблоном может быть не очень успешным, но цель этой статьи состояла в том, чтобы показать вам, как вы можете начать его использовать.
Если вы все еще заинтересованы в изучении OpenCV и его более сложном использовании в iOS, я рекомендую Обнаружение объектов в реальном времени с использованием MSER в iOS , которое проведет вас через обнаружение изображений с помощью задней камеры iPhone.