mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-12 04:45:37 +00:00
1、seetaface6兼容linux系统
2、支持设置全局缓存路径 3、优化部分功能
This commit is contained in:
@@ -111,7 +111,6 @@
|
||||
| smartjavaai-common | 基础通用模块,封装了公共功能,供各算法模块共享使用 |
|
||||
| smartjavaai-face | 人脸功能模块 |
|
||||
| smartjavaai-objectdetection | 目标检测模块 |
|
||||
| smartjavaai-seetaface6-lib | seetaface6人脸算法JNI接口封装 |
|
||||
|
||||
可以根据需求对每个模块单独引入,也可以通过引入`smartjavaai-all`方式引入所有模块。
|
||||
|
||||
@@ -134,7 +133,7 @@
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-all</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
```
|
||||
### 3、完整示例代码
|
||||
@@ -167,6 +166,10 @@
|
||||
## 更新日志
|
||||
|
||||
|
||||
## [v1.0.10] - 2025-04-19
|
||||
- 兼容 SeetaFace6 在 Linux 系统下的运行
|
||||
- 新增全局缓存路径设置功能
|
||||
- 优化若干功能细节,提升稳定性与性能
|
||||
## [v1.0.8] - 2025-04-13
|
||||
- 新增目标检测功能
|
||||
- 模型调用接口统一封装
|
||||
|
||||
19
pom.xml
19
pom.xml
@@ -6,13 +6,12 @@
|
||||
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
<packaging>pom</packaging>
|
||||
<description>SmartJavaAI</description>
|
||||
<modules>
|
||||
<module>smartjavaai-face</module>
|
||||
<module>smartjavaai-common</module>
|
||||
<module>smartjavaai-seetaface6-lib</module>
|
||||
<module>smartjavaai-objectdetection</module>
|
||||
<module>smartjavaai-all</module>
|
||||
</modules>
|
||||
@@ -37,13 +36,13 @@
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -185,6 +184,18 @@
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>5.8.16</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>5.8.16</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-all</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
<name>smartjavaai-all</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package cn.smartjavaai.common.config;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.hutool.system.UserInfo;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 全局配置
|
||||
* @author dwj
|
||||
* @date 2025/4/15
|
||||
*/
|
||||
@Slf4j
|
||||
public class Config {
|
||||
|
||||
/**
|
||||
* 默认缓存文件夹
|
||||
*/
|
||||
private static final String CACHE_DIR = "smartjavaai_cache";
|
||||
|
||||
private static String cachePath;
|
||||
|
||||
static{
|
||||
createCachePath();
|
||||
if(StringUtils.isNotBlank(cachePath)){
|
||||
System.setProperty("DJL_CACHE_DIR", cachePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置缓存路径的方法
|
||||
public static void setCachePath(String customeCachePath) {
|
||||
if (StringUtils.isNotBlank(customeCachePath)) {
|
||||
/*if(!FileUtils.isValidDirectory(customeCachePath)){
|
||||
throw new IllegalArgumentException("无效的缓存路径");
|
||||
}*/
|
||||
cachePath = customeCachePath;
|
||||
FileUtil.mkdir(cachePath);
|
||||
// 如果需要在此时直接设置系统属性
|
||||
System.setProperty("DJL_CACHE_DIR", cachePath);
|
||||
} else {
|
||||
throw new IllegalArgumentException("缓存路径不允许为空");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存路径的方法
|
||||
public static String getCachePath() {
|
||||
if(StringUtils.isBlank(cachePath)){
|
||||
createCachePath();
|
||||
}
|
||||
if(StringUtils.isNotBlank(cachePath)){
|
||||
System.setProperty("DJL_CACHE_DIR", cachePath);
|
||||
}
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
// 获取当前缓存路径的系统属性(如果需要在其他地方使用)
|
||||
public static String getCachePathFromSystem() {
|
||||
return System.getProperty("DJL_CACHE_DIR");
|
||||
}
|
||||
|
||||
private static void createCachePath(){
|
||||
String osName = SystemUtil.getOsInfo().getName();
|
||||
log.info("当前操作系统:{}", osName);
|
||||
if(osName.toLowerCase().contains("windows")){
|
||||
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
|
||||
FileUtil.mkdir(cachePath);
|
||||
}else if(osName.toLowerCase().contains("linux")){
|
||||
cachePath = "/root/" + CACHE_DIR;
|
||||
FileUtil.mkdir(cachePath);
|
||||
}else if(osName.toLowerCase().contains("mac")){
|
||||
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
|
||||
FileUtil.mkdir(cachePath);
|
||||
}else{
|
||||
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
|
||||
FileUtil.mkdir(cachePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -18,4 +18,14 @@ public class FileUtils {
|
||||
File file = new File(filePath);
|
||||
return file.exists() && !file.isDirectory(); // 确保是文件且存在
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目录是否存在
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValidDirectory(String path) {
|
||||
File file = new File(path);
|
||||
return file.exists() && file.isDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
<name>smartjavaai-face</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
@@ -37,9 +37,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-seetaface6-lib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<groupId>io.gitee.dengwenjie</groupId>
|
||||
<artifactId>seeta-sdk-platform</artifactId>
|
||||
<version>1.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -20,6 +20,11 @@ public class FaceConfig {
|
||||
*/
|
||||
public static final float NMS_THRESHOLD = 0.45F;
|
||||
|
||||
/**
|
||||
* 默认相似度阈值
|
||||
*/
|
||||
public static final float SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD = 0.85F;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ public class FaceModelConfig {
|
||||
*/
|
||||
private double confidenceThreshold = FaceConfig.DEFAULT_CONFIDENCE_THRESHOLD;
|
||||
|
||||
/**
|
||||
* 相似度阈值 作用:判断是否为同一人脸
|
||||
*/
|
||||
private double similarityThreshold = 0D;
|
||||
|
||||
/**
|
||||
* 非极大抑制阈值 作用:消除重叠检测框,保留最优结果
|
||||
*/
|
||||
@@ -40,6 +45,11 @@ public class FaceModelConfig {
|
||||
*/
|
||||
private DeviceEnum device;
|
||||
|
||||
/**
|
||||
* gpu设备ID 当device为GPU时生效
|
||||
*/
|
||||
private int gpuId = 0;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.smartjavaai.face;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.model.FeatureExtractionModel;
|
||||
import cn.smartjavaai.face.model.RetinaFaceModel;
|
||||
@@ -122,6 +123,7 @@ public class FaceModelFactory {
|
||||
//人脸特征提取
|
||||
registerAlgorithm("featureextractionmodel", FeatureExtractionModel.class);
|
||||
registerAlgorithm("seetaface6model", SeetaFace6Model.class);
|
||||
log.info("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class FaceDao {
|
||||
* @throws SQLException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public String findKeyByIndex(int index) throws SQLException, ClassNotFoundException {
|
||||
public String findKeyByIndex(long index) throws SQLException, ClassNotFoundException {
|
||||
SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
|
||||
return sqliteHelper.executeQuery("select \"key\" from " + TABLE_NAME_IMG + " where \"index\"=" + index);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
log.info("释放资源");
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
|
||||
@@ -220,7 +220,6 @@ public class RetinaFaceModel extends AbstractFaceModel implements AutoCloseable{
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
log.info("释放资源");
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package cn.smartjavaai.face.model;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.AbstractFaceModel;
|
||||
@@ -10,13 +16,14 @@ import cn.smartjavaai.face.entity.FaceData;
|
||||
import cn.smartjavaai.face.entity.FaceResult;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import com.seeta.pool.*;
|
||||
import com.seeta.sdk.*;
|
||||
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 org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -38,40 +45,76 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
|
||||
private FaceModelConfig config;
|
||||
|
||||
private static final Object lock = new Object(); // 全局锁
|
||||
private FaceDetectorPool faceDetectorPool;
|
||||
private FaceRecognizerPool faceRecognizerPool;
|
||||
private FaceLandmarkerPool faceLandmarkerPool;
|
||||
|
||||
private FaceDatabasePool faceDatabasePool;
|
||||
|
||||
/**
|
||||
* 默认相似度阈值
|
||||
*/
|
||||
public static final float SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD = 0.62F;
|
||||
|
||||
|
||||
@Override
|
||||
public void loadModel(FaceModelConfig config) {
|
||||
this.config = config;
|
||||
if (NativeLoader.seetaFace6SDK == null) {
|
||||
synchronized (lock) {
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new FaceException("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 FaceException("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();
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new FaceException("modelPath is null");
|
||||
}
|
||||
//设置默认相似度阈值
|
||||
if(config.getSimilarityThreshold() <= 0){
|
||||
config.setSimilarityThreshold(SEETAFACE_DEFAULT_SIMILARITY_THRESHOLD);
|
||||
}
|
||||
//加载依赖库
|
||||
NativeLoader.loadNativeLibraries(config);
|
||||
log.info("Loading seetaFace6 library successfully.");
|
||||
String[] faceDetectorModelPath = {config.getModelPath() + File.separator + "face_detector.csta"};
|
||||
String[] faceRecognizerModelPath = {config.getModelPath() + File.separator + "face_recognizer.csta"};
|
||||
String[] faceLandmarkerModelPath = {config.getModelPath() + File.separator + "face_landmarker_pts5.csta"};
|
||||
SeetaDevice device = SeetaDevice.SEETA_DEVICE_AUTO;
|
||||
int gpuId = 0;
|
||||
if(Objects.nonNull(config.getDevice())){
|
||||
device = config.getDevice() == DeviceEnum.CPU ? SeetaDevice.SEETA_DEVICE_CPU : SeetaDevice.SEETA_DEVICE_GPU;
|
||||
if(config.getGpuId() >= 0 && device == SeetaDevice.SEETA_DEVICE_GPU){
|
||||
gpuId = config.getGpuId();
|
||||
}
|
||||
}
|
||||
try {
|
||||
SeetaModelSetting faceDetectorPoolSetting = new SeetaModelSetting(gpuId, faceDetectorModelPath, device);
|
||||
SeetaConfSetting faceDetectorPoolConfSetting = new SeetaConfSetting(faceDetectorPoolSetting);
|
||||
|
||||
SeetaModelSetting faceRecognizerPoolSetting = new SeetaModelSetting(gpuId, faceRecognizerModelPath, device);
|
||||
SeetaConfSetting faceRecognizerPoolConfSetting = new SeetaConfSetting(faceRecognizerPoolSetting);
|
||||
|
||||
SeetaModelSetting faceLandmarkerPoolSetting = new SeetaModelSetting(gpuId, faceLandmarkerModelPath, device);
|
||||
SeetaConfSetting faceLandmarkerPoolConfSetting = new SeetaConfSetting(faceLandmarkerPoolSetting);
|
||||
|
||||
SeetaModelSetting faceDatabasePoolSetting = new SeetaModelSetting(gpuId, faceRecognizerModelPath, device);
|
||||
SeetaConfSetting faceDatabasePoolConfSetting = new SeetaConfSetting(faceDatabasePoolSetting);
|
||||
|
||||
this.faceDetectorPool = new FaceDetectorPool(faceDetectorPoolConfSetting);
|
||||
this.faceRecognizerPool = new FaceRecognizerPool(faceRecognizerPoolConfSetting);
|
||||
this.faceLandmarkerPool = new FaceLandmarkerPool(faceLandmarkerPoolConfSetting);
|
||||
this.faceDatabasePool = new FaceDatabasePool(faceDatabasePoolConfSetting);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
log.info("start load faceDb...");
|
||||
loadFaceDb();
|
||||
log.info("Load faceDb success!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new FaceException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,9 +153,21 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
synchronized (lock) {
|
||||
SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
|
||||
FaceDetector predictor = null;
|
||||
try {
|
||||
predictor = faceDetectorPool.borrowObject();
|
||||
SeetaRect[] seetaResult = predictor.Detect(imageData);
|
||||
return FaceUtils.convertToDetectionResponse(seetaResult, config);
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +225,76 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取5点坐标,循序依次为,左眼中心、右眼中心、鼻尖、左嘴角和右嘴角
|
||||
* @param imageData
|
||||
* @return
|
||||
*/
|
||||
private SeetaPointF[] getMaskPoint(SeetaImageData imageData) {
|
||||
FaceDetector faceDetector = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
try {
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaResult[0], pointFS);
|
||||
return pointFS;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪人脸
|
||||
* @param imageData
|
||||
* @return
|
||||
*/
|
||||
private SeetaImageData getMaxCropFace(SeetaImageData imageData){
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
//提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = getMaskPoint(imageData);
|
||||
//裁剪人脸
|
||||
SeetaImageData cropImageData = new SeetaImageData(faceRecognizer.GetCropFaceWidthV2(), faceRecognizer.GetCropFaceHeightV2(), faceRecognizer.GetCropFaceChannelsV2());
|
||||
faceRecognizer.CropFaceV2(imageData, pointFS, cropImageData);
|
||||
return cropImageData;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] featureExtraction(BufferedImage image) {
|
||||
if(!ImageUtils.isImageValid(image)){
|
||||
@@ -177,10 +302,56 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
synchronized (lock) {
|
||||
return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
|
||||
}
|
||||
FaceDetector faceDetector = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaResult[0], pointFS);
|
||||
|
||||
//提取特征
|
||||
float[] features = new float[faceRecognizer.GetExtractFeatureSize()];
|
||||
boolean isSuccess = faceRecognizer.Extract(imageData, pointFS, features);
|
||||
if(!isSuccess){
|
||||
throw new FaceException("人脸特征提取失败");
|
||||
}
|
||||
return features;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -228,8 +399,20 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
if(Objects.isNull(feature1) || Objects.isNull(feature2)){
|
||||
throw new FaceException("特征向量无效");
|
||||
}
|
||||
synchronized (lock) {
|
||||
return NativeLoader.seetaFace6SDK.calculateSimilarity(feature1, feature2);
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
try {
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
return faceRecognizer.CalculateSimilarity(feature1, feature2);
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,27 +459,85 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
|
||||
SeetaImageData imageData2 = new SeetaImageData(image2.getWidth(), image2.getHeight(), 3);
|
||||
imageData2.data = ImageUtils.getMatrixBGR(image2);
|
||||
synchronized (lock) {
|
||||
//裁剪
|
||||
byte[][] cropImg1 = NativeLoader.seetaFace6SDK.crop(imageData1);
|
||||
byte[][] cropImg2 = NativeLoader.seetaFace6SDK.crop(imageData2);
|
||||
if(cropImg1 == null || cropImg1.length == 0){
|
||||
throw new FaceException("未发现人脸");
|
||||
}
|
||||
if(cropImg2 == null || cropImg2.length == 0){
|
||||
throw new FaceException("未发现人脸");
|
||||
}
|
||||
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);
|
||||
|
||||
FaceRecognizer faceRecognizer = null;
|
||||
FaceDatabase faceDatabase = null;
|
||||
FaceLandmarker faceLandmarker = null;
|
||||
FaceDetector faceDetector = null;
|
||||
try {
|
||||
faceRecognizer = faceRecognizerPool.borrowObject();
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
faceDetector = faceDetectorPool.borrowObject();
|
||||
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = faceDetector.Detect(imageData1);
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS1 = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData1, seetaResult[0], pointFS1);
|
||||
|
||||
//裁剪人脸
|
||||
SeetaImageData cropImageData1 = new SeetaImageData(faceRecognizer.GetCropFaceWidthV2(), faceRecognizer.GetCropFaceHeightV2(), faceRecognizer.GetCropFaceChannelsV2());
|
||||
faceRecognizer.CropFaceV2(imageData1, pointFS1, cropImageData1);
|
||||
|
||||
//图片2:检测人脸
|
||||
SeetaRect[] seetaResult2 = faceDetector.Detect(imageData2);
|
||||
if(Objects.isNull(seetaResult2) || seetaResult2.length == 0){
|
||||
throw new FaceException("未检测到人脸");
|
||||
}
|
||||
//图片2:提取第一个人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS2 = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData2, seetaResult2[0], pointFS2);
|
||||
|
||||
//图片2:裁剪人脸
|
||||
SeetaImageData cropImageData2 = new SeetaImageData(faceRecognizer.GetCropFaceWidthV2(), faceRecognizer.GetCropFaceHeightV2(), faceRecognizer.GetCropFaceChannelsV2());
|
||||
faceRecognizer.CropFaceV2(imageData2, pointFS2, cropImageData2);
|
||||
|
||||
return faceDatabase.CompareByCroppedFace(cropImageData1, cropImageData2);
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDetector != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(faceDetector); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceRecognizer != null) {
|
||||
try {
|
||||
faceRecognizerPool.returnObject(faceRecognizer); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public float featureComparison(byte[] imageData1, byte[] imageData2) {
|
||||
if(Objects.isNull(imageData1) || Objects.isNull(imageData2)){
|
||||
@@ -338,13 +579,11 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
synchronized (lock) {
|
||||
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]);
|
||||
FaceDatabase faceDatabase = null;
|
||||
try {
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
SeetaImageData cropImageData = getMaxCropFace(imageData);
|
||||
long index = faceDatabase.RegisterByCroppedFace(cropImageData);
|
||||
if (index < 0) {
|
||||
log.info("register face fail: key={}, index={}", key, index);
|
||||
return false;
|
||||
@@ -353,13 +592,25 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
FaceData face = new FaceData();
|
||||
face.setKey(key);
|
||||
face.setIndex(index);
|
||||
face.setImgData(bytes[0]);
|
||||
face.setImgData(cropImageData.data);
|
||||
try {
|
||||
new FaceDao(config.getFaceDbPath()).save(face);
|
||||
} catch (SQLException | ClassNotFoundException e) {
|
||||
throw new FaceException("保存人脸库失败", e);
|
||||
}
|
||||
return true;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,9 +651,13 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
* @param faceData
|
||||
* @return
|
||||
*/
|
||||
private boolean register(String key, FaceData faceData) {
|
||||
synchronized (lock) {
|
||||
long index = NativeLoader.seetaFace6SDK.registerCroppedFace(faceData.getImgData());
|
||||
private boolean registerCroppedFace(String key, FaceData faceData) {
|
||||
FaceDatabase faceDatabase = null;
|
||||
try {
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
SeetaImageData cropImageData = new SeetaImageData(faceData.getWidth(), faceData.getHeight(), faceData.getChannel());
|
||||
cropImageData.data = faceData.getImgData();
|
||||
long index = faceDatabase.RegisterByCroppedFace(cropImageData);
|
||||
if (index < 0) {
|
||||
log.info("register face fail: key={}, index={}", key, index);
|
||||
return false;
|
||||
@@ -414,6 +669,18 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
throw new FaceException(e);
|
||||
}
|
||||
return rows > 0;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,9 +724,33 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
synchronized (lock) {
|
||||
RecognizeResult recognizeResult = NativeLoader.seetaFace6SDK.query(imageData);
|
||||
return searchFaceDb(recognizeResult);
|
||||
FaceDatabase faceDatabase = null;
|
||||
try {
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
SeetaPointF[] points = getMaskPoint(imageData);
|
||||
long[] index = new long[1];
|
||||
float[] similarity = new float[1];
|
||||
long result = faceDatabase.QueryTop(imageData, points, 1, index, similarity);
|
||||
if(result < 1){
|
||||
return null;
|
||||
}
|
||||
//检查相似度
|
||||
if(similarity[0] < config.getSimilarityThreshold()){
|
||||
return null;
|
||||
}
|
||||
return searchFaceDb(index[0], similarity[0]);
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,21 +776,32 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
if(!checkFaceDb()){
|
||||
throw new FaceException("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
synchronized (lock) {
|
||||
try {
|
||||
List<Long> list = new FaceDao(config.getFaceDbPath()).findIndexList(keys);
|
||||
if (list == null) {
|
||||
return 0;
|
||||
|
||||
FaceDatabase faceDatabase = null;
|
||||
try {
|
||||
List<Long> list = new FaceDao(config.getFaceDbPath()).findIndexList(keys);
|
||||
if (list == null) {
|
||||
return 0;
|
||||
}
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
int rows = 0;
|
||||
for (long index : list) {
|
||||
int row = faceDatabase.Delete(index);
|
||||
rows += row;
|
||||
}
|
||||
new FaceDao(config.getFaceDbPath()).deleteFace(keys);
|
||||
return rows;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
long[] array = list.stream().mapToLong(Long::longValue).toArray();
|
||||
long rows = NativeLoader.seetaFace6SDK.delete(array);
|
||||
new FaceDao(config.getFaceDbPath()).deleteFace(keys);
|
||||
return rows;
|
||||
} catch (SQLException | ClassNotFoundException e) {
|
||||
throw new FaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -507,16 +809,31 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
if(!checkFaceDb()){
|
||||
throw new FaceException("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
|
||||
}
|
||||
synchronized (lock) {
|
||||
long rows = NativeLoader.seetaFace6SDK.delete(new long[]{-1});
|
||||
|
||||
FaceDatabase faceDatabase = null;
|
||||
try {
|
||||
faceDatabase = faceDatabasePool.borrowObject();
|
||||
faceDatabase.Clear();
|
||||
long rows = 0;
|
||||
try {
|
||||
new FaceDao(config.getFaceDbPath()).deleteAll();
|
||||
rows = new FaceDao(config.getFaceDbPath()).deleteAll();
|
||||
} catch (SQLException | ClassNotFoundException e) {
|
||||
throw new FaceException("删除人脸库失败", e);
|
||||
}
|
||||
return rows;
|
||||
} catch (FaceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
}finally {
|
||||
if (faceDatabase != null) {
|
||||
try {
|
||||
faceDatabasePool.returnObject(faceDatabase); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,17 +848,15 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
private FaceResult searchFaceDb(RecognizeResult recognizeResult) {
|
||||
if(recognizeResult != null && recognizeResult.index >= 0){
|
||||
private FaceResult searchFaceDb(long index,float similar) {
|
||||
if(index >= 0){
|
||||
String key = null;
|
||||
synchronized (lock) {
|
||||
try {
|
||||
key = new FaceDao(config.getFaceDbPath()).findKeyByIndex(recognizeResult.index);
|
||||
} catch (SQLException | ClassNotFoundException e) {
|
||||
throw new FaceException("查询人脸库失败", e);
|
||||
}
|
||||
return new FaceResult(key, recognizeResult.similar);
|
||||
try {
|
||||
key = new FaceDao(config.getFaceDbPath()).findKeyByIndex(index);
|
||||
} catch (SQLException | ClassNotFoundException e) {
|
||||
throw new FaceException("查询人脸库失败", e);
|
||||
}
|
||||
return new FaceResult(key, similar);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -571,7 +886,7 @@ public class SeetaFace6Model extends AbstractFaceModel {
|
||||
}
|
||||
list.forEach(face -> {
|
||||
try {
|
||||
register(face.getKey(), face);
|
||||
registerCroppedFace(face.getKey(), face);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -209,7 +209,6 @@ public class UltraLightFastGenericFaceModel extends AbstractFaceModel implements
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
log.info("释放资源");
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
|
||||
@@ -9,7 +9,7 @@ import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import com.seetaface.model.SeetaRect;
|
||||
import com.seeta.sdk.SeetaRect;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
@@ -74,10 +74,10 @@ public class FaceUtils {
|
||||
List<DetectionRectangle> rectangleList = new ArrayList<DetectionRectangle>();
|
||||
for(SeetaRect rect : seetaResult){
|
||||
//过滤置信度
|
||||
if(config.getConfidenceThreshold() > 0 && rect.score < config.getConfidenceThreshold()){
|
||||
/*if(config.getConfidenceThreshold() > 0){
|
||||
continue;
|
||||
}
|
||||
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height, rect.score);
|
||||
}*/
|
||||
DetectionRectangle rectangle = new DetectionRectangle(rect.x, rect.y, rect.width, rect.height, 0);
|
||||
rectangleList.add(rectangle);
|
||||
}
|
||||
detectionResponse.setRectangleList(rectangleList);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
package com.seetaface;
|
||||
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.setting.dialect.Props;
|
||||
import cn.hutool.system.OsInfo;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.face.FaceModelConfig;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import com.seeta.sdk.SeetaDevice;
|
||||
import com.seeta.sdk.util.DllItem;
|
||||
import com.seeta.sdk.util.LoadNativeCore;
|
||||
import jdk.dynalink.linker.support.Lookup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -12,7 +23,10 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 依赖库加载器
|
||||
@@ -22,122 +36,202 @@ import java.nio.file.StandardCopyOption;
|
||||
public class NativeLoader {
|
||||
|
||||
|
||||
private static Path tempNativeDir;
|
||||
private static Path seetaface6NativePath;
|
||||
private static final String[] WIN_LIBS = {"tennis.dll","tennis_haswell.dll","tennis_pentium.dll","tennis_sandy_bridge.dll","SeetaAuthorize.dll","SeetaFaceAntiSpoofingX600.dll","SeetaFaceDetector600.dll","SeetaFaceLandmarker600.dll","SeetaFaceRecognizer610.dll","SeetaFace6JNI.dll"};
|
||||
//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 = {"libSeetaAuthorize.so","libtennis.so","libtennis_haswell.so","libtennis_pentium.so","libtennis_sandy_bridge.so","libSeetaFaceDetector600.so","libSeetaAgePredictor600.so","libSeetaEyeStateDetector200.so","libSeetaFaceAntiSpoofingX600.so","libSeetaFaceLandmarker600.so","libSeetaFaceRecognizer610.so","libSeetaGenderPredictor600.so","libSeetaMaskDetector200.so","libSeetaPoseEstimation600.so","libSeetaFaceTracking600.so","libSeetaQualityAssessor300.so"};
|
||||
private static final String[] LINUX_UBUNTU_LIBS = {"libSeetaAuthorize.so","libtennis.so","libtennis_haswell.so","libtennis_pentium.so","libtennis_sandy_bridge.so","libSeetaFaceDetector600.so","libSeetaAgePredictor600.so","libSeetaEyeStateDetector200.so","libSeetaFaceAntiSpoofingX600.so","libSeetaFaceLandmarker600.so","libSeetaFaceRecognizer610.so","libSeetaGenderPredictor600.so","libSeetaMaskDetector200.so","libSeetaPoseEstimation600.so","libSeetaFaceTracking600.so","libSeetaQualityAssessor300.so"};
|
||||
|
||||
private static final String TEMP_DIR = "smartjavaai-native-libs";
|
||||
private static final String SEETAFACE_LIB_DIR = "seetaface6";
|
||||
|
||||
public static SeetaFace6JNI seetaFace6SDK;
|
||||
|
||||
public static final String AMD64 = "amd64";
|
||||
|
||||
public static final String x86_64 = "amd64";
|
||||
|
||||
/**
|
||||
* 定义dll 路径和加载顺序的文件
|
||||
*/
|
||||
private static final String PROPERTIES_FILE_NAME = "dll.properties";
|
||||
|
||||
|
||||
public static void loadNativeLibraries(String modelPath) {
|
||||
|
||||
public static void loadNativeLibraries(FaceModelConfig config) {
|
||||
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);
|
||||
OsInfo osInfo = SystemUtil.getOsInfo();
|
||||
//检查当前系统是否支持
|
||||
if(!osInfo.isWindows() && !osInfo.isLinux()){
|
||||
throw new FaceException("当前系统不支持:" + osInfo.getName());
|
||||
}
|
||||
|
||||
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);
|
||||
//判断硬件架构是否支持GPU
|
||||
if(config.getDevice() != null && config.getDevice().equals(DeviceEnum.GPU)){
|
||||
//GPU仅支持amd64
|
||||
if(!osInfo.getArch().contains("amd64") && !osInfo.getArch().contains("x86_64")){
|
||||
throw new FaceException("seetaface6 GPU模型不支持当前arch:" + osInfo.getArch());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 按顺序加载库(确保依赖关系)
|
||||
for (String libName : libNames) {
|
||||
log.info("Loading library: " + tempNativeDir + File.separator + libName);
|
||||
System.load(tempNativeDir + File.separator + libName);
|
||||
seetaface6NativePath = Paths.get(Config.getCachePath(), SEETAFACE_LIB_DIR);
|
||||
//创建目录
|
||||
FileUtil.mkdir(seetaface6NativePath);
|
||||
log.info("seetaface6依赖库路径: " + seetaface6NativePath.toAbsolutePath().toString());
|
||||
//拷贝依赖库到缓存目录
|
||||
List<File> fileList = getLibFiles(osInfo, config.getDevice());
|
||||
if(fileList != null && !fileList.isEmpty()){
|
||||
// 加载依赖库文件
|
||||
fileList.forEach(file -> {
|
||||
System.load(file.getAbsolutePath());
|
||||
log.info(String.format("load %s finish", file.getAbsolutePath()));
|
||||
});
|
||||
}
|
||||
} 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 osInfo
|
||||
* @return
|
||||
*/
|
||||
private static List<File> getLibFiles(OsInfo osInfo,DeviceEnum deviceEnum){
|
||||
try {
|
||||
String device = getDevice(deviceEnum);
|
||||
log.info("当前设备:{}", device);
|
||||
//获取dll文件列表
|
||||
List<DllItem> baseList = new ArrayList<>();
|
||||
List<DllItem> jniList = new ArrayList<>();
|
||||
InputStream propsInputStream = LoadNativeCore.class.getResourceAsStream(getPropertiesPath());
|
||||
Props props = new Props();
|
||||
props.load(propsInputStream);
|
||||
String prefix = getPrefix();
|
||||
props.forEach((keyObj, valuObj) -> {
|
||||
String key = (String) keyObj;
|
||||
String value = (String) valuObj;
|
||||
DllItem dllItem = new DllItem();
|
||||
dllItem.setKey(key);
|
||||
if (key.contains("base")) {
|
||||
if (value.contains("tennis")) {
|
||||
dllItem.setValue(prefix + "base/" + device + "/" + value);
|
||||
} else {
|
||||
dllItem.setValue(prefix + "base/" + value);
|
||||
}
|
||||
baseList.add(dllItem);
|
||||
} else {
|
||||
dllItem.setValue(prefix + value);
|
||||
jniList.add(dllItem);
|
||||
}
|
||||
});
|
||||
//给dll文件排序
|
||||
List<String> basePath = getSortedPath(baseList);
|
||||
List<String> sdkPath = getSortedPath(jniList);
|
||||
List<File> fileList = new ArrayList<>();
|
||||
//拷贝文件到临时目录
|
||||
for (String baseSo : basePath) {
|
||||
fileList.add(extractLibrary(baseSo));
|
||||
}
|
||||
for (String sdkSo : sdkPath) {
|
||||
fileList.add(extractLibrary(sdkSo));
|
||||
}
|
||||
return fileList;
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("拷贝依赖库失败",e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDevice(DeviceEnum deviceEnum) {
|
||||
String device = "CPU";
|
||||
if ("amd64".equals(getArch()) && deviceEnum != null) {
|
||||
device = deviceEnum == DeviceEnum.GPU ? "GPU" : "CPU";
|
||||
}
|
||||
return device;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回路径文件前缀
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getPrefix() {
|
||||
String arch = getArch();
|
||||
//aarch64
|
||||
String os = SystemUtil.getOsInfo().getName();
|
||||
//Windows操作系统
|
||||
if (os != null && os.toLowerCase().startsWith("windows")) {
|
||||
os = "/windows/";
|
||||
} else if (os != null && os.toLowerCase().startsWith("linux")) {//Linux操作系统
|
||||
os = "/linux/";
|
||||
} else { //其它操作系统
|
||||
//安卓 乌班图等等,先不写
|
||||
return null;
|
||||
}
|
||||
// "/seetaface6/windows/amd64"
|
||||
return "/" + SEETAFACE_LIB_DIR + os + arch + "/";
|
||||
}
|
||||
|
||||
private static String getArch() {
|
||||
String arch = SystemUtil.getOsInfo().getArch().toLowerCase();
|
||||
if (arch.startsWith("amd64")
|
||||
|| arch.startsWith("x86_64")
|
||||
|| arch.startsWith("x86-64")
|
||||
|| arch.startsWith("x64")) {
|
||||
arch = "amd64";
|
||||
} else if (arch.contains("aarch")) {
|
||||
arch = "aarch64";
|
||||
} else if (arch.contains("arm")) {
|
||||
arch = "arm";
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取dll配置文件路径
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
private static String getPropertiesPath() {
|
||||
return getPrefix() + PROPERTIES_FILE_NAME;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 拷贝依赖库到临时目录
|
||||
* @param libName
|
||||
* @param libDir
|
||||
* @param libPath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void extractLibrary(String libName,String libDir) throws IOException {
|
||||
String resourcePath = "/native" + libDir + "/" + libName;
|
||||
try (InputStream in = NativeLoader.class.getResourceAsStream(resourcePath)) {
|
||||
private static File extractLibrary(String libPath) throws IOException {
|
||||
String resourcePath = libPath;
|
||||
try (InputStream in = com.seetaface.NativeLoader.class.getResourceAsStream(resourcePath)) {
|
||||
if (in == null) throw new FileNotFoundException(resourcePath);
|
||||
|
||||
Path targetPath = tempNativeDir.resolve(libName);
|
||||
Path path = Paths.get(resourcePath);
|
||||
String fileName = path.getFileName().toString();
|
||||
Path targetPath = seetaface6NativePath.resolve(fileName);
|
||||
Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
log.info("copy target path success : " + targetPath.toAbsolutePath().toString());
|
||||
|
||||
log.info("copy target path success : {}", targetPath.toAbsolutePath().toString());
|
||||
// 设置可执行权限
|
||||
if (!System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
if (!SystemUtil.getOsInfo().getName().toLowerCase().contains("win")) {
|
||||
targetPath.toFile().setExecutable(true);
|
||||
}
|
||||
return targetPath.toFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取依赖库目录
|
||||
* @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
|
||||
* 将获得的配置进行排序 并生成路径
|
||||
*
|
||||
* @param list
|
||||
* @return List<String>
|
||||
*/
|
||||
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;
|
||||
private static List<String> getSortedPath(List<DllItem> list) {
|
||||
return list.stream().sorted(Comparator.comparing(dllItem -> {
|
||||
int i = dllItem.getKey().lastIndexOf(".") + 1;
|
||||
String substring = dllItem.getKey().substring(i);
|
||||
return Integer.valueOf(substring);
|
||||
})).map(DllItem::getValue).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-objectdetection</artifactId>
|
||||
<version>1.0.8</version>
|
||||
<version>1.0.10</version>
|
||||
<name>smartjavaai-objectdetection</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -99,7 +99,7 @@ public class DetectorModel implements AutoCloseable{
|
||||
/**
|
||||
* 目标检测-将检测结果绘制到原图
|
||||
* @param imagePath
|
||||
* @return
|
||||
* @param outputPath
|
||||
*/
|
||||
public void detectAndDraw(String imagePath, String outputPath){
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.smartjavaai.objectdetection.model;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.objectdetection.DetectorModelConfig;
|
||||
import cn.smartjavaai.objectdetection.DetectorModelEnum;
|
||||
import cn.smartjavaai.objectdetection.exception.DetectionException;
|
||||
@@ -21,6 +22,10 @@ public class ObjectDetectionModelFactory {
|
||||
|
||||
private static final ConcurrentHashMap<String, DetectorModel> modelMap = new ConcurrentHashMap<>();
|
||||
|
||||
static{
|
||||
log.info("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
|
||||
// 私有构造函数,防止外部创建实例
|
||||
private ObjectDetectionModelFactory() {}
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.8</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-seetaface6-lib</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/licenses/MIT</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>dengwenjie</publishingServerId>
|
||||
<tokenAuth>true</tokenAuth>
|
||||
<deploymentName>${project.groupId}:${project.artifactId}:${project.version}</deploymentName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
|
||||
<doclint>none</doclint>
|
||||
<additionalJOptions>
|
||||
<additionalJOption>-Xdoclint:none</additionalJOption>
|
||||
</additionalJOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- 必须添加:SCM信息 -->
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/geekwenjie/SmartJavaAI.git</connection>
|
||||
<developerConnection>scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git</developerConnection>
|
||||
<url>http://github.com/geekwenjie/SmartJavaAI/tree/master</url>
|
||||
</scm>
|
||||
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>dengwenjie</name>
|
||||
<email>775747758@qq.com</email>
|
||||
<roles>
|
||||
<role>Project Manager</role>
|
||||
<role>Architect</role>
|
||||
</roles>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user