人脸识别(四)考生考试人脸验证的实现。

您所在的位置:网站首页 酷酷打字在线怎么作弊 人脸识别(四)考生考试人脸验证的实现。

人脸识别(四)考生考试人脸验证的实现。

2024-06-03 04:36| 来源: 网络整理| 查看: 265

需求:

考生需要在考试前将自己的人脸录入到系统当中。(拍照录入或者直接导入,方法二选一)考生在考试时,需要进行人脸识别,通过人脸进行身份验证,验证成功后,登录成功。

上周的博客中我讲到了如何拍照将人脸录入系统中,这次我把剩下的讲完。 先讲简单的吧,用上传照片将人脸导入系统当中。

实现效果是这个样子的:

这里写图片描述

还是之前的界面,我改了一下,上面的输入框是用来输入帐号的,点击选择文件的按钮,选好要上传的图片后,点击图片上传按钮。将图片提交到后台,上传给face++解析,得到返回值处理过后返回给用户,这里返回的是上传的照片不对是因为我上传了一张猫的照片,不是人像。好了,简单看一下代码吧,前端代码:

假装这是注册页面 video,canvas{ border:1px solid gray; width:400px; height:400px; border-radius:50%; } 图片上传 function uploadPic(){ var formData = new FormData($( "#pic" )[0]); $.ajax({ url: '/face/picture' , /*这是处理文件上传的servlet*/ type: 'POST', data: formData, async: false, cache: false, contentType: false, processData: false, success: function (returndata) { alert(returndata.message) }, error: function (returndata) { alert(returndata); } }); } function hasUserMedia(){//判断是否支持调用设备api,因为浏览器不同所以判断方式不同哦 return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); } if(hasUserMedia()){ //alert(navigator.mozGetUserMedia) navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; var video=document.querySelector("video"); var canvas=document.querySelector("canvas"); var streaming = false; navigator.getUserMedia({ video:true,//开启视频 audio:false//先关闭音频,因为会有回响,以后两台电脑通信不会有响声 },function(stream){//将视频流交给video video.src=window.URL.createObjectURL(stream); streaming = true; },function(err){ console.log("capturing",err) }); document.querySelector("#capture").addEventListener("click",function(event){ if(streaming){ //alert(video.clientHeight) //canvas.width = video.clientWidth; //canvas.height= video.clientHeight; canvas.width = 800; canvas.height = 800; var context = canvas.getContext('2d'); imgString = canvas.toDataURL("image/png") context.drawImage(video,20,20) var info = { name: $("#name").val(), imgString: canvas.toDataURL("image/png") } $.post("/face/photograph",info,function(data){ alert(data.message) },"json") } }) }else{ alert("浏览器暂不支持") }

我加了一些代码在之前拍照上传的html里面,其实就是ajax提交带着上传标签的表单而已,没什么好说的。

后台代码:

@RequestMapping(value="/picture") public JsonResult picture(MultipartFile file,String name) throws IOException { if(file == null || "".equals(file.getOriginalFilename())) { return new JsonResult("0", "上传的照片为空", null); } String str = FaceUtil.check(file.getBytes()); JSONObject json = JSONObject.fromObject(str); try { String faces = json.getString("faces"); if("[]".equals(faces)) { return new JsonResult("0", "对不起,您上传的不是用户头像或者照片质量不达标,请重新上传!", null); } JSONObject josnToken = JSONObject.fromObject(faces.substring(1, faces.length()-1)); String token = josnToken.getString("face_token"); FaceUser user = new FaceUser(); user.setName(name); user.setFaceToken(token); faceService.add(user); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); return new JsonResult("0", "系统繁忙,请稍后重试!", null); } return new JsonResult("1", "上传成功,请登录!", null); }

我把图片上传到face++并得到返回值的过程封装到了FaceUtil里面,代码如下:

package com.avie.ltd.util; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import javax.net.ssl.SSLException; import sun.misc.BASE64Decoder; public class FaceUtil { static String url = "https://api-cn.faceplusplus.com/facepp/v3/detect"; public static String checkFace(String imgString) throws IOException { byte[] buff = getStringImage(imgString.substring(imgString.indexOf(",")+1)); return check( buff); } public static String check(byte[] buff) { HashMap map = new HashMap(); HashMap byteMap = new HashMap(); map.put("api_key", "your api key"); map.put("api_secret", "your api secret"); map.put("return_landmark", "1"); map.put("return_attributes", "gender,age,smiling,headpose,facequality,blur,eyestatus,emotion,ethnicity,beauty,mouthstatus,eyegaze,skinstatus"); byteMap.put("image_file", buff); String str =null; try{ byte[] bacd = post(url, map, byteMap); str = new String(bacd); System.out.println(str); }catch (Exception e) { e.printStackTrace(); } return str; } /** * Base64字符串转 二进制流 * * @param base64String Base64 * @return base64String * @throws IOException 异常 */ @SuppressWarnings("restriction") public static byte[] getStringImage(String base64String) throws IOException { BASE64Decoder decoder = new sun.misc.BASE64Decoder(); return base64String != null ? decoder.decodeBuffer(base64String) : null; } private final static int CONNECT_TIME_OUT = 30000; private final static int READ_OUT_TIME = 50000; private static String boundaryString = getBoundary(); protected static byte[] post(String url, HashMap map, HashMap fileMap) throws Exception { HttpURLConnection conne; URL url1 = new URL(url); conne = (HttpURLConnection) url1.openConnection(); conne.setDoOutput(true); conne.setUseCaches(false); conne.setRequestMethod("POST"); conne.setConnectTimeout(CONNECT_TIME_OUT); conne.setReadTimeout(READ_OUT_TIME); conne.setRequestProperty("accept", "*/*"); conne.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundaryString); conne.setRequestProperty("connection", "Keep-Alive"); conne.setRequestProperty("user-agent", "Mozilla/4.0 (compatible;MSIE 6.0;Windows NT 5.1;SV1)"); DataOutputStream obos = new DataOutputStream(conne.getOutputStream()); Iterator iter = map.entrySet().iterator(); while(iter.hasNext()){ Map.Entry entry = (Map.Entry) iter.next(); String key = entry.getKey(); String value = entry.getValue(); obos.writeBytes("--" + boundaryString + "\r\n"); obos.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"\r\n"); obos.writeBytes("\r\n"); obos.writeBytes(value + "\r\n"); } if(fileMap != null && fileMap.size() > 0){ Iterator fileIter = fileMap.entrySet().iterator(); while(fileIter.hasNext()){ Map.Entry fileEntry = (Map.Entry) fileIter.next(); obos.writeBytes("--" + boundaryString + "\r\n"); obos.writeBytes("Content-Disposition: form-data; name=\"" + fileEntry.getKey() + "\"; filename=\"" + encode(" ") + "\"\r\n"); obos.writeBytes("\r\n"); obos.write(fileEntry.getValue()); obos.writeBytes("\r\n"); } } obos.writeBytes("--" + boundaryString + "--" + "\r\n"); obos.writeBytes("\r\n"); obos.flush(); obos.close(); InputStream ins = null; int code = conne.getResponseCode(); try{ if(code == 200){ ins = conne.getInputStream(); }else{ ins = conne.getErrorStream(); } }catch (SSLException e){ e.printStackTrace(); return new byte[0]; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buff = new byte[4096]; int len; while((len = ins.read(buff)) != -1){ baos.write(buff, 0, len); } byte[] bytes = baos.toByteArray(); ins.close(); return bytes; } private static String getBoundary() { StringBuilder sb = new StringBuilder(); Random random = new Random(); for(int i = 0; i < 32; ++i) { sb.append("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".charAt(random.nextInt("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_".length()))); } return sb.toString(); } private static String encode(String value) throws Exception{ return URLEncoder.encode(value, "UTF-8"); } public static byte[] getBytesFromFile(File f) { if (f == null) { return null; } try { FileInputStream stream = new FileInputStream(f); ByteArrayOutputStream out = new ByteArrayOutputStream(1000); byte[] b = new byte[1000]; int n; while ((n = stream.read(b)) != -1) out.write(b, 0, n); stream.close(); out.close(); return out.toByteArray(); } catch (IOException e) { } return null; } }

由于我在之前的博客中讲过这些代码,所以这里不再详述,不懂的去看我之前的博客。那么到这里我们就把第一个需求完全实现了吗?没有!在我之前讲人脸对比的博客中,提到过人脸对比的传参列表如下:

官方传参列表

要实现人脸对比,至少要传两张照片,这两张照片,可以是二进制流,可以是图片的url,也可以是之前上传到face++的照片的face_token。在这里面,最好的方式应该是传face_token,这种方式不用再上传整张图片,face++那边也不用再解析你的图片,直接调用你之前上传的图片即可。那么要想让face++永久的保留我们现在上传的图片以供以后使用,我们就还需要将得到的图片的face_token存到我们在之前建的face_set中去(对face_set不清楚的同学可以去这里看我之前的介绍:调用face++api实现人脸对比)。具体做法来看如下代码:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue(5)); @Autowired private FaceUserService faceService; @RequestMapping(value="/photograph") public JsonResult getFace(String imgString,String name) throws IOException { String str = FaceUtil.checkFace(imgString); String token = ""; JSONObject json = JSONObject.fromObject(str); try { String faces = json.getString("faces"); if("[]".equals(faces)) { return new JsonResult("0", "对不起,您上传的不是用户头像或者照片质量不达标,请重新上传!", null); } JSONObject josnToken = JSONObject.fromObject(faces.substring(1, faces.length()-1)); token = josnToken.getString("face_token"); FaceUser user = new FaceUser(); user.setName(name); user.setFaceToken(token); faceService.add(user); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); return new JsonResult("0", "系统繁忙,请稍后重试!", null); } executor.execute(new AddFace(token)); return new JsonResult("1", "上传成功,请登录!", null); }

这里我对之前的拍照上传的代码做了些修改。最开始先new了一个线程池,进入getFace方法,大体和之前博客的一样,就是提交照片给face++检测然后拿到返回值处理,不同的是,当我检测成功之后,往线程池提交了一个AddFace()的线程,并在构造方法里传入人脸检测完返回的face_token的值。那我们再来看看这个线程的代码:

package com.avie.ltd.controller; import java.util.ArrayList; import java.util.List; import org.apache.http.message.BasicNameValuePair; import com.avie.ltd.util.PostUtil; public class AddFace implements Runnable { String addUrl = "https://api-cn.faceplusplus.com/facepp/v3/faceset/addface"; String faceToken = ""; public AddFace(String faceToken) { this.faceToken = faceToken; } @Override public void run() { // TODO Auto-generated method stub while (true) { // 创建参数队列 List formparams = new ArrayList(); formparams.add(new BasicNameValuePair("api_key", "your api key")); formparams.add(new BasicNameValuePair("api_secret", "your api secret")); formparams.add(new BasicNameValuePair("outer_id", "myface_1")); formparams.add(new BasicNameValuePair("face_tokens", faceToken)); // 发送请求 if (PostUtil.post(formparams, addUrl)) { System.out.println("上传成功,结束线程!"); return; } try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }

线程通过构造方法拿到face_token的值,run方法里面是一个while(true)永真循环,里面做的事情就是把api_key,api_secret,创建的face_set的outer_id,及face_token做参数,发送一个http请求给face++,让face++把这个face_token存入face_set里面保存起来,假如这个操作失败(因为我是免费用户,所以有被face++服务器拒绝处理的风险,这个我在之前博客中提到过)则线程睡眠三秒继续发送请求(因为有并发量限制,所以频繁的请求没有意义,让线程睡眠一段时间再请求比较好)。发送http请求过程我封装在了PostUtil工具类里面,来看代码:

package com.avie.ltd.util; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.clienthods.CloseableHttpResponse; import org.apache.http.clienthods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import net.sf.json.JSONObject; public class PostUtil { /** * 发送 post请求访问本地应用并根据传递参数不同返回不同结果 */ public static boolean post(List formparams,String url) { // 创建默认的httpClient实例. CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建httppost HttpPost httppost = new HttpPost(url); UrlEncodedFormEntity uefEntity; boolean isSuccess = false; try { uefEntity = new UrlEncodedFormEntity(formparams, "UTF-8"); httppost.setEntity(uefEntity); System.out.println("executing request " + httppost.getURI()); CloseableHttpResponse response = httpclient.execute(httppost); try { HttpEntity entity = response.getEntity(); if (entity != null) { String returnString = EntityUtils.toString(entity, "UTF-8"); System.out.println("--------------------------------------"); System.out.println("Response content: " + returnString); System.out.println("--------------------------------------"); JSONObject json = JSONObject.fromObject(returnString); String faceAdded = json.getString("face_added"); if("1".equals(faceAdded)) { System.out.println("添加成功"); isSuccess = true; } } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭连接,释放资源 try { httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return isSuccess; } }

这个方法用httpclient发送请求,然后我拿到返回值,将返回的字符串解析成json格式,得到其中key为face_added的值,这个值代表你这次请求成功添加了几个token,我们这里只上传了一个,所以当且只当face_added参数为1时,请求才是成功的。假如发生任何的异常,请求肯定是不成功的,捕获异常返回false即可。下面就是我进行了一次成功上传时,控制台的输出:

executing request https://api-cn.faceplusplus.com/facepp/v3/faceset/addface -------------------------------------- Response content: {"error_message":"CONCURRENCY_LIMIT_EXCEEDED"} -------------------------------------- net.sf.json.JSONException: JSONObject["face_added"] not found. at net.sf.json.JSONObject.getString(JSONObject.java:2092) at com.avie.ltd.util.PostUtil.post(PostUtil.java:44) at com.avie.ltd.controller.AddFace.run(AddFace.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) executing request https://api-cn.faceplusplus.com/facepp/v3/faceset/addface -------------------------------------- Response content: {"error_message":"CONCURRENCY_LIMIT_EXCEEDED"} -------------------------------------- net.sf.json.JSONException: JSONObject["face_added"] not found. at net.sf.json.JSONObject.getString(JSONObject.java:2092) at com.avie.ltd.util.PostUtil.post(PostUtil.java:44) at com.avie.ltd.controller.AddFace.run(AddFace.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) executing request https://api-cn.faceplusplus.com/facepp/v3/faceset/addface -------------------------------------- Response content: {"error_message":"CONCURRENCY_LIMIT_EXCEEDED"} -------------------------------------- net.sf.json.JSONException: JSONObject["face_added"] not found. at net.sf.json.JSONObject.getString(JSONObject.java:2092) at com.avie.ltd.util.PostUtil.post(PostUtil.java:44) at com.avie.ltd.controller.AddFace.run(AddFace.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) executing request https://api-cn.faceplusplus.com/facepp/v3/faceset/addface -------------------------------------- Response content: {"faceset_token": "9cb520b964d58b3484565f6788ac750a", "time_used": 458, "face_count": 3, "face_added": 1, "request_id": "1515854017,48089174-0cf5-40ef-811f-ec703f229951", "outer_id": "myface_1", "failure_detail": []} -------------------------------------- 添加成功 上传成功,结束线程!

可以清楚的看到,程序抛出了多次异常,原因是在json字符串中没有找到face_added这个key,因为返回值是{“error_message”:”CONCURRENCY_LIMIT_EXCEEDED”}这个样子的,意思是超出并发量限制,服务器拒绝执行。不过因为我们捕获了这个异常,所以线程并没有结束,而是等待三秒继续发出请求,如此失败了三次之后,执行成功,结束线程。

最终展示效果如下:

这里写图片描述

数据库里成功插入数据:

这里写图片描述

第一个需求实现完了,来看第二个:

考生在考试时,需要进行人脸识别,通过人脸进行身份验证,验证成功后,登录成功。

先来看前台代码:

假装这是注册页面 video,canvas{ border:1px solid gray; width:400px; height:400px; border-radius:50%; } 拍照上传 function hasUserMedia(){//判断是否支持调用设备api,因为浏览器不同所以判断方式不同哦 return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); } if(hasUserMedia()){ //alert(navigator.mozGetUserMedia) navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; var video=document.querySelector("video"); var canvas=document.querySelector("canvas"); var streaming = false; navigator.getUserMedia({ video:true,//开启视频 audio:false//先关闭音频,因为会有回响,以后两台电脑通信不会有响声 },function(stream){//将视频流交给video video.src=window.URL.createObjectURL(stream); streaming = true; },function(err){ console.log("capturing",err) }); document.querySelector("#capture").addEventListener("click",function(event){ if(streaming){ //alert(video.clientHeight) //canvas.width = video.clientWidth; //canvas.height= video.clientHeight; canvas.width = 800; canvas.height = 800; var context = canvas.getContext('2d'); imgString = canvas.toDataURL("image/png") context.drawImage(video,20,20) var info = { name: $("#name").val(), imgString: canvas.toDataURL("image/png") } $.post("/face/login",info,function(data){ alert(data.message) },"json") } }) }else{ alert("浏览器暂不支持") }

前台代码和之前的差不多,只不过这里只需输入一个账号即可,效果如下图:

这里写图片描述

当我输入账号,点击登录,检测成功提示如下图:

这里写图片描述

那接着来看看后台代码:

@RequestMapping(value="/login") public JsonResult login(String imgString,String name) throws IOException { String str = FaceUtil.checkFace(imgString); String token = ""; JSONObject json = JSONObject.fromObject(str); try { String faces = json.getString("faces"); if("[]".equals(faces)) { return new JsonResult("0", "请你靠近摄像头以获得质量更好的照片!", null); } JSONObject josnToken = JSONObject.fromObject(faces.substring(1, faces.length()-1)); token = josnToken.getString("face_token"); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); return new JsonResult("0", "系统繁忙,请稍后重试!", null); } FaceUser user = faceService.getUser(name); if(user == null) { return new JsonResult("0", "不存在该账号!", null); } if(PostUtil.compare(token, user.getFaceToken())) { return new JsonResult("1", "人脸相符,登录成功!", null); } return new JsonResult("0", "人脸不符,登录失败", null); }

将拍摄到的图片和输入的账号传入后台,先将照片上传到face++进行检测,如果成功,则拿到它的face_token,然后将该账号之前存的face_token取出,一起传入PostUtil.compare(token, user.getFaceToken())这个方法进行比较,为真则人脸相符,为假则不相符,compare方法代码如下:

/** * 发送 post请求访问本地应用并根据传递参数不同返回不同结果 * * */ public static boolean compare(String token1, String token2) { List formparams = new ArrayList(); formparams.add(new BasicNameValuePair("api_key", "your api key")); formparams.add(new BasicNameValuePair("api_secret", "your secret")); formparams.add(new BasicNameValuePair("face_token1", token1)); formparams.add(new BasicNameValuePair("face_token2", token2)); while (true) { String returnString = postHttp(formparams, compareUrl); try { JSONObject json = JSONObject.fromObject(returnString); String confidence = json.getString("confidence"); System.out.println("置信度为" + confidence); JSONObject thresholdsJson = json.getJSONObject("thresholds"); String e5 = thresholdsJson.getString("1e-5"); System.out.println("误识率为十万分之一的置信度阈值为:" + e5); if (Double.valueOf(confidence) >= Double.valueOf(e5)) { System.out.println("极度相似,登录成功!"); return true; } else { System.out.println("相似度不高,登录失败!"); return false; } } catch (JSONException e) { e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private static String postHttp(List formparams, String url) { // 创建默认的httpClient实例. CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建httppost HttpPost httppost = new HttpPost(url); UrlEncodedFormEntity uefEntity; String returnStr = ""; try { uefEntity = new UrlEncodedFormEntity(formparams, "UTF-8"); httppost.setEntity(uefEntity); System.out.println("executing request " + httppost.getURI()); CloseableHttpResponse response = httpclient.execute(httppost); try { HttpEntity entity = response.getEntity(); if (entity != null) { returnStr = EntityUtils.toString(entity, "UTF-8"); System.out.println("--------------------------------------"); System.out.println("Response content: " + returnStr); System.out.println("--------------------------------------"); } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭连接,释放资源 try { httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return returnStr; }

这里将两个face_token用http请求发送到face++那边,得到返回参数,如果服务器拒接访问,则每隔0.5秒发送一次请求,知道成功获得返回参数为止,然后将置信度与返回的阈值进行比较,若置信度大于十万分之一的阈值,则认为两张照片一样,返回前台告诉登录成功。下面是我其中一次请求成功控制台打印出来的参数:

executing request https://api-cn.faceplusplus.com/facepp/v3/compare -------------------------------------- Response content: {"confidence": 82.123, "request_id": "1515916409,2b5f3e22-7ab7-4c81-b0af-13cb6cdc705b", "time_used": 387, "thresholds": {"1e-3": 62.327, "1e-5": 73.975, "1e-4": 69.101}} -------------------------------------- 置信度为82.123 误识率为十万分之一的置信度阈值为:73.975 极度相似,登录成功!

至此,人脸识别的需求已经全部实现,我对人脸识别的研究也将暂告一段落,在以后的博客中我将继续与大家分享我在工作中研究和学习到的知识,感谢大家的关注与支持,byebye。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3