一个 cv::Mat 实例的作用就像是图像数据的头,其中包含着描述图像格式的信息。图像数据只是被引用,并能为多个 cv::Mat 实例共享。OpenCV 使用类似于 ARC 的引用计数方法,以保证当最后一个来自cv::Mat 的引用也消失的时候,图像数据会被释放。图像数据本身是图像连续的行的数组 (对 N 维矩阵来说,这个数据是由连续的 N-1 维数据组成的数组)。使用step[] 数组中包含的值,图像的任一像素地址都可通过下面的指针运算得到:
uchar pixelPtr = cvMat.data + rowIndex cvMat.step[0] + colIndex * cvMat.step[1]
每个像素的数据格式可以通过 type() 方法获得。除了常用的每通道 8 位无符号整数的灰度图 (1 通道,CV_8UC1) 和彩色图 (3 通道,CV_8UC3),OpenCV 还支持很多不常用的格式,例如CV_16SC3 (每像素 3 通道,每通道使用 16 位有符号整数),甚至CV_64FC4 (每像素 4 通道,每通道使用 64 位浮点数)。
cv::Algorithm
Algorithm 是 OpenCV 中实现的很多算法的抽象基类,包括将在我们的 demo 工程中用到的 FaceRecognizer。它提供的 API 与苹果的 Core Image 框架中的CIFilter 有些相似之处。创建一个Algorithm 的时候使用算法的名字来调用 Algorithm::create(),并且可以通过get() 和set()方法来获取和设置各个参数,这有点像是键值编码。另外,Algorithm 从底层就支持从/向 XML 或 YAML 文件加载/保存参数的功能。
在 iOS 上使用 OpenCV
添加 OpenCV 到你的工程中
集成 OpenCV 到你的工程中有三种方法:
使用 CocoaPods 就好: pod “OpenCV”。
下载官方 iOS 框架发行包,并把它添加到工程里。
从 GitHub 拉下代码,并根据教程自己编译 OpenCV 库。
Objective-C++
如前面所说,OpenCV 是一个 C++ 的 API,因此不能直接在 Swift 和 Objective-C 代码中使用,但能在 Objective-C++ 文件中使用。
Objective-C++ 是 Objective-C 和 C++ 的混合物,让你可以在 Objective-C 类中使用 C++ 对象。clang 编译器会把所有后缀名为.mm 的文件都当做是 Objective-C++。一般来说,它会如你所期望的那样运行,但还是有一些使用 Objective-C++ 的注意事项。内存管理是你最应该格外注意的点,因为 ARC 只对 Objective-C 对象有效。当你使用一个 C++ 对象作为类属性的时候,其唯一有效的属性就是assign。因此,你的dealloc 函数应确保 C++ 对象被正确地释放了。
第二重要的点就是,如果你在 Objective-C++ 头文件中引入了 C++ 头文件,当你在工程中使用该 Objective-C++ 文件的时候就泄露了 C++ 的依赖。任何引入你的 Objective-C++ 类的 Objective-C 类也会引入该 C++ 类,因此该 Objective-C 文件也要被声明为 Objective-C++ 的文件。这会像森林大火一样在工程中迅速蔓延。所以,应该把你引入 C++ 文件的地方都用#ifdef __cplusplus 包起来,并且只要可能,就尽量只在.mm 实现文件中引入 C++ 头文件。
要获得更多如何混用 C++ 和 Objective-C 的细节,请查看 Matt Galloway 写的这篇教程。
Demo:人脸检测与识别
现在,我们对 OpenCV 及如何把它集成到我们的应用中有了大概认识,那让我们来做一个小 demo 应用:从 iPhone 的摄像头获取视频流,对它持续进行人脸检测,并在屏幕上标出来。当用户点击一个脸孔时,应用会尝试识别这个人。如果识别结果正确,用户必须点击 “Correct”。如果识别错误,用户必须选择正确的人名来纠正错误。我们的人脸识别器就会从错误中学习,变得越来越好。
demo 应用中人脸检测与识别系统线框图
本 demo 应用的源码可从 GitHub 获得。
视频拍摄
OpenCV 的 highgui 模块中有个类,CvVideoCamera,它把 iPhone 的摄像机抽象出来,让我们的 app 通过一个代理函数- (void)processImage:(cv::Mat&)image 来获得视频流。CvVideoCamera 实例可像下面这样进行设置:
CvVideoCamera *videoCamera = [[CvVideoCamera alloc] initWithParentView:view];
videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480;
videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
videoCamera.defaultFPS = 30;
videoCamera.grayscaleMode = NO;
videoCamera.delegate = self;
摄像头的帧率被设置为 30 帧每秒, 我们实现的 processImage 函数将每秒被调用 30 次。因为我们的 app 要持续不断地检测人脸,所以我们应该在这个函数里实现人脸的检测。要注意的是,如果对某一帧进行人脸检测的时间超过 1/30 秒,就会产生掉帧现象。
人脸检测
其实你并不需要使用 OpenCV 来做人脸检测,因为 Core Image 已经提供了 CIDetector 类。用它来做人脸检测已经相当好了,并且它已经被优化过,使用起来也很容易:
CIDetector *faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];