使用 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。 将 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 代码将相当简单:

  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 Swift 在行动

在本文中,我们展示了如何将 C++ 代码与 Swift 集成,并创建用于将 C++ 代码与 Swift 连接起来的包装类。 现在,当我们通过摄像头检测到 Toptal Logo 时,我们打开 Toptal 的主页。

使用 OpenCV Swift 检测 Toptal 的徽标

对于未来的更新,您可能希望在后台线程中运行 CV 模板匹配。 这样,您不会阻塞主线程,并且 UI 将保持响应。 因为这是一个简单的 OpenCV 示例,模板匹配可能不会特别成功,但本文的目的是向您展示如何开始使用它。

如果您仍然对学习 OpenCV 及其在 iOS 中更复杂的用途感兴趣,我推荐在 iOS 中使用 MSER 进行实时对象检测,它会引导您使用 iPhone 的后置摄像头进行图像检测。