OpenCV 教程:在 iOS 中使用 MSER 进行实时对象检测
已发表: 2022-03-11在过去几年中,手机的平均性能显着提高。 无论是纯粹的 CPU 马力还是 RAM 容量,现在在移动硬件上执行计算繁重的任务变得更加容易。 尽管这些移动技术正朝着正确的方向发展,但在移动平台上仍有很多工作要做,尤其是随着增强现实、虚拟现实和人工智能的出现。
计算机视觉的一个主要挑战是检测图像中感兴趣的对象。 人眼和大脑做得非常出色,在机器上复制它仍然是一个梦想。 近几十年来,已经开发出一些方法来在机器中模仿这一点,而且它正在变得越来越好。
在本教程中,我们将探索一种用于检测图像中的斑点的算法。 我们还将使用开源库 OpenCV 中的算法来实现一个原型 iPhone 应用程序,该应用程序使用后置摄像头获取图像并检测其中的对象。
OpenCV 教程
OpenCV 是一个开源库,提供主要计算机视觉和机器学习算法的实现。 如果您想实现一个应用程序来检测人脸、扑克桌上的扑克牌,甚至是一个简单的应用程序来为任意图像添加效果,那么 OpenCV 是一个不错的选择。
OpenCV 是用 C/C++ 编写的,并且具有适用于所有主要平台的包装库。 这使得它在 iOS 环境中特别容易使用。 要在 Objective-C iOS 应用程序中使用它,请从官方网站下载 OpenCV iOS 框架。 请确保您使用的是 OpenCV for iOS 的 2.4.11 版本(本文假设您正在使用),因为最新版本 3.0 在头文件的组织方式上有一些破坏兼容性的变化。 有关如何安装它的详细信息记录在其网站上。
质谱仪
MSER 是Maximally Stable Extremal Regions 的缩写,是可用于图像内斑点检测的众多方法之一。 简而言之,该算法识别其外边界像素强度高于(给定阈值)内边界像素强度的连续像素集。 如果这些区域在不同强度的情况下变化不大,则称它们是最大稳定的。
尽管存在许多其他 blob 检测算法,但这里选择了 MSER,因为它的运行时间复杂度相当低,为 O(n log(log(n))),其中 n 是图像上的像素总数。 该算法还对模糊和缩放具有鲁棒性,这在处理通过实时源(例如手机的摄像头)获取的图像时具有优势。
出于本教程的目的,我们将设计应用程序来检测 Toptal 的徽标。 该符号有尖角,这可能会导致人们思考角检测算法在检测 Toptal 徽标方面的有效性。 毕竟,这样的算法既易于使用又易于理解。 虽然基于角点的方法在检测与背景明显分离的物体(例如白色背景上的黑色物体)时可能具有很高的成功率,但在现实世界中很难实现对 Toptal 徽标的实时检测图像,算法将不断检测数百个角落。
战略
对于应用程序通过相机获取的每一帧图像,首先将其转换为灰度。 灰度图像只有一个颜色通道,但徽标仍然可见。 这使算法更容易处理图像,并显着减少算法必须处理的数据量,而几乎没有额外收益。
接下来,我们将使用 OpenCV 的实现算法来提取所有 MSER。 接下来,每个 MSER 将通过将其最小边界矩形转换为正方形来进行归一化。 这一步很重要,因为可以从不同的角度和距离获取徽标,这将增加透视失真的容差。
此外,为每个 MSER 计算了许多属性:
- 孔数
- MSER 的面积与其凸包面积之比
- MSER 的面积与其最小面积矩形的面积之比
- MSER 骨架长度与 MSER 面积之比
- MSER 的面积与其最大轮廓面积之比
为了检测图像中的 Toptal 徽标,所有 MSER 的属性都与已经学习的 Toptal 徽标属性进行比较。 出于本教程的目的,根据经验选择每个属性的最大允许差异。
最后,选择最相似的区域作为结果。
iOS 应用程序
从 iOS 使用 OpenCV 很容易。 如果您还没有这样做,这里简要介绍了设置 Xcode 以创建 iOS 应用程序并在其中使用 OpenCV 所涉及的步骤:
创建一个新项目名称“SuperCool Logo Detector”。 作为语言,选择 Objective-C。
添加一个新的 Prefix Header (.pch) 文件并将其命名为 PrefixHeader.pch
进入项目“SuperCool Logo Detector”构建目标,在“构建设置”选项卡中,找到“前缀标题”设置。 您可以在 LLVM 语言部分找到它,或使用搜索功能。
将“PrefixHeader.pch”添加到前缀标题设置
此时,如果您尚未安装 OpenCV for iOS 2.4.11,请立即安装。
将下载的框架拖放到项目中。 在 Target Settings 中选中“Linked Frameworks and Libraries”。 (它应该是自动添加的,但最好是安全的。)
此外,链接以下框架:
- AV基金会
- 资产库
- 核心媒体
打开“PrefixHeader.pch”并添加以下 3 行:
#ifdef __cplusplus #include <opencv2/opencv.hpp> #endif”
将自动创建的代码文件的扩展名从“ .m”更改为“ .mm”。 OpenCV 是用 C++ 编写的,使用 *.mm 表示您将使用 Objective-C++。
在 ViewController.h 中导入“opencv2/highgui/cap_ios.h”并更改 ViewController 以符合协议 CvVideoCameraDelegate:
#import <opencv2/highgui/cap_ios.h>
打开 Main.storyboard 并将 UIImageView 放在初始视图控制器上。
为 ViewController.mm 创建一个名为“imageView”的出口
创建一个变量“CvVideoCamera *camera;” 在 ViewController.h 或 ViewController.mm 中,并使用对后置摄像头的引用对其进行初始化:
camera = [[CvVideoCamera alloc] initWithParentView: _imageView]; camera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack; camera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; camera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; camera.defaultFPS = 30; camera.grayscaleMode = NO; camera.delegate = self;
如果你现在构建项目,Xcode 会警告你没有从 CvVideoCameraDelegate 实现“processImage”方法。 现在,为了简单起见,我们将只从相机获取图像并用简单的文本覆盖它们:
- 在“viewDidAppear”中添加一行:
[camera start];
现在,如果您运行该应用程序,它会询问您访问相机的权限。 然后你应该看到来自相机的视频。
在“processImage”方法中添加以下两行:
const char* str = [@"Toptal" cStringUsingEncoding: NSUTF8StringEncoding]; cv::putText(image, str, cv::Point(100, 100), CV_FONT_HERSHEY_PLAIN, 2.0, cv::Scalar(0, 0, 255));
差不多就是这样。 现在您有一个非常简单的应用程序,它可以在来自相机的图像上绘制文本“Toptal”。 我们现在可以从这个更简单的应用程序中构建目标徽标检测应用程序。 为简洁起见,在本文中,我们将仅讨论对理解应用程序整体工作方式至关重要的少数代码段。 GitHub 上的代码有相当多的注释来解释每个部分的作用。
由于该应用程序只有一个目的,即检测 Toptal 的徽标,因此一旦启动,就会从给定的模板图像中提取 MSER 特征,并将值存储在内存中:
cv::Mat logo = [ImageUtils cvMatFromUIImage: templateImage]; //get gray image cv::Mat gray; cvtColor(logo, gray, CV_BGRA2GRAY); //mser with maximum area is std::vector<cv::Point> maxMser = [ImageUtils maxMser: &gray]; //get 4 vertices of the maxMSER minrect cv::RotatedRect rect = cv::minAreaRect(maxMser); cv::Point2f points[4]; rect.points(points); //normalize image cv::Mat M = [GeometryUtil getPerspectiveMatrix: points toSize: rect.size]; cv::Mat normalizedImage = [GeometryUtil normalizeImage: &gray withTranformationMatrix: &M withSize: rect.size.width]; //get maxMser from normalized image std::vector<cv::Point> normalizedMser = [ImageUtils maxMser: &normalizedImage]; //remember the template self.logoTemplate = [[MSERManager sharedInstance] extractFeature: &normalizedMser]; //store the feature [self storeTemplate];
该应用程序只有一个带有开始/停止按钮的屏幕,所有必要的信息,如 FPS 和检测到的 MSER 的数量,都会自动绘制在图像上。 只要应用程序没有停止,对于相机中的每一帧图像,都会调用以下 processImage 方法:

-(void)processImage:(cv::Mat &)image { cv::Mat gray; cvtColor(image, gray, CV_BGRA2GRAY); std::vector<std::vector<cv::Point>> msers; [[MSERManager sharedInstance] detectRegions: gray intoVector: msers]; if (msers.size() == 0) { return; }; std::vector<cv::Point> *bestMser = nil; double bestPoint = 10.0; std::for_each(msers.begin(), msers.end(), [&] (std::vector<cv::Point> &mser) { MSERFeature *feature = [[MSERManager sharedInstance] extractFeature: &mser]; if(feature != nil) { if([[MLManager sharedInstance] isToptalLogo: feature] ) { double tmp = [[MLManager sharedInstance] distance: feature ]; if ( bestPoint > tmp ) { bestPoint = tmp; bestMser = &mser; } } } }); if (bestMser) { NSLog(@"minDist: %f", bestPoint); cv::Rect bound = cv::boundingRect(*bestMser); cv::rectangle(image, bound, GREEN, 3); } else { cv::rectangle(image, cv::Rect(0, 0, W, H), RED, 3); } // Omitted debug code [FPS draw: image]; }
这种方法本质上是创建原始图像的灰度副本。 它识别所有 MSER 并提取它们的相关特征,对每个 MSER 与模板的相似度进行评分并挑选出最佳的。 最后,它在最佳 MSER 周围绘制绿色边界,并用元信息覆盖图像。
以下是此应用程序中几个重要类及其方法的定义。 他们的目的在评论中描述。
几何工具.h
/* This static class provides perspective transformation function */ @interface GeometryUtil : NSObject /* Return perspective transformation matrix for given points to square with origin [0,0] and with size (size.width, size.width) */ + (cv::Mat) getPerspectiveMatrix: (cv::Point2f[]) points toSize: (cv::Size2f) size; /* Returns new perspecivly transformed image with given size */ + (cv::Mat) normalizeImage: (cv::Mat *) image withTranformationMatrix: (cv::Mat *) M withSize: (float) size; @end
MSERManager.h
/* Singelton class providing function related to msers */ @interface MSERManager : NSObject + (MSERManager *) sharedInstance; /* Extracts all msers into provided vector */ - (void) detectRegions: (cv::Mat &) gray intoVector: (std::vector<std::vector<cv::Point>> &) vector; /* Extracts feature from the mser. For some MSERs feature can be NULL !!! */ - (MSERFeature *) extractFeature: (std::vector<cv::Point> *) mser; @end
MLManager.h
/* This singleton class wraps object recognition function */ @interface MLManager : NSObject + (MLManager *) sharedInstance; /* Stores feature from the biggest MSER in the templateImage */ - (void) learn: (UIImage *) templateImage; /* Sum of the differences between logo feature and given feature */ - (double) distance: (MSERFeature *) feature; /* Returns true if the given feature is similar to the one learned from the template */ - (BOOL) isToptalLogo: (MSERFeature *) feature; @end
将所有内容连接在一起后,使用此应用程序,您应该能够使用 iOS 设备的摄像头从不同角度和方向检测 Toptal 的徽标。
结论
在本文中,我们展示了使用 OpenCV 从图像中检测简单对象是多么容易。 整个代码可在 GitHub 上找到。 随意分叉和发送推送请求,因为欢迎贡献。
与任何机器学习问题一样,通过使用不同的特征集和不同的对象分类方法,可以提高此应用程序中徽标检测的成功率。 但是,我希望本文能帮助您开始使用 MSER 进行对象检测和计算机视觉技术的应用。
延伸阅读
- J. Matas、O. Chum、M. Urban 和 T. Pajdla。 “来自最大稳定极值区域的稳健宽基线立体声。”
- 诺伊曼,卢卡斯; 马塔斯,吉里(2011)。 “一种在现实世界图像中进行文本定位和识别的方法”