使用虹軟SDK實現離線人臉註冊,人臉登錄(H5-JS前端,java後臺)

前言:

一開始找人臉識別的第三方接口,選擇了百度,就是發請求給百度的接口,解析人家返回的數據。

但是這樣的話,如果沒有網絡,或者沒有外網。程序在局域網中使用的時候,自然就gg了。

人臉識別嘛,大家瞭解的最多的就是現在手機自帶的人臉識別,這個肯定不會說在你沒有網絡的情況下用不了。

然後就找離線人臉識別吧,就發現虹軟這個可以把算法下載到本地……

第一步,獲取SDK

首先註冊開發者賬號,創建一個應用,得到兩個東西,用於激活SDK引擎的,SDK key 和 AppID。

百度搜索“虹軟”,進入官網,點擊右上角開放平臺下的人臉識別SDK,如圖:

然後進入如下頁面點擊右上角開發者中心

新建應用,獲取SDK之前,還有不可或缺的一步……實名認證

提交完資料一般兩個小時之內就有結果了。

-

創建完應用點擊這個下載按鈕把SDK下載到本地,我這裏選擇的是java版本的,我也不會其他的……

 下載完後解壓,就是這樣一個文件夾

文件夾中的doc文件夾中的一個pdf文檔,就是開發者文檔了。

文件夾中包括開發者文檔、API文檔、示例程序、jar包、引擎庫dll文件。

文檔中有對這個文件夾結構的詳細介紹,這裏就不重複了。

 

第二步,寫後臺

 

springboot項目,maven管理,項目結構:

把文件夾中的jar包複製到項目中的lib(自己創建的)文件夾下,

依賴這樣寫:

----我這裏用的是SQLserver

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.3.RELEASE</version>
</parent>
	
<dependencies>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>3.8.1</version>
		<scope>test</scope>
	</dependency>
		
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
         <groupId>org.mybatis.spring.boot</groupId>
         <artifactId>mybatis-spring-boot-starter</artifactId>
         <version>1.3.5</version>
     </dependency>
        
     <dependency>
		<groupId>com.microsoft.sqlserver</groupId>
		<artifactId>sqljdbc4</artifactId>
		<version>4.0</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>
		
	<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.5</version>
    </dependency>

	<dependency>
		<groupId>com.arcsoft.face</groupId>
		<artifactId>arcsoft-sdk-face</artifactId>
		<version>2.2.0.1</version>
		<scope>system</scope>
		<systemPath>${basedir}/lib/arcsoft-sdk-face-2.2.0.1.jar</systemPath>
	</dependency>

</dependencies>
<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<configuration>
				<includeSystemScope>true</includeSystemScope>
			</configuration>
		</plugin>
	</plugins>
	<finalName>face</finalName>
</build>

還要創建一個temp文件夾存放臨時生成的圖片文件。

啓動類就不貼了,下面這個是引擎激活類:

我放在了啓動包下,這個類要單獨運行一遍,用來激活在這個設備的引擎,如果換了設備還要再運行激活一遍

/**
 * 設備引擎激活
* @author zsz
* @version 
* @創建時間:2019年10月29日 下午2:51:24
*/
public class FaceActivation{

	public static void main(String[] args) {
        //填自己的AppID和SDKkey
		String appId = "";
        String sdkKey = "";
        
        //dll文件所在目錄
        FaceEngine faceEngine = new FaceEngine("D:\\人臉識別\\ArcSoft_ArcFace_Java_Windows_x64_V2.2\\libs\\WIN64");
        //激活引擎
        int activeCode = faceEngine.activeOnline(appId, sdkKey);
        

        if (activeCode != ErrorInfo.MOK.getValue() && activeCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
            System.out.println("引擎激活失敗");
        }else {
        	System.out.println("引擎激活成功");
        }
	}
}

Utils包裏面是一個base64跟圖片互轉的工具類,反正不是自己寫的……

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class Base64Utils {
	    /**
	     * 圖片轉化成base64字符串
	     * @param imgPath
	     * @return
	     */
	    public static String GetImageStr(String imgPath) {// 將圖片文件轉化爲字節數組字符串,並對其進行Base64編碼處理
	        String imgFile = imgPath;// 待處理的圖片
	        InputStream in = null;
	        byte[] data = null;
	        String encode = null; // 返回Base64編碼過的字節數組字符串
	        // 對字節數組Base64編碼
	        BASE64Encoder encoder = new BASE64Encoder();
	        try {
	            // 讀取圖片字節數組
	            in = new FileInputStream(imgFile);
	            data = new byte[in.available()];
	            in.read(data);
	            encode = encoder.encode(data);
	        } catch (IOException e) {
	            e.printStackTrace();
	        } finally {
	            try {
	                in.close();
	            } catch (IOException e) {
	                // TODO Auto-generated catch block
	                e.printStackTrace();
	            }
	        }
	        return encode;
	    }

	    /**
	     * base64字符串轉化成圖片
	     * 
	     * @param imgData
	     *            圖片編碼
	     * @param imgFilePath
	     *            存放到本地路徑
	     * @return
	     * @throws IOException
	     */
	    @SuppressWarnings("finally")
	    public static boolean GenerateImage(String imgData, String imgFilePath) throws IOException { // 對字節數組字符串進行Base64解碼並生成圖片
	        if (imgData == null) // 圖像數據爲空
	            return false;
	        BASE64Decoder decoder = new BASE64Decoder();
	        OutputStream out = null;
	        try {
	            out = new FileOutputStream(imgFilePath);
	            // Base64解碼
	            byte[] b = decoder.decodeBuffer(imgData);
	            for (int i = 0; i < b.length; ++i) {
	                if (b[i] < 0) {// 調整異常數據
	                    b[i] += 256;
	                }
	            }
	            out.write(b);
	        } catch (FileNotFoundException e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        } catch (IOException e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        } finally {
	            out.flush();
	            out.close();
	            return true;
	        }
	    }
	}

實體類包pojo,一個User類,跟數據庫的表是對應的,

在SQLserver中的表設計是這樣的

public class User {

	//用戶名
	private String username;
	//用戶人臉特徵
	private byte[] extract;
	
	public User(String username, byte[] extract) {
		super();
		this.username = username;
		this.extract = extract;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public byte[] getExtract() {
		return extract;
	}
	public void setExtract(byte[] extract) {
		this.extract = extract;
	}
	
	
}

face包裏面有兩個類,一個獲取人臉特徵的,一個人臉特徵對比的。

獲取人臉特徵:

public class MyFaceFeature {

	public byte[] extract(File file,FaceEngine faceEngine) {
		 
		//人臉檢測
        ImageInfo imageInfo = getRGBData(file);
        List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
        int detectCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), ImageFormat.CP_PAF_BGR24, faceInfoList);
        if(faceInfoList == null || faceInfoList.size()==0) {
        	return null;
        }
        
        //人臉屬性檢測
        FunctionConfiguration configuration = new FunctionConfiguration();
        configuration.setSupportAge(true);
        configuration.setSupportFace3dAngle(true);
        configuration.setSupportGender(true);
        configuration.setSupportLiveness(true);
        int processCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), ImageFormat.CP_PAF_BGR24, faceInfoList, configuration);
        //3D信息檢測
        List<Face3DAngle> face3DAngleList = new ArrayList<Face3DAngle>();
        int face3dCode = faceEngine.getFace3DAngle(face3DAngleList);
        if(face3DAngleList == null || face3DAngleList.size() == 0) {
        	return null;
        }
        System.out.println("3D角度:" + face3DAngleList.get(0).getPitch() + "," + face3DAngleList.get(0).getRoll() + "," + face3DAngleList.get(0).getYaw());

        //活體檢測
        List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
        int livenessCode = faceEngine.getLiveness(livenessInfoList);
        System.out.println("活體:" + livenessInfoList.get(0).getLiveness());

        //只有等於1纔是活體,-1是沒有人臉,0是非活體(照片之類的),2是多張人臉
        if(livenessInfoList.get(0).getLiveness()!=1) {
        	return null;
        }

        //特徵提取
        FaceFeature faceFeature = new FaceFeature();
        int extractCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), ImageFormat.CP_PAF_BGR24, faceInfoList.get(0), faceFeature);
		
        //返回特徵值
		return faceFeature.getFeatureData();
	}
}

人臉特徵對比:

public class FaceContrast {

	public float contrast(byte[] face1,byte[] face2,FaceEngine faceEngine) {
		 //特徵比對
        FaceFeature targetFaceFeature = new FaceFeature();
        targetFaceFeature.setFeatureData(face1);
        FaceFeature sourceFaceFeature = new FaceFeature();
        sourceFaceFeature.setFeatureData(face2);
        FaceSimilar faceSimilar = new FaceSimilar();
        int compareCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
        //返回相似度
        return faceSimilar.getScore();
	}
}

到這裏就剩下controller層service層和dao層了。

controller層:

一個註冊一個登錄

@RestController
@RequestMapping("face")
@CrossOrigin
public class FaceController {
	FaceEngine faceEngine = new FaceEngine();
	{
		// 引擎配置
		EngineConfiguration engineConfiguration = new EngineConfiguration();
		
		//檢測模式 圖片/視頻,這裏選擇的是圖片模式
		engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
		
		//人臉檢測角度
		engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);

		// 功能配置
		FunctionConfiguration functionConfiguration = new FunctionConfiguration();
		//是否支持年齡檢測
		functionConfiguration.setSupportAge(true);
		//是否支持3D人臉檢測
		functionConfiguration.setSupportFace3dAngle(true);
		//是否支持人臉檢測
		functionConfiguration.setSupportFaceDetect(true);
		//是否支持人臉識別
		functionConfiguration.setSupportFaceRecognition(true);
		//是否支持性別檢測
		functionConfiguration.setSupportGender(true);
		//是否支持RGB活體檢測
		functionConfiguration.setSupportLiveness(true);
		//是否支持RGB活體檢測
		functionConfiguration.setSupportIRLiveness(true);
		
		//設置引擎功能配置
		engineConfiguration.setFunctionConfiguration(functionConfiguration);

		// 初始化引擎
		int initCode = faceEngine.init(engineConfiguration);

		if (initCode != ErrorInfo.MOK.getValue()) {
			System.out.println("初始化引擎失敗");
		}
	}
	
	@Autowired
	private FaceService service;

	@RequestMapping("add")
	public String faceAdd(String user_id, String base) {
		// 去掉base64編碼中的圖片頭信息
		String base64 = base.split(",")[1];
		try {
			//base64轉圖片
			Base64Utils.GenerateImage(base64, "./temp/temp.jpg");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//獲取人臉特徵
		byte[] extract = new MyFaceFeature().extract(new File("./temp/"+user_id+"temp.jpg"), faceEngine);
		if(extract == null) {
			return "{\"msg\":\"檢測人臉失敗\"}";
		}
		
		//把人臉特徵報錯到數據庫
		return service.faceAdd(new User(user_id,extract))?"{\"msg\":\"註冊成功\"}":"{\"msg\":\"註冊失敗\"}";
	}

	
	@RequestMapping("login")
	public String login(String base) {
		// 去掉base64編碼中的圖片頭信息
		String base64 = base.split(",")[1];
		try {
			Base64Utils.GenerateImage(base64, "./temp/temp.jpg");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//獲取當前人臉特徵
		byte[] extract = new MyFaceFeature().extract(new File("./temp/temp.jpg"), faceEngine);
		if(extract==null) {
			return null;
		}
		//查詢所有人臉 一一對比
		////人臉對比對象
		FaceContrast faceContrast = new FaceContrast();
		////獲取所有註冊過的用戶
		List<User> userList = service.getAllUserFace();
		////循環對比
		for(User user : userList) {
			//如果大於0.8就表示是同一人
			if(faceContrast.contrast(extract, user.getExtract(), faceEngine)>0.8) {
				System.out.println("成功");
				return "{\"user_id\":\""+user.getUsername()+"\"}";
			}
		}
		System.out.println("失敗");
		return null;
	}
}

server包 ,service層:

沒有業務,直接傳給下一層

@Service
public class FaceService {
	
	@Autowired
	private FaceMapper mapper;

	//註冊人臉
	public Boolean faceAdd(User user) {
		return mapper.add(user);
	}
	//查找人臉
	public List<User> getAllUserFace() {
		return mapper.getAllUser();
	}
}

mapper包,dao層

@Mapper
public interface FaceMapper {

	@Insert("insert into user_face values(#{username},#{extract})")
	Boolean add(User user);

	@Select("select * from user_face")
	List<User> getAllUser();
}

後臺就是這些,註釋寫的比較詳細了

在啓動項目的時候會報錯,no libarcsoft_face_engine_jni in java.library.path

見另一個博客:https://blog.csdn.net/qq_41890624/article/details/102812953

接下來就是前端

一個index頁面和,兩個按鈕,跳轉到註冊和登錄

頁面:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>主頁</title>
	</head>
	<body>
		<button id="register" style="width: 700px;height: 100px;">註冊</button>
		<button id="login" style="width: 700px;height: 100px;">登錄</button>
	</body>
	<script src="js/index.js"></script>
</html>

 

JS:

document.getElementById("register").onclick = function(){
	window.location.href = "register.html";
}

document.getElementById("login").onclick = function(){
	window.location.href = "login.html";
}

 註冊頁面register

頁面:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>人臉註冊</title>
	</head>
	<body>
		<video id="video" src="" autoplay="autoplay" loop="loop" muted="muted"></video>
		<canvas id="canvas" style="border:1px solid #d3d3d3;"></canvas>
		<br />
		<input type="text" id="user_id" placeholder="請輸入賬號" style="width: 700px; height: 100px; font-size: 40px;"/>
		<div id="support"></div>
		<button id="ok" style="width: 700px;height: 100px; font-size: 40px;">確認</button>
		<div id="msg" style="font-size: 50px;"></div>
	</body>
	<script src="js/register.js"></script>
	<script src="js/jquery-1.8.2.min.js"></script>
</html>

JS:

//判斷瀏覽器是否支持HTML5 Canvas
window.onload = function() {
	try {
		//動態創建一個canvas元 ,並獲取他2Dcontext。如果出現異常則表示不支持 
		document.createElement("canvas").getContext("2d");
	} catch(e) {
		document.getElementByIdx("support").innerHTML = "瀏覽器不支持HTML5 CANVAS";
	}
}

//這段代 主要是獲取攝像頭的視頻流並顯示在Video 籤中
window.addEventListener("DOMContentLoaded", function() {
	var canvas = document.getElementById("canvas"),
		context = canvas.getContext("2d"),
		img = document.getElementById("img"),
		video = document.getElementById("video"),
		videoObj = {
			"video": true
		},
		errBack = function(error) {
			console.log("Video capture error: ", error.code);
			alert("不支持");
		};

	canvas.width = 450;
	canvas.height = 600;

	var button = document.getElementById("ok");
	button.onclick = function(){
		context.drawImage(video, 0, 0, 450, 600);
		// 把畫布的內容轉換爲base64編碼格式的圖片
	    var data = canvas.toDataURL( 'image/png', 1 );  //1表示質量(無損壓縮)
		var user_id = document.getElementById("user_id").value;
		document.getElementById("msg").innerText = "註冊中……";
	    
	    $.ajax({
            url: 'http://172.16.2.207:8080/face/add',
            cache:false,
            type: 'POST',
            data: {
				base : data,
				user_id : user_id
	    	},
            dataType: 'json',
            success : function(rs){
				document.getElementById("msg").innerText = rs.msg;
            },
            error: function(e){
            	console.log(e);
            }
            
        });
	}
	
	//請求攝像頭權限
	navigator.mediaDevices.getUserMedia(videoObj)
		.then(function(stream){
			//成功回調函數,把流給video標籤
			video.srcObject = stream;
			video.play();
		})
		.catch(errBack);

}, false);

登錄頁面login

頁面:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<video id="video" src="" autoplay="autoplay" loop="loop" muted="muted"></video>
		<canvas id="canvas" style="border:1px solid #d3d3d3;"></canvas>
		<div id="support"></div>
		<div id="msg" style="font-size: 50px;"></div>
	</body>
	<script type="text/javascript" src="js/login.js" ></script>
		<script src="js/jquery-1.8.2.min.js"></script>
</html>

JS:

//判斷瀏覽器是否支持HTML5 Canvas
window.onload = function() {
	try {
		//動態創建一個canvas元 ,並獲取他2Dcontext。如果出現異常則表示不支持 
		document.createElement("canvas").getContext("2d");
	} catch(e) {
		document.getElementByIdx("support").innerHTML = "瀏覽器不支持HTML5 CANVAS";
	}
}


//這段代 主要是獲取攝像頭的視頻流並顯示在Video 籤中
window.addEventListener("DOMContentLoaded", function() {
	var canvas = document.getElementById("canvas"),
		context = canvas.getContext("2d"),
		video = document.getElementById("video"),
		videoObj = {
			"video": true
		},
		errBack = function(error) {
			console.log("Video capture error: ", error.code);
			alert("不支持");
		};

	canvas.width = 450;
	canvas.height = 600;
	
	//請求攝像頭權限
	this.navigator.mediaDevices.getUserMedia(videoObj)
	.then(function(stream){
		video.srcObject = stream;
		video.play();
		//拍照按鈕,每0.5秒canvas從video中畫到畫布,轉成base64發給後臺
		var login = setInterval(function() {
			context.drawImage(video, 0, 0, 450, 600);
			// 把畫布的內容轉換爲base64編碼格式的圖片
		    var data = canvas.toDataURL( 'image/png', 1 );  //1表示質量(無損壓縮)
		    
			$.ajax({
	            url: 'http://172.16.2.207:8080/face/login',
	            cache:false,
	            type: 'POST',
	            data: {
					base: data
		    	},
	            dataType: 'json',
	            success : function(rs){
	            	if(rs!=null){
            			document.getElementById("msg").innerText = "用戶"+rs.user_id+"登錄成功";
            			//隱藏控件
            			$("#video").hide();
  						$("#canvas").hide();
						
						//停止定時發送
            			clearInterval(login);
	            	}
	            },
	            error: function(e){
	            }
	        });
			
		}, 500);
	})
	.catch(errBack);
	

}, false);

前端就是這些了,註釋也加的很詳細了。

後臺代碼碼雲地址:https://gitee.com/vhukze/face.git

 

如果本文幫助到了你,別忘了點贊加關注哦

你點的每個贊,我都認真當成了喜歡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章