1. 程式人生 > >trackingjs+websocket+百度人臉識別API,實現人臉簽到

trackingjs+websocket+百度人臉識別API,實現人臉簽到

在公司做了個年會的簽到、抽獎系統。用java web做的,用公司的辦公app掃二維碼碼即可簽到,掃完碼就在大螢幕上顯示這個人的照片。之後領導讓我改得高大上一點,用人臉識別來簽到,就把掃二維碼的步驟改成人臉識別。

瞭解了相關技術後,大致思路如下:先用websocket與後臺建立通訊;用trackingjs在頁面呼叫電腦攝像頭,監聽人臉,發現有人臉進入螢幕了,就把圖片轉成base64字串,通過websocket傳送到後端;後端拿到圖片,呼叫百度的人臉識別API,去人臉庫中匹配(當然事先要在百度雲建立好了自己的人臉庫),得到相似度最高的那個人的資訊,簽到表中紀錄這個人,然後把這個人在人臉庫中的姓名、照片等資訊返回給前端顯示。流程圖如圖所示。

-------中間隔了幾天,實際嘗試後,發現上面的思路有問題,websocket傳輸的資料大小最大為8KB,超出就自動與後臺斷開了,沒法傳圖片。

所以又改變了一下,直接上流程圖。其實就是把圖片改為用ajax傳給controller

下面給出程式碼

拍攝頁面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;//向後臺發照片的冷卻時間
			
	        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:
	@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();
	}
controller 中,BaiduFaceAPI類中的 identifyUserBybase64()方法,以及base64字串轉byte[]的方法。

百度雲人臉識別文件地址:點選開啟連結

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>
最後發張成果圖,我事先在百度人臉庫傳了一張胡歌的圖片,然後用手機開啟一張胡歌的圖片,讓電腦攝像頭拍攝,抓到了人臉,識別出了這是胡歌。