使用 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。 將 OpenCV 添加到 Podfile pod '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 包含值NO
的枚舉,這會導致與 Objective-C BOOL 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; }
在其中,我們配置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];
.
現在終於 Swift
到目前為止,我們專注於 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 Swift 在行動
在本文中,我們展示瞭如何將 C++ 代碼與 Swift 集成,並創建用於將 C++ 代碼與 Swift 連接起來的包裝類。 現在,當我們通過攝像頭檢測到 Toptal Logo 時,我們打開 Toptal 的主頁。
對於未來的更新,您可能希望在後台線程中運行 CV 模板匹配。 這樣,您不會阻塞主線程,並且 UI 將保持響應。 因為這是一個簡單的 OpenCV 示例,模板匹配可能不會特別成功,但本文的目的是向您展示如何開始使用它。
如果您仍然對學習 OpenCV 及其在 iOS 中更複雜的用途感興趣,我推薦在 iOS 中使用 MSER 進行實時對象檢測,它會引導您使用 iPhone 的後置攝像頭進行圖像檢測。