OpenCV 및 Swift를 사용한 객체 감지
게시 됨: 2022-03-11Swift는 우리와 함께한지 꽤 되었으며 반복을 통해 현대 객체 지향 프로그래밍 언어의 모든 기능을 제공합니다. 여기에는 옵션, 제네릭, 튜플, 메서드, 확장 및 프로토콜 등을 지원하는 구조체가 포함됩니다. 그러나 애플리케이션이 C++를 사용하여 작성된 라이브러리에 의존한다면 더 이상 Swift에 의존할 수 없습니다. 다행히도 Objective-C++가 우리를 도와줍니다.
Swift는 도입된 이후로 Objective-C와 상호 운용성이 뛰어났으며 우리는 Objective-C를 사용하여 Swift와 C++를 연결할 것입니다. Objective-C++는 C++ 코드와 연결할 수 있는 기능이 있는 Objective-C에 불과하며, 이를 사용하여 이 블로그 게시물에서 OpenCV를 사용하여 이미지 내부의 Toptal 로고를 인식하는 간단한 앱을 만들 것입니다. 해당 로고를 감지하면 Toptal 홈페이지가 열립니다.
OpenCV 웹 페이지에 명시된 대로:
"OpenCV는 실시간 응용 프로그램에 중점을 두고 계산 효율성을 위해 설계되었습니다. 최적화된 C/C++로 작성된 라이브러리는 멀티 코어 처리를 활용할 수 있습니다."
이것은 빠르고 안정적인 컴퓨터 비전을 개발하려는 경우 훌륭한 솔루션입니다.
OpenCV Objective-C++ 래퍼 만들기
이 튜토리얼에서는 이미지 내부의 Toptal 로고와 일치하는 응용 프로그램을 디자인하고 Toptal 웹 페이지를 엽니다. 시작하려면 새 Xcode 프로젝트를 만들고 pod init
를 사용하여 CocoaPods를 설정하세요. Podfile pod 'OpenCV
에 OpenCV를 추가하고 터미널에서 pod install
를 실행합니다. use_frameworks
의 주석 처리를 제거하십시오! Podfile 내부의 문.
이제 Xcode 프로젝트 내부에 OpenCV가 있으면 Swift와 연결해야 합니다. 다음은 관련된 단계에 대한 간략한 개요입니다.
1단계: 새 Objective-C 클래스 OpenCVWrapper
를 만듭니다. Xcode가 " Objective-C 브리징 헤더를 구성하시겠습니까? ” “ 브리징 헤더 생성 ”을 선택합니다. 브리징 헤더는 Objective-C 클래스를 가져오는 곳이며, 그런 다음 Swift 내에서 볼 수 있습니다.
2단계: Objective-C 내에서 C++를 사용하려면 파일 확장자를 OpenCVWrapper.m
에서 OpenCVWrapper.mm
로 변경해야 합니다. Xcode의 프로젝트 네비게이터 내에서 파일 이름을 변경하기만 하면 됩니다. .mm
를 확장자로 추가하면 파일 형식이 Objective-C에서 Objective-C++로 변경됩니다.
3단계: 다음 가져오기를 사용하여 OpenCV를 OpenCVWrapper.mm
로 가져옵니다. #import "OpenCVWrapper.h"
위에 주어진 가져오기를 작성하는 것이 중요합니다. 이렇게 하면 잘 알려진 BOOL 충돌을 피할 수 있기 때문입니다. OpenCV에는 Objective-C BOOL NO
값과 충돌을 일으키는 값이 NO
인 열거형이 있습니다. 이러한 열거형을 사용하는 클래스가 필요하지 않은 경우 이것이 가장 간단한 방법입니다. 그렇지 않으면 OpenCV를 가져오기 전에 BOOL을 정의 해제해야 합니다.
#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; }
그 안에서 우리는 주어진 parentView
내부에 비디오를 렌더링하는 CvVideoCamera
를 구성하고 대리자를 통해 분석을 위해 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
내부에서CvVideoCamera
가 콘텐츠를 렌더링할UIImageView
를 만듭니다. -
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 스위프트
이 기사에서는 C++ 코드를 Swift와 통합하고 C++ 코드를 Swift와 연결하기 위한 래퍼 클래스를 만드는 방법을 보여주었습니다. 이제 카메라를 통해 Toptal 로고를 감지하면 Toptal의 홈페이지를 엽니다.
향후 업데이트를 위해 백그라운드 스레드에서 CV 템플릿 일치를 실행할 수 있습니다. 이렇게 하면 기본 스레드를 차단하지 않고 UI가 응답하는 상태를 유지합니다. 이것은 OpenCV의 간단한 예이기 때문에 템플릿 일치가 그다지 성공적이지 않을 수 있지만 이 기사의 목적은 사용을 시작하는 방법을 보여주는 것입니다.
iOS에서 OpenCV와 더 복잡한 사용법을 배우는 데 여전히 관심이 있다면 iOS에서 MSER을 사용한 실시간 객체 감지를 추천합니다.