trackingjs+websocket+百度人臉識別API,實現人臉簽到
阿新 • • 發佈:2019-01-08
在公司做了個年會的簽到、抽獎系統。用java web做的,用公司的辦公app掃二維碼碼即可簽到,掃完碼就在大螢幕上顯示這個人的照片。之後領導讓我改得高大上一點,用人臉識別來簽到,就把掃二維碼的步驟改成人臉識別。
瞭解了相關技術後,大致思路如下:先用websocket與後臺建立通訊;用trackingjs在頁面呼叫電腦攝像頭,監聽人臉,發現有人臉進入螢幕了,就把圖片轉成base64字串,通過websocket傳送到後端;後端拿到圖片,呼叫百度的人臉識別API,去人臉庫中匹配(當然事先要在百度雲建立好了自己的人臉庫),得到相似度最高的那個人的資訊,簽到表中紀錄這個人,然後把這個人在人臉庫中的姓名、照片等資訊返回給前端顯示。流程圖如圖所示。
-------中間隔了幾天,實際嘗試後,發現上面的思路有問題,websocket傳輸的資料大小最大為8KB,超出就自動與後臺斷開了,沒法傳圖片。
所以又改變了一下,直接上流程圖。其實就是把圖片改為用ajax傳給controller
下面給出程式碼
拍攝頁面trackingjs.jsp
controller:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html > <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script src="js/jquery-1.9.1.js"></script> <script src="js/tracking-min.js"></script> <script src="js/face-min.js"></script> <style> * { padding: 0; margin: 0; } .container { position: relative; width: 581px; height: 436px; float:left; } .message{ float:left; } video, #canvas { position: absolute; width: 581px; height: 436px; } </style> <script> $(function () { var video = document.getElementById('video'); var canvas = document.getElementById('canvas'); var context = canvas.getContext('2d'); var shortCut = document.getElementById('shortCut'); var scContext = shortCut.getContext('2d'); var time =10000;//向後臺發照片的冷卻時間 var tracker = new tracking.ObjectTracker('face'); tracker.setInitialScale(4); tracker.setStepSize(2); tracker.setEdgesDensity(0.1); tracking.track('#video', tracker, {camera: true}); var flag=true; tracker.on('track', function (event) { if (event.data.length === 0) { context.clearRect(0, 0, canvas.width, canvas.height); }else{ context.clearRect(0, 0, canvas.width, canvas.height); event.data.forEach(function (rect) { context.strokeStyle = '#ff0000'; context.strokeRect(rect.x, rect.y, rect.width, rect.height); context.fillStyle = "#ff0000"; //console.log(rect.x, rect.width, rect.y, rect.height); }); if(flag){ console.log("拍照"); getPhoto(); flag=false; setTimeout(function(){flag=true;},time); }else{ //console.log("冷卻中"); } } }); function getPhoto() { scContext.drawImage(video,0,0,290,218); var imgStr = shortCut.toDataURL("image/png"); //講拍照的圖片資料傳送到controller,呼叫百度雲,簽到,返回簽到結果 $.ajax({ url:"identifyUser", type:"post", dataType:"json", data:{ imgStr:imgStr.substring(imgStr.indexOf(",")+1) }, success:function(result){ if(result.result == "true"){ if(result.user != "404"){ send("user_info:"+result.user); } } } }); } var websocket = null; //判斷當前瀏覽器是否支援WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://localhost:8081/BaiduFace/websocket"); } else { alert('當前瀏覽器不支援websocket!請更換瀏覽器!'); } //連線發生錯誤的回撥方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket連線發生錯誤"); }; //連線成功建立的回撥方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket連線成功"); } ; //接收到訊息的回撥方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); }; //連線關閉的回撥方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket連線關閉"); }; //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); }; //將訊息顯示在網頁上 function setMessageInnerHTML(innerHTML) { document.getElementById('checkinMsg').innerHTML += innerHTML + '<br/>'; } //關閉WebSocket連線 function closeWebSocket() { websocket.close(); } //傳送訊息 function send(msg) { websocket.send(msg); } }); </script> </head> <body> <div class="container"> <video id="video" preload autoplay loop muted></video> <canvas id="canvas" width="581" height="436"></canvas> </div> <div class="message"> <canvas id="shortCut" width="290" height="218" ></canvas> <div id="checkinMsg"></div> </div> </body> </html>
controller 中,BaiduFaceAPI類中的 identifyUserBybase64()方法,以及base64字串轉byte[]的方法。@RequestMapping(value="/identifyUser") public void identifyUser(HttpServletRequest request,HttpServletResponse response) throws IOException, InterruptedException{ response.setHeader("Content-Type", "application/json;charset=utf-8"); PrintWriter pw= response.getWriter(); String imgStr = request.getParameter("imgStr"); BaiduFaceAPI baiduApi = new BaiduFaceAPI(); JSONObject obj= baiduApi.identifyUserBybase64(imgStr);//返回百度雲的計算結果 System.out.println(obj.toString()); Map<String, Object> resultMap = new HashMap<String, Object>(); int result_num = obj.getInt("result_num");//人臉個數 if(result_num == 1){ JSONObject result0 = obj.getJSONArray("result").getJSONObject(0); resultMap.put("result", "true"); double score = result0.getJSONArray("scores").getDouble(0);//與人臉庫中最相似的人臉的相似度 if(score>=85){//暫且設為如果大於85則可以認為是同一個人 resultMap.put("user",result0.getString("user_info")); }else{ resultMap.put("user","404"); } }else{ resultMap.put("result","false"); } pw.write(net.sf.json.JSONObject.fromObject(resultMap).toString()); pw.flush(); pw.close(); }
百度雲人臉識別文件地址:點選開啟連結
public class BaiduFaceAPI {
//設定APPID/AK/SK
private static final String APP_ID = "你的appid";
private static final String API_KEY = "你的apikey";
private static final String SECRET_KEY = "你的secretkey";
//定義AipFace
private AipFace client;
/**
* 建構函式,例項化AipFace
*/
public BaiduFaceAPI(){
client = new AipFace(APP_ID, API_KEY, SECRET_KEY);
// 可選:設定網路連線引數
client.setConnectionTimeoutInMillis(2000);//建立連線的超時時間
client.setSocketTimeoutInMillis(60000);//通過開啟的連線傳輸資料的超時時間(單位:毫秒)
// 可選:設定代理伺服器地址, http和socket二選一,或者均不設定
//client.setHttpProxy("proxy_host", proxy_port); // 設定http代理
//client.setSocketProxy("proxy_host", proxy_port); // 設定socket代理
}//人臉識別。從人臉庫中查詢相似度最高的1張圖片
public JSONObject identifyUserBybase64(String base64Str){
// 傳入可選引數呼叫介面
HashMap<String, String> options = new HashMap<String, String>();
//options.put("ext_fields", "faceliveness");//判斷活體
options.put("user_top_num", "1");
String groupId = "group1";
byte[] byt = ImageUtil.base64StrToByteArray(base64Str);
return client.identifyUser(groupId, byt, options);
}
}
public static byte[] base64StrToByteArray(String imgStr)
{ //對位元組陣列字串進行Base64解碼並生成圖片
if (imgStr == null) //影象資料為空
return null;
BASE64Decoder decoder = new BASE64Decoder();
try
{
//Base64解碼
byte[] b = decoder.decodeBuffer(imgStr);
for(int i=0;i<b.length;++i)
{
if(b[i]<0)
{//調整異常資料
b[i]+=256;
}
}
return b;
}
catch (Exception e)
{
return null;
}
}
websocket服務端:package com.digitalchina.communication.remote.service;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/websocket")
public class WebsocketServer {
//靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
private static int onlineCount = 0;
//concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。若要實現服務端與單一客戶端通訊的話,可以使用Map來存放,其中Key可以為使用者標識
private static CopyOnWriteArraySet<WebsocketServer> webSocketSet = new CopyOnWriteArraySet<WebsocketServer>();
//與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
private Session session;
/**
* 連線建立成功呼叫的方法
* @param session 可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
*/
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //線上數加
System.out.println("有新連線加入!當前線上人數為" + getOnlineCount());
}
/**
* 連線關閉呼叫的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //線上數減
System.out.println("有一連線關閉!當前線上人數為" + getOnlineCount());
}
/**
* 收到客戶端訊息後呼叫的方法
* @param message 客戶端傳送過來的訊息
* @param session 可選的引數
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的訊息:" + message);
//群發訊息
for(WebsocketServer item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發生錯誤時呼叫
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("發生錯誤");
error.printStackTrace();
}
/**
* 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要新增的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebsocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebsocketServer.onlineCount--;
}
}
大螢幕歡迎頁面jsp:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html >
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>大螢幕</title>
<script src="js/jquery-1.9.1.js"></script>
<script type="text/javascript">
$(function(){
var websocket = null;
//判斷當前瀏覽器是否支援WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8081/BaiduFace/websocket");
} else {
alert('當前瀏覽器不支援websocket!請更換瀏覽器!');
}
//連線發生錯誤的回撥方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連線發生錯誤");
};
//連線成功建立的回撥方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連線成功");
} ;
//接收到訊息的回撥方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//連線關閉的回撥方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連線關閉");
};
//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function () {
closeWebSocket();
};
//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('checkinMsg').innerHTML += innerHTML + '<br/>';
}
//關閉WebSocket連線
function closeWebSocket() {
websocket.close();
}
//傳送訊息
function send(msg) {
websocket.send(msg);
}
});
</script>
</head>
<body>
<div id="checkinMsg"></div>
</body>
</html>
最後發張成果圖,我事先在百度人臉庫傳了一張胡歌的圖片,然後用手機開啟一張胡歌的圖片,讓電腦攝像頭拍攝,抓到了人臉,識別出了這是胡歌。