使用 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 的后置摄像头进行图像检测。