OpenCV 및 Swift를 사용한 객체 감지

게시 됨: 2022-03-11

Swift는 우리와 함께한지 꽤 되었으며 반복을 통해 현대 객체 지향 프로그래밍 언어의 모든 기능을 제공합니다. 여기에는 옵션, 제네릭, 튜플, 메서드, 확장 및 프로토콜 등을 지원하는 구조체가 포함됩니다. 그러나 애플리케이션이 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 코드는 매우 간단합니다:

  1. ViewController.swift 내부에서 CvVideoCamera 가 콘텐츠를 렌더링할 UIImageView 를 만듭니다.
  2. OpenCVWrapper 의 인스턴스를 만들고 UIImageView 인스턴스를 전달합니다.
  3. 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의 홈페이지를 엽니다.

OpenCV Swift로 Toptal 로고 감지하기

향후 업데이트를 위해 백그라운드 스레드에서 CV 템플릿 일치를 실행할 수 있습니다. 이렇게 하면 기본 스레드를 차단하지 않고 UI가 응답하는 상태를 유지합니다. 이것은 OpenCV의 간단한 예이기 때문에 템플릿 일치가 그다지 성공적이지 않을 수 있지만 이 기사의 목적은 사용을 시작하는 방법을 보여주는 것입니다.

iOS에서 OpenCV와 더 복잡한 사용법을 배우는 데 여전히 관심이 있다면 iOS에서 MSER을 사용한 실시간 객체 감지를 추천합니다.