iOS - 人脸特征关键点获取

1. OpenCV + Face + stasm

OpenCV OpenCV3.0以上的包

Face 是指的是OpenCV的拓展包,iOS没有直接集成. 需要收到打包成静态库文件使用

stasm是一个已经写好的c++算法,用来获取人脸信息,依赖于OpenCV.

1.1.1 首先先从OpenCV的官网将OpenCV的包下载下来,下载地址

下载解压完成后

1.1.2 将OpenCV的拓展包下载下来, 下载地址

拓展包下载解压完成后

1.1.3 stasm 算法的下载 下载地址

stasm 算法

三个文件都下载完成之后,开始合并 OpenCV 的静态库

1.2.1 将OpenCV的拓展包 opencv_contrib-master 下的 modules 目录下的所有文件copy到 OpenCV包目录下的modules中.

1.2.2 文件copy完成后,这个时候打开终端 cd 到 OpenCV包目录下 platforms -> ios 下

platforms -> ios 下

1.2.3 执行 build_framework.py 文件, 这是一个python文件,mac自带了python2.7的环境,所有可以执行

执行命令 python build_framework.py (需要确保在上述路径下,否则会找不到这个文件)

1.2.4 执行该脚本耗时很长,所有执行前需要确保 已经将拓展包中modules目录下的文件copy到了OpenCV -> modules 目录下

1.2.5 执行成功, 经过漫长的等待后, 该目录下生成了一个 build的文件夹,该文件夹目录下就有已经打包好的OpenCV静态库的包,拉进项目 可以直接使用.

build

1.3.1 在Xcode 中使用 stasm 算法获取 人脸关键点信息

先配置好 stasm 算法中需要的训练库(之前下载链接中有)

在 stasm->MOD_1 ->facedet.cpp -> OpenFaceDetector_ 方法中 正脸目前测试 haarcascade_frontalface_alt2 效果最佳

训练库设置

使用 (因为是C++的方法 所以需要改为.mm 使用)

int stasm_search_single(   // wrapper for stasm_search_auto and friends
    int*        foundface, // out: 0=no face, 1=found face
    float*      landmarks, // out: x0, y0, x1, y1, ..., caller must allocate
    const char* img,       // in: gray image data, top left corner at 0,0
    int         width,     // in: image width
    int         height,    // in: image height
    const char* imgpath,   // in: image path, used only for err msgs and debug
    const char* datadir)   // in: directory of face detector files
/*
 *  配置参数
 *  参数1: 表示是否成功  0=no face, 1=found face
 *  参数2: 成功后 获取的关键点位置
 *  参数3: 图片的灰度图的二进制值
 *  参数4/5: 图片的宽高值
 *  参数6: 图片路径 调试用 可以不传
 *  参数7: 训练库文件的目录 注意只是到当前目录 不是全路径
 */

// 参数1
int foundface;

/*
 * 参数2
 * stasm_NLANDMARKS 是 stasm的一个宏 一个int值 值为77 表示可以获取到77个点
 * 乘以2 是因为返回的值是float 每一个点都有x,y 返回值为:x0, y0, x1, y1, 相当于两个元素才能合成一个点
 */
float landmarks[2 * stasm_NLANDMARKS];

/*
 * 参数3
 * 图片的灰度图, 可以直接使用OpenCV的方法获取
 */

// 将UIImage 转成 cv::Mat 
cv::Mat cvFaceImage;
UIImageToMat(image, cvFaceImage);

// 将cv::Mat的image 转成灰度图
// CV_RGBA2GRAY 表示 将四通道RGBA的图片 转成灰度图 iOS中图片默认是RGBA的
cv::Mat cvGrayFaceImage;
cv::cvtColor(cvFaceImage, cvGrayFaceImage,CV_RGBA2GRAY);
const char* imgData = (const char*)cvGrayFaceImage.data;

//参数4/5 图片的宽高 cv::Mat 的图片可以直接取
int imgCols = cvGrayFaceImage.cols;
int imgRows = cvGrayFaceImage.rows;

// 参数6 不传

/*
 * 参数7 训练库文件的目录 
 * 这里需要注意 iOS获取项目中文件 需要通过Bundle获取
 * [NSBundle mainBundle].bundlePath 作为目录 是可以拿到项目中所以文件的 不管有没有其他子文件夹了。
 * 其他平台中, 那么就指定路径
 */
 const char *xmlPath = [[NSBundle mainBundle].bundlePath UTF8String];
 

// 方法有返回值 如果为0 那么就说明方法调用出现问题
int stasmActionError = stasm_search_single(&foundface, landmarks,imgData, imgCols, imgRows, "", xmlPath);
if (!stasmActionError){
    //  通过打印 stasm_lasterr() 可以看到错误信息
    printf("Error in stasm_search_single: %s\n", stasm_lasterr());
}

if (!foundface) {
    printf("No face found");
}else{
    // 这里就说明已经获取到关键点了 
    // 将关键点显示出来
    
   // 将RGBA四通道的图片 转成 BGR三通道
   cv::Mat cvFaceImage_BGR;
   cv::cvtColor(cvFaceImage, cvFaceImage_BGR, CV_RGBA2BGR);
   
   for (int i = 0; i < stasm_NLANDMARKS; i++){
        // 生成一个当前点脚标的String字符串
        std::string number = std::to_string(i);
        // 获取中心点 根据x在前 y在后 两个元素为一个点的顺序
        cv::Point center(cvRound(landmarks[i * 2 ]), cvRound(landmarks[i * 2+1]));
        // 将点画上去 
        cv::circle(cvFaceImage_BGR, center, 0.25, cv::Scalar(255, 0, 0), 2, 8, 0);
        // 将脚标也画上去
        cv::putText(cvFaceImage_BGR,number,center, cv::FONT_HERSHEY_PLAIN,0.7, cv::Scalar(0, 0, 255));
    }
    
    
   // 将BGR三通道的图片 转成 RGBA四通道
   cv::Mat cvFaceImageResult;
   cv::cvtColor(cvFaceImage_BGR, cvFaceImageResult, CV_BGR2RGBA);
    
   //绘制出关键点的image
   UIImage *result = MatToUIImage(cvFaceImageResult);
   
   cvFaceImage_BGR.release();
   cvFaceImageResult.release();
}

// 释放cv::Mat
cvFaceImage.release();
cvGrayFaceImage.release();


关于 cv::circle 和 cv::putText

/* 绘制圆形
 * img: cv::Mat的 image, 注意 这里需要转成三通道BGR的图片, 因为OpenCV中用的是这种格式 
 * center: 中心点
 * radius: 半径
 * color: 颜色 创建方法 cv::Scalar(B, G, R);
 * thickness: 如果是正数,表示组成圆的线条的粗细程度。否则,表示圆是否被填充
 * lineType: 线条类型 正常用  LINE_8 / 8 就OK
    * FILLED  = -1, 
    * LINE_4  = 4, //!< 4-connected line
    * LINE_8  = 8, //!< 8-connected line
    * LINE_AA = 16 //!< antialiased line
 * shift: 圆心坐标点和半径值的小数点位数 也就是说中心点和半径的值的精度化
 */
 void circle(InputOutputArray img, Point center, int radius,
                       const Scalar& color, int thickness = 1,
                       int lineType = LINE_8, int shift = 0);
                       
                       

/* 绘制文字
 * img: cv::Mat的 image, 注意 这里需要转成三通道BGR的图片, 因为OpenCV中用的是这种格式 
 * text: 待显示的文字
 * org: 文字在图像中的左下角 坐标.
 * fontFace: 字体类型,
    * 可选择字体:
         FONT_HERSHEY_SIMPLEX, 
         FONT_HERSHEY_PLAIN,                
         FONT_HERSHEY_DUPLEX,
         FONT_HERSHEY_COMPLEX,
         FONT_HERSHEY_TRIPLEX, 
         FONT_HERSHEY_COMPLEX_SMALL,
         FONT_HERSHEY_SCRIPT_SIMPLEX, orFONT_HERSHEY_SCRIPT_COMPLEX,以上所有类型都可以配合 FONT_HERSHEY_ITALIC使用,产生斜体效果。   
 * fontScale: 字体大小,该值和字体内置大小相乘得到字体大小
 * color: 颜色 创建方法 cv::Scalar(B, G, R);
 * thickness: 写字的线的粗细,类似于0.38的笔尖和0.5的笔尖
 * lineType: 写字的线条类型 和上诉一致
 * bottomLeftOrigin: true: 图像数据原点在左下角. false: 图像数据原点在左上角.
 */
void putText( InputOutputArray img, const String& text, Point org,
                         int fontFace, double fontScale, Scalar color,
                         int thickness = 1, int lineType = LINE_8,
                         bool bottomLeftOrigin = false )
OpenCV效果图

优点: 基于OpenCV + 拓展包, 效率快, 不需要联网.正面点精确率高. 免费.市面上大厂提供的SDK基本都是收费的.

缺点: OpenCV + 拓展包合并出来的静态库文件太大, 不适合在APP上面. C++代码编写. 使用起来不如OC方便.

有一个缩小的方法: 不使用拓展包的face模块, 直接使用OpenCV中的objdetect模块. 效果差不多,就是效率低了很多.

缩小版使用方法:

直接Pod OpenCV2 大于3.0版本. 或者直接去官网下载一个OpenCV的包 也要大于3.0, 然后不用 上述 1.2.1 那一步, 直接去执行脚本文件,生成一个OpenCV的静态库.

然后将 stasm 目录下所有文件 导入 include <opencv2/face.hpp> 的代码换成 include <opencv2/objdetect.hpp> 就OK了. 其他使用步骤方法和上述一致.

注意点: 因为OpenCV中有一些宏定义和系统的冲突, 所以OpenCV的头文件导入, 需要在最前面, 否则会报错.

方法获取

2. iOS11 原生框架 Vision

Vision 是 Apple 在 WWDC 2017 推出的图像识别框架。

Vision 的设计理念

苹果最擅长的,把复杂的事情简单化,Vision的设计理念也正是如此。

对于使用者我们抽象的来说,我们只需要:提出问题-->经过机器-->得到结果。

开发者不需要是计算机视觉专家,开发者只需要得到结果即可,一切复杂的事情交给Vision。

Vision 获取人脸信息 使用

1,创建处理图片处理对应的VNImageRequestHandler对象。

2, 创建对应的识别 VNDetectFaceLandmarksRequest 请求,指定 Complete Handler 识别成功后进行回调执行的一个Block)

3,发送识别请求

#import <Vision/Vision.h>
//1
CIImage *faceCIImage = [[CIImage alloc]initWithImage:image];
VNImageRequestHandler *vnRequestHeader = [[VNImageRequestHandler alloc] initWithCIImage:faceCIImage options:@{}];
//2
__weak ViewController *weakSelf = self;
VNDetectFaceLandmarksRequest *faceRequest = [[VNDetectFaceLandmarksRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
    [weakSelf faceLandmarks:request.results];
}];
// 3
[vnRequestHeader performRequests:@[face_request] error:NULL];



// 获取信息成功后 处理
- (void)faceLandmarks:(NSArray *)faces{
    // 可能是多张脸
    [faces enumerateObjectsUsingBlock:^(VNFaceObservation *face, NSUInteger idx, BOOL * _Nonnull stop) {
       
       /*
        * face: VNFaceObservation 对象, 里面包含了 landmarks 位置信息, boundingBox 脸的大小 等等信息
        */
       
       
        // 取出单个脸的 landmarks 
        VNFaceLandmarks2D *landmarks = face.landmarks;
        // 声明一个存关键位置的数组
        NSMutableArray *face_landmarks = [NSMutableArray array];
        
        // landmarks 是一个对象,对象中有左眼位置,右眼,鼻子,鼻梁等等属性 根据需求自己添加
        [face_landmarks addObject:landmarks.faceContour];
        [face_landmarks addObject:landmarks.leftEye];
        [face_landmarks addObject:landmarks.rightEye];
        [face_landmarks addObject:landmarks.leftEyebrow];
        [face_landmarks addObject:landmarks.rightEyebrow];
        [face_landmarks addObject:landmarks.outerLips];
        [face_landmarks addObject:landmarks.innerLips];
        [face_landmarks addObject:landmarks.nose];
        [face_landmarks addObject:landmarks.noseCrest];
        [face_landmarks addObject:landmarks.medianLine];
        [face_landmarks addObject:landmarks.outerLips];
        [face_landmarks addObject:landmarks.innerLips];
        [face_landmarks addObject:landmarks.leftPupil];
        [face_landmarks addObject:landmarks.rightPupil];
        
        
        // 当前脸在图片中的位置和大小
        // 注意: boundingBox 返回的x,y,w,h 的比例 不是直接的值,所以需要转换
        
        // 这里的 image_wh是一个宏 表示的图片的宽高大小 也可以直接image.size.width....
        
        CGFloat faceRectWidth = image_wh * face.boundingBox.size.width;
        CGFloat faceRectHeight = image_wh * face.boundingBox.size.height;
        CGFloat faceRectX = face.boundingBox.origin.x * image_wh;
        // Y默认的位置是左下角
        CGFloat faceRectY = face.boundingBox.origin.y * image_wh;
        
        // 遍历位置信息
        [face_landmarks enumerateObjectsUsingBlock:^(VNFaceLandmarkRegion2D *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // VNFaceLandmarkRegion2D *obj 是一个对象. 表示当前的一个部位
            // 遍历当前部分所有的点
            for (int i=0; i<obj.pointCount; i++) {
                // 取出点
                CGPoint point = obj.normalizedPoints[i];
                
                // 计算出center
                /*
                 * 这里的 point 的 x,y 表示也比例, 表示当前点在脸的比例值
                 * 因为Y点是在左下角, 所以我们需要转换成左上角
                 * 这里的center 关键点 可以根据需求保存起来
                 */
                CGPoint center = CGPointMake(faceRectX + faceRectWidth * point.x,  image_wh - 
(faceRectY + faceRectHeight * point.y));
               
                // 将点显示出来
                UIView *point_view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 3, 3)];
                point_view.backgroundColor = [UIColor redColor];
                point_view.center = center;                
                // 将点添加到imageView上即可 需要注意,当前image的bounds 应该和图片大小一样大
                [imageView addSubview:point_view];

            }
        }];
        
    }];
    
}
单脸效果
多脸效果

多脸效果 因为图片设置的小 所以有点密

关于Vision 这里有一篇其他文章推荐 Vision

优点: 苹果原生,使用方便,多脸识别. 免费, 不需要联网
缺点: iOS11 以上才能使用. 精确率不如OpenCV

3.腾讯优图SDK

典型案例: 天天P图

对请求图片进行五官定位,计算构成人脸轮廓的90个点,包括眉毛(左右各8点)、眼睛(左右各8点)、鼻子(13点)、嘴巴(22点)、脸型轮廓(21点)、眼珠[或瞳孔](2点)。


优图示范效果图

优图SDK
工具包下载

使用

//1. 注册sdk 需要Conf.m 文件中 填写  appId ,secretId, secretKey
NSString *auth = [Auth appSign:1000000 userId:nil];
TXQcloudFrSDK *sdk = [[TXQcloudFrSDK alloc] initWithName:[Conf instance].appId authorization:auth endPoint:[Conf instance].API_END_POINT];

/*
 * TXQcloudFrSDK 中有很多方法 人脸检测,人脸对比等等.. 用法都一样,直接请求 参数类型不一致,详见TXQcloudFrSDK.h
 */

// 2 发送请求获取关键点
 [sdk faceShape:image successBlock:^(id responseObject) {
 // 该方法 responseObject 是一个字典
  [self handleFacePoint:responseObject];
 } failureBlock:^(NSError *error) {
  NSLog(@"error : %@",error);
 }];

// 3 处理关键点信息

- (void)handleFacePoint:(NSDictionary *)responseObject{
    // 将人脸信息提取出来
    NSArray * face_shape = [responseObject objectForKey:@"face_shape"];
    if (face_shape.count == 0) {
        NSLog(@"没有找到脸的位置");
        return;
    }
    
    NSDictionary *face_shape_first = [face_shape firstObject];
    [face_shape_first enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray * obj, BOOL * _Nonnull stop) {
        NSLog(@"位置 %@",key);
        [obj enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"x: %@  Y: %@",[dict objectForKey:@"x"],[dict objectForKey:@"y"]);
            // 这里获取到的点 是直接匹配上传图片对应位置的, 不需要二次转换
            CGFloat x = [[dict objectForKey:@"x"] doubleValue];
            CGFloat y = [[dict objectForKey:@"y"] doubleValue];
         
            UIView *pointView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 3, 3)];
            pointView.center = CGPointMake(x , y );
            pointView.backgroundColor = [UIColor redColor];
            [imageView addSubview:pointView];
            
        }];
        NSLog(@"----");
    }];
    
    
}


优图SDK效果图

其他SDK, 科大讯飞,百度,阿里,face++…. 等等有很多大公司都提供了人脸信息获取的接口

第三方SDK 优缺点
优点: 集成方便,识别快, 精确率高。
缺点: 收费,需要依赖网络请求. 也提供了本地识别的SDK,价格很高.

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,534评论 15 534
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 69,823评论 26 501
  • 字里行间流露出来的确
    展信佳hl阅读 8评论 0 0
  • 命运负责玩牌 但是洗牌的却是我们自己!
    恬君阅读 24评论 0 0
  • 上一章【连载】狗狗的一生(上)2 四 我的要求其实很简单 人类的生活其实还是蛮有规律的。每天当初醒的太阳揉开了橘黄...
    北斗星君阅读 138评论 6 6