需求: 考生需要在考試前將自己的人臉錄入到系統當中。(拍照錄入或者直接匯入,方法二選一) 考生在考試時,需要進行Face Service,通過人臉進行身分識別驗證,驗證成功後,登入成功。
上周的部落格中我講到了如何拍照將人臉錄入系統中,這次我把剩下的講完。
先講簡單的吧,用上傳照片將人臉匯入系統當中。
實現效果是這個樣子的:
還是之前的介面,我改了一下,上面的輸入框是用來輸入帳號的,點擊選擇檔案的按鈕,選好要上傳的圖片後,點擊圖片上傳按鈕。將圖片提交到後台,上傳給face++解析,得到傳回值處理過後返回給使用者,這裡返回的是上傳的照片不對是因為我上傳了一張貓的照片,不是人像。好了,簡單看一下代碼吧,前端代碼:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>假裝這是註冊頁面</title> <style> video,canvas{ border:1px solid gray; width:400px; height:400px; border-radius:50%; } </style> </head> <body> <video autoplay style="position: absolute;left: 1%;display: none;"></video> <canvas id="myCanvas" style="position: absolute;left: 50%;"></canvas> <form id="pic" action="/face/picture" enctype="multipart/form-data"> <input type="text" id="name" style="position: absolute;left: 30%;top: 15%" placeholder="請填入您的帳號"> <input type="file" name="file" style="position: absolute;left: 30%;top: 35%" > <button type="button" style="position: absolute;left: 30%;top: 45%" onclick="uploadPic()">圖片上傳</button> </form> <!-- <button id="capture" style="position: absolute;left: 30%;top: 25%">拍照上傳</button> --> <script src="http://code.jquery.com/jquery-latest.js"></script> <script type="text/javascript"> 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("瀏覽器暫不支援") } </script> </body> </html>
我加了一些代碼在之前拍照上傳的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<String, String> map = new HashMap<>(); HashMap<String, byte[]> 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<String, String> map, HashMap<String, byte[]> 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<String, String> 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<String, byte[]> fileEntry = (Map.Entry<String, byte[]>) 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<Runnable>(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<BasicNameValuePair> 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"));