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 算法的下载 下载地址
三个文件都下载完成之后,开始合并 OpenCV 的静态库
1.2.1 将OpenCV的拓展包 opencv_contrib-master 下的 modules 目录下的所有文件copy到 OpenCV包目录下的modules中.
1.2.2 文件copy完成后,这个时候打开终端 cd 到 OpenCV包目录下 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静态库的包,拉进项目 可以直接使用.
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 + 拓展包, 效率快, 不需要联网.正面点精确率高. 免费.市面上大厂提供的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点)。
优图示范效果图
使用
//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, 科大讯飞,百度,阿里,face++…. 等等有很多大公司都提供了人脸信息获取的接口
第三方SDK 优缺点
优点: 集成方便,识别快, 精确率高。
缺点: 收费,需要依赖网络请求. 也提供了本地识别的SDK,价格很高.