代码地址如下:
http://www.demodashi.com/demo/12011.html

之前公司项目需要,研究了一下人脸识别和活体识别,并运用免费的讯飞人脸识别,在其基础上做了二次开发,添加了活体识别。项目需要就开发了张嘴和摇头两个活体动作的识别。 这里简单介绍一下当时的开发思路和一些个人拙见,欢迎大神指点。

首先说一下讯飞第三方的人脸识别的几个缺点:1.识别不稳定,各点坐标跳动偏差比较大,不容易捕捉;2.CPU使用率比较高,连续识别一会儿手机会明显发烫,手机配置低的,就会反应很慢,本人使用的iPhone 6s,配置还可以,还算比较流畅,但也会发烫。3.屏幕小的手机识别率相对会低一点,当然这也和手机的配置脱不了干系。

下面开始我们的活体识别开发之路:

确定位置

讯飞的人脸识别坐标跳动比较大,如果全屏识别发现很容易出现错误的识别,导致识别错误的被通过,所以为了降低这个可能性,特意加了脸部位置的限制,把识别位置和范围大大缩小,大大提高了识别精度和成功率。

原版的Demo里给出了人脸框的坐标,也显示出了人脸的框,代码如下:

-(void)drawPointWithPoints:(NSArray *)arrPersons
{
if (context) {
CGContextClearRect(context, self.bounds) ;
}
context = UIGraphicsGetCurrentContext();
for (NSDictionary *dicPerson in arrPersons) {
if ([dicPerson objectForKey:POINTS_KEY]) {
for (NSString *strPoints in [dicPerson objectForKey:POINTS_KEY]) {
CGPoint p = CGPointFromString(strPoints);
CGContextAddEllipseInRect(context, CGRectMake(p.x - 1 , p.y - 1 , 2 , 2));
}
}
BOOL isOriRect=NO;
if ([dicPerson objectForKey:RECT_ORI]) {
isOriRect=[[dicPerson objectForKey:RECT_ORI] boolValue];
}
if ([dicPerson objectForKey:RECT_KEY]) {
CGRect rect=CGRectFromString([dicPerson objectForKey:RECT_KEY]);
if(isOriRect){//完整矩形
CGContextAddRect(context,rect) ;
}
else{ //只画四角
// 左上
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y+rect.size.height/8);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width/8, rect.origin.y);
//右上
CGContextMoveToPoint(context, rect.origin.x+rect.size.width*7/8, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height/8);
//左下
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y+rect.size.height*7/8);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width/8, rect.origin.y+rect.size.height);
//右下
CGContextMoveToPoint(context, rect.origin.x+rect.size.width*7/8, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height*7/8);
}
}
}
[[UIColor greenColor] set];
CGContextSetLineWidth(context, 2);
CGContextStrokePath(context);
}

在这段代码的启发下,我对此作了改装,把动态的人脸框,改成了静态的框,这个静态框,就是指示和限定人脸位置的框,根据屏幕大小画出的,代码如下:

-(void)drawFixedPointWithPoints:(NSArray *)arrFixed
{
for (NSDictionary *dicPerson in arrFixed) {
if ([dicPerson objectForKey:POINTS_KEY]) {
for (NSString *strPoints in [dicPerson objectForKey:POINTS_KEY]) {
CGPoint p = CGPointFromString(strPoints);
CGContextAddEllipseInRect(context, CGRectMake(p.x - 1 , p.y - 1 , 2 , 2));
}
}
if ([dicPerson objectForKey:RECT_KEY]) {
CGRect rect=CGRectFromString([dicPerson objectForKey:RECT_KEY]);
// 左上
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y+rect.size.height/8);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width/8, rect.origin.y);
//右上
CGContextMoveToPoint(context, rect.origin.x+rect.size.width*7/8, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height/8);
//左下
CGContextMoveToPoint(context, rect.origin.x, rect.origin.y+rect.size.height*7/8);
CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width/8, rect.origin.y+rect.size.height);
//右下
CGContextMoveToPoint(context, rect.origin.x+rect.size.width*7/8, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height);
CGContextAddLineToPoint(context, rect.origin.x+rect.size.width, rect.origin.y+rect.size.height*7/8);
}
}
[[UIColor blueColor] set];
CGContextSetLineWidth(context, 2);
CGContextStrokePath(context);
}

这里的框是限定脸部位置的,所以脸部位置超出设置的范围的时候,就需要停止人脸识别,停止动作识别,并给出用户提示,提示用户调整位置,或者明确告诉用户,脸部距离屏幕太近了,或者太远了。判定脸部位置的代码如下:

#pragma mark --- 判断位置
-(BOOL)identifyYourFaceLeft:(CGFloat)left right:(CGFloat)right top:(CGFloat)top bottom:(CGFloat)bottom
{
//判断位置
if (right - left < 230 || bottom - top < 250) {
self.textLabel.text = @"太远了...";
[self delateNumber];//清数据
isCrossBorder = YES;
return YES;
}else if (right - left > 320 || bottom - top > 320) {
self.textLabel.text = @"太近了...";
[self delateNumber];//清数据
isCrossBorder = YES;
return YES;
}else{
if (isJudgeMouth != YES) {
self.textLabel.text = @"请重复张嘴动作...";
[self tomAnimationWithName:@"openMouth" count:2];
#pragma mark --- 限定脸部位置为中间位置
if (left < 100 || top < 100 || right > 460 || bottom > 400) {
isCrossBorder = YES;
isJudgeMouth = NO;
self.textLabel.text = @"调整下位置先...";
[self delateNumber];//清数据
return YES;
}
}else if (isJudgeMouth == YES && isShakeHead != YES) {
self.textLabel.text = @"请重复摇头动作...";
[self tomAnimationWithName:@"shakeHead" count:4];
number = 0;
}else{
takePhotoNumber += 1;
if (takePhotoNumber == 2) {
[self timeBegin];
}
}
isCrossBorder = NO;
}
return NO;
}

这个方法基于Demo中第三方封装库中给的代理方法-(NSString)praseDetect:(NSDictionary )positionDic OrignImage:(IFlyFaceImage*)faceImg; 判断脸部并返回人脸的脸框的坐标,所以利用给的脸部框坐标做判断,超出设置的范围时停止识别。

其中,脸部框两边的坐标左边大于一定值且右边小于一定值的时候,判定为脸部位置“太远了”;同理,脸部框两边的坐标左边小于设定边框点且右边大于设定边框右边点的时候,判定为脸部位置“太近了”;如果位置正确,则脸部位置到达正确位置,这个时候显示脸部各点,并开始活体动作识别:张嘴和摇头。我这里先做张嘴,再做摇头。

张嘴识别

张嘴识别,这里的嘴部定点有五个:上、下、左、右、中。这里我取的是上下左右四个点,并判断上下点的距离变化和左右点的距离变化,一开始只判断了上下点距离变化超过设定值得时候就判断为张嘴,后来测试过程中,上下晃动屏幕,会判断失败,直接通过。所以为了解决这个bug,并判断更严谨,加上了左右点的判断,即上下点变化大于设定值并且左右点变化小于设定值的时候判定为张嘴动作识别通过。代码如下:

#pragma mark --- 判断是否张嘴
-(void)identifyYourFaceOpenMouth:(NSString *)key p:(CGPoint )p
{
if ([key isEqualToString:@"mouth_upper_lip_top"]) {
upperY = p.y;
}
if ([key isEqualToString:@"mouth_lower_lip_bottom"]) {
lowerY = p.y;
}
if ([key isEqualToString:@"mouth_left_corner"]) {
leftX = p.x;
}
if ([key isEqualToString:@"mouth_right_corner"]) {
rightX = p.x;
}
if (rightX && leftX && upperY && lowerY && isJudgeMouth != YES) {
number ++;
if (number == 1 || number == 300 || number == 600 || number ==900) {
mouthWidthF = rightX - leftX < 0 ? abs(rightX - leftX) : rightX - leftX;
mouthHeightF = lowerY - upperY < 0 ? abs(lowerY - upperY) : lowerY - upperY;
NSLog(@"%d,%d",mouthWidthF,mouthHeightF);
}else if (number > 1200) {
[self delateNumber];//时间过长时重新清除数据
[self tomAnimationWithName:@"openMouth" count:2];
}
mouthWidth = rightX - leftX < 0 ? abs(rightX - leftX) : rightX - leftX;
mouthHeight = lowerY - upperY < 0 ? abs(lowerY - upperY) : lowerY - upperY;
NSLog(@"%d,%d",mouthWidth,mouthHeight);
NSLog(@"张嘴前:width=%d,height=%d",mouthWidthF - mouthWidth,mouthHeight - mouthHeightF);
if (mouthWidth && mouthWidthF) {
//张嘴验证完毕
if (mouthHeight - mouthHeightF >= 20 && mouthWidthF - mouthWidth >= 15) {
isJudgeMouth = YES;
imgView.animationImages = nil;
}
}
}
}

张嘴动作识别通过后,开始判断摇头动作。

摇头识别

摇头识别,这里的摇头动作相比于张嘴动作,摇头动作我没有限制位置,张嘴识别必须在设置的框内完成动作,摇头动作不需要,因为摇头动作幅度大,需要的位置大,如果再限定位置的话,识别要求比较高,不容易识别通过,用户体验差。

摇头识别的思路比较简单,没有做细致的计算分析,仅仅是判断了鼻尖的点的坐标改变大于设定值,即判定为摇头动作通过。代码如下:

#pragma mark --- 判断是否摇头
-(void)identifyYourFaceShakeHead:(NSString *)key p:(CGPoint )p
{
if ([key isEqualToString:@"mouth_middle"] && isJudgeMouth == YES) {
if (bigNumber == 0 ) {
firstNumber = p.x;
bigNumber = p.x;
smallNumber = p.x;
}else if (p.x > bigNumber) {
bigNumber = p.x;
}else if (p.x < smallNumber) {
smallNumber = p.x;
}
//摇头验证完毕
if (bigNumber - smallNumber > 60) {
isShakeHead = YES;
[self delateNumber];//清数据
}
}
}

其实这样判断摇头是有bug的,左右晃动手机超过一定的距离,也会判定摇头通过,当时时间紧张,没做过多处理,所以就暂时这样判定了。

其他细节

判断比较数据,我用了计数法,取得是不同时间点的帧图片上的点的位置并记录下来,然后和初始值做比较,所以如果判断不符合要求,需要清除数据,并重新开始记录并判定。

另外Demo里给出了两种记录动作的方式,一种是有声音的拍照,一种是无声音的截图,可以为人脸的对比做铺垫。

文件目录截图

尾声

Demo的gitHub欢迎大家下载、参考、指正、交流,如果对您有帮助,感谢star,另外建立了一个活体识别交流群:498197808,欢迎同道中人加入,大家一起交流学习。

iOS活体人脸识别的Demo和一些思路

代码地址如下:
http://www.demodashi.com/demo/12011.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

iOS活体人脸识别的Demo和一些思路的更多相关文章

  1. IOS CoreData 多表查询demo解析

    在IOS CoreData中,多表查询上相对来说,没有SQL直观,但CoreData的功能还是可以完成相关操作的. 下面使用CoreData进行关系数据库的表与表之间的关系演示.生成CoreData和 ...

  2. 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq

    常量,字段,构造方法   常量 1.什么是常量 ​ 常量是值从不变化的符号,在编译之前值就必须确定.编译后,常量值会保存到程序集元数据中.所以,常量必须是编译器识别的基元类型的常量,如:Boolean ...

  3. uniapp安卓ios百度人脸识别、活体检测、人脸采集APP原生插件

    插件亮点 1 支持安卓平板(横竖屏均可),苹果的iPad.2 颜色图片均可更换. 特别提醒 此插件包含 android 端和 iOS 端,考虑到有些同学只做其中一个端的 app,特意分为 2 个插件, ...

  4. Java版 人脸识别SDK demo

    虹软人脸识别SDK之Java版,支持SDK 1.1+,以及当前最新版本2.0,滴滴,抓紧上车! 前言 由于业务需求,最近跟人脸识别杠上了,本以为虹软提供的SDK是那种面向开发语言的,结果是一堆dll· ...

  5. iOS之一些实用的Demo

    图像浏览及处理 FLAnimatedImage - gif播放处理的工具. CLImageEditor - 超强的图片编辑库,快速帮你实现旋转,防缩,滤镜等等一系列麻烦的事情. ios-image-f ...

  6. [置顶] ios 时间定时器 NSTimer应用demo

    原创文章,转载请注明出处:http://blog.csdn.net/donny_zhang/article/details/9251917 demo功能:ios NSTimer应用demo .ipho ...

  7. 基于虹软sdk,java实现人脸识别(demo)

    ## 开发环境准备:###开发使用到的软件和工具:* Jdk8.mysql5.7.libarcsoft_face.dll(so).libarcsoft_face_engine.dll(so).liba ...

  8. java引用Arcface,实现人脸识别(demo)

    ## 开发环境准备: ###开发使用到的软件和工具: * Jdk8.mysql5.7.libarcsoft_face.dll(so).libarcsoft_face_engine.dll(so).li ...

  9. 人脸识别 ArcFace Demo [Windows]

    Arcsoft ArcfaceDemo for Windows, VS2013 C++   使用虹软技术开发完成 使用步骤: 1.下载SDK包,32位Windows平台将五个SDK包里lib中的文件到 ...

随机推荐

  1. (五)mysql表操作和约束条件

    (1)表基本操作 1)创建表 create table 表名(字段 数据类型 约束条件) mysql> create table student1(id int,name varchar(50) ...

  2. (3)python 列表和元组

    列表 元组 字段 等都是一种数据结构.其中列表和元组比较相似都是顺序保存的,都可以通过索引来找到元素. 列表和元组的主要区别:列表是可以修改的,元组不可以修改.列表用[]表示,元组用()表示 一.列表 ...

  3. 字典树(Trie Tree)

    终于要开始更新我的ACM学习之路了,不过没想到却是因为一次Java大作业,有趣,%yuan老师. 字典树是一种很简单的树形结构,主要用来进行词频统计,在算法竞赛中有时也会碰到. 字典树的基本思路是,通 ...

  4. Scala 实现快速排序和归并排序

    def quickSort1(array: Array[Int]): Array[Int] = { def swap(x: Int, y: Int): Unit = { val tmp = array ...

  5. 线段树【CF620E】The Child and Sequence

    Description At the children's day, the child came to Picks's house, and messed his house up. Picks w ...

  6. Tarjan+topsort(DP)【P3387】 [模板]缩点

    Description 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次 ...

  7. 洛谷——P1589 泥泞路

    P1589 泥泞路 题目描述 暴雨过后,FJ的农场到镇上的公路上有一些泥泞路,他有若干块长度为L的木板可以铺在这些泥泞路上,问他至少需要多少块木板,才能把所有的泥泞路覆盖住. 输入输出格式 输入格式: ...

  8. 水流(water)(BFS)(DFS)

    水流(water) 时间限制: 1 Sec  内存限制: 64 MB提交: 9  解决: 2[提交][状态][讨论版] 题目描述 全球气候变暖,小镇A面临水灾,于是你必须买一些泵把水抽走.泵的抽水能力 ...

  9. [AGC012F]Prefix Median

    题目大意: 给定一个长度为$2n-1(n\le50)$的数组$a$,可以重排$a$中的元素,生成一个长度为$n$的数组$b$,其中$b_i$为$a_1\sim a_{2i-1}$的中位数.求对于给定的 ...

  10. 两道Java面试题!

    body { font-family: 微软雅黑; font-size: 14px; line-height: 2; } html, body { color: inherit; background ...