人臉識別活體檢測之眨眨眼和張張嘴


【這段時間有點忙,終於截止今天2018.06.22完成了人臉識別的最后一道程序——活體檢測之眨眨眼和張張嘴】
關於人臉識別的內容我之前也寫過好幾篇博文,其中有:
{java實現人臉識別源碼}
{C#winforms實現windows窗體人臉識別}
{人臉識別活體檢測測試案例}
大家可以去看看,其中兼容性也比較良好,支持常用瀏覽器,火狐、谷歌、360、UC瀏覽器以及opera瀏覽器,源碼也已經上傳成功,所需要的JS文件和jar也都上傳到資源頁。


好了,我們廢話不多說,先來說說今天的內容。
眨眨眼和張張嘴在哪里用的到呢?如果大家不知道其他的軟件,大家肯定知道支付寶軟件吧,其中支付寶軟件登陸的時候就會語音提示眨眨眼和張張嘴來驗證是不是本人登陸(限制照片登陸)
這里寫圖片描述

以前剛開始寫的時候我寫的人臉識別用照片也可以識別,然后好多粉絲留言說這太不安全了,還有幾個用嫌棄的眼神對我說:你這做的還有啥用,我拿着你的照片來登陸,居然能登陸成功,還不如以前的密碼登陸安全呢。(其實他沒做出來~),我也沒說啥,畢竟是自己做的不完善呢,忍氣吞聲繼續深入研究。終於做出來了后來的活體檢測,也就是上面的第二個鏈接!
但是……
這個活體檢測有時候會失誤!會失誤!會失誤!!!
這是個很大的bug,於是我就開始開發現在的眨眨眼和張張嘴~


一、首先我來說一下運行過程:
首先進入頁面,login.jsp中,點擊檢測按鈕
這里寫圖片描述
接着你會聽到語音提示:現在請張張嘴(這個語音提示可是專門找美女錄得音呢~)
你就按照語音提示張張嘴就可以了,張合張合重復一遍就大概可以驗證成功了,接着你就會聽見語音提示:現在請眨眨眼,這時候你眨眨眼就行了,驗證通過會進入我的網站,期間如果你的臉沒有放置好的話就會提示:
這里寫圖片描述
當你看到提示這個的時候你把臉調整的放到框內就可以了。實在不行你就刷新一下網頁重新來一遍吧。


二、技術分析
技術方面有以下幾個步驟:
第一,js調用前台攝像頭讀取人臉信息,提交到后台的FaceServlet中,通過ajax進行交互,參數分別是:

  1. type : “post”,//提交方式
  2. url : “faceServlet”,//請求地址
  3. data : {
    “img” : imgData1,
    “tag” : “eye”//區分是眨眼還是張嘴
    }//數據

4.dataType : “json”
上面就是ajax請求的參數,然后寫個回調函數就可以了。
第二、拿着Base64圖像數據之后去檢測去,檢測回來的數據如下所示:

{
“timestamp”: 1528371144,
“result”: {
“face_list”: [{
“expression”: {
“probability”: 0.9999684095,
“type”: “none”
},
“face_probability”: 0.9626089931,
“glasses”: {
“probability”: 0.9998494387,
“type”: “common”
},
“location”: {
“height”: 307,
“rotation”: 2,
“width”: 273,
“left”: 296.1046143,
“top”: 163.5777588
},
“landmark72”: [
{
“y”: 241.360199,
“x”: 292.4519043
},
{
“y”: 284.3699341,
“x”: 295.9116211
},
{
“y”: 328.4156189,
“x”: 304.021759
},
{
“y”: 371.3819275,
“x”: 314.0935364
},
{
“y”: 416.5806274,
“x”: 333.3151245
},
{
“y”: 459.6279297,
“x”: 370.47229
},
{
“y”: 476.2438354,
“x”: 412.0462036
},
{
“y”: 469.1072998,
“x”: 457.7119141
},
{
“y”: 435.0166626,
“x”: 501.7189941
},
{
“y”: 390.2756348,
“x”: 532.2461548
},
{
“y”: 345.0927124,
“x”: 548.138916
},
{
“y”: 299.1921692,
“x”: 559.2930298
},
{
“y”: 254.285141,
“x”: 565.3004761
},
{
“y”: 230.1785583,
“x”: 329.6694946
},
{
“y”: 222.2650757,
“x”: 343.285614
},
{
“y”: 220.7796631,
“x”: 356.7460938
},
{
“y”: 223.9135742,
“x”: 371.3406677
},
{
“y”: 233.6182861,
“x”: 385.2095947
},
{
“y”: 235.2540588,
“x”: 370.6172485
},
{
“y”: 236.2314148,
“x”: 356.4389343
},
{
“y”: 234.2461243,
“x”: 341.9369507
},
{
“y”: 227.4796143,
“x”: 355.9927368
},
{
“y”: 193.4365387,
“x”: 313.8055115
},
{
“y”: 170.465332,
“x”: 331.9764404
},
{
“y”: 166.3371429,
“x”: 356.399353
},
{
“y”: 170.4036255,
“x”: 380.4868774
},
{
“y”: 188.2350311,
“x”: 401.4260559
},
{
“y”: 188.1490021,
“x”: 379.4369202
},
{
“y”: 186.7609863,
“x”: 356.0379028
},
{
“y”: 188.6333923,
“x”: 333.6030884
},
{
“y”: 237.4863892,
“x”: 465.0245972
},
{
“y”: 228.826889,
“x”: 479.3971558
},
{
“y”: 228.0735474,
“x”: 494.7539673
},
{
“y”: 231.1828613,
“x”: 509.2977905
},
{
“y”: 239.077301,
“x”: 523.2540283
},
{
“y”: 242.3865967,
“x”: 508.8378296
},
{
“y”: 242.7297668,
“x”: 493.300415
},
{
“y”: 240.5341339,
“x”: 478.2283325
},
{
“y”: 233.886261,
“x”: 491.254364
},
{
“y”: 192.1312256,
“x”: 450.9655762
},
{
“y”: 176.1939087,
“x”: 474.1013794
},
{
“y”: 174.3426819,
“x”: 500.0958252
},
{
“y”: 180.6152649,
“x”: 525.2724609
},
{
“y”: 205.0406799,
“x”: 544.15802
},
{
“y”: 198.3281097,
“x”: 522.0322266
},
{
“y”: 193.9849396,
“x”: 498.171875
},
{
“y”: 193.6101837,
“x”: 474.463501
},
{
“y”: 235.1314545,
“x”: 405.7365417
},
{
“y”: 263.1231384,
“x”: 398.9992065
},
{
“y”: 291.5641174,
“x”: 392.697998
},
{
“y”: 321.401062,
“x”: 382.6131592
},
{
“y”: 322.6088257,
“x”: 400.7704468
},
{
“y”: 323.8301086,
“x”: 443.7107544
},
{
“y”: 324.1907959,
“x”: 464.1107788
},
{
“y”: 293.3712769,
“x”: 453.4411926
},
{
“y”: 264.8468323,
“x”: 448.3047485
},
{
“y”: 236.6197968,
“x”: 443.6154175
},
{
“y”: 305.3646545,
“x”: 421.223877
},
{
“y”: 383.9639282,
“x”: 375.137085
},
{
“y”: 363.3876343,
“x”: 393.6142578
},
{
“y”: 360.5367126,
“x”: 422.6240845
},
{
“y”: 366.1363525,
“x”: 451.1436768
},
{
“y”: 389.2103271,
“x”: 469.8604736
},
{
“y”: 407.2568359,
“x”: 450.6833191
},
{
“y”: 413.2620239,
“x”: 420.4364014
},
{
“y”: 403.9484863,
“x”: 390.8678589
},
{
“y”: 377.3647461,
“x”: 396.2229004
},
{
“y”: 376.8469238,
“x”: 421.9415588
},
{
“y”: 379.1690674,
“x”: 447.9805908
},
{
“y”: 390.7108154,
“x”: 446.4016724
},
{
“y”: 391.1112061,
“x”: 421.5936279
},
{
“y”: 388.0070801,
“x”: 396.4388733
}
],
“face_token”: “05f2d8abe9fb3ff0a7b2e590c3af1b94”,
“face_shape”: {
“probability”: 0.5348426104,
“type”: “oval”
},
“race”: {
“probability”: 0.9990831614,
“type”: “yellow”
},
“angle”: {
“yaw”: 0.9113687873,
“roll”: 2.805583239,
“pitch”: -3.07931757
},
“landmark”: [
{
“y”: 227.4796143,
“x”: 355.9927368
},
{
“y”: 233.886261,
“x”: 491.254364
},
{
“y”: 305.3646545,
“x”: 421.223877
},
{
“y”: 385.065155,
“x”: 421.7819214
}
],
",
“quality”: {
“completeness”: 1,
“occlusion”: {
“left_eye”: 0,
“chin_contour”: 0.2168905884,
“mouth”: 0,
“right_cheek”: 0.02743142098,
“left_cheek”: 0.01728247851,
“nose”: 0.08578856289,
“right_eye”: 0
},
“blur”: 8.371096101E-7,
“illumination”: 54
},
“face_type”: {
“probability”: 0.9988574386,
“type”: “human”
}
}],
“face_num”: 1
},
“cached”: 0,
“error_code”: 0,
“log_id”: 2850674365,
“error_msg”: “SUCCESS”
}

看到上面的json數據,我當時一看是懵逼的狀態,這json解析起來有點費力,經過一番折騰,終於可以解析出來了,其中解析方法如下:


public static Landmark  parsingFaceJson(JSONObject json_str){
		Landmark landmark = new Landmark();
		//開始解析json
		//JSONObject  dataJson=new JSONObject(json_str);
		//找到result節點
		JSONObject  response_result=json_str.getJSONObject("result");
		//繼續找face_list節點
		JSONArray face_list_jsonArray=response_result.getJSONArray("face_list");
		JSONObject face_list_jsonObject=face_list_jsonArray.getJSONObject(0);
		//找到landmark(關鍵點)節點,4個關鍵點位置,左眼中心、右眼中心、鼻尖、嘴中心
		final JSONArray landmark_jsonArray=face_list_jsonObject.getJSONArray("landmark");
		//左眼中心
		landmark.setLeft_eye_zhongxin(new ArrayList<Double>(){
			{add((Double) landmark_jsonArray.getJSONObject(0).get("y")); 
			add((Double) landmark_jsonArray.getJSONObject(0).get("x"));}
			});
		//右眼中心
		landmark.setRight_eye_zhongxin(new ArrayList<Double>(){
			{add((Double) landmark_jsonArray.getJSONObject(1).get("y")); 
			add((Double) landmark_jsonArray.getJSONObject(1).get("x"));}
			});
		//鼻尖
		landmark.setNose_zhongxin(new ArrayList<Double>(){
			{add((Double) landmark_jsonArray.getJSONObject(2).get("y")); 
			add((Double) landmark_jsonArray.getJSONObject(2).get("x"));}
			});
		//嘴中心
		landmark.setMouse_zhongxin(new ArrayList<Double>(){
			{add((Double) landmark_jsonArray.getJSONObject(3).get("y")); 
			add((Double) landmark_jsonArray.getJSONObject(3).get("x"));}
			});
		//繼續找landmark72節點
		final JSONArray landmark72_jsonArray=face_list_jsonObject.getJSONArray("landmark72");
		//左眼上邊
		landmark.setLeft_eye_top(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(14).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(14).get("x"));}
			});
		
		//左眼下邊
		landmark.setLeft_eye_bottom(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(19).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(19).get("x"));}
			});
		//右眼上邊
		landmark.setRight_eye_top(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(32).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(32).get("x"));}
			});
		//右眼下邊
		landmark.setRight_eye_bottom(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(36).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(36).get("x"));}
			});
		//嘴巴上邊
		landmark.setMouse__top(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(60).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(60).get("x"));}
			});
		//嘴巴下邊
		landmark.setMouse__bottom(new ArrayList<Double>(){
			{add((Double) landmark72_jsonArray.getJSONObject(70).get("y")); 
			add((Double) landmark72_jsonArray.getJSONObject(70).get("x"));}
			});
		return landmark;
	}

其中的Landmark 類型 是個實體類,實體類的源碼請參考本文末~
解析完json就可以分析數據了,當時在此處遇到了個難點,很是想不通的一個邏輯,於是就去各大java群里面進行提問,功夫不負有心人,遇到一大佬給我話了個圖解決了我當時的難點,(這邊要特別鳴謝一下大佬!!)圖:
這里寫圖片描述
光看圖大家肯定看不明白,我在這里先解釋一下吧,我當時的思路是:前台通過定時器,每1s進行提交一次圖像數據,然后在后台進行比較上一次執行結果和下一次執行結果的值,然后想不通這里面的業務是什么樣子的,於是就有了上面的這張圖。
第三、分析數據:
張嘴:通過json獲取嘴中心的點和上邊、下邊的位置,張嘴和閉嘴是有差別的,差別如下(解析的json數據):

嘴巴分析:
	
----閉着嘴巴:
第一次的上嘴唇:361.1332397
第一次的嘴唇中心:378.8190308
第一次的下嘴唇:382.1697388

上中:17
下中:4

第一次的上嘴唇:390.951355
第一次的嘴唇中心:409.0862122
第一次的下嘴唇:414.6711731


上中:19
下中:5

第一次的上嘴唇:402.4402466
第一次的嘴唇中心:417.4428406
第一次的下嘴唇:419.6887207

上中:15
下中:2

第一次的上嘴唇:403.1029358
第一次的嘴唇中心:419.4420166
第一次的下嘴唇:424.0343018

上中:16
下中:5

第一次的上嘴唇:445.7279663
第一次的嘴唇中心:455.8665161
第一次的下嘴唇:457.3482971

上中:10
下中:2

第二次的上嘴唇:454.5872192
第二次的嘴唇中心:462.1132812
第二次的下嘴唇:463.1278381

上中:8
下中:1

上中:嘴唇上邊與嘴唇中心的距離
下中:嘴唇下邊與嘴唇中心的距離

眨眼:正常人的眼鏡都是有兩只(左眼和右眼),所以這邊我們比較的時候就比張嘴驗證麻煩了,思路還是和張嘴的思路是一樣的,下面的返回解析的分析數據:

***********************************************************************************
閉着眼的左眼上邊:250.549469
閉着眼的左眼中間:252.7034607
閉着眼的左眼下邊:258.3751831
閉着眼的左眼上邊與中間的值:2.153991700000006
閉着眼的左眼下邊與中間的值:5.6717224000000215
閉着眼的右眼上邊:248.886261
閉着眼的右眼中間:251.9490204
閉着眼的右眼下邊:257.2275391
閉着眼的右眼上邊與中間的值:3.0627594000000045
閉着眼的右眼下邊與中間的值:5.2785187000000064

閉着眼的左眼上邊:236.7052612
閉着眼的左眼中間:239.7859192
閉着眼的左眼下邊:245.471283
閉着眼的左眼上邊與中間的值:3.0806579999999997
閉着眼的左眼下邊與中間的值:5.685363800000005
閉着眼的右眼上邊:234.837738
閉着眼的右眼中間:238.6846619
閉着眼的右眼下邊:244.2976685
閉着眼的右眼上邊與中間的值:3.8469239000000073
閉着眼的右眼下邊與中間的值:5.613006599999977

閉着眼的左眼上邊:245.0596619
閉着眼的左眼中間:246.9369202
閉着眼的左眼下邊:252.2763367
閉着眼的左眼上邊與中間的值:1.877258299999994
閉着眼的左眼下邊與中間的值:5.339416499999999
閉着眼的右眼上邊:241.4249573
閉着眼的右眼中間:244.3227234
閉着眼的右眼下邊:249.2035217
閉着眼的右眼上邊與中間的值:2.8977661000000126
閉着眼的右眼下邊與中間的值:4.880798300000009
*********************************************************************************
閉着眼的左眼上邊與中間的值:2.843383799999998
閉着眼的左眼下邊與中間的值:6.379211400000003
閉着眼的右眼上邊與中間的值:3.6415863
閉着眼的右眼下邊與中間的值:5.569457999999997
閉着眼的左眼上邊與中間的值:7.1395263
閉着眼的左眼下邊與中間的值:8.606811599999986
閉着眼的右眼上邊與中間的值:7.101028499999984
閉着眼的右眼下邊與中間的值:7.526168800000022
閉着眼的左眼上邊與中間的值:4.141037000000011
閉着眼的左眼下邊與中間的值:7.363510200000007
閉着眼的右眼上邊與中間的值:4.587234499999994
閉着眼的右眼下邊與中間的值:6.540771500000034
閉着眼的左眼上邊與中間的值:2.4197997999999643
閉着眼的左眼下邊與中間的值:5.460693300000003
閉着眼的右眼上邊與中間的值:2.9811705999999845
閉着眼的右眼下邊與中間的值:4.65585329999999

上面數據一大推,大家看出來個123就行了。
驗證成功返回前台就行了。


關鍵代碼貼上來:
張張嘴的驗證:

/**
	 * 
	* @Description: 該方法的主要作用:張張嘴驗證
	* @Title: face_jiance
	* @param  @param img
	* @param  @param response
	* @param  @param request 設定文件  
	* @return  返回類型:void   
	* @throws
	* 個人博客:https://blog.csdn.net/qq_34137397
	 */
	private void face_mouse(String img, HttpServletResponse response,
			HttpServletRequest request) {

		/*if (dataMap.get(1) == null) {
			// 第一次請求
			landmark = face_jiance(img);
			System.out.println("第一次的上嘴唇:"+landmark.getMouse__top().get(0));
			System.out.println("第一次的嘴唇中心:"+landmark.getMouse_zhongxin().get(0));
			System.out.println("第一次的下嘴唇:"+landmark.getMouse__bottom().get(0));
			dataMap.put(1, landmark);
		} else {*/
			// 不是第一次請求
			Landmark landmark_next = face_jiance(img);
			// 和前一次的數據進行比較
			//Landmark landmark_pre = (Landmark) dataMap.get(1);
			// 嘴唇上面的位置相對於中心點對比
			PrintWriter writer;
			if ((landmark_next.getMouse_zhongxin().get(0) - landmark_next
					.getMouse__top().get(0)) > 40&&(landmark_next.getMouse__bottom().get(0) - landmark_next
							.getMouse_zhongxin().get(0)) > 30) {
					
					try {
						writer = response.getWriter();
						writer.print("1");
					} catch (IOException e) {
						// TODO 異常執行塊!
						e.printStackTrace();
				}
			}else{
				try {
					writer = response.getWriter();
					writer.print("0");
				} catch (IOException e) {
					// TODO 異常執行塊!
					e.printStackTrace();
			}
			}
			//dataMap.put(1,landmark_next);			//放進去  方便下次進行比較

		}

	/*}*/

眨眨眼的驗證代碼:

	
	/**
	 * 
	* @Description: 該方法的主要作用:眨眼對比
	* @Title: face_eye
	* @param  @param img
	* @param  @param response
	* @param  @param request 設定文件  
	* @return  返回類型:void   
	* @throws
	* 個人博客:https://blog.csdn.net/qq_34137397
	 */
	private void face_eye(String img, HttpServletResponse response,
			HttpServletRequest request) {
		Landmark landmark_next = face_jiance(img);
		 System.out.println("閉着眼的左眼上邊與中間的值:"+((landmark_next.getLeft_eye_zhongxin().get(0))-(landmark_next.getLeft_eye_top().get(0))));
		 System.out.println("閉着眼的左眼下邊與中間的值:"+((landmark_next.getLeft_eye_bottom().get(0))-(landmark_next.getLeft_eye_zhongxin().get(0))));
		 System.out.println("閉着眼的右眼上邊與中間的值:"+((landmark_next.getRight_eye_zhongxin().get(0))-(landmark_next.getRight_eye_top().get(0))));
		 System.out.println("閉着眼的右眼下邊與中間的值:"+((landmark_next.getRight_eye_bottom().get(0))-(landmark_next.getRight_eye_zhongxin().get(0))));
		System.out.println("__________________________________________________");
		 PrintWriter writer;
		if ((landmark_next.getLeft_eye_zhongxin().get(0))
				- (landmark_next.getLeft_eye_top().get(0)) < 6
				&& (landmark_next.getRight_eye_zhongxin().get(0))
						- (landmark_next.getRight_eye_top().get(0)) < 6) {
			System.out.println("進來了上邊驗證成功");
			// 繼續判斷下邊的
			if ((landmark_next.getLeft_eye_bottom().get(0))
					- (landmark_next.getLeft_eye_zhongxin().get(0)) < 6.6
					&& (landmark_next.getRight_eye_bottom().get(0))
							- (landmark_next.getRight_eye_zhongxin().get(0)) < 6.6) {
				System.out.println("進來了下邊驗證成功");
				try {
					writer = response.getWriter();
					writer.print("1");
				} catch (IOException e) {
					// TODO 異常執行塊!
					e.printStackTrace();
				}
			} else {
				try {
					writer = response.getWriter();
					writer.print("0");
				} catch (IOException e) {
					// TODO 異常執行塊!
					e.printStackTrace();
				}
			}
		}
	}
	

最后上傳源碼:

1.jar包、js和css下載地址:我的資源頁
2.Landmark實體類
3.解析json類Json_Parsing
4.Servlet:FaceServlet
5.readme日志分析
6.web.xml
7.login.jsp
這里寫圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM