【这段时间有点忙,终于截止今天2018.06.22完成了人脸识别的最后一道程序——活体检测之眨眨眼和张张嘴】
关于人脸识别的内容我之前也写过好几篇博文,其中有:
大家可以去看看,其中兼容性也比较良好,支持常用浏览器,火狐、谷歌、360、UC浏览器以及opera浏览器,源码也已经上传成功,所需要的JS文件和jar也都上传到资源页。
好了,我们废话不多说,先来说说今天的内容。
眨眨眼和张张嘴在哪里用的到呢?如果大家不知道其他的软件,大家肯定知道支付宝软件吧,其中支付宝软件登陆的时候就会语音提示眨眨眼和张张嘴来验证是不是本人登陆(限制照片登陆)
以前刚开始写的时候我写的人脸识别用照片也可以识别,然后好多粉丝留言说这太不安全了,还有几个用嫌弃的眼神对我说:你这做的还有啥用,我拿着你的照片来登陆,居然能登陆成功,还不如以前的密码登陆安全呢。(其实他没做出来~),我也没说啥,毕竟是自己做的不完善呢,忍气吞声继续深入研究。终于做出来了后来的活体检测,也就是上面的第二个链接!
但是……
这个活体检测有时候会失误!会失误!会失误!!!
这是个很大的bug,于是我就开始开发现在的眨眨眼和张张嘴~
一、首先我来说一下运行过程:
首先进入页面,login.jsp中,点击检测按钮
接着你会听到语音提示:现在请张张嘴(这个语音提示可是专门找美女录得音呢~)
你就按照语音提示张张嘴就可以了,张合张合重复一遍就大概可以验证成功了,接着你就会听见语音提示:现在请眨眨眼,这时候你眨眨眼就行了,验证通过会进入我的网站,期间如果你的脸没有放置好的话就会提示:
当你看到提示这个的时候你把脸调整的放到框内就可以了。实在不行你就刷新一下网页重新来一遍吧。
二、技术分析
技术方面有以下几个步骤:
第一,js调用前台摄像头读取人脸信息,提交到后台的FaceServlet中,通过ajax进行交互,参数分别是:
- type : “post”,//提交方式
- url : “faceServlet”,//请求地址
- 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(); } } } }