trackingjs+websocket+百度人臉識(shí)別API實(shí)現(xiàn)人臉簽到
在公司做了個(gè)年會(huì)的簽到、抽獎(jiǎng)系統(tǒng)。用java web做的,用公司的辦公app掃二維碼碼即可簽到,掃完碼就在大屏幕上顯示這個(gè)人的照片。之后領(lǐng)導(dǎo)讓我改得高大上一點(diǎn),用人臉識(shí)別來(lái)簽到,就把掃二維碼的步驟改成人臉識(shí)別。
了解了相關(guān)技術(shù)后,大致思路如下:先用websocket與后臺(tái)建立通訊;用trackingjs在頁(yè)面調(diào)用電腦攝像頭,監(jiān)聽(tīng)人臉,發(fā)現(xiàn)有人臉進(jìn)入屏幕了,就把圖片轉(zhuǎn)成base64字符串,通過(guò)websocket發(fā)送到后端;后端拿到圖片,調(diào)用百度的人臉識(shí)別API,去人臉庫(kù)中匹配(當(dāng)然事先要在百度云建立好了自己的人臉庫(kù)),得到相似度最高的那個(gè)人的信息,簽到表中紀(jì)錄這個(gè)人,然后把這個(gè)人在人臉庫(kù)中的姓名、照片等信息返回給前端顯示。流程圖如圖所示。

中間隔了幾天,實(shí)際嘗試后,發(fā)現(xiàn)上面的思路有問(wèn)題,websocket傳輸?shù)臄?shù)據(jù)大小最大為8KB,超出就自動(dòng)與后臺(tái)斷開(kāi)了,沒(méi)法傳圖片。
所以又改變了一下,直接上流程圖。其實(shí)就是把圖片改為用ajax傳給controller

下面給出代碼
拍攝頁(yè)面trackingjs.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>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;//向后臺(tái)發(fā)照片的冷卻時(shí)間
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");
//講拍照的圖片數(shù)據(jù)發(fā)送到controller,調(diào)用百度云,簽到,返回簽到結(jié)果
$.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;
//判斷當(dāng)前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8081/BaiduFace/websocket");
} else {
alert('當(dāng)前瀏覽器不支持websocket!請(qǐng)更換瀏覽器!');
}
//連接發(fā)生錯(cuò)誤的回調(diào)方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發(fā)生錯(cuò)誤");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
} ;
//接收到消息的回調(diào)方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關(guān)閉");
};
//監(jiān)聽(tīng)窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒(méi)斷開(kāi)就關(guān)閉窗口,server端會(huì)拋異常。
window.onbeforeunload = function () {
closeWebSocket();
};
//將消息顯示在網(wǎng)頁(yè)上
function setMessageInnerHTML(innerHTML) {
document.getElementById('checkinMsg').innerHTML += innerHTML + '<br/>';
}
//關(guān)閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發(fā)送消息
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:
@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);//返回百度云的計(jì)算結(jié)果
System.out.println(obj.toString());
Map<String, Object> resultMap = new HashMap<String, Object>();
int result_num = obj.getInt("result_num");//人臉個(gè)數(shù)
if(result_num == 1){
JSONObject result0 = obj.getJSONArray("result").getJSONObject(0);
resultMap.put("result", "true");
double score = result0.getJSONArray("scores").getDouble(0);//與人臉庫(kù)中最相似的人臉的相似度
if(score>=85){//暫且設(shè)為如果大于85則可以認(rèn)為是同一個(gè)人
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();
}
controller 中,BaiduFaceAPI類(lèi)中的 identifyUserBybase64()方法,以及base64字符串轉(zhuǎn)byte[]的方法。
百度云人臉識(shí)別文檔地址:點(diǎn)擊打開(kāi)鏈接
public class BaiduFaceAPI {
//設(shè)置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;
/**
* 構(gòu)造函數(shù),實(shí)例化AipFace
*/
public BaiduFaceAPI(){
client = new AipFace(APP_ID, API_KEY, SECRET_KEY);
// 可選:設(shè)置網(wǎng)絡(luò)連接參數(shù)
client.setConnectionTimeoutInMillis(2000);//建立連接的超時(shí)時(shí)間
client.setSocketTimeoutInMillis(60000);//通過(guò)打開(kāi)的連接傳輸數(shù)據(jù)的超時(shí)時(shí)間(單位:毫秒)
// 可選:設(shè)置代理服務(wù)器地址, http和socket二選一,或者均不設(shè)置
//client.setHttpProxy("proxy_host", proxy_port); // 設(shè)置http代理
//client.setSocketProxy("proxy_host", proxy_port); // 設(shè)置socket代理
}//人臉識(shí)別。從人臉庫(kù)中查找相似度最高的1張圖片
public JSONObject identifyUserBybase64(String base64Str){
// 傳入可選參數(shù)調(diào)用接口
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)
{ //對(duì)字節(jié)數(shù)組字符串進(jìn)行Base64解碼并生成圖片
if (imgStr == null) //圖像數(shù)據(jù)為空
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)
{//調(diào)整異常數(shù)據(jù)
b[i]+=256;
}
}
return b;
}
catch (Exception e)
{
return null;
}
} websocket服務(wù)端:
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 {
//靜態(tài)變量,用來(lái)記錄當(dāng)前在線(xiàn)連接數(shù)。應(yīng)該把它設(shè)計(jì)成線(xiàn)程安全的。
private static int onlineCount = 0;
//concurrent包的線(xiàn)程安全Set,用來(lái)存放每個(gè)客戶(hù)端對(duì)應(yīng)的MyWebSocket對(duì)象。若要實(shí)現(xiàn)服務(wù)端與單一客戶(hù)端通信的話(huà),可以使用Map來(lái)存放,其中Key可以為用戶(hù)標(biāo)識(shí)
private static CopyOnWriteArraySet<WebsocketServer> webSocketSet = new CopyOnWriteArraySet<WebsocketServer>();
//與某個(gè)客戶(hù)端的連接會(huì)話(huà),需要通過(guò)它來(lái)給客戶(hù)端發(fā)送數(shù)據(jù)
private Session session;
/**
* 連接建立成功調(diào)用的方法
* @param session 可選的參數(shù)。session為與某個(gè)客戶(hù)端的連接會(huì)話(huà),需要通過(guò)它來(lái)給客戶(hù)端發(fā)送數(shù)據(jù)
*/
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在線(xiàn)數(shù)加
System.out.println("有新連接加入!當(dāng)前在線(xiàn)人數(shù)為" + getOnlineCount());
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //在線(xiàn)數(shù)減
System.out.println("有一連接關(guān)閉!當(dāng)前在線(xiàn)人數(shù)為" + getOnlineCount());
}
/**
* 收到客戶(hù)端消息后調(diào)用的方法
* @param message 客戶(hù)端發(fā)送過(guò)來(lái)的消息
* @param session 可選的參數(shù)
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來(lái)自客戶(hù)端的消息:" + message);
//群發(fā)消息
for(WebsocketServer item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發(fā)生錯(cuò)誤時(shí)調(diào)用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("發(fā)生錯(cuò)誤");
error.printStackTrace();
}
/**
* 這個(gè)方法與上面幾個(gè)方法不一樣。沒(méi)有用注解,是根據(jù)自己需要添加的方法。
* @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--;
}
}
大屏幕歡迎頁(yè)面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;
//判斷當(dāng)前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8081/BaiduFace/websocket");
} else {
alert('當(dāng)前瀏覽器不支持websocket!請(qǐng)更換瀏覽器!');
}
//連接發(fā)生錯(cuò)誤的回調(diào)方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發(fā)生錯(cuò)誤");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
} ;
//接收到消息的回調(diào)方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
};
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關(guān)閉");
};
//監(jiān)聽(tīng)窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接,防止連接還沒(méi)斷開(kāi)就關(guān)閉窗口,server端會(huì)拋異常。
window.onbeforeunload = function () {
closeWebSocket();
};
//將消息顯示在網(wǎng)頁(yè)上
function setMessageInnerHTML(innerHTML) {
document.getElementById('checkinMsg').innerHTML += innerHTML + '<br/>';
}
//關(guān)閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發(fā)送消息
function send(msg) {
websocket.send(msg);
}
});
</script>
</head>
<body>
<div id="checkinMsg"></div>
</body>
</html>我事先在百度人臉庫(kù)傳了一張圖片,然后用手機(jī)打開(kāi)一張,讓電腦攝像頭拍攝,抓到了人臉,識(shí)別出了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js判斷在哪個(gè)瀏覽器打開(kāi)項(xiàng)目的方法
這篇文章主要介紹了js判斷在哪個(gè)瀏覽器打開(kāi)項(xiàng)目的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
網(wǎng)頁(yè)整體變灰白色(兼容各瀏覽器)實(shí)例
網(wǎng)頁(yè)整體變灰白色(兼容各瀏覽器)實(shí)例,需要的朋友可以參考一下2013-04-04
再談javascript常見(jiàn)錯(cuò)誤及解決方法
下面小編就為大家?guī)?lái)一篇再談javascript常見(jiàn)錯(cuò)誤及解決方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09
javascript數(shù)據(jù)結(jié)構(gòu)之串的概念與用法分析
這篇文章主要介紹了javascript數(shù)據(jù)結(jié)構(gòu)之串的概念與用法,簡(jiǎn)單講述了串的概念、功能并結(jié)合實(shí)例形式分析了基于javascript實(shí)現(xiàn)串的遍歷、比較、查找等相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
js實(shí)現(xiàn)圖片放大并跟隨鼠標(biāo)移動(dòng)特效
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)圖片放大并跟隨鼠標(biāo)移動(dòng)特效,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
IE8利用自帶的setCapture和releaseCapture解決iframe的拖拽事件方法
最近有個(gè)需求須要實(shí)現(xiàn)左右拖拽功能,頁(yè)面右邊是個(gè)iframe頁(yè)面,在chrome測(cè)試通過(guò)之后,發(fā)現(xiàn)在ie8上面效果不是很理想,查閱相關(guān)資料找到可以使用ie自帶的setCapture和releaseCapture來(lái)解決,需要的朋友可以參考下2016-10-10
layui 圖片上傳+表單提交+ Spring MVC的實(shí)例
今天小編就為大家分享一篇layui 圖片上傳+表單提交+ Spring MVC的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
webpack4之SplitChunksPlugin使用指南
這篇文章主要介紹了webpack4之SplitChunksPlugin使用指南,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

