百度人脸SDK离线版
前言
1、下载百度人脸识别SDK离线版。
2、开发工具:IntelliJ IDEA
百度人脸识别官网:https://cloud.baidu.com/doc/FACE/s/Ol0rre5u5
注意:最后面有基于SDK的简单人脸库实现,可添加修改人脸库,且可以分析比对分数
步骤
一、基本测试
1、解压下载的百度人脸识别SDK包(x64内带教程文档)。
2、IDEA中直接打开FaceOfflineSdk
3、然后引入当前项目下的opencv-jar目录下的jar包
-
引入点这里,idea右上角
-
跟着操作即可,然后引入当前项目下的opencv-jar目录下的jar包
-
一般引入后应用,在modules那边的dependencies就可以看到。
-
打开这个工具,然后将百度申请的16位激活码填充,点击激活后会生成license文件夹,然后复制这个文件夹替换对应的license文件。
-
再到idea找到com.jni.face.Face.java然后执行main方法就可以开始测试了,这个类中的方法都有注释,根据需求调用就好了。
二、整合项目
创建一个项目直接FaceOfflineSdk目录下的几个文件夹复制到新项目中
同上面一样引入opencv-320.jar包即可
三、打包,及打包后遇到的问题
打包/后
- 打包后发布将项目下的所有dll文件放到 C:\windows\system32 或 C:\Program Files\Java\jre1.8.0_181\bin 目录下(这个目录下可以找 face_sdk.log 查看日志)。
- 打包后需要自己在代码上动态的将16位激活码替换license下的license.key文件,联网情况下自动激活的。
- 将license文件夹和models文件夹放到一个新的文件夹中,然后将这个新的文件夹路径替换下面代码中的那个模型路径。
Face api = new Face();
// model_path为模型文件夹路径,即models文件夹(里面存的是人脸识别的模型文件)
String modelPath = ""; // D:\\FaceOfflineSdk\\
int res = api.sdkInit(modelPath);
if (res != 0) {
System.out.printf("sdk init fail and error =%d\n", res);
return;
}
// sdk销毁,释放内存防内存泄漏
api.sdkDestroy();
打包遇到的问题:
-
打包后Windows下一直报 -4 错误(一直找不到模型)。
解决:需要使用双斜杠,单斜杠虽然通用但是Windows下无效。(找了一天问题差点气嘎了,后面问百度技术才解决的)
-
打包war后报 java.lang.NoClassDefFoundError: org/opencv/imgcodecs/Imgcodecs 错误。
解决:操作pom.xml,打包后没有将opencv-320.jar生成到lib下,需要改成如下
<build>
<finalName>face-analysis-service</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<!-- 打包war防止上面引入的本地jar添加到 lib-provided 文件夹中而读取不了-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>${project.basedir}/opencv-jar</directory>
<targetPath>WEB-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
以上即可!
其他博客参考:https://www.jianshu.com/p/f4e6de80e72a
基于百度SDK重写了人脸库用来分析比对
实现注意:
1、更改com.jni.struct.Feature对象,将这个对象加入实现序列化(因为要存到文件中)。
2、实现比较简单,利用Face.java下的几个方法实现如下逻辑
3、我这里使用了文件形式存储,也可写成数据库存储,根据需求分组查询就好;每个特征值是 512字节(0.5kb)比较小,所以存数据库也是可以的
准备一个ArrayList<Map<String, Feature>> featureList;
一、人脸校验是否是没有人脸,或者是否多人脸,如果是则返回错误;如果就一个人脸就正常进入下一步(方法:detect)。
二、校验featureList中是否存在这个人脸,如果没有则添加,有就修改。
三、将照片转成特征值(方法:faceFeature),然后添加/修改成本地文件,并添加/修改到featureList。
四、如果是删除人员照片则,删除featureList中特征值,再删除本地文件。
五、分析校验:将当前通行照片转成特征值,然后循环featureList拿出特征值比较(比较方法:compareFeature)并然后float的分数和map的key工号。
com.jni.struct.Feature对象修改如下
新建一个类,这里定义FaceEntity.java
package com.common.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 人脸识别分数
*
* @author yyq
*/
@Data
public class FaceEntity implements Serializable {
/**
* ID
*/
private String id;
/**
* 人脸识别分数
*/
private float score;
}
然后就是实现了,创建FaceUtil.java类
比较简单的实现,复制过去就可以用,需要注意 ImageUtil.NGINXPATH 等于 D://face_file//,把它改成自己的路径就可以了
package com.common.util;
import com.common.entity.FaceEntity;
import com.jni.face.Face;
import com.jni.face.TimeUtil;
import com.jni.struct.FaceBox;
import com.jni.struct.Feature;
import com.jni.struct.FeatureInfo;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* 人脸识别自定义分析库
*
* @author yyq
* @date 2023-04-01
*/
public class FaceUtil {
/**
* 人脸库
*/
public static ArrayList<Map<String, Feature>> list = new ArrayList<>();
/**
* 人脸特征值文件存储路径
*/
public static String filePath = "/upload/face_datebase/";
/**
* 人脸库加载状态:true加载完毕
*/
public static boolean load_state = false;
/**
* 检测到人脸,是否只有一张
* 0没有人脸 1有多个人脸 2只有一个人脸
*
* @return
*/
public static int detect(long matAddr, int type) {
FaceBox[] infos = Face.detect(matAddr, type);// 检测人脸
if (infos == null) {
return 0;
} else if (infos.length > 1) {
return 1;
} else if (infos.length == 1) {
return 2;
}
return 0;
}
/**
* 获取人脸特征值
*
* @param matAddr
* @param type
* @return
*/
public static Feature getFeature(long matAddr, int type) {
FeatureInfo[] fea1List = Face.faceFeature(matAddr, type);
if (fea1List != null && fea1List.length > 0) {
return fea1List[0].feature;
}
return null;
}
/**
* 添加到人脸库
*
* @param id
* @param feature
* @return
*/
public static boolean addUser(String id, Feature feature, boolean file) {
try {
Map<String, Feature> featureMap = new HashMap<>();
featureMap.put(id, feature);
FaceUtil.list.add(featureMap);
if (file) {// 是否存储文件
writeObject(ImageUtil.NGINXPATH + FaceUtil.filePath, id, feature);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 修改人脸库指定下标的人脸
*
* @param id
* @param feature
* @return
*/
public static boolean updateUser(int index, String id, Feature feature) {
try {
// 修改人脸库list
FaceUtil.list.get(index).put(id, feature);
// 重新写入
writeObject(ImageUtil.NGINXPATH + FaceUtil.filePath, id, feature);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 添加/修改
*
* @param id
* @param feature
* @return -1添加失败 -2修改失败 0 操作失败 1添加成功 2修改成功
*/
public static int addOrUpdateUser(String id, Feature feature) {
try {
int index = exists(id);
if (index != -1) {
boolean b = updateUser(index, id, feature);
return b ? 2 : -2;
} else {
boolean b = addUser(id, feature, true);
return b ? 1 : -1;
}
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 删除人脸库的人脸
*
* @param id
* @return
*/
public static boolean deleteUser(String id) {
try {
for (int i = 0; i < FaceUtil.list.size(); i++) {
Map<String, Feature> featureMap = FaceUtil.list.get(i);
if (getKey(featureMap, id)) {
FaceUtil.list.remove(i);
ImageUtil.deleteFile(ImageUtil.NGINXPATH + FaceUtil.filePath + id);
return true;
}
}
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 判断是否存在人脸库
*
* @param id
* @return
*/
public static int exists(String id) {
try {
for (int i = 0; i < FaceUtil.list.size(); i++) {
Map<String, Feature> featureMap = FaceUtil.list.get(i);
if (getKey(featureMap, id)) {
return i;
}
}
return -1;
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* 获取判断map中是否有这个key
*
* @param map
* @param key
* @return
*/
public static boolean getKey(Map<String, Feature> map, String key) {
for (Map.Entry<String, Feature> m : map.entrySet()) {
if (m.getKey().equals(key)) {
return true;
}
}
return false;
}
/**
* 比较人脸
*
* @param path
* @return
*/
public static FaceEntity compareFace(String path) {
try {
// 如果库里没有特征值
if (FaceUtil.list.size() <= 0) {
return null;
}
Mat mat = Imgcodecs.imread(path);// 开始将路径图片转成特征值
long matAddr = mat.getNativeObjAddr();
int type = 0;// type 0: 表示rgb生活照特征值,1:表示rgb证件照特征值 2:表示nir近红外特征值
FeatureInfo[] fea1List = Face.faceFeature(matAddr, type);
if (fea1List == null || fea1List.length <= 0) {
return null;
}
// 循环库
FaceEntity faceEntity = new FaceEntity();
float maxScore = 0; // 最大分数
String maxId = null; // 最大分数的ID
for (Map<String, Feature> featureMap : FaceUtil.list) {
for (Map.Entry<String, Feature> m : featureMap.entrySet()) {
Feature feature = m.getValue();
for (FeatureInfo info : fea1List) {
float score = Face.compareFeature(feature, info.feature, type);// 比较特征值
if (score > maxScore) {
maxScore = score;
maxId = m.getKey();
}
}
}
}
faceEntity.setId(maxId);
faceEntity.setScore(maxScore);
return faceEntity;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 初始化人脸库list
*/
public static void initList() {
try {
String path = ImageUtil.NGINXPATH + FaceUtil.filePath;
File file = new File(path);
if (!file.exists()) {
return;
}
File[] files = file.listFiles();
for (File f : files) {
String id = f.getName();
Feature feature = (Feature) readObject(path + id);
addUser(id, feature, false);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 写入文件
*
* @param path
* @param obj
* @throws IOException
*/
public static void writeObject(String path, String fileName, Object obj) throws IOException {
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
File f = new File(path + fileName);
if (f.exists()) {
ImageUtil.deleteFile(path + fileName);
f = new File(path + fileName);
}
FileOutputStream out = new FileOutputStream(f);
ObjectOutputStream objwrite = new ObjectOutputStream(out);
objwrite.writeObject(obj);
objwrite.flush();
objwrite.close();
}
/**
* 读取文件
*
* @param path
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static Object readObject(String path) throws IOException, ClassNotFoundException {
FileInputStream in = new FileInputStream(path);
ObjectInputStream objread = new ObjectInputStream(in);
Object obj = objread.readObject();
objread.close();
return obj;
}
public static void main(String[] args) {
//sdk初始化
Face api = new Face();
// model_path为模型文件夹路径,即models文件夹(里面存的是人脸识别的模型文件)
// 传空为采用默认路径,若想定置化路径,请填写全局路径如:d:\\face (models模型文件夹目录放置后为d:\\face\\models)
// 若模型文件夹采用定置化路径,则激活文件(license.ini, license.key)也可采用定制化路径放置到该目录如d:\\face\\license
// 亦可在激活文件默认生成的路径
String modelPath = ""; // F:\Development-Tool\IDEA-SVN\FaceOfflineSdk\models
int res = api.sdkInit(modelPath);
if (res != 0) {
System.out.printf("sdk init fail and error =%d\n", res);
return;
}
long begin = TimeUtil.getTimeStamp();
/**
*
* 加载人脸库
*
*/
// 初始化人脸库
initList();
System.out.println("---------------------人脸库总数:" + list.size());
/**
*
* 新增图片到人脸库
*
*
*/
// 开始将路径图片转成特征值
Mat mat = Imgcodecs.imread("images/1.jpg");
long matAddr = mat.getNativeObjAddr();
int type = 0;// type 0: 表示rgb生活照特征值,1:表示rgb证件照特征值 2:表示nir近红外特征值
int detect = detect(matAddr, type);
if (detect == 0) {
System.out.println("---------------------没有检测到人脸");
} else if (detect == 1) {
System.out.println("---------------------检测到多张人脸");
} else {
System.out.println("---------------------检测到一张人脸");
Feature feature = getFeature(matAddr, type);
if (feature != null) {
int i = addOrUpdateUser("101", feature);
if (i == 0) {
System.out.println("---------------------【人脸注册异常】");
} else if (i == -1) {
System.out.println("---------------------【人脸添加失败】");
} else if (i == -2) {
System.out.println("---------------------【人脸修改失败】");
} else if (i == 1) {
System.out.println("---------------------【人脸添加成功】");
} else if (i == 2) {
System.out.println("---------------------【人脸修改成功】");
}
} else {
System.out.println("---------------------获取人脸特征值失败");
}
}
long end = TimeUtil.getTimeStamp();
System.out.println("新增耗时 ----------- :" + (end - begin));
begin = TimeUtil.getTimeStamp();
// 图片路径人脸比对
FaceEntity faceEntity = compareFace(ImageUtil.NGINXPATH + "1.jpg"); // ImageUtil.NGINXPATH 等于 D://face_file//
if (faceEntity != null) {
System.out.println("---------------------[人脸比对返回] id:" + faceEntity.getId() + " 分数:" + faceEntity.getScore());
} else {
System.out.println("---------------------[人脸比对-人脸库为空]");
}
end = TimeUtil.getTimeStamp();
System.out.println("比对耗时 ----------- :" + (end - begin));
// sdk销毁,释放内存防内存泄漏
api.sdkDestroy();
}
}
执行人脸注册的时候服务突然关闭/停止问题解决
问题:通过接口注册人脸照片到人脸库的时候,突然服务就停止了!
解决:由于百度算法不支持主线程操作,所以我们写个子线程就好了。
如下
声明子线程处理
package com.common.thread;
import com.common.util.FaceUtil;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import java.util.concurrent.Callable;
public class FaceDetectTask implements Callable<Integer> {
/** 人脸照片地址*/
private String path;
public FaceDetectTask(String path) {
this.path=path;
}
/** 人脸检测校验*/
@Override
public Integer call() throws Exception {
Mat mat = Imgcodecs.imread(path);
if (mat.empty()) {
System.out.println("image not exist or empty");
return 0;
}
long matAddr = mat.getNativeObjAddr();
// type 0: 表示rgb 人脸检测 1:表示nir人脸检测
int type = 0;
return FaceUtil.detect(matAddr, type);// 这个是上面自定义封装的
}
}
调用
// 人脸检测
FaceDetectTask detectTask = new FaceDetectTask(path);// path图片路径
Future<Integer> future = pool.submit(detectTask);
Integer detect = 0;
while (true){// 防止没有执行完返回空值
if (future.isDone()){
detect = future.get();
break;
}
}
if (detect == 0) {
remarks = "照片没有检测到人脸-注册失败";
} else if (detect == 1) {
remarks = "照片检测到多张人脸-注册失败";
} else {
// 逻辑处理
}
其他
定制化说明
检测多人脸
一张照片多人脸检测,默认只会返回一条记录,这个时候就需要定制化了。
1、将SDK目录下的conf文件夹名称改为config;
2、然后api.sdkInit(“F:\SDK”),初始化模型路径需要改成绝对路径,不然不生效,不知道为啥不能用空字符串(也就是当前目录)
3、根据对应使用修改config目录下的json文件的值(参考官方文档“能力定制化说明”)
模型路径指向
把models文件夹丢到F:\SDK,初始化的时候使用:api.sdkInit(“F:\SDK”)
激活指向
把license文件夹丢到F:\SDK,初始化的时候使用:api.sdkInit(“F:\SDK”)
说明
1、将这三个文件包丢到同一个目录下,然后初始化的时候一步加载完成
2、Windows下文件夹路径使用双斜杠,这里只显示单斜杠而已
3、如果觉得项目下太多文件,可以把config、models、license这三个文件夹单独拎出来放到一个文件夹里(如,放到“F:\SDK”下,像上面那样初始化设置路径即可);还有dll文件开发时可以把全部丢到 C:\Program Files\Java\jdk1.8.0_181\bin 下,这样就看起来整洁了许多,dll发布到时候再丢到 C:\Program Files\Java\jre1.8.0_181\bin 即可