前言:
一開始找人臉識別的第三方接口,選擇了百度,就是發請求給百度的接口,解析人家返回的數據。
但是這樣的話,如果沒有網絡,或者沒有外網。程序在局域網中使用的時候,自然就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
如果本文幫助到了你,別忘了點贊加關注哦
你點的每個贊,我都認真當成了喜歡