代碼地址以下:<br>http://www.demodashi.com/demo/12011.htmlhtml
以前公司項目須要,研究了一下人臉識別和活體識別,並運用免費的訊飛人臉識別,在其基礎上作了二次開發,添加了活體識別。項目須要就開發了張嘴和搖頭兩個活體動做的識別。 這裏簡單介紹一下當時的開發思路和一些我的拙見,歡迎大神指點。 首先說一下訊飛第三方的人臉識別的幾個缺點:1.識別不穩定,各點座標跳動誤差比較大,不容易捕捉;2.CPU使用率比較高,連續識別一下子手機會明顯發燙,手機配置低的,就會反應很慢,本人使用的iPhone 6s,配置還能夠,還算比較流暢,但也會發燙。3.屏幕小的手機識別率相對會低一點,固然這也和手機的配置脫不了干係。 下面開始咱們的活體識別開發之路:git
肯定位置
訊飛的人臉識別座標跳動比較大,若是全屏識別發現很容易出現錯誤的識別,致使識別錯誤的被經過,因此爲了下降這個可能性,特地加了臉部位置的限制,把識別位置和範圍大大縮小,大大提升了識別精度和成功率。 原版的Demo裏給出了人臉框的座標,也顯示出了人臉的框,代碼以下:github
-(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); }
在這段代碼的啓發下,我對此做了改裝,把動態的人臉框,改爲了靜態的框,這個靜態框,就是指示和限定人臉位置的框,根據屏幕大小畫出的,代碼以下:objective-c
-(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); }
這裏的框是限定臉部位置的,因此臉部位置超出設置的範圍的時候,就須要中止人臉識別,中止動做識別,並給出用戶提示,提示用戶調整位置,或者明確告訴用戶,臉部距離屏幕太近了,或者太遠了。斷定臉部位置的代碼以下:ide
#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; } } } }
張嘴動做識別經過後,開始判斷搖頭動做。url
搖頭識別
搖頭識別,這裏的搖頭動做相比於張嘴動做,搖頭動做我沒有限制位置,張嘴識別必須在設置的框內完成動做,搖頭動做不須要,由於搖頭動做幅度大,須要的位置大,若是再限定位置的話,識別要求比較高,不容易識別經過,用戶體驗差。 搖頭識別的思路比較簡單,沒有作細緻的計算分析,僅僅是判斷了鼻尖的點的座標改變大於設定值,即斷定爲搖頭動做經過。代碼以下:spa
#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的,左右晃動手機超過必定的距離,也會斷定搖頭經過,當時時間緊張,沒作過多處理,因此就暫時這樣斷定了。.net
其餘細節
判斷比較數據,我用了計數法,取得是不一樣時間點的幀圖片上的點的位置並記錄下來,而後和初始值作比較,因此若是判斷不符合要求,須要清除數據,並從新開始記錄並斷定。 另外Demo裏給出了兩種記錄動做的方式,一種是有聲音的拍照,一種是無聲音的截圖,能夠爲人臉的對比作鋪墊。
文件目錄截圖
尾聲
Demo的gitHub歡迎你們下載、參考、指正、交流,若是對您有幫助,感謝star,另外創建了一個活體識別交流羣:498197808,歡迎同道中人加入,你們一塊兒交流學習。
iOS活體人臉識別的Demo和一些思路
代碼地址以下:<br>http://www.demodashi.com/demo/12011.html
注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權