Wykrywanie obiektów za pomocą OpenCV i Swift

Opublikowany: 2022-03-11

Swift jest z nami już od jakiegoś czasu i poprzez swoje iteracje przyniósł nam wszystkie cechy współczesnego języka programowania obiektowego. Należą do nich opcje, typy generyczne, krotki, struktury obsługujące metody, rozszerzenia i protokoły oraz wiele innych. Ale jeśli Twoja aplikacja opiera się na bibliotece napisanej w C++, nie możesz już liczyć na Swift. Na szczęście Objective-C++ jest tutaj, aby nam pomóc.

Od momentu wprowadzenia Swift ma świetną interoperacyjność z Objective-C i będziemy używać Objective-C do łączenia Swifta z C++. Objective-C++ to nic innego jak Objective-C z możliwością łączenia się z kodem C++, a używając tego, w tym poście na blogu, stworzymy prostą aplikację, która będzie rozpoznawać logo Toptal wewnątrz obrazu za pomocą OpenCV. Gdy wykryjemy to logo, otworzymy stronę główną Toptal.

Jak podano na stronie OpenCV:

„OpenCV został zaprojektowany z myślą o wydajności obliczeniowej i silnym nacisku na aplikacje działające w czasie rzeczywistym. Napisana w zoptymalizowanym C/C++ biblioteka może korzystać z przetwarzania wielordzeniowego”.

To świetne rozwiązanie, jeśli zależy Ci na szybkim rozwoju i niezawodnym widzeniu komputerowym.

Tworzenie opakowania OpenCV Objective-C++

W tym samouczku zaprojektujemy aplikację, która dopasuje logo Toptal do obrazu i otworzy stronę internetową Toptal. Aby rozpocząć, utwórz nowy projekt Xcode i skonfiguruj CocoaPods za pomocą pod init . Dodaj OpenCV do Podfile pod 'OpenCV i uruchom pod install w Terminalu. Pamiętaj, aby odkomentować use_frameworks ! oświadczenie wewnątrz Podfile.

Teraz, gdy mamy OpenCV w projekcie Xcode, musimy połączyć go ze Swiftem. Oto krótki zarys odpowiednich kroków:

Krok 1: Utwórz nową klasę Objective-C OpenCVWrapper . Gdy Xcode zapyta Cię „ Czy chcesz skonfigurować nagłówek mostkowania Objective-C? ” wybierz „ Utwórz nagłówek pomostowy ”. Nagłówek pomostowy to miejsce, w którym importujesz klasy Objective-C, a następnie są one widoczne w Swift.

Krok 2: Aby używać C++ w Objective-C, musimy zmienić rozszerzenie pliku z OpenCVWrapper.m na OpenCVWrapper.mm . Możesz to zrobić, po prostu zmieniając nazwę pliku w nawigatorze projektu Xcode. Dodanie .mm jako rozszerzenia zmieni typ pliku z Objective-C na Objective-C++.

Krok 3: Zaimportuj OpenCV do OpenCVWrapper.mm za pomocą następującego importu. Ważne jest, aby wpisać podany import powyżej #import "OpenCVWrapper.h" ponieważ w ten sposób unikniemy dobrze znanego konfliktu BOOL. OpenCV zawiera wyliczenie, które ma wartość NO , co powoduje konflikt z wartością Objective-C BOOL NO . Jeśli nie potrzebujesz klas korzystających z takiego wyliczenia, to jest to najprostszy sposób. W przeciwnym razie przed importowaniem OpenCV musisz cofnąć definicję BOOL.

 #ifdef __cplusplus #import <opencv2/opencv.hpp> #import <opencv2/imgcodecs/ios.h> #import <opencv2/videoio/cap_ios.h> #endif

Krok 4: Dodaj #import "OpenCVWrapper.h" do nagłówka pomostowego.

Otwórz OpenCVWrapper.mm i stwórz prywatny interfejs, w którym zadeklarujemy prywatne właściwości:

 @interface OpenCVWrapper() <CvVideoCameraDelegate> @property (strong, nonatomic) CvVideoCamera *videoCamera; @property (assign, nonatomic) cv::Mat logoSample; @end

Aby utworzyć CvVideoCamera , musimy przekazać do niej UIImageView i zrobimy to za pomocą naszego inicjatora desygnatora.

 - (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; }

Wewnątrz konfigurujemy CvVideoCamera , która renderuje wideo wewnątrz danego parentView i poprzez delegata wysyła nam obraz cv::Mat do analizy.

processImage: metoda pochodzi z protokołu CvVideoCameraDelegate , a wewnątrz niej wykonamy dopasowanie szablonu.

 - (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]; } }

Najpierw konwertujemy dany obraz na obraz w skali szarości, ponieważ w metodzie init przekonwertowaliśmy nasz szablon logo Toptal dopasowujący obraz do skali szarości. Następnym krokiem jest sprawdzenie dopasowań z określonym progiem, co pomoże nam w skalowaniu i kątach. Musimy sprawdzić kąty, ponieważ można zrobić zdjęcie pod różnymi kątami, w których nadal chcemy wykrywać logo. Po osiągnięciu zadanego progu wywołamy delegata i otworzymy stronę internetową Toptal.

Nie zapomnij dodać NSCameraUsageDescription do pliku Info.plist, w przeciwnym razie aplikacja ulegnie awarii zaraz po wywołaniu [self.videoCamera start]; .

Teraz wreszcie Swift

Do tej pory skupialiśmy się na Objective-C++, ponieważ cała logika odbywa się w nim. Nasz kod Swift będzie dość prosty:

  1. Wewnątrz ViewController.swift , utwórz UIImageView , w którym CvVideoCamera wyrenderuje zawartość.
  2. Utwórz instancję OpenCVWrapper i przekaż do niej instancję UIImageView .
  3. Zaimplementuj protokół OpenCVWrapperDelegate , aby otworzyć stronę internetową Toptal po wykryciu 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 w akcji

W tym artykule pokazaliśmy, jak zintegrować kod C++ ze Swiftem i utworzyć klasy opakowujące, które są tutaj do łączenia kodu C++ z Swiftem. Teraz, gdy wykryjemy logo Toptal przez kamerę, otwieramy stronę główną Toptal.

Wykrywanie logo Toptala za pomocą OpenCV Swift

W przypadku przyszłych aktualizacji możesz chcieć uruchomić dopasowywanie szablonów CV w wątku w tle. W ten sposób nie zablokujesz głównego wątku, a interfejs użytkownika pozostanie responsywny. Ponieważ jest to prosty przykład OpenCV, dopasowanie szablonów może nie być szczególnie skuteczne, ale celem tego artykułu było pokazanie, jak możesz zacząć z niego korzystać.

Jeśli nadal jesteś zainteresowany nauką OpenCV i jego bardziej złożonymi zastosowaniami w iOS, polecam Wykrywanie obiektów w czasie rzeczywistym za pomocą MSER w iOS , który prowadzi Cię przez wykrywanie obrazu za pomocą tylnej kamery iPhone'a.