iOS开发iOS移动开发社区Swift开发

iOS-OpenCV笔记:实现简单的人脸识别(一)

2018-03-23  本文已影响1251人  Harveyhhw
人脸识别.jpeg

前言:人脸识别技术已经逐渐进入到人们的生活中,并广泛运用到了移动端,包括手机人脸解锁,金融领域人脸验证,美颜相机人脸动态添加表情和美化等。

今天介绍的是一个开源的计算机视觉和机器学习库:OpenCV。通过这篇文我们将了解它的基础知识,并学习如何在 iOS 设备上实现基于OpenCV的人脸检测与人脸识别。

一、OpenCV 概述:

优势:

计算机视觉市场巨大而且持续增长,且这方面没有标准API,而标准的API将简化计算机视觉程序和解决方案的开发,OpenCV致力于成为这样的标准API。

OpenCV通过优化的C代码的编写,对其执行速度带来了可观的提升,并且可以通过购买Intel的IPP高性能多媒体函数库(Integrated Performance Primitives)得到更快的处理速度。

下图为 OpenCV 与当前其他主流视觉函数库的性能比较。
性能比较.jpg

应用领域:

  1. 人机互动
  2. 物体识别
  3. 图像分割
  4. 人脸识别
  5. 动作识别
  6. 运动跟踪
  7. 机器人
  8. 运动分析
  9. 机器视觉
  10. 结构分析
  11. 汽车安全驾驶

二、在 iOS 上集成 OpenCV 库

集成 OpenCV 到工程有以下三种方式:
  1. 使用Cocopods进行管理依赖 pod ''OpenCV''
  2. 在官网OpenCV-iOS framework直接下载编译好的库
  3. GitHub 拉下源码,编译成framework,导入工程中

下面介绍的是第3种,我编译的版本为 OpenCV-3.4.1,参考:

OpenCV_iOS 安装官方文档

我的运行环境:

  1. macOS High Sierra Version: 10.13.3
  2. Xcode Version: 9.2

必需软件包

也可以使用Homebrew,在终端中输入:“brew install cmake”,自动安装 CMake。

从Git仓库获取最新的OpenCV

启动Git客户端从这里克隆OpenCV存储库
在MacOS中,可以在终端中使用以下命令完成:

cd〜/ <my_working _directory>
git clone https://github.com/opencv/opencv.git

使用CMake和命令行从源代码构建OpenCV

  1. 为Xcode制作符号链接,让OpenCV构建脚本查找编译器,头文件等。
 cd /
 sudo ln -s /Applications/Xcode.app/Contents/Developer DeveloperDeveloper
  1. 构建OpenCV框架:
 cd〜/ <my_working_directory>
 python opencv/platforms/ios/build_framework.py ios

如果一切正常,几十分钟后你会得到/<my_working_directory> /ios/opencv2.framework。(因为要编译支持的 CPU 架构 armv7,armv7s,i386,x86_64,arm64,所以很慢)

编译完成提示: opencv2.framework installed.jpg

将编译完成的库导入工程

  1. 将opencv2.framework添加到你的Xcode项目中。

    我编译的版本是:OpenCV-3.4.1

  2. 添加需要用到的依赖库:

  1. 修改 Build Settings
  1. 修改项目prefix.pch 文件(若没有的话,自己生成一个),将文件内容修改成如下所示
#import <Availability.h>
#ifdef __cplusplus
#import <opencv2/opencv.hpp>
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#endif

5.将调用 OpenCV 的文件后缀修改为 .mm。

三、OpenCV 库的介绍

下面通过 OpenCV 官方文档 来了解它的API

1. OpenCV 主要模块

OpenCV 的 API 是用 C++ 编写的,具有模块化结构,由不同的模块组成,这些模块中包含范围极为广泛的各种方法,从底层的图像颜色空间转换到高层的机器学习工具。

以下是其最重要的模块:
1521713887508.jpg

2. cv命名空间

所有的OpenCV类和函数都被放置在cv命名空间中。因此, OpenCV 的类的前面会有个 cv:: 前缀,就像 cv::Mat、 cv::Algorithm 等等。
如果要从你的代码访问这些功能,需要使用 cv:: 声明,或着指令:using namespace cv ,例如:

#include "opencv2/core/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, CV_RANSAC, 5);
...

或者

#include "opencv2/core/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, CV_RANSAC, 5 );
...

一些当前或未来的OpenCV外部名称可能与STL或其他库冲突。在这种情况下,使用显式名称空间说明符来解决名称冲突:

Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);

3. 自动内存管理

OpenCV自动处理所有内存。

// 创建一个大小8Mb的矩阵
Mat A(1000, 1000, CV_64F);

// 创建一个相同的矩阵;
// 这是一个即时操作,无论矩阵大小如何。
Mat B = A;
// 将A的第3行创建另一个矩阵; 任何一个的数据都没有被复制 
Mat C = B.row(3);
// 现在创建矩阵的单独副本
Mat D = B.clone();
// 将B的第5行复制到C,即复制A的第5行,到A的第3行.
B.row(5).copyTo(C);
// 现在让A和D共享数据; 之后,A 的修改版本仍然由B和C引用.
A = D;
// 现在使B成为一个空矩阵(它没有引用内存缓冲区),
// 但修改后的A版本仍然会被C引用,
// 尽管C只是原来A的一行
B.release();

// 最后,制作C的完整副本。因此,大修改后的
// 矩阵将被解除分配,因为它不被任何人引用
C = C.clone();

你会发现使用Mat和其他基本结构很简单。但是,如果不考虑自动内存管理,创建高级类甚至用户数据类型又如何呢?对于他们来说,OpenCV提供了Ptr<>类似于std::shared_ptrC ++ TR1 的模板类。所以,而不是使用普通的指针:

T *  ptr  =  new  T (...);

你可以使用:

Ptr <T> ptr  =  new  T (...);

也就是说,Ptr<T> ptr封装了一个指向 T 的实例和引用计数器相关的指针。详情请参阅 Ptr 说明。

4. 基础类和操作

OpenCV 包含几百个类。为简便起见,我们只看几个基础的类和操作,进一步阅读请参考全部文档

cv::Mat

cv::Mat 是 OpenCV 的核心数据结构,用来表示任意 N 维矩阵。因为图像只是 2 维矩阵的一个特殊场景,所以也是使用 cv::Mat 来表示的。也就是说,cv::Mat 将是你在 OpenCV 中用到最多的类。

一个 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 文件加载/保存参数的功能。

四、人脸检测与识别

1. 人脸检测

从 iPhone 的摄像头获取视频流,对它持续进行人脸检测。
OpenCV 的 highgui 模块中有个类,CvVideoCamera,它把 iPhone 的摄像机抽象出来,让我们的 app 通过一个代理函数 - (void)processImage:(cv::Mat&)image 来获得视频流。CvVideoCamera 实例如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.videoCamera = [[CvVideoCamera alloc] initWithParentView:_imageView];
    self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;
    self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480;
    self.videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
    self.videoCamera.defaultFPS = 30;
    self.videoCamera.grayscaleMode = NO;
    self.videoCamera.delegate = self;
}

#pragma mark - Protocol CvVideoCameraDelegate
- (void)processImage:(cv::Mat &)image {
    // Do some OpenCV stuff with the image
    cv::Mat image_copy;
    cvtColor(image, image_copy, cv::COLOR_BGR2GRAY);
    // invert image
    bitwise_not(image_copy, image_copy);
    //Convert BGR to BGRA (three channel to four channel)
    cv::Mat bgr;
    cvtColor(image_copy, bgr, cv::COLOR_GRAY2BGR);
    cvtColor(bgr, image, cv::COLOR_BGR2BGRA);
}

OpenCV 提供了一套物体检测功能,经过训练后能够检测出任何你需要的物体。关于训练与检测过程的详情可参考此原始论文

这些训练好的参数文件可在 OpenCV 发行包里的 data/haarcascades 文件夹中找到,将它导入工程就可以使用。

// 正面人脸检测器训练参数的文件路径
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);

在使用所需要的参数对人脸检测器进行初始化后,就可以用它进行人脸检测了:

cv::Mat img;
vector<cv::Rect> 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) );

2. 人脸识别

OpenCV 自带了三个人脸识别算法:Eigenfaces,Fisherfaces 和局部二值模式直方图 (LBPH)。如果你想知道它们的工作原理及相互之间的区别,请阅读 OpenCV 的详细文档
后面的demo采用的是 LBPH 算法。它会根据用户的输入自动更新,而不需要在每添加一个人或纠正一次出错的判断的时候都要重新进行一次彻底的训练。

下载完后将 opencv 与 opencv_contrib 合并,输入控制台:

$ cd <opencv_build_directory>
$ cmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules <opencv_source_directory>
$ make -j5
$ sudo make install

然后在 opencv/platforms/ios 文件下找到编译脚本 build_framework.py 打开,添加 contrib 的路径和修改默认配置,如下:

1. 找到:
class Builder:
    def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, targets):
        self.opencv = os.path.abspath(opencv)
       //添加这一行:contrib的路径 
        self.contrib = os.path.abspath(contrib)

2. 找到:
   parser.add_argument('--contrib', metavar='DIR', default=none, help...
   将 default=none,改成 default=folder

最后再一次执行脚本 build_framework.py 编译,将contrib加入:

python opencv/platforms/ios/build_framework.py --contrib opencv_contrib ios_contrib

opencv2.framework的大小从250MB变成407MB,因为上面的方式是将 opencv_contrib 的所有模块一起构建,如果你不想所有的模块,官方还提供构建可选项,详情请到官方文档了解,这里就不过多介绍了。

3. 真机实现效果如下: IMG_1605.jpg

IMG_1609.png IMG_1607.png
详细代码已上传到我的GitHub:

OpenCV-iOS-FaceRecDemo

注:Demo不包含opencv2.framework,我手动编译的 Opencv+Contrib 库版本为 3.4.1,大约407MB上传不了,Git上传单个文件只允许<100MB,所以你可以在这个地址下载我编译好的库:Opencv+Contrib-3.4.1,如有遇到问题,请留言。

下一篇:iOS-OpenCV笔记:详解人脸识别原理(二)

总结

本文主要是介绍 OpenCV 的基础知识,以及如何将它集成到 iOS 的项目中。通过学习和参考一些优秀的文章和代码,初步实现了一个简单的人脸识别功能。其具体的算法实现原理和更多有趣的功能还需要我去长时间研究,之后我还会继续写几篇文章记录对 OpenCV 的学习。

进一步阅读

上一篇下一篇

猜你喜欢

热点阅读