mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-14 13:48:56 +00:00
集成算法seetaface6
This commit is contained in:
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
<name>smartjavaai-face</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
@@ -25,6 +25,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -34,11 +35,24 @@
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-seetaface6-lib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.36.0.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -51,4 +54,34 @@ public abstract class AbstractFaceAlgorithm implements FaceAlgorithm{
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(String key, String imagePath) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(String key, InputStream inputStream) throws Exception {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceResult search(String imagePath) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceResult search(InputStream inputStream) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeRegister(String... keys) throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clearFace() throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package cn.smartjavaai.face;
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.translate.TranslateException;
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -86,4 +88,46 @@ public interface FaceAlgorithm {
|
||||
*/
|
||||
float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception;
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
* @param key
|
||||
* @param imagePath
|
||||
* @return
|
||||
*/
|
||||
boolean register(String key, String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
* @param key
|
||||
* @param inputStream
|
||||
* @return
|
||||
*/
|
||||
boolean register(String key, InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询人脸
|
||||
* @param imagePath
|
||||
* @return
|
||||
*/
|
||||
FaceResult search(String imagePath) throws Exception;
|
||||
|
||||
/**
|
||||
* 查询人脸
|
||||
* @param inputStream
|
||||
* @return
|
||||
*/
|
||||
FaceResult search(InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除已标记人脸
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
long removeRegister(String... keys) throws Exception;
|
||||
|
||||
/**
|
||||
* 清空人脸库数据
|
||||
*/
|
||||
long clearFace() throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.face.algo.FeatureExtractionAlgo;
|
||||
import cn.smartjavaai.face.algo.RetinaFace;
|
||||
import cn.smartjavaai.face.algo.SeetaFace6Algo;
|
||||
import cn.smartjavaai.face.algo.UltraLightFastGenericFace;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -37,6 +38,7 @@ public class FaceAlgorithmFactory {
|
||||
public static FaceAlgorithm createFaceAlgorithm(ModelConfig config) throws Exception {
|
||||
Class<?> clazz = registry.get(config.getAlgorithmName().toLowerCase());
|
||||
if(clazz == null){
|
||||
System.out.println("No such algorithm: " + config.getAlgorithmName().toLowerCase());
|
||||
throw new IllegalArgumentException("Unsupported algorithm");
|
||||
}
|
||||
FaceAlgorithm algorithm = (FaceAlgorithm) clazz.newInstance();
|
||||
@@ -114,6 +116,7 @@ public class FaceAlgorithmFactory {
|
||||
registerAlgorithm("ultralightfastgenericface", UltraLightFastGenericFace.class);
|
||||
//人脸特征提取
|
||||
registerAlgorithm("featureExtraction", FeatureExtractionAlgo.class);
|
||||
registerAlgorithm("seetaface6", SeetaFace6Algo.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ public class ModelConfig {
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
/**
|
||||
* 人脸库路径
|
||||
*/
|
||||
private String faceDbPath;
|
||||
|
||||
|
||||
public String getAlgorithmName() {
|
||||
return algorithmName;
|
||||
@@ -72,4 +77,12 @@ public class ModelConfig {
|
||||
public void setModelPath(String modelPath) {
|
||||
this.modelPath = modelPath;
|
||||
}
|
||||
|
||||
public String getFaceDbPath() {
|
||||
return faceDbPath;
|
||||
}
|
||||
|
||||
public void setFaceDbPath(String faceDbPath) {
|
||||
this.faceDbPath = faceDbPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* RetinaFace实现
|
||||
* @author dwj
|
||||
*/
|
||||
public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
@@ -51,7 +50,7 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
* 加载人脸特征提取模型
|
||||
* @param config
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public void loadFaceFeatureModel(ModelConfig config) throws Exception {
|
||||
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
|
||||
@@ -71,12 +70,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征提取
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) throws Exception {
|
||||
Path imageFile = Paths.get(imagePath);
|
||||
@@ -85,12 +84,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return predictor.predict(img);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征提取
|
||||
* @param inputStream 输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) throws Exception {
|
||||
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
|
||||
@@ -98,13 +97,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return predictor.predict(img);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 计算相似度
|
||||
* @param feature1 图1特征
|
||||
* @param feature2 图2特征
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
|
||||
float ret = 0.0f;
|
||||
@@ -119,13 +118,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征比较
|
||||
* @param imagePath1 图1路径
|
||||
* @param imagePath2 图2路径
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
|
||||
float[] feature1 = featureExtraction(imagePath1);
|
||||
@@ -133,19 +132,19 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
|
||||
/**
|
||||
*//**
|
||||
* 特征比较
|
||||
* @param inputStream1 图1输入流
|
||||
* @param inputStream2 图2输入流
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
*//*
|
||||
@Override
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
float[] feature1 = featureExtraction(inputStream1);
|
||||
float[] feature2 = featureExtraction(inputStream2);
|
||||
return calculSimilar(feature1, feature2);
|
||||
}
|
||||
}*/
|
||||
|
||||
/*@Override
|
||||
public float[] recognize(FaceRegion region) {
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
package cn.smartjavaai.face.algo;
|
||||
|
||||
import cn.smartjavaai.common.entity.Point;
|
||||
import cn.smartjavaai.common.entity.Rectangle;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.AbstractFaceAlgorithm;
|
||||
import cn.smartjavaai.face.FaceDetectedResult;
|
||||
import cn.smartjavaai.face.ModelConfig;
|
||||
import cn.smartjavaai.face.dao.FaceDao;
|
||||
import cn.smartjavaai.face.entity.FaceData;
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
import com.seetaface.NativeLoader;
|
||||
import com.seetaface.SeetaFace6JNI;
|
||||
import com.seetaface.model.RecognizeResult;
|
||||
import com.seetaface.model.SeetaImageData;
|
||||
import com.seetaface.model.SeetaRect;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* SeetaFace6 人脸算法
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class SeetaFace6Algo extends AbstractFaceAlgorithm {
|
||||
|
||||
|
||||
private ModelConfig config;
|
||||
|
||||
|
||||
@Override
|
||||
public void loadModel(ModelConfig config) throws Exception {
|
||||
this.config = config;
|
||||
if (NativeLoader.seetaFace6SDK == null) {
|
||||
synchronized (SeetaFace6JNI.class) {
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new Exception("modelPath is null");
|
||||
}
|
||||
//加载依赖库
|
||||
NativeLoader.loadNativeLibraries(config.getModelPath());
|
||||
log.info("Loading seetaFace6 library successfully.");
|
||||
NativeLoader.seetaFace6SDK = new SeetaFace6JNI();
|
||||
//加载模型
|
||||
boolean isSuccess = NativeLoader.seetaFace6SDK.initModel(config.getModelPath());
|
||||
if(!isSuccess){
|
||||
throw new Exception("seetaFace6模型初始化失败," + config.getModelPath());
|
||||
}
|
||||
log.info("Load seetaFace6 model success!");
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
log.info("start load faceDb...");
|
||||
loadFaceDb();
|
||||
log.info("Load faceDb success!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceDetectedResult detect(String imagePath) throws Exception {
|
||||
// 将图片路径转换为 BufferedImage
|
||||
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
|
||||
return convertToFaceDetectedResult(seetaResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
|
||||
BufferedImage image = ImageIO.read(imageInputStream);
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
|
||||
return convertToFaceDetectedResult(seetaResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(String imagePath) throws Exception {
|
||||
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(InputStream inputStream) throws Exception {
|
||||
BufferedImage image = ImageIO.read(inputStream);
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
|
||||
return NativeLoader.seetaFace6SDK.calculateSimilarity(feature1, feature2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
|
||||
return featureComparison(new FileInputStream(Paths.get(imagePath1).toAbsolutePath().toString()),
|
||||
new FileInputStream(Paths.get(imagePath2).toAbsolutePath().toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
|
||||
BufferedImage image1 = ImageIO.read(inputStream1);
|
||||
BufferedImage image2 = ImageIO.read(inputStream2);
|
||||
SeetaImageData imageData1 = new SeetaImageData(image1.getWidth(), image1.getHeight(), 3);
|
||||
imageData1.data = ImageUtils.getMatrixBGR(image1);
|
||||
|
||||
SeetaImageData imageData2 = new SeetaImageData(image2.getWidth(), image2.getHeight(), 3);
|
||||
imageData2.data = ImageUtils.getMatrixBGR(image2);
|
||||
//裁剪
|
||||
byte[][] cropImg1 = NativeLoader.seetaFace6SDK.crop(imageData1);
|
||||
byte[][] cropImg2 = NativeLoader.seetaFace6SDK.crop(imageData2);
|
||||
if(cropImg1 == null || cropImg1.length == 0){
|
||||
throw new Exception("未发现人脸");
|
||||
}
|
||||
if(cropImg2 == null || cropImg2.length == 0){
|
||||
throw new Exception("未发现人脸");
|
||||
}
|
||||
|
||||
BufferedImage cropImage1 = ImageUtils.bgrToBufferedImage(cropImg1[0], 256, 256);
|
||||
BufferedImage cropImage2 = ImageUtils.bgrToBufferedImage(cropImg2[0], 256, 256);
|
||||
|
||||
SeetaImageData cropImageData1 = new SeetaImageData(cropImage1.getWidth(), cropImage1.getHeight(), 3);
|
||||
cropImageData1.data = ImageUtils.getMatrixBGR(cropImage1);
|
||||
SeetaImageData cropImageData2 = new SeetaImageData(cropImage2.getWidth(), cropImage2.getHeight(), 3);
|
||||
cropImageData2.data = ImageUtils.getMatrixBGR(cropImage2);
|
||||
return NativeLoader.seetaFace6SDK.compare(cropImageData1, cropImageData2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为FaceDetectedResult
|
||||
* @param seetaResult
|
||||
* @return
|
||||
*/
|
||||
private FaceDetectedResult convertToFaceDetectedResult(SeetaRect[] seetaResult){
|
||||
FaceDetectedResult faceDetectedResult = new FaceDetectedResult();
|
||||
List<Rectangle> RectangleList = new ArrayList<Rectangle>();
|
||||
List<Double> probabilities = new ArrayList<Double>();
|
||||
if(seetaResult != null && seetaResult.length > 0){
|
||||
for(SeetaRect rect : seetaResult){
|
||||
Rectangle rectangle = new Rectangle();
|
||||
List<Point> pointList = new ArrayList<>();
|
||||
pointList.add(new Point(rect.x,rect.y));
|
||||
pointList.add(new Point(rect.x + rect.width,rect.y));
|
||||
pointList.add(new Point(rect.x,rect.y + rect.height));
|
||||
pointList.add(new Point(rect.x + rect.width,rect.y + rect.height));
|
||||
rectangle.setPointList(pointList);
|
||||
rectangle.setHeight(rect.height);
|
||||
rectangle.setWidth(rect.width);
|
||||
RectangleList.add(rectangle);
|
||||
probabilities.add(new Double(rect.score));
|
||||
}
|
||||
}
|
||||
faceDetectedResult.setProbabilities(probabilities);
|
||||
faceDetectedResult.setRectangles(RectangleList);
|
||||
return faceDetectedResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(String key, String imagePath) throws Exception {
|
||||
return register(key, new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean register(String key, InputStream inputStream) throws Exception {
|
||||
if(!checkFaceDb()){
|
||||
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
//裁剪人脸
|
||||
BufferedImage image = ImageIO.read(inputStream);
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
byte[][] bytes = NativeLoader.seetaFace6SDK.crop(imageData);
|
||||
if (bytes == null || bytes.length == 0) {
|
||||
log.info("register face fail: key={}, error=no valid face", key);
|
||||
return false;
|
||||
}
|
||||
long index = NativeLoader.seetaFace6SDK.registerCroppedFace(bytes[0]);
|
||||
if (index < 0) {
|
||||
log.info("register face fail: key={}, index={}", key, index);
|
||||
return false;
|
||||
}
|
||||
//持久化到sqlite数据库
|
||||
FaceData face = new FaceData();
|
||||
face.setKey(key);
|
||||
face.setIndex(index);
|
||||
face.setImgData(bytes[0]);
|
||||
new FaceDao(config.getFaceDbPath()).save(face);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean register(String key, FaceData faceData) throws Exception {
|
||||
long index = NativeLoader.seetaFace6SDK.registerCroppedFace(faceData.getImgData());
|
||||
if (index < 0) {
|
||||
log.info("register face fail: key={}, index={}", key, index);
|
||||
return false;
|
||||
}
|
||||
int rows = new FaceDao(config.getFaceDbPath()).updateIndex(index, faceData);
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public FaceResult search(String imagePath) throws Exception {
|
||||
return search(new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceResult search(InputStream inputStream) throws Exception{
|
||||
if(!checkFaceDb()){
|
||||
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
BufferedImage image = ImageIO.read(inputStream);
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
RecognizeResult recognizeResult = NativeLoader.seetaFace6SDK.query(imageData);
|
||||
return searchFaceDb(recognizeResult);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeRegister(String... keys) throws Exception {
|
||||
if(!checkFaceDb()){
|
||||
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
List<Long> list = new FaceDao(config.getFaceDbPath()).findIndexList(keys);
|
||||
if (list == null) {
|
||||
return 0;
|
||||
}
|
||||
long[] array = list.stream().mapToLong(Long::longValue).toArray();
|
||||
long rows = NativeLoader.seetaFace6SDK.delete(array);
|
||||
new FaceDao(config.getFaceDbPath()).deleteFace(keys);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clearFace() throws Exception{
|
||||
if(!checkFaceDb()){
|
||||
throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
long rows = NativeLoader.seetaFace6SDK.delete(new long[]{-1});
|
||||
new FaceDao(config.getFaceDbPath()).deleteAll();
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否存在人脸库
|
||||
* @return
|
||||
*/
|
||||
private boolean checkFaceDb(){
|
||||
if(Objects.nonNull(config) && StringUtils.isNotBlank(config.getFaceDbPath())){
|
||||
File file = new File(config.getFaceDbPath());
|
||||
return file.exists() && file.isFile();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private FaceResult searchFaceDb(RecognizeResult recognizeResult) throws SQLException, ClassNotFoundException {
|
||||
if(recognizeResult != null && recognizeResult.index >= 0){
|
||||
String key = new FaceDao(config.getFaceDbPath()).findKeyByIndex(recognizeResult.index);
|
||||
return new FaceResult(key, recognizeResult.similar);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载人脸库
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
private void loadFaceDb() throws SQLException, ClassNotFoundException {
|
||||
if(!checkFaceDb()){
|
||||
log.info("未配置人脸库");
|
||||
return;
|
||||
}
|
||||
//分页查询人脸库
|
||||
int pageNo = 0, pageSize = 100;
|
||||
while (true) {
|
||||
List<FaceData> list = new FaceDao(config.getFaceDbPath()).findFace(pageNo, pageSize);
|
||||
if (list == null) {
|
||||
break;
|
||||
}
|
||||
list.forEach(face -> {
|
||||
try {
|
||||
register(face.getKey(), face);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
if (list.size() < pageSize) {
|
||||
break;
|
||||
}
|
||||
pageNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package cn.smartjavaai.face.dao;
|
||||
|
||||
import cn.smartjavaai.face.entity.FaceData;
|
||||
import cn.smartjavaai.face.sqllite.RowMapper;
|
||||
import cn.smartjavaai.face.sqllite.SqliteHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.BeanUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 人脸库持久层
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class FaceDao {
|
||||
|
||||
private static final String TABLE_NAME_IMG = "face";
|
||||
|
||||
|
||||
private String dbFilePath;
|
||||
|
||||
public FaceDao(String dbFilePath) {
|
||||
this.dbFilePath = dbFilePath;
|
||||
}
|
||||
|
||||
|
||||
public void save(FaceData faceData) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{faceData.getIndex(),faceData
|
||||
.getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用index查询key
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public String findKeyByIndex(int index) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery("select \"key\" from " + TABLE_NAME_IMG + " where \"index\"=" + index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除全部
|
||||
*
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public long deleteAll() throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
long rows = sqliteHelper.executeUpdate("delete from " + TABLE_NAME_IMG);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询index
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public List<Long> findIndexList(String... keys) throws SQLException, ClassNotFoundException {
|
||||
// 使用 Stream API
|
||||
String inKeys = Arrays.stream(keys)
|
||||
.map(s -> "'" + s + "'")
|
||||
.reduce((s1, s2) -> s1 + "," + s2)
|
||||
.orElse("");
|
||||
String sql = "select \"index\" from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
|
||||
log.info("sql:{}", sql.toString());
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery(sql, new RowMapper<Long>() {
|
||||
@Override
|
||||
public Long mapRow(ResultSet rs, int index) throws SQLException {
|
||||
return rs.getLong(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除人脸
|
||||
* @param keys
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public boolean deleteFace(String... keys) throws SQLException, ClassNotFoundException {
|
||||
String inKeys = Arrays.stream(keys)
|
||||
.map(s -> "'" + s + "'")
|
||||
.reduce((s1, s2) -> s1 + "," + s2)
|
||||
.orElse("");
|
||||
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
String sql = "delete from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
|
||||
log.info("sql:{}", sql.toString());
|
||||
sqliteHelper.executeUpdate(sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询人脸
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public List<FaceData> findFace(int pageNo, int pageSize) throws SQLException, ClassNotFoundException {
|
||||
String sql = "select \"key\",\"img_data\",\"width\",\"height\",\"channel\" from " + TABLE_NAME_IMG +
|
||||
" limit " + pageNo * pageSize + "," + pageSize;
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery(sql, new RowMapper<FaceData>() {
|
||||
@Override
|
||||
public FaceData mapRow(ResultSet rs, int index) throws SQLException {
|
||||
FaceData face = new FaceData();
|
||||
face.setKey(rs.getString("key"));
|
||||
face.setImgData(rs.getBytes("img_data"));
|
||||
face.setWidth(rs.getInt("width"));
|
||||
face.setHeight(rs.getInt("height"));
|
||||
face.setChannel(rs.getInt("channel"));
|
||||
return face;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新index
|
||||
* @param index
|
||||
* @param faceData
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int updateIndex(long index,FaceData faceData) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{index,faceData
|
||||
.getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.smartjavaai.face.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人脸数据
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class FaceData {
|
||||
|
||||
private String key;
|
||||
private long index;
|
||||
private byte[] imgData;
|
||||
private int width = 256;
|
||||
private int height = 256;
|
||||
private int channel = 3;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.smartjavaai.face.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人脸查询结果
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class FaceResult {
|
||||
|
||||
private String key;
|
||||
private float similar;
|
||||
|
||||
public FaceResult() {
|
||||
}
|
||||
|
||||
public FaceResult(String key, float similar) {
|
||||
this.key = key;
|
||||
this.similar = similar;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* ResultSetExtractor
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface ResultSetExtractor<T> {
|
||||
|
||||
public abstract T extractData(ResultSet rs);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* RowMapper
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface RowMapper<T> {
|
||||
public abstract T mapRow(ResultSet rs, int index) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sqlite帮助类
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class SqliteHelper {
|
||||
|
||||
private Connection connection;
|
||||
private Statement statement;
|
||||
private ResultSet resultSet;
|
||||
private String dbFilePath;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param dbFilePath sqlite db 文件路径
|
||||
* @throws ClassNotFoundException
|
||||
* @throws SQLException
|
||||
*/
|
||||
public SqliteHelper(String dbFilePath) throws ClassNotFoundException, SQLException {
|
||||
this.dbFilePath = dbFilePath;
|
||||
connection = getConnection(dbFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* @param dbFilePath db文件路径
|
||||
* @return 数据库连接
|
||||
* @throws ClassNotFoundException
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Connection getConnection(String dbFilePath) throws ClassNotFoundException, SQLException {
|
||||
Connection conn = null;
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
conn = DriverManager.getConnection("jdbc:sqlite:" + dbFilePath);
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql查询
|
||||
* @param sql sql select 语句
|
||||
* @param rse 结果集处理类对象
|
||||
* @return 查询结果
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> T executeQuery(String sql, ResultSetExtractor<T> rse) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
T rs = rse.extractData(resultSet);
|
||||
return rs;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行select查询,返回结果列表
|
||||
*
|
||||
* @param sql sql select 语句
|
||||
* @param rm 结果集的行数据处理类对象
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> List<T> executeQuery(String sql, RowMapper<T> rm) throws SQLException, ClassNotFoundException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
while (resultSet.next()) {
|
||||
rsList.add(rm.mapRow(resultSet, resultSet.getRow()));
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
return rsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单查询某个字段
|
||||
* @param sql
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public String executeQuery(String sql) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
if(resultSet.next()){
|
||||
return resultSet.getString(1);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新sql语句
|
||||
* @param sql
|
||||
* @return 更新行数
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int executeUpdate(String sql) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
int c = getStatement().executeUpdate(sql);
|
||||
return c;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行多个sql更新语句
|
||||
* @param sqls
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public void executeUpdate(String...sqls) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
for (String sql : sqls) {
|
||||
getStatement().executeUpdate(sql);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新 sql List
|
||||
* @param sqls sql列表
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public void executeUpdate(List<String> sqls) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
for (String sql : sqls) {
|
||||
getStatement().executeUpdate(sql);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() throws ClassNotFoundException, SQLException {
|
||||
if (null == connection) connection = getConnection(dbFilePath);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private Statement getStatement() throws SQLException, ClassNotFoundException {
|
||||
if (null == statement) statement = getConnection().createStatement();
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库资源关闭和释放
|
||||
*/
|
||||
public void destroyed() {
|
||||
try {
|
||||
if (null != statement) {
|
||||
statement.close();
|
||||
statement = null;
|
||||
}
|
||||
|
||||
if (null != connection) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
|
||||
if (null != resultSet) {
|
||||
resultSet.close();
|
||||
resultSet = null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Sqlite数据库关闭时异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行select查询,返回结果列表
|
||||
*
|
||||
* @param sql sql select 语句
|
||||
* @param clazz 实体泛型
|
||||
* @return 实体集合
|
||||
* @throws SQLException 异常信息
|
||||
* @throws ClassNotFoundException 异常信息
|
||||
*/
|
||||
public <T> List<T> executeQueryList(String sql, Class<T> clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
while (resultSet.next()) {
|
||||
T t = clazz.newInstance();
|
||||
for (Field field : t.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
field.set(t,resultSet.getObject(field.getName()));
|
||||
}
|
||||
rsList.add(t);
|
||||
}
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
return rsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行sql查询,适用单条结果集
|
||||
* @param sql sql select 语句
|
||||
* @param clazz 结果集处理类对象
|
||||
* @return 查询结果
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public <T> T executeQuery(String sql, Class<T> clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
try {
|
||||
resultSet = getStatement().executeQuery(sql);
|
||||
T t = clazz.newInstance();
|
||||
for (Field field : t.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
field.set(t,resultSet.getObject(field.getName()));
|
||||
}
|
||||
return t;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库更新sql语句
|
||||
* @param tableName 表名
|
||||
* @param param key-value键值对,key:表中字段名,value:值
|
||||
* @return 更新行数
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public int executeInsertOrUpdate(String tableName, Map<String,Object> param) throws SQLException, ClassNotFoundException {
|
||||
try {
|
||||
StringBuffer sql = new StringBuffer();
|
||||
sql.append("INSERT OR REPLACE INTO ");
|
||||
sql.append(tableName);
|
||||
sql.append(" ( ");
|
||||
for (String key : param.keySet()) {
|
||||
sql.append("'" + key + "'");
|
||||
sql.append(",");
|
||||
}
|
||||
sql.delete(sql.length()-1,sql.length());
|
||||
sql.append(") VALUES ( ");
|
||||
for (String key : param.keySet()) {
|
||||
sql.append("'");
|
||||
sql.append(param.get(key));
|
||||
sql.append("',");
|
||||
}
|
||||
sql.delete(sql.length()-1,sql.length());
|
||||
sql.append(");");
|
||||
log.info("sql:{}", sql.toString());
|
||||
int c = getStatement().executeUpdate(sql.toString());
|
||||
return c;
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
|
||||
public int executeUpdate(String sql, Object[] args) throws SQLException, ClassNotFoundException {
|
||||
Connection conn = getConnection();
|
||||
if (args == null || args.length == 0) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
return stmt.executeUpdate(sql);
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
} else {
|
||||
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
stmt.setObject(i + 1, args[i]);
|
||||
}
|
||||
return stmt.executeUpdate();
|
||||
} finally {
|
||||
destroyed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
Normal file
144
smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
Normal file
@@ -0,0 +1,144 @@
|
||||
package com.seetaface;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
/**
|
||||
* 依赖库加载器
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class NativeLoader {
|
||||
|
||||
|
||||
private static Path tempNativeDir;
|
||||
private static final String[] WIN_LIBS = {"tennis","tennis_haswell","tennis_pentium","tennis_sandy_bridge","SeetaAuthorize","SeetaFaceAntiSpoofingX600","SeetaFaceDetector600","SeetaFaceLandmarker600","SeetaFaceRecognizer610","SeetaFace6JNI"};
|
||||
private static final String[] LINUX_CENTOS_LIBS = {"libmain.so"};
|
||||
private static final String[] LINUX_UBUNTU_LIBS = {"libdependency1.so", "libdependency2.so", "libmain.so"};
|
||||
|
||||
private static final String TEMP_DIR = "smartjavaai-native-libs";
|
||||
|
||||
public static SeetaFace6JNI seetaFace6SDK;
|
||||
|
||||
|
||||
|
||||
public static void loadNativeLibraries(String modelPath) {
|
||||
try {
|
||||
// 创建临时目录
|
||||
tempNativeDir = Files.createTempDirectory(TEMP_DIR);
|
||||
log.info("create temp native directory: " + tempNativeDir.toAbsolutePath().toString());
|
||||
|
||||
// 获取当前平台库列表
|
||||
String libDir = getLibDir();
|
||||
String[] libNames = getPlatformLibs(libDir);
|
||||
|
||||
// 批量提取库文件
|
||||
for (String libName : libNames) {
|
||||
extractLibrary(libName,libDir);
|
||||
}
|
||||
|
||||
String separator = System.getProperty("path.separator");
|
||||
String sysLib = System.getProperty("java.library.path");
|
||||
if (sysLib.endsWith(separator)) {
|
||||
System.setProperty("java.library.path", sysLib + tempNativeDir);
|
||||
} else {
|
||||
System.setProperty("java.library.path", sysLib + separator + tempNativeDir);
|
||||
}
|
||||
try {
|
||||
//使java.library.path生效
|
||||
Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
|
||||
sysPathsField.setAccessible(true);
|
||||
sysPathsField.set(null, null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 按顺序加载库(确保依赖关系)
|
||||
for (String libName : libNames) {
|
||||
System.loadLibrary(libName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Native library loading failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String[] getPlatformLibs(String libDir) {
|
||||
if (libDir.contains("windows")) return WIN_LIBS;
|
||||
if (libDir.contains("centos")) return LINUX_CENTOS_LIBS;
|
||||
if (libDir.contains("ubuntu")) return LINUX_UBUNTU_LIBS;
|
||||
throw new UnsupportedOperationException("Unsupported OS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝依赖库到临时目录
|
||||
* @param libName
|
||||
* @param libDir
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void extractLibrary(String libName,String libDir) throws IOException {
|
||||
String resourcePath = "/native" + libDir + "/" + libName + ".dll";
|
||||
try (InputStream in = NativeLoader.class.getResourceAsStream(resourcePath)) {
|
||||
if (in == null) throw new FileNotFoundException(resourcePath);
|
||||
|
||||
Path targetPath = tempNativeDir.resolve(libName);
|
||||
Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
log.info("copy target path success : " + targetPath.toAbsolutePath().toString());
|
||||
|
||||
// 设置可执行权限
|
||||
if (!System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
targetPath.toFile().setExecutable(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取依赖库目录
|
||||
* @return
|
||||
*/
|
||||
private static String getLibDir() {
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
if (osName.contains("win")) {
|
||||
return "/windows";
|
||||
} else if (osName.contains("linux")) {
|
||||
String linuxOsName = getLinuxOsName();
|
||||
if(StringUtils.isBlank(linuxOsName)){
|
||||
throw new UnsupportedOperationException("Unsupported platform");
|
||||
};
|
||||
if(linuxOsName.contains("ubuntu")){
|
||||
return "/linux/ubuntu";
|
||||
}else if(linuxOsName.contains("centos")){
|
||||
return "/linux/centos";
|
||||
}
|
||||
}
|
||||
throw new UnsupportedOperationException("Unsupported platform");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取linux系统名称
|
||||
* @return
|
||||
*/
|
||||
private static String getLinuxOsName(){
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("/etc/os-release"))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("ID=")) {
|
||||
String distro = line.substring(3).replace("\"", "").trim();
|
||||
return distro;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("Failed to read /etc/os-release: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
127
smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
Normal file
127
smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.seetaface;
|
||||
|
||||
|
||||
import com.seetaface.model.RecognizeResult;
|
||||
import com.seetaface.model.SeetaImageData;
|
||||
import com.seetaface.model.SeetaPointF;
|
||||
import com.seetaface.model.SeetaRect;
|
||||
|
||||
/**
|
||||
* seetaface6 sdk
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaFace6JNI {
|
||||
|
||||
/**
|
||||
* 初始化,指定人脸识别模型文件目录
|
||||
*
|
||||
* @param modelDir
|
||||
* @return
|
||||
*/
|
||||
|
||||
public native boolean initModel(String modelDir);
|
||||
|
||||
/**
|
||||
* 检测人脸
|
||||
*
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native SeetaRect[] detect(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 根据人脸检测关键点
|
||||
* 关键定定位输入的是原始图片和人脸检测结果,给出指定人脸上的关键点的依次坐标。
|
||||
* 这里检测到的5点坐标循序依次为,左眼中心、右眼中心、鼻尖、左嘴角和右嘴角。
|
||||
* 注意这里的左右是基于图片内容的左右,并不是图片中人的左右,即左眼中心就是图片中左边的眼睛的中心。
|
||||
*
|
||||
* @param img
|
||||
* @param faces
|
||||
* @return
|
||||
*/
|
||||
public native SeetaPointF[] mark(SeetaImageData img, SeetaRect faces);
|
||||
|
||||
/**
|
||||
* 1 v 1 人脸比对
|
||||
*
|
||||
* @param img1
|
||||
* @param img2
|
||||
* @return 相似度范围在0~1,返回负数表示出错
|
||||
*/
|
||||
public native float compare(SeetaImageData img1, SeetaImageData img2);
|
||||
|
||||
/**
|
||||
* 提取人脸区域特性
|
||||
* @param face crop方法返回的人脸图像
|
||||
* @return
|
||||
*/
|
||||
public native float[] extractCroppedFace(byte[] face);
|
||||
|
||||
/**
|
||||
* 提取一个图像中最大人脸的特征
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native float[] extractMaxFace(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 计算两个特性的相似度
|
||||
* @param features1
|
||||
* @param features2
|
||||
* @return
|
||||
*/
|
||||
public native float calculateSimilarity(float[] features1, float[] features2);
|
||||
|
||||
/**
|
||||
* 注册人脸
|
||||
*
|
||||
* @param img
|
||||
* @return The returned value is the index of face database. Reture -1 if failed
|
||||
*/
|
||||
public native long register(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 注册裁剪后的人脸,推荐使用该方法
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public native long registerCroppedFace(byte[] bytes);
|
||||
|
||||
/**
|
||||
* 从人脸库中搜索,返回相似度最高的索引
|
||||
*
|
||||
* @param img
|
||||
* @return index saves the index of face databese, which is same as the retured value by Register. similar saves the most similar.
|
||||
*/
|
||||
public native RecognizeResult query(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 用裁剪后的人脸进行搜索
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public native RecognizeResult queryByCroppedFace(byte[] bytes);
|
||||
|
||||
/**
|
||||
* 将人脸从数据库中删除
|
||||
* @param index -1: 删除所有
|
||||
* @return 返回删除记录数
|
||||
*/
|
||||
public native long delete(long[] index);
|
||||
|
||||
/**
|
||||
* 人脸提取
|
||||
*
|
||||
* @param img
|
||||
* @return The returned value is face data. Reture null if failed
|
||||
*/
|
||||
public native byte[][] crop(SeetaImageData img);
|
||||
|
||||
/**
|
||||
* 图片活体检测
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public native int predictImage(SeetaImageData img);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
|
||||
/**
|
||||
* 人脸识别结果
|
||||
*/
|
||||
public class RecognizeResult {
|
||||
public int index;
|
||||
public float similar;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* 人脸识别参数
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaImageData {
|
||||
public SeetaImageData() {
|
||||
|
||||
}
|
||||
|
||||
public SeetaImageData(int width, int height, int channels) {
|
||||
this.data = new byte[width * height * channels];
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
public SeetaImageData(int width, int height) {
|
||||
this(width, height, 3);
|
||||
}
|
||||
|
||||
public byte[] data;
|
||||
public int width;
|
||||
public int height;
|
||||
public int channels;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* SeetaPointF
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaPointF {
|
||||
public double x;
|
||||
public double y;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
/**
|
||||
* SeetaPointF
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaRect {
|
||||
public int x;
|
||||
public int y;
|
||||
public int width;
|
||||
public int height;
|
||||
public float score;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SeetaRect{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
", score=" + score +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user