【C++】基于 OpenCV 的人脸识别(强烈推荐)
2016-01-23 14:18 17718人阅读 评论(4) 收藏 举报
分类: 人工智能(2)
目录(?)[+]
原文网址:http://www.jianshu.com/p/96be2417cc98
一点背景知识
OpenCV 是一个开源的计算机视觉和机器学习库。它包含成千上万优化过的算法,为各类计算机视觉应用提供了一个通用工具包。根据这个项目的关于页面,OpenCV 已被普遍运用在各类项目上,从谷歌街景的图片拼接,到交互艺术展览的技术实现中,都有 OpenCV 的身影。算法
OpenCV 起始于 1999 年 Intel 的一个内部研究项目。从那时起,它的开发就一直很活跃。进化到如今,它已支持如 OpenCL 和 OpenGL 等现代技术,也支持如 iOS 和 Android 等平台。数组
1999 年,半条命发布后大红大热。Intel 奔腾 3 处理器是当时最高级的 CPU,400-500 MHZ 的时钟频率已被认为是至关快。2006 年 OpenCV 1.0 版本发布的时候,当时主流 CPU 的性能也只和 iPhone 5 的 A6 处理器至关。尽管计算机视觉从传统上被认为是计算密集型应用,但咱们的移动设备性能已明显地超出可以执行有用的计算机视觉任务的阈值,带着摄像头的移动设备能够在计算机视觉平台上大有所为。安全
在本文中,我会从一个 iOS 开发者的视角概述一下 OpenCV,并介绍一点基础的类和概念。随后,会讲到如何集成 OpenCV 到你的 iOS 项目中以及一些 Objective-C++ 基础知识。最后,咱们会看一个 demo 项目,看看如何在 iOS 设备上使用 OpenCV 实现人脸检测与人脸识别。markdown
OpenCV 概述
概念
OpenCV 的 API 是 C++ 的。它由不一样的模块组成,这些模块中包含范围极为普遍的各类方法,从底层的图像颜色空间转换到高层的机器学习工具。网络
使用 C++ API 并非绝大多数 iOS 开发者天天都作的事,你须要使用 Objective-C++ 文件来调用 OpenCV 的函数。 也就是说,你不能在 Swift 或者 Objective-C 语言内调用 OpenCV 的函数。 这篇 OpenCV 的iOS 教程告诉你只要把全部用到 OpenCV 的类的文件后缀名改成 .mm 就好了,包括视图控制器类也是如此。这么干或许能行得通,却不是什么好主意。正确的方式是给全部你要在 app 中使用到的 OpenCV 功能写一层 Objective-C++ 封装。这些 Objective-C++ 封装把 OpenCV 的 C++ API 转化为安全的 Objective-C API,以方便地在全部 Objective-C 类中使用。走封装的路子,你的工程中就能够只在这些封装中调用 C++ 代码,从而避免掉不少让人头痛的问题,好比直接改文件后缀名会由于在错误的文件中引用了一个 C++ 头文件而产生难以追踪的编译错误。数据结构
OpenCV 声明了命名空间 cv,所以 OpenCV 的类的前面会有个 cv:: 前缀,就像 cv::Mat、 cv::Algorithm 等等。你也能够在 .mm 文件中使用 using namespace cv 来避免在一堆类名前使用 cv:: 前缀。可是,在某些类名前你必须使用命名空间前缀,好比cv::Rect 和cv::Point,由于它们会跟定义在 MacTypes.h 中的Rect 和 Point 相冲突。尽管这只是我的偏好问题,我仍是偏向在任何地方都使用 cv:: 以保持一致性。app
模块
下面是在官方文档中列出的最重要的模块。框架
core:简洁的核心模块,定义了基本的数据结构,包括稠密多维数组 Mat 和其余模块须要的基本函数。
imgproc:图像处理模块,包括线性和非线性图像滤波、几何图像转换 (缩放、仿射与透视变换、通常性基于表的重映射)、颜色空间转换、直方图等等。
video:视频分析模块,包括运动估计、背景消除、物体跟踪算法。
calib3d:包括基本的多视角几何算法、单体和立体相机的标定、对象姿态估计、双目立体匹配算法和元素的三维重建。
features2d:包含了显著特征检测算法、描述算子和算子匹配算法。
objdetect:物体检测和一些预约义的物体的检测 (如人脸、眼睛、杯子、人、汽车等)。
ml:多种机器学习算法,如 K 均值、支持向量机和神经网络。
highgui:一个简单易用的接口,提供视频捕捉、图像和视频编码等功能,还有简单的 UI 接口 (iOS 上可用的仅是其一个子集)。
gpu:OpenCV 中不一样模块的 GPU 加速算法 (iOS 上不可用)。
ocl:使用 OpenCL 实现的通用算法 (iOS 上不可用)。
一些其它辅助模块,如 Python 绑定和用户贡献的算法。
基础类和操做
OpenCV 包含几百个类。为简便起见,咱们只看几个基础的类和操做,进一步阅读请参考所有文档。过一遍这几个核心类应该足以对这个库的机理产生一些感受认识。机器学习
cv::Mat
cv::Mat 是 OpenCV 的核心数据结构,用来表示任意 N 维矩阵。由于图像只是 2 维矩阵的一个特殊场景,因此也是使用 cv::Mat 来表示的。也就是说,cv::Mat 将是你在 OpenCV 中用到最多的类。ide
一个 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}];
NSArray *faces = [faceDetector featuresInImage:image];
从该图片中检测到的每一张面孔都在数组 faces 中保存着一个 CIFaceFeature 实例。这个实例中保存着这张面孔的所处的位置和宽高,除此以外,眼睛和嘴的位置也是可选的。
另外一方面,OpenCV 也提供了一套物体检测功能,通过训练后可以检测出任何你须要的物体。该库为多个场景自带了能够直接拿来用的检测参数,如人脸、眼睛、嘴、身体、上半身、下半身和笑脸。检测引擎由一些很是简单的检测器的级联组成。这些检测器被称为 Haar 特征检测器,它们各自具备不一样的尺度和权重。在训练阶段,决策树会经过已知的正确和错误的图片进行优化。关于训练与检测过程的详情可参考此原始论文。当正确的特征级联及其尺度与权重经过训练确立之后,这些参数就可被加载并初始化级联分类器了:
// 正面人脸检测器训练参数的文件路径
NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@”haarcascade_frontalface_alt2”
ofType:@”xml”];
const CFIndex CASCADE_NAME_LEN = 2048;
char CASCADE_NAME = (char ) malloc(CASCADE_NAME_LEN);
CFStringGetFileSystemRepresentation( (CFStringRef)faceCascadePath, CASCADE_NAME, CASCADE_NAME_LEN);
CascadeClassifier faceDetector;
faceDetector.load(CASCADE_NAME);
这些参数文件可在 OpenCV 发行包里的 data/haarcascades 文件夹中找到。
在使用所须要的参数对人脸检测器进行初始化后,就能够用它进行人脸检测了:
cv::Mat img;
vector faceRects;
double scalingFactor = 1.1;
int minNeighbors = 2;
int flags = 0;
cv::Size minimumSize(30,30);
faceDetector.detectMultiScale(img, faceRects,
scalingFactor, minNeighbors, flags
cv::Size(30, 30) );
检测过程当中,已训练好的分类器会用不一样的尺度遍历输入图像的每个像素,以检测不一样大小的人脸。参数 scalingFactor 决定每次遍历分类器后尺度会变大多少倍。参数minNeighbors 指定一个符合条件的人脸区域应该有多少个符合条件的邻居像素才被认为是一个可能的人脸区域;若是一个符合条件的人脸区域只移动了一个像素就再也不触发分类器,那么这个区域很是可能并非咱们想要的结果。拥有少于minNeighbors 个符合条件的邻居像素的人脸区域会被拒绝掉。若是minNeighbors 被设置为 0,全部可能的人脸区域都会被返回回来。参数flags 是 OpenCV 1.x 版本 API 的遗留物,应该始终把它设置为 0。最后,参数minimumSize 指定咱们所寻找的人脸区域大小的最小值。faceRects 向量中将会包含对img 进行人脸识别得到的全部人脸区域。识别的人脸图像能够经过cv::Mat 的 () 运算符提取出来,调用方式很简单:cv::Mat faceImg = img(aFaceRect)。
不论是使用 CIDetector 仍是 OpenCV 的 CascadeClassifier,只要咱们得到了至少一我的脸区域,咱们就能够对图像中的人进行识别了。
人脸识别
OpenCV 自带了三我的脸识别算法:Eigenfaces,Fisherfaces 和局部二值模式直方图 (LBPH)。若是你想知道它们的工做原理及相互之间的区别,请阅读 OpenCV 的详细文档。
针对于咱们的 demo app,咱们将采用 LBPH 算法。由于它会根据用户的输入自动更新,而不须要在每添加一我的或纠正一次出错的判断的时候都要从新进行一次完全的训练。
要使用 LBPH 识别器,咱们也用 Objective-C++ 把它封装起来。这个封装中暴露如下函数:
(void)updateWithFace:(UIImage )img name:(NSString )name;
像下面这样用工厂方法来建立一个 LBPH 实例:
(FJFaceRecognizer )faceRecognizerWithFile:(NSString )path {
FJFaceRecognizer *fr = [FJFaceRecognizer new];
fr->_faceClassifier = createLBPHFaceRecognizer();
fr->_faceClassifier->load(path.UTF8String);
return fr;
}
预测函数能够像下面这样实现:
(NSString )predict:(UIImage)img confidence:(double *)confidence {
cv::Mat src = [img cvMatRepresentationGray];
int label;
self->_faceClassifier->predict(src, label, *confidence);
return _labelsArray[label];
}
请注意,咱们要使用一个类别方法把 UIImage 转化为 cv::Mat。此转换自己却是至关简单直接:使用CGBitmapContextCreate 建立一个指向cv::Image 中的 data 指针所指向的数据的CGContextRef。当咱们在此图形上下文中绘制此UIImage 的时候,cv::Image 的data 指针所指就是所须要的数据。更有趣的是,咱们能对一个 Objective-C 类建立一个 Objective-C++ 的类别,而且确实管用。
另外,OpenCV 的人脸识别器仅支持整数标签,可是咱们想使用人的名字做标签,因此咱们得经过一个 NSArray 属性来对两者实现简单的转换。
一旦识别器给了咱们一个识别出来的标签,咱们把此标签给用户看,这时候就须要用户给识别器一个反馈。用户能够选择,“是的,识别正确”,也能够选择,“不,这是 Y,不是 X”。在这两种状况下,咱们均可以经过人脸图像和正确的标签来更新 LBPH 模型,以提升将来识别的性能。使用用户的反馈来更新人脸识别器的方式以下:
如此“预测,得到反馈,更新循环”,就是文献上所说的监督式学习。
结论 OpenCV 是一个强大而用途普遍的库,覆盖了不少现现在仍在活跃的研究领域。想在一篇文章中给出详细的使用说明只会是让人徒劳的事情。所以,本文仅意在从较高层次对 OpenCV 库作一个概述。同时,还试图就如何集成 OpenCV 库到你的 iOS 工程中给出一些实用建议,并经过一我的脸识别的例子来向你展现如何在一个真正的项目中使用 OpenCV。若是你以为 OpenCV 对你的项目有用, OpenCV 的官方文档写得很是好很是详细,请继续前行,创造出下一个伟大的 app!