赞
踩
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包即可
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
实现注意:
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工号。
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; }
比较简单的实现,复制过去就可以用,需要注意 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 即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。