mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-09 19:18:52 +00:00
1、【核心升级】升级DJL版本到0.34.0
2、【平台支持】新增对 Linux ARM64 架构的全面支持 3、【通用视觉】集成零样本目标检测模型 4、【活体检测】优化视频检测流程,实现 Predictor 视频会话级复用 5、【人脸识别】SQLite人脸查询改进线程池 6、【人脸识别】修复 Milvus 向量库下 listFaces 接口的调用异常
This commit is contained in:
15
README.md
15
README.md
@@ -242,6 +242,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div align="left">
|
||||
<p>零样本目标检测<br>(ZeroShot Object Detection)</p>
|
||||
- YOLO-World 模型 <br>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div align="center">
|
||||
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/vision/yolo-world.png" height = "200px"/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div align="left">
|
||||
@@ -483,7 +496,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
|
||||
<dependency>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>all</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>all</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<artifactId>bom</artifactId>
|
||||
<name>bom</name>
|
||||
<description>统一版本管理的 BOM 包,同时支持 import 和全量依赖</description>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<name>common</name>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.smartjavaai.common.executor;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/11/26
|
||||
*/
|
||||
public class GlobalExecutor {
|
||||
|
||||
private static volatile ExecutorService executor;
|
||||
|
||||
public static ExecutorService getExecutor() {
|
||||
if (executor == null) {
|
||||
synchronized (GlobalExecutor.class) {
|
||||
if (executor == null) {
|
||||
int cores = Runtime.getRuntime().availableProcessors();
|
||||
executor = new ThreadPoolExecutor(
|
||||
cores,
|
||||
cores * 2,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>(),
|
||||
runnable -> {
|
||||
Thread t = new Thread(runnable);
|
||||
t.setDaemon(true); // 守护线程
|
||||
return t;
|
||||
},
|
||||
new ThreadPoolExecutor.DiscardOldestPolicy()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<smartjavaai.version>1.0.27</smartjavaai.version>
|
||||
<smartjavaai.version>1.1.0</smartjavaai.version>
|
||||
<!--如果打包运行,需要替换成你的main-->
|
||||
<exec.mainClass>smartai.examples.face.facedet.FaceDetDemo</exec.mainClass>
|
||||
|
||||
@@ -88,11 +88,10 @@
|
||||
<artifactId>face</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-jni</artifactId>
|
||||
<version>2.5.1-0.32.0</version>
|
||||
<version>2.7.1-0.34.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -129,7 +128,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -167,19 +166,45 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${javacv.version}</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<scope>runtime</scope>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>6.1.1-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>0.3.26-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>4.9.0-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
@@ -213,7 +238,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<smartjavaai.version>1.0.27</smartjavaai.version>
|
||||
<smartjavaai.version>1.1.0</smartjavaai.version>
|
||||
<!--如果打包运行,需要替换成你的main-->
|
||||
<exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-jni</artifactId>
|
||||
<version>2.5.1-0.32.0</version>
|
||||
<version>2.7.1-0.34.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -170,14 +170,43 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${javacv.version}</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>6.1.1-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>0.3.26-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>4.9.0-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -214,7 +243,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<smartjavaai.version>1.0.27</smartjavaai.version>
|
||||
<smartjavaai.version>1.1.0</smartjavaai.version>
|
||||
<!--如果打包运行,需要替换成你的main-->
|
||||
<exec.mainClass>smartai.examples.speech.asr.common.OcrRecognizeDemo</exec.mainClass>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<smartjavaai.version>1.0.27</smartjavaai.version>
|
||||
<smartjavaai.version>1.1.0</smartjavaai.version>
|
||||
<!--如果打包运行,需要替换成你的main-->
|
||||
<exec.mainClass>smartai.examples.nlp.translation.TranslationDemo</exec.mainClass>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-jni</artifactId>
|
||||
<version>2.5.1-0.32.0</version>
|
||||
<version>2.7.1-0.34.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -116,25 +116,25 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TranslationDemo {
|
||||
//指定翻译模型:NLLB,切换模型需同时修改modelEnum及modelPath
|
||||
config.setModelEnum(TranslationModeEnum.NLLB_MODEL);
|
||||
//指定模型路径,需将模型路径修改为本地的模型路径
|
||||
config.setModelPath("/Users/xxx/Documents/develop/model/trans/traced_translation_cpu.pt");
|
||||
config.setModelPath("/Users/wenjie/Documents/develop/model/translate/nllb/traced_translation_cpu.pt");
|
||||
config.setDevice(DeviceEnum.CPU);
|
||||
return TranslationModelFactory.getInstance().getModel(config);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<smartjavaai.version>1.0.27</smartjavaai.version>
|
||||
<smartjavaai.version>1.1.0</smartjavaai.version>
|
||||
<!--如果打包运行,需要替换成你的main-->
|
||||
<exec.mainClass>smartai.examples.vision.ObjectDetectionDemo</exec.mainClass>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-jni</artifactId>
|
||||
<version>2.5.1-0.32.0</version>
|
||||
<version>2.7.1-0.34.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.windows-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -179,12 +179,11 @@
|
||||
<classifier>${javacv.platform.linux-x86_64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<!--PyTorch离线平台依赖-->
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -203,14 +202,57 @@
|
||||
<version>1.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${javacv.version}</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>6.1.1-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>openblas</artifactId>
|
||||
<version>0.3.26-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
<version>4.9.0-1.5.10</version>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu-precxx11</artifactId>
|
||||
<classifier>${djl.platform.linux-x86_64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.linux-aarch64}</classifier>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.tensorflow</groupId>
|
||||
<artifactId>tensorflow-native-cpu</artifactId>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
<scope>runtime</scope>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.mxnet</groupId>
|
||||
<artifactId>mxnet-native-mkl</artifactId>
|
||||
<classifier>${javacv.platform.linux-arm64}</classifier>
|
||||
<scope>runtime</scope>
|
||||
<version>1.9.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
|
||||
<dependency>
|
||||
@@ -244,7 +286,7 @@
|
||||
<groupId>ai.djl.pytorch</groupId>
|
||||
<artifactId>pytorch-native-cpu</artifactId>
|
||||
<classifier>${djl.platform.osx-aarch64}</classifier>
|
||||
<version>2.5.1</version>
|
||||
<version>2.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ClsDemo {
|
||||
|
||||
public ClsModel getModel(){
|
||||
ClsModelConfig config = new ClsModelConfig();
|
||||
//实例分割模型,切换模型需要同时修改modelEnum及modelPath
|
||||
//切换模型需要同时修改modelEnum及modelPath
|
||||
config.setModelEnum(ClsModelEnum.YOLOV8);
|
||||
//模型所在路径,synset.txt也需要放在同目录下
|
||||
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/cls/yolo11m-cls.onnx");
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package smartai.examples.vision;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||
import cn.smartjavaai.zeroshot.model.ZeroDetModel;
|
||||
import cn.smartjavaai.zeroshot.model.ZeroDetModelFactory;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 零样本目标检测
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZeroShotObjectDetectionDemo {
|
||||
|
||||
|
||||
//设备类型
|
||||
public static DeviceEnum device = DeviceEnum.CPU;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAll() throws IOException {
|
||||
//修改缓存路径
|
||||
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取零样本目标检测模型
|
||||
*/
|
||||
public ZeroDetModel getModel(){
|
||||
ZeroDetConfig config = new ZeroDetConfig();
|
||||
//零样本目标检测模型,切换模型需要同时修改modelEnum及modelPath
|
||||
config.setModelEnum(ZeroDetModelEnum.OWLV2_BASE_PATCH16);
|
||||
//模型所在路径
|
||||
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/zero/owlv2-base-patch16");
|
||||
config.setDevice(device);
|
||||
//置信度阈值
|
||||
config.setThreshold(0.5f);
|
||||
return ZeroDetModelFactory.getInstance().getModel(config);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 零样本目标检测
|
||||
* 特性:
|
||||
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||
*/
|
||||
@Test
|
||||
public void zeroDetection(){
|
||||
try {
|
||||
ZeroDetModel detectorModel = getModel();
|
||||
//创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
|
||||
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/zero/000000039769.jpg"));
|
||||
//输入图片以及条件
|
||||
R<DetectionResponse> result = detectorModel.detect(image, new String[]{"cat","remote control"});
|
||||
if(result.isSuccess()){
|
||||
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||
}else{
|
||||
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 零样本目标检测并绘制检测结果
|
||||
* 特性:
|
||||
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||
*/
|
||||
@Test
|
||||
public void zeroDetectionAndDraw() {
|
||||
try {
|
||||
ZeroDetModel detectorModel = getModel();
|
||||
String[] candidates = new String[]{"cat","remote control"};
|
||||
//保存绘制后图片以及返回检测结果
|
||||
R<DetectionResponse> result = detectorModel.detectAndDraw(candidates, "src/main/resources/zero/000000039769.jpg","output/cat_detected.png");
|
||||
if(result.isSuccess()){
|
||||
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||
}else{
|
||||
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 零样本目标检测并绘制检测结果
|
||||
* 特性:
|
||||
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||
*/
|
||||
@Test
|
||||
public void zeroDetectionAndDraw2(){
|
||||
try {
|
||||
ZeroDetModel detectorModel = getModel();
|
||||
//创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
|
||||
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/zero/000000039769.jpg"));
|
||||
String[] candidates = new String[]{"cat","remote control"};
|
||||
R<DetectionResponse> result = detectorModel.detectAndDraw(image, candidates);
|
||||
if(result.isSuccess()){
|
||||
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||
//保存图片
|
||||
ImageUtils.save(result.getData().getDrawnImage(), "output/cat_detected.png");
|
||||
}else{
|
||||
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>face</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>face</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
@@ -26,7 +26,7 @@
|
||||
<!-- <maven.compiler.target>11</maven.compiler.target>-->
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
<javacv.version>1.5.8</javacv.version>
|
||||
<javacv.version>1.5.10</javacv.version>
|
||||
<javacv.ffmpeg.version>5.1.2-1.5.8</javacv.ffmpeg.version>
|
||||
</properties>
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -176,7 +176,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
|
||||
imageData.data = BufferedImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("无人脸数据");
|
||||
}
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
@@ -456,7 +456,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
|
||||
imageData.data = BufferedImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("无人脸数据");
|
||||
}
|
||||
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
|
||||
@@ -510,7 +510,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("无人脸数据");
|
||||
}
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
@@ -642,7 +642,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
throw new FaceException("无人脸数据");
|
||||
}
|
||||
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
|
||||
|
||||
@@ -9,6 +9,7 @@ import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.translate.TranslateException;
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
@@ -50,6 +51,30 @@ public class CommonFaceDetModel implements FaceDetModel{
|
||||
|
||||
private FaceDetConfig config;
|
||||
|
||||
@Override
|
||||
public Predictor<Image, DetectedObjects> borrowPredictor() throws Exception {
|
||||
if(predictorPool == null){
|
||||
throw new FaceException("请先加载模型");
|
||||
}
|
||||
return predictorPool.borrowObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnPredictor(Predictor<Image, DetectedObjects> predictor){
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
predictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
|
||||
@@ -131,4 +131,20 @@ public interface FaceDetModel extends AutoCloseable{
|
||||
default void setFromFactory(boolean fromFactory){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Predictor
|
||||
* @return
|
||||
*/
|
||||
default Predictor<Image, DetectedObjects> borrowPredictor() throws Exception{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
/**
|
||||
* 归还Predictor
|
||||
* @param predictor
|
||||
*/
|
||||
default void returnPredictor(Predictor<Image, DetectedObjects> predictor){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package cn.smartjavaai.face.model.facedect;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import cn.smartjavaai.common.entity.DetectionInfo;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.model.facedect.mtcnn.MtcnnPredictors;
|
||||
import cn.smartjavaai.face.seetaface.SeetaFace6FaceDetPredictors;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/11/24
|
||||
*/
|
||||
public class FaceDetectManager implements AutoCloseable{
|
||||
|
||||
|
||||
private FaceDetModel faceDetModel;
|
||||
|
||||
public FaceDetectManager(FaceDetModel faceDetModel) {
|
||||
this.faceDetModel = faceDetModel;
|
||||
}
|
||||
|
||||
private MtcnnPredictors mtcnnPredictors;
|
||||
|
||||
private SeetaFace6FaceDetPredictors seetaFace6FaceDetPredictors;
|
||||
|
||||
private Predictor<Image, DetectedObjects> commonPredictor;
|
||||
|
||||
|
||||
|
||||
public void borrowPredictors(){
|
||||
try {
|
||||
//mtcnn
|
||||
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||
MtcnnFaceDetModel mtcnnFaceDetModel = (MtcnnFaceDetModel) faceDetModel;
|
||||
mtcnnPredictors = mtcnnFaceDetModel.borrowPredictors();
|
||||
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||
//SeetaFace6
|
||||
SeetaFace6FaceDetModel seetaFace6FaceDetModel = (SeetaFace6FaceDetModel) faceDetModel;
|
||||
seetaFace6FaceDetPredictors = seetaFace6FaceDetModel.borrowPredictors();
|
||||
}else{
|
||||
//其他通用模型
|
||||
commonPredictor = faceDetModel.borrowPredictor();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("获取predictors异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
public R<DetectionInfo> detectTopFace(Image image){
|
||||
DetectionResponse detectionResponse = null;
|
||||
try {
|
||||
//mtcnn
|
||||
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||
MtcnnFaceDetModel mtcnnFaceDetModel = (MtcnnFaceDetModel) faceDetModel;
|
||||
DetectedObjects detections = mtcnnFaceDetModel.detectCoreByPredictors(image, mtcnnPredictors);
|
||||
detectionResponse = FaceUtils.convertToDetectionResponse(detections, image);
|
||||
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||
//SeetaFace6
|
||||
SeetaFace6FaceDetModel seetaFace6FaceDetModel = (SeetaFace6FaceDetModel) faceDetModel;
|
||||
detectionResponse = seetaFace6FaceDetModel.detectByPredictors(image, seetaFace6FaceDetPredictors);
|
||||
}else{
|
||||
DetectedObjects detections = commonPredictor.predict(image);
|
||||
detectionResponse = FaceUtils.convertToDetectionResponse(detections, image);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("获取predictors异常", e);
|
||||
}
|
||||
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
|
||||
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||
}
|
||||
DetectionInfo detectionInfo = detectionResponse.getDetectionInfoList().get(0);
|
||||
return R.ok(detectionInfo);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
try {
|
||||
//mtcnn
|
||||
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||
mtcnnPredictors.close();
|
||||
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||
//SeetaFace6
|
||||
seetaFace6FaceDetPredictors.close();
|
||||
}else{
|
||||
faceDetModel.getPool().returnObject(commonPredictor);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("归还predictors异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,52 +221,51 @@ public class MtcnnFaceDetModel extends CommonFaceDetModel{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 使用MtcnnPredictors进行人脸检测
|
||||
* @param image
|
||||
* @param predictors
|
||||
* @return
|
||||
*/
|
||||
public DetectedObjects detectCoreByPredictors(Image image, MtcnnPredictors predictors){
|
||||
Predictor<NDList, NDList> pNetPredictor = predictors.pNetPredictor;
|
||||
Predictor<NDList, NDList> rNetPredictor = predictors.rNetPredictor;
|
||||
Predictor<NDList, NDList> oNetPredictor = predictors.oNetPredictor;
|
||||
try (NDManager manager = pNetModel.getNDManager().newSubManager();){
|
||||
int h = image.getHeight();
|
||||
int w = image.getWidth();
|
||||
//第一阶段
|
||||
NDList outputPnet = PNetModel.firstStage(manager, pNetPredictor, image);
|
||||
|
||||
|
||||
// /**
|
||||
// * 转换为FaceDetectedResult
|
||||
// * @param mtcnnBatchResult
|
||||
// * @return
|
||||
// */
|
||||
// public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){
|
||||
// if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes)
|
||||
// || CollectionUtils.isEmpty(mtcnnBatchResult.points)
|
||||
// || CollectionUtils.isEmpty(mtcnnBatchResult.probs)){
|
||||
// return null;
|
||||
// }
|
||||
// DetectionResponse detectionResponse = new DetectionResponse();
|
||||
// List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
|
||||
//
|
||||
// NDArray boxes = mtcnnBatchResult.boxes.get(0);
|
||||
// NDArray probs = mtcnnBatchResult.probs.get(0);
|
||||
// NDArray points = mtcnnBatchResult.points.get(0);
|
||||
//
|
||||
// if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){
|
||||
// return null;
|
||||
// }
|
||||
// long numBoxes = boxes.getShape().get(0);
|
||||
// for (int i = 0; i < numBoxes; i++) {
|
||||
// float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2]
|
||||
// float score = probs.getFloat(i);
|
||||
// NDArray pointND = points.get(i); // shape [5,2]
|
||||
// float[] flatPoints = pointND.toFloatArray(); // 一维长度 10
|
||||
// List<Point> keyPoints = new ArrayList<Point>();
|
||||
// for (int p = 0; p < 5; p++) {
|
||||
// keyPoints.add(new Point(flatPoints[p * 2], flatPoints[p * 2 + 1]));
|
||||
// }
|
||||
// int x = Math.round(boxCoords[0]);
|
||||
// int y = Math.round(boxCoords[1]);
|
||||
// int w = Math.round(boxCoords[2] - boxCoords[0]);
|
||||
// int h = Math.round(boxCoords[3] - boxCoords[1]);
|
||||
//
|
||||
// DetectionRectangle rectangle = new DetectionRectangle(x, y, w, h);
|
||||
// FaceInfo faceInfo = new FaceInfo(keyPoints);
|
||||
// DetectionInfo detectionInfo = new DetectionInfo(rectangle, score, faceInfo);
|
||||
// detectionInfoList.add(detectionInfo);
|
||||
// }
|
||||
// detectionResponse.setDetectionInfoList(detectionInfoList);
|
||||
// return detectionResponse;
|
||||
// }
|
||||
if(CollectionUtils.isEmpty(outputPnet)){
|
||||
return DJLCommonUtils.buildEmptyDetectedObjects();
|
||||
}
|
||||
NDArray boxes = outputPnet.get(0);
|
||||
NDArray image_inds = outputPnet.get(1);
|
||||
NDArray imgs = outputPnet.get(2);
|
||||
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_inds) || DJLCommonUtils.isNDArrayEmpty(imgs)){
|
||||
return DJLCommonUtils.buildEmptyDetectedObjects();
|
||||
}
|
||||
NDList pad = MtcnnUtils.pad(boxes, w, h);
|
||||
//第二阶段
|
||||
NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs, boxes, pad, image_inds);
|
||||
if(CollectionUtils.isEmpty(outputRnet)){
|
||||
return DJLCommonUtils.buildEmptyDetectedObjects();
|
||||
}
|
||||
NDArray image_indsFiltered = outputRnet.get(0);
|
||||
NDArray scoresFiltered = outputRnet.get(1);
|
||||
boxes = outputRnet.get(2);
|
||||
if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_indsFiltered) || DJLCommonUtils.isNDArrayEmpty(scoresFiltered)){
|
||||
return DJLCommonUtils.buildEmptyDetectedObjects();
|
||||
}
|
||||
//第三阶段
|
||||
MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs, boxes, w, h, scoresFiltered, image_indsFiltered);
|
||||
return FaceUtils.toDetectedObjects(oNetResult, w, h);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -293,6 +292,57 @@ public class MtcnnFaceDetModel extends CommonFaceDetModel{
|
||||
return fromFactory;
|
||||
}
|
||||
|
||||
|
||||
public MtcnnPredictors borrowPredictors() throws Exception {
|
||||
if(pnetPredictorPool == null || rnetPredictorPool == null || onetPredictorPool == null){
|
||||
return null;
|
||||
}
|
||||
Predictor<NDList, NDList> p = pnetPredictorPool.borrowObject();
|
||||
Predictor<NDList, NDList> r = rnetPredictorPool.borrowObject();
|
||||
Predictor<NDList, NDList> o = onetPredictorPool.borrowObject();
|
||||
return new MtcnnPredictors(p, r, o, this);
|
||||
}
|
||||
|
||||
public void returnPredictor(Predictor<NDList, NDList> pNetPredictor, Predictor<NDList, NDList> rNetPredictor, Predictor<NDList, NDList> oNetPredictor) {
|
||||
if (pNetPredictor != null) {
|
||||
try {
|
||||
pnetPredictorPool.returnObject(pNetPredictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
pNetPredictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rNetPredictor != null) {
|
||||
try {
|
||||
rnetPredictorPool.returnObject(rNetPredictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
rNetPredictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oNetPredictor != null) {
|
||||
try {
|
||||
onetPredictorPool.returnObject(oNetPredictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
oNetPredictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (fromFactory) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.smartjavaai.face.model.facedect;
|
||||
|
||||
import ai.djl.engine.Engine;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
@@ -13,7 +15,9 @@ import cn.smartjavaai.common.utils.ImageUtils;
|
||||
import cn.smartjavaai.face.config.FaceDetConfig;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.factory.FaceDetModelFactory;
|
||||
import cn.smartjavaai.face.model.facedect.mtcnn.MtcnnPredictors;
|
||||
import cn.smartjavaai.face.seetaface.NativeLoader;
|
||||
import cn.smartjavaai.face.seetaface.SeetaFace6FaceDetPredictors;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import com.seeta.pool.*;
|
||||
import com.seeta.sdk.*;
|
||||
@@ -124,6 +128,27 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
|
||||
}
|
||||
}
|
||||
|
||||
public DetectionResponse detectByPredictors(Image image, SeetaFace6FaceDetPredictors predictors) {
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
FaceDetector predictor = predictors.faceDetector;
|
||||
FaceLandmarker faceLandmarker = predictors.faceLandmarker;
|
||||
try {
|
||||
SeetaRect[] seetaResult = predictor.Detect(imageData);
|
||||
List<SeetaPointF[]> seetaPointFSList = new ArrayList<SeetaPointF[]>();
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
//提取人脸的5点人脸标识
|
||||
SeetaPointF[] pointFS = new SeetaPointF[faceLandmarker.number()];
|
||||
faceLandmarker.mark(imageData, seetaRect, pointFS);
|
||||
seetaPointFSList.add(pointFS);
|
||||
}
|
||||
return FaceUtils.convertToDetectionResponse(seetaResult, seetaPointFSList);
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("目标检测错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public R<DetectionResponse> detectAndDraw(Image image) {
|
||||
R<DetectionResponse> result = detect(image);
|
||||
@@ -276,6 +301,33 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
|
||||
return R.ok(drawnImage);
|
||||
}
|
||||
|
||||
public SeetaFace6FaceDetPredictors borrowPredictors() throws Exception {
|
||||
if(faceDetectorPool == null || faceLandmarkerPool == null){
|
||||
return null;
|
||||
}
|
||||
FaceDetector predictor = faceDetectorPool.borrowObject();
|
||||
predictor.set(FaceDetector.Property.PROPERTY_THRESHOLD, config.getConfidenceThreshold() > 0 ? config.getConfidenceThreshold() : THRESHOLD);
|
||||
FaceLandmarker faceLandmarker = faceLandmarkerPool.borrowObject();
|
||||
return new SeetaFace6FaceDetPredictors(predictor, faceLandmarker, this);
|
||||
}
|
||||
|
||||
public void returnPredictor(FaceDetector predictor, FaceLandmarker faceLandmarker) {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
faceDetectorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
if (faceLandmarker != null) {
|
||||
try {
|
||||
faceLandmarkerPool.returnObject(faceLandmarker); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.smartjavaai.face.model.facedect.mtcnn;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import cn.smartjavaai.face.model.facedect.MtcnnFaceDetModel;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/11/24
|
||||
*/
|
||||
public class MtcnnPredictors implements AutoCloseable{
|
||||
|
||||
public Predictor<NDList, NDList> pNetPredictor;
|
||||
public Predictor<NDList, NDList> rNetPredictor;
|
||||
public Predictor<NDList, NDList> oNetPredictor;
|
||||
|
||||
// 标记是否由外部借用,用于控制 close 行为
|
||||
private MtcnnFaceDetModel model;
|
||||
|
||||
public MtcnnPredictors(Predictor<NDList, NDList> p, Predictor<NDList, NDList> r, Predictor<NDList, NDList> o, MtcnnFaceDetModel m) {
|
||||
this.pNetPredictor = p;
|
||||
this.rNetPredictor = r;
|
||||
this.oNetPredictor = o;
|
||||
this.model = m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
model.returnPredictor(pNetPredictor, rNetPredictor, oNetPredictor);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import ai.djl.engine.Engine;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.ImageFactory;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
@@ -28,8 +29,14 @@ import cn.smartjavaai.face.enums.LivenessModelEnum;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.factory.FaceDetModelFactory;
|
||||
import cn.smartjavaai.face.factory.LivenessModelFactory;
|
||||
import cn.smartjavaai.face.model.facedect.FaceDetectManager;
|
||||
import cn.smartjavaai.face.model.facedect.MtcnnFaceDetModel;
|
||||
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
||||
import cn.smartjavaai.face.model.facedect.mtcnn.MtcnnPredictors;
|
||||
import cn.smartjavaai.face.model.liveness.criterial.LivenessCriteriaFactory;
|
||||
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator;
|
||||
import cn.smartjavaai.face.seetaface.SeetaFace6FaceDetPredictors;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import com.seeta.sdk.FaceAntiSpoofing;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import nu.pattern.OpenCV;
|
||||
@@ -124,8 +131,12 @@ public class CommonLivenessModel implements LivenessDetModel{
|
||||
return detectVideo(new FFmpegFrameGrabber(videoPath));
|
||||
}
|
||||
|
||||
private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
|
||||
try {
|
||||
protected R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
|
||||
Predictor<Image, Float> predictor = null;
|
||||
try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
|
||||
//初始化predictors
|
||||
faceDetectManager.borrowPredictors();
|
||||
predictor = predictorPool.borrowObject();
|
||||
//滑动窗口
|
||||
Deque<Float> scoreWindow = new ArrayDeque<>();
|
||||
grabber.start();
|
||||
@@ -147,7 +158,8 @@ public class CommonLivenessModel implements LivenessDetModel{
|
||||
converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
|
||||
}
|
||||
Mat mat = converterToMat.convert(frame);
|
||||
R<LivenessResult> livenessScore = detectTopFace(SmartImageFactory.getInstance().fromMat(mat));
|
||||
Image image = SmartImageFactory.getInstance().fromMat(mat);
|
||||
R<LivenessResult> livenessScore = detectVideoFrame(faceDetectManager, image, predictor);
|
||||
mat.release();
|
||||
if(!livenessScore.isSuccess()){
|
||||
log.debug("第" + frameIndex + "帧处理失败:" + livenessScore.getMessage());
|
||||
@@ -175,6 +187,24 @@ public class CommonLivenessModel implements LivenessDetModel{
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
} finally {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
predictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
grabber.release();
|
||||
} catch (FFmpegFrameGrabber.Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return R.fail(R.Status.Unknown);
|
||||
}
|
||||
@@ -262,6 +292,40 @@ public class CommonLivenessModel implements LivenessDetModel{
|
||||
}
|
||||
}
|
||||
|
||||
private R<LivenessResult> detectVideoFrame(FaceDetectManager faceDetectManager, Image image, Predictor<Image, Float> predictor) {
|
||||
//预处理图片
|
||||
Image processedImage = null;
|
||||
try {
|
||||
//检测人脸
|
||||
R<DetectionInfo> detectResult = faceDetectManager.detectTopFace(image);
|
||||
if(!detectResult.isSuccess()){
|
||||
return R.fail(detectResult.getCode(), detectResult.getMessage());
|
||||
}
|
||||
DetectionInfo detectionInfo = detectResult.getData();
|
||||
if(config.getModelEnum() == LivenessModelEnum.IIC_FL_MODEL){
|
||||
processedImage = new DJLImagePreprocessor(image, detectionInfo.getDetectionRectangle())
|
||||
.setExtendRatio(96f / 112f)
|
||||
.enableSquarePadding(true)
|
||||
.enableScaling(true)
|
||||
.setTargetSize(128)
|
||||
.enableCenterCrop(true)
|
||||
.setCenterCropSize(112)
|
||||
.process();
|
||||
}
|
||||
Float result = null;
|
||||
if(processedImage != null){
|
||||
result = predictor.predict(processedImage);
|
||||
ImageUtils.releaseOpenCVMat(processedImage);
|
||||
}else{
|
||||
result = predictor.predict(image);
|
||||
}
|
||||
LivenessStatus status = result >= config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
|
||||
return R.ok(new LivenessResult(status, result));
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("活体检测错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<LivenessResult> detectTopFace(Image image) {
|
||||
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);
|
||||
|
||||
@@ -10,6 +10,7 @@ import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.*;
|
||||
import cn.smartjavaai.common.entity.face.FaceInfo;
|
||||
import cn.smartjavaai.common.entity.face.LivenessResult;
|
||||
@@ -21,14 +22,19 @@ import cn.smartjavaai.common.preprocess.DJLImagePreprocessor;
|
||||
import cn.smartjavaai.common.utils.*;
|
||||
import cn.smartjavaai.face.config.LivenessConfig;
|
||||
import cn.smartjavaai.face.constant.MiniVisionConstant;
|
||||
import cn.smartjavaai.face.enums.LivenessModelEnum;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.factory.LivenessModelFactory;
|
||||
import cn.smartjavaai.face.model.facedect.FaceDetectManager;
|
||||
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator;
|
||||
import com.seeta.sdk.*;
|
||||
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 org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.bytedeco.javacv.OpenCVFrameConverter;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -59,6 +65,8 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
|
||||
|
||||
private GenericObjectPool<Predictor<Image, float[]>> sePredictorPool;
|
||||
|
||||
private OpenCVFrameConverter.ToOrgOpenCvCoreMat converterToMat = null;
|
||||
|
||||
|
||||
/**
|
||||
* 模型策略
|
||||
@@ -224,6 +232,147 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
|
||||
Predictor<Image, float[]> predictor = null;
|
||||
Predictor<Image, float[]> sePredictor = null;
|
||||
try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
|
||||
//初始化predictors
|
||||
faceDetectManager.borrowPredictors();
|
||||
predictor = predictorPool.borrowObject();
|
||||
sePredictor = sePredictorPool.borrowObject();
|
||||
//滑动窗口
|
||||
Deque<Float> scoreWindow = new ArrayDeque<>();
|
||||
grabber.start();
|
||||
// 获取视频总帧数
|
||||
int totalFrames = grabber.getLengthInFrames();
|
||||
log.debug("视频总帧数:{},检测帧数:{}", totalFrames, config.getFrameCount());
|
||||
if(totalFrames < config.getFrameCount()){
|
||||
return R.fail(10001, "视频帧数低于检测帧数");
|
||||
}
|
||||
// 逐帧处理视频
|
||||
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
||||
if(frameIndex >= config.getMaxVideoDetectFrames()){
|
||||
return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames());
|
||||
}
|
||||
// 获取当前帧
|
||||
Frame frame = grabber.grabImage();
|
||||
if (frame != null) {
|
||||
if(converterToMat == null){
|
||||
converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
|
||||
}
|
||||
Mat mat = converterToMat.convert(frame);
|
||||
Image image = SmartImageFactory.getInstance().fromMat(mat);
|
||||
R<LivenessResult> livenessScore = detectVideoFrame(faceDetectManager, image, predictor, sePredictor);
|
||||
mat.release();
|
||||
if(!livenessScore.isSuccess()){
|
||||
log.debug("第" + frameIndex + "帧处理失败:" + livenessScore.getMessage());
|
||||
continue;
|
||||
}else{
|
||||
log.debug("第" + frameIndex + "帧活体检测结果:" + livenessScore);
|
||||
scoreWindow.add(livenessScore.getData().getScore());
|
||||
}
|
||||
// 如果累计检测帧数 >= 配置值,开始判断
|
||||
if (scoreWindow.size() >= config.getFrameCount()) {
|
||||
float avgScore = (float) scoreWindow.stream()
|
||||
.mapToDouble(Float::doubleValue)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
log.debug("滑动窗口平均得分: {}", avgScore);
|
||||
grabber.stop();
|
||||
LivenessStatus livenessStatus = avgScore > config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
|
||||
return R.ok(new LivenessResult(livenessStatus, avgScore));
|
||||
}
|
||||
}
|
||||
}
|
||||
grabber.stop();
|
||||
if(scoreWindow.size() < config.getFrameCount()){
|
||||
return R.fail(1000, "有效帧数量不足,无法完成活体检测");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException(e);
|
||||
} finally {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
predictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sePredictor != null) {
|
||||
try {
|
||||
sePredictorPool.returnObject(sePredictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
sePredictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
grabber.release();
|
||||
} catch (FFmpegFrameGrabber.Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return R.fail(R.Status.Unknown);
|
||||
}
|
||||
|
||||
private R<LivenessResult> detectVideoFrame(FaceDetectManager faceDetectManager, Image image, Predictor<Image, float[]> predictor, Predictor<Image, float[]> sePredictor) {
|
||||
try {
|
||||
//检测人脸
|
||||
R<DetectionInfo> detectResult = faceDetectManager.detectTopFace(image);
|
||||
if(!detectResult.isSuccess()){
|
||||
return R.fail(detectResult.getCode(), detectResult.getMessage());
|
||||
}
|
||||
DetectionInfo detectionInfo = detectResult.getData();
|
||||
float[] result = null;
|
||||
float[] seResult = null;
|
||||
//预处理图片
|
||||
Image processedImage = new DJLImagePreprocessor(image, detectionInfo.getDetectionRectangle())
|
||||
.setExtendRatio(2.7f)
|
||||
.enableSquarePadding(true)
|
||||
.enableScaling(true)
|
||||
.setTargetSize(80)
|
||||
.process();
|
||||
result = predictor.predict(processedImage);
|
||||
ImageUtils.releaseOpenCVMat(processedImage);
|
||||
//预处理图片
|
||||
Image seProcessedImage = new DJLImagePreprocessor(image, detectionInfo.getDetectionRectangle())
|
||||
.setExtendRatio(4)
|
||||
.enableSquarePadding(true)
|
||||
.enableScaling(true)
|
||||
.setTargetSize(80)
|
||||
.process();
|
||||
seResult = sePredictor.predict(seProcessedImage);
|
||||
ImageUtils.releaseOpenCVMat(seProcessedImage);
|
||||
if(Objects.isNull(result) && Objects.isNull(seResult)){
|
||||
throw new FaceException("活体检测错误");
|
||||
}
|
||||
//计算结果
|
||||
int maxIndex = ArrayUtils.sumAndFindMaxIndex(result, seResult, 3);
|
||||
BigDecimal score = Objects.isNull(result) ? BigDecimal.ZERO : BigDecimal.valueOf(result[maxIndex]);
|
||||
BigDecimal seScore = Objects.isNull(seResult) ? BigDecimal.ZERO : BigDecimal.valueOf(seResult[maxIndex]);
|
||||
BigDecimal avgSocre = score.add(seScore).divide(BigDecimal.valueOf(2), 2, RoundingMode.HALF_UP);
|
||||
//活体
|
||||
if(maxIndex == 1){
|
||||
LivenessStatus livenessStatus = avgSocre.floatValue() > config.getRealityThreshold() ? LivenessStatus.LIVE : LivenessStatus.NON_LIVE;
|
||||
return R.ok(new LivenessResult(livenessStatus, avgSocre.floatValue()));
|
||||
}else{//非活体
|
||||
return R.ok(new LivenessResult(LivenessStatus.NON_LIVE, BigDecimal.ONE.subtract(avgSocre).floatValue()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("活体检测错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
public GenericObjectPool<Predictor<Image, float[]>> getPredictorPool() {
|
||||
return predictorPool;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package cn.smartjavaai.face.model.liveness;
|
||||
|
||||
import ai.djl.engine.Engine;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.*;
|
||||
import cn.smartjavaai.common.entity.face.FaceInfo;
|
||||
@@ -15,6 +17,8 @@ import cn.smartjavaai.common.enums.face.LivenessStatus;
|
||||
import cn.smartjavaai.face.constant.LivenessConstant;
|
||||
import cn.smartjavaai.face.exception.FaceException;
|
||||
import cn.smartjavaai.face.factory.LivenessModelFactory;
|
||||
import cn.smartjavaai.face.model.facedect.FaceDetectManager;
|
||||
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
||||
import cn.smartjavaai.face.seetaface.NativeLoader;
|
||||
import cn.smartjavaai.face.utils.FaceUtils;
|
||||
import cn.smartjavaai.face.utils.Seetaface6Utils;
|
||||
@@ -23,6 +27,7 @@ import com.seeta.sdk.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import nu.pattern.OpenCV;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.bytedeco.javacv.Java2DFrameUtils;
|
||||
@@ -60,6 +65,9 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new FaceException("modelPath is null");
|
||||
}
|
||||
if(Objects.isNull(config.getDetectModel())){
|
||||
throw new FaceException("未指定人脸检测模型");
|
||||
}
|
||||
this.config = config;
|
||||
//加载依赖库
|
||||
NativeLoader.loadNativeLibraries(config.getDevice());
|
||||
@@ -176,10 +184,41 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
}
|
||||
}
|
||||
|
||||
private R<LivenessResult> detectVideoFrame(Image image, FaceDetectManager faceDetectManager, FaceAntiSpoofing faceAntiSpoofing) {
|
||||
//检测人脸
|
||||
R<DetectionInfo> detectResult = faceDetectManager.detectTopFace(image);
|
||||
if(!detectResult.isSuccess()){
|
||||
return R.fail(detectResult.getCode(), detectResult.getMessage());
|
||||
}
|
||||
DetectionInfo detectionInfo = detectResult.getData();
|
||||
if(Objects.isNull(detectionInfo)){
|
||||
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||
}
|
||||
if(detectionInfo.getFaceInfo().getKeyPoints() == null || detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
|
||||
return R.fail(1002,"人脸关键点keyPoints为空");
|
||||
}
|
||||
FaceAntiSpoofing.Status status = null;
|
||||
try {
|
||||
SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
SeetaRect seetaRect = Seetaface6Utils.convertToSeetaRect(detectionInfo.getDetectionRectangle());
|
||||
SeetaPointF[] landmarks = Seetaface6Utils.convertToSeetaPointF(detectionInfo.getFaceInfo().getKeyPoints());
|
||||
//检测视频
|
||||
status = faceAntiSpoofing.PredictVideo(imageData, seetaRect, landmarks);
|
||||
return R.ok(new LivenessResult(Seetaface6Utils.convertToLivenessStatus(status)));
|
||||
} catch (Exception e) {
|
||||
throw new FaceException("活体检测错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
|
||||
FaceAntiSpoofing faceAntiSpoofing = null;
|
||||
try {
|
||||
try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
|
||||
//初始化predictors
|
||||
faceDetectManager.borrowPredictors();
|
||||
faceAntiSpoofing = faceAntiSpoofingPool.borrowObject();
|
||||
//重置视频
|
||||
faceAntiSpoofing.ResetVideo();
|
||||
@@ -194,14 +233,14 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
// 逐帧处理视频
|
||||
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
||||
if(frameIndex >= config.getMaxVideoDetectFrames()){
|
||||
return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames());
|
||||
return R.fail(10002, "视频中未检测到人脸,超出最大检测帧数:" + config.getMaxVideoDetectFrames());
|
||||
}
|
||||
// 获取当前帧
|
||||
Frame frame = grabber.grabImage();
|
||||
if (frame != null) {
|
||||
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
|
||||
Image image = SmartImageFactory.getInstance().fromBufferedImage(bufferedImage);
|
||||
R<LivenessResult> livenessStatus = detectTopFace(image, false);
|
||||
R<LivenessResult> livenessStatus = detectVideoFrame(image, faceDetectManager, faceAntiSpoofing);
|
||||
if(!livenessStatus.isSuccess()){
|
||||
log.debug("第" + frameIndex + "帧处理失败:" + livenessStatus.getMessage());
|
||||
continue;
|
||||
@@ -225,10 +264,17 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
log.warn("归还Predictor失败", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
grabber.release();
|
||||
} catch (FFmpegFrameGrabber.Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return R.fail(1000, "有效帧数量不足,无法完成活体检测");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public R<DetectionResponse> detect(Image image) {
|
||||
FaceAntiSpoofing faceAntiSpoofing = null;
|
||||
@@ -246,7 +292,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||
}
|
||||
for(SeetaRect seetaRect : seetaResult){
|
||||
@@ -346,7 +392,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
|
||||
imageData.data = ImageUtils.getMatrixBGR(image);
|
||||
//检测人脸
|
||||
SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
|
||||
if(Objects.isNull(seetaResult)){
|
||||
if(Objects.isNull(seetaResult) || seetaResult.length == 0){
|
||||
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||
}
|
||||
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.smartjavaai.face.seetaface;
|
||||
|
||||
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
||||
import com.seeta.sdk.FaceDetector;
|
||||
import com.seeta.sdk.FaceLandmarker;
|
||||
|
||||
/**
|
||||
* SeetaFace6 人脸检测Detector
|
||||
* @author dwj
|
||||
*/
|
||||
public class SeetaFace6FaceDetPredictors implements AutoCloseable{
|
||||
|
||||
public FaceDetector faceDetector;
|
||||
public FaceLandmarker faceLandmarker;
|
||||
public SeetaFace6FaceDetModel model;
|
||||
|
||||
public SeetaFace6FaceDetPredictors(FaceDetector faceDetector, FaceLandmarker faceLandmarker, SeetaFace6FaceDetModel model) {
|
||||
this.faceDetector = faceDetector;
|
||||
this.faceLandmarker = faceLandmarker;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
model.returnPredictor(faceDetector, faceLandmarker);
|
||||
}
|
||||
}
|
||||
@@ -637,7 +637,8 @@ public class MilvusClient implements VectorDBClient {
|
||||
|
||||
List<FaceVector> result = new ArrayList<>();
|
||||
for (QueryResultsWrapper.RowRecord row : records) {
|
||||
String id = (String) row.get(VectorDBConstants.FieldNames.ID_FIELD);
|
||||
Object idObj = row.get(VectorDBConstants.FieldNames.ID_FIELD);
|
||||
String id = idObj != null ? idObj.toString() : null;
|
||||
Object vectorObj = row.get(VectorDBConstants.FieldNames.VECTOR_FIELD);
|
||||
float[] vector = null;
|
||||
if (vectorObj instanceof List<?>) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.smartjavaai.face.vector.core;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.common.executor.GlobalExecutor;
|
||||
import cn.smartjavaai.common.utils.SimilarityUtil;
|
||||
import cn.smartjavaai.face.dao.FaceDao;
|
||||
import cn.smartjavaai.face.entity.FaceSearchParams;
|
||||
@@ -23,12 +24,9 @@ import java.util.stream.Collectors;
|
||||
public class SQLiteClient implements VectorDBClient {
|
||||
|
||||
private final FaceDao faceDao;
|
||||
//private final List<FaceVector> memoryIndex = new CopyOnWriteArrayList<>();
|
||||
private final ConcurrentHashMap<String, FaceVector> memoryIndex = new ConcurrentHashMap<>();
|
||||
private int featureDimension; // 维度
|
||||
|
||||
private final ExecutorService executor = Executors.newFixedThreadPool(4);
|
||||
|
||||
private SQLiteConfig config;
|
||||
|
||||
/**
|
||||
@@ -162,7 +160,7 @@ public class SQLiteClient implements VectorDBClient {
|
||||
return similarity >= faceSearchParams.getThreshold() ?
|
||||
new FaceSearchResult(vector.getId(), similarity, vector.getMetadata()) :
|
||||
null;
|
||||
}, executor))
|
||||
}, GlobalExecutor.getExecutor()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 收集结果并过滤null
|
||||
@@ -185,15 +183,7 @@ public class SQLiteClient implements VectorDBClient {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ocr</artifactId>
|
||||
@@ -42,7 +42,7 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>ocr</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -7,7 +7,7 @@
|
||||
<name>SmartJavaAI</name>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<description>SmartJavaAI</description>
|
||||
<modules>
|
||||
@@ -26,7 +26,7 @@
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<djl.version>0.32.0</djl.version>
|
||||
<djl.version>0.34.0</djl.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>speech</artifactId>
|
||||
@@ -57,7 +57,7 @@
|
||||
</dependencies>
|
||||
|
||||
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>speech</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>translate</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>vision</artifactId>
|
||||
<version>1.0.27</version>
|
||||
<version>1.1.0</version>
|
||||
<name>vision</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ActionRecModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ActionRecModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private ActionRecModel createFaceDetModel(ActionRecModelConfig config) {
|
||||
private ActionRecModel createModel(ActionRecModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -66,8 +66,9 @@ public interface ClipModel extends AutoCloseable{
|
||||
|
||||
/**
|
||||
* 图片特征比较
|
||||
* @param image1 图1
|
||||
* @param image2 图2
|
||||
* @param image1
|
||||
* @param image2
|
||||
* @param scale
|
||||
* @return
|
||||
*/
|
||||
default R<Float> compareImage(Image image1, Image image2, float scale){
|
||||
@@ -115,10 +116,12 @@ public interface ClipModel extends AutoCloseable{
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文本特征比较
|
||||
* @param feature1 文本1
|
||||
* @param feature2 文本2
|
||||
* 特征比较
|
||||
* @param feature1
|
||||
* @param feature2
|
||||
* @param scale
|
||||
* @return
|
||||
*/
|
||||
default R<Float> compareFeatures(float[] feature1, float[] feature2, float scale){
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ClipModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class ClipModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private ClipModel createFaceDetModel(ClipModelConfig config) {
|
||||
private ClipModel createModel(ClipModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ClsModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class ClsModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private ClsModel createFaceDetModel(ClsModelConfig config) {
|
||||
private ClsModel createModel(ClsModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -54,7 +54,7 @@ public class InstanceSegModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class InstanceSegModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private InstanceSegModel createFaceDetModel(InstanceSegModelConfig config) {
|
||||
private InstanceSegModel createModel(InstanceSegModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -54,7 +54,7 @@ public class ObbDetModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class ObbDetModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private ObbDetModel createFaceDetModel(ObbDetModelConfig config) {
|
||||
private ObbDetModel createModel(ObbDetModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PersonDetModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class PersonDetModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private PersonDetModel createFaceDetModel(PersonDetModelConfig config) {
|
||||
private PersonDetModel createModel(PersonDetModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PoseDetModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class PoseDetModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private PoseModel createFaceDetModel(PoseModelConfig config) {
|
||||
private PoseModel createModel(PoseModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SemSegModelFactory {
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createFaceDetModel(config);
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class SemSegModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private SemSegModel createFaceDetModel(SemSegModelConfig config) {
|
||||
private SemSegModel createModel(SemSegModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.smartjavaai.zeroshot.config;
|
||||
|
||||
import cn.smartjavaai.common.config.ModelConfig;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 零样本目标检测模型参数配置
|
||||
*
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class ZeroDetConfig extends ModelConfig {
|
||||
|
||||
/**
|
||||
* 模型
|
||||
*/
|
||||
private ZeroDetModelEnum modelEnum;
|
||||
|
||||
|
||||
/**
|
||||
* 模型路径
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
/**
|
||||
* 置信度阈值
|
||||
*/
|
||||
private float threshold = 0.3f;
|
||||
|
||||
|
||||
public ZeroDetConfig() {
|
||||
}
|
||||
|
||||
public ZeroDetConfig(ZeroDetModelEnum modelEnum, DeviceEnum device) {
|
||||
this.modelEnum = modelEnum;
|
||||
setDevice(device);
|
||||
}
|
||||
|
||||
public ZeroDetConfig(ZeroDetModelEnum modelEnum) {
|
||||
this.modelEnum = modelEnum;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.smartjavaai.zeroshot.criteria;
|
||||
|
||||
import ai.djl.Device;
|
||||
import ai.djl.huggingface.translator.ZeroShotObjectDetectionTranslatorFactory;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.VisionLanguageInput;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.modality.cv.translator.YoloWorldTranslatorFactory;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import ai.djl.translate.TranslatorFactory;
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 零样本目标检测Criteria工厂
|
||||
* @author dwj
|
||||
*/
|
||||
public class ZeroDetCriteriaFactory {
|
||||
|
||||
|
||||
public static Criteria<VisionLanguageInput, DetectedObjects> createCriteria(ZeroDetConfig config) {
|
||||
Device device = null;
|
||||
if(!Objects.isNull(config.getDevice())){
|
||||
device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId());
|
||||
}
|
||||
TranslatorFactory translatorFactory = null;
|
||||
if(config.getModelEnum() == ZeroDetModelEnum.OWLV2_BASE_PATCH16){
|
||||
translatorFactory = new ZeroShotObjectDetectionTranslatorFactory();
|
||||
}else if(config.getModelEnum() == ZeroDetModelEnum.YOLOV8S_WORLDV2){
|
||||
translatorFactory = new YoloWorldTranslatorFactory();
|
||||
}
|
||||
Criteria<VisionLanguageInput, DetectedObjects> criteria =
|
||||
Criteria.builder()
|
||||
.setTypes(VisionLanguageInput.class, DetectedObjects.class)
|
||||
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null :
|
||||
config.getModelEnum().getModelUri())
|
||||
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
|
||||
.optDevice(device)
|
||||
.optEngine(config.getModelEnum().getEngine())
|
||||
.optTranslatorFactory(translatorFactory)
|
||||
.optProgress(new ProgressBar())
|
||||
.build();
|
||||
|
||||
return criteria;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.smartjavaai.zeroshot.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 检测参数
|
||||
* @author dwj
|
||||
*/
|
||||
@Data
|
||||
public class DetectParams {
|
||||
|
||||
/**
|
||||
* 置信度阈值
|
||||
*/
|
||||
private float threshold = 0.3f;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.smartjavaai.zeroshot.enums;
|
||||
|
||||
/**
|
||||
* 零样本目标检测模型枚举
|
||||
* @author dwj
|
||||
*/
|
||||
public enum ZeroDetModelEnum {
|
||||
|
||||
YOLOV8S_WORLDV2("PyTorch", "djl://ai.djl.pytorch/yolov8s-worldv2"),
|
||||
OWLV2_BASE_PATCH16("PyTorch", "djl://ai.djl.huggingface.pytorch/google/owlv2-base-patch16");
|
||||
|
||||
|
||||
/**
|
||||
* 根据名称获取枚举 (忽略大小写和下划线变体)
|
||||
*/
|
||||
public static ZeroDetModelEnum fromName(String name) {
|
||||
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
|
||||
for (ZeroDetModelEnum model : values()) {
|
||||
if (model.name().replaceAll("_", "").equals(formatted)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知模型名称: " + name);
|
||||
}
|
||||
|
||||
private final String modelUri;
|
||||
|
||||
/**
|
||||
* 模型引擎
|
||||
*/
|
||||
private final String engine;
|
||||
|
||||
ZeroDetModelEnum(String engine, String modelUri) {
|
||||
this.modelUri = modelUri;
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
public String getModelUri() {
|
||||
return modelUri;
|
||||
}
|
||||
|
||||
public String getEngine() {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.smartjavaai.zeroshot.exception;
|
||||
|
||||
/**
|
||||
* 零样本目标检测异常
|
||||
* @author dwj
|
||||
*/
|
||||
public class ZeroDetException extends RuntimeException{
|
||||
|
||||
public ZeroDetException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ZeroDetException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public ZeroDetException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ZeroDetException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ZeroDetException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package cn.smartjavaai.zeroshot.model;
|
||||
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.engine.Engine;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.VisionLanguageInput;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.objectdetection.exception.DetectionException;
|
||||
import cn.smartjavaai.vision.utils.DetectedObjectsFilter;
|
||||
import cn.smartjavaai.vision.utils.DetectorUtils;
|
||||
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||
import cn.smartjavaai.zeroshot.criteria.ZeroDetCriteriaFactory;
|
||||
import cn.smartjavaai.zeroshot.exception.ZeroDetException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 零样本目标检测模型
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class CommonZeroDetModel implements ZeroDetModel {
|
||||
|
||||
|
||||
private ZeroDetConfig config;
|
||||
|
||||
private ZooModel<VisionLanguageInput, DetectedObjects> model;
|
||||
|
||||
private GenericObjectPool<Predictor<VisionLanguageInput, DetectedObjects>> predictorPool;
|
||||
|
||||
@Override
|
||||
public void loadModel(ZeroDetConfig config) {
|
||||
if(Objects.isNull(config.getModelEnum())){
|
||||
throw new DetectionException("未配置模型枚举");
|
||||
}
|
||||
Criteria<VisionLanguageInput, DetectedObjects> criteria = ZeroDetCriteriaFactory.createCriteria(config);
|
||||
this.config = config;
|
||||
try {
|
||||
model = criteria.loadModel();
|
||||
// 创建池子:每个线程独享 Predictor
|
||||
this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model));
|
||||
int predictorPoolSize = config.getPredictorPoolSize();
|
||||
if(config.getPredictorPoolSize() <= 0){
|
||||
predictorPoolSize = Runtime.getRuntime().availableProcessors(); // 默认等于CPU核心数
|
||||
}
|
||||
predictorPool.setMaxTotal(predictorPoolSize);
|
||||
log.debug("当前设备: " + model.getNDManager().getDevice());
|
||||
log.debug("当前引擎: " + Engine.getInstance().getEngineName());
|
||||
log.debug("模型推理器线程池最大数量: " + predictorPoolSize);
|
||||
} catch (IOException | ModelNotFoundException | MalformedModelException e) {
|
||||
throw new DetectionException("模型加载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<DetectionResponse> detect(Image image, String[] candidates) {
|
||||
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(image, candidates));
|
||||
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image);
|
||||
return R.ok(detectionResponse);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 模型核心推理方法
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public DetectedObjects detectCore(VisionLanguageInput input) {
|
||||
Predictor<VisionLanguageInput, DetectedObjects> predictor = null;
|
||||
try {
|
||||
predictor = predictorPool.borrowObject();
|
||||
DetectedObjects detectedObjects = predictor.predict(input);
|
||||
//过滤
|
||||
if(Objects.nonNull(detectedObjects) && detectedObjects.getNumberOfObjects() > 0){
|
||||
DetectedObjectsFilter detectedObjectsFilter = new DetectedObjectsFilter(null, config.getThreshold());
|
||||
detectedObjects = detectedObjectsFilter.filter(detectedObjects);
|
||||
}
|
||||
return detectedObjects;
|
||||
} catch (Exception e) {
|
||||
throw new DetectionException("零样本目标检测错误", e);
|
||||
}finally {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
log.debug("释放资源");
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
predictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<DetectionResponse> detectAndDraw(Image image, String[] candidates) {
|
||||
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(image, candidates));
|
||||
image.drawBoundingBoxes(detectedObjects);
|
||||
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image);
|
||||
detectionResponse.setDrawnImage(image);
|
||||
return R.ok(detectionResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<DetectionResponse> detectAndDraw(String[] candidates, String imagePath, String outputPath) {
|
||||
try {
|
||||
Image img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(img, candidates));
|
||||
img.drawBoundingBoxes(detectedObjects);
|
||||
img.save(Files.newOutputStream(Paths.get(outputPath)), "png");
|
||||
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, img);
|
||||
return R.ok(detectionResponse);
|
||||
} catch (IOException e) {
|
||||
throw new ZeroDetException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fromFactory = false;
|
||||
|
||||
@Override
|
||||
public void setFromFactory(boolean fromFactory) {
|
||||
this.fromFactory = fromFactory;
|
||||
}
|
||||
public boolean isFromFactory() {
|
||||
return fromFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
try {
|
||||
if (predictorPool != null) {
|
||||
predictorPool.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭 predictorPool 失败", e);
|
||||
}
|
||||
try {
|
||||
if (model != null) {
|
||||
model.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭 model 失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.smartjavaai.zeroshot.model;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.VisionLanguageInput;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.entity.R;
|
||||
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||
|
||||
/**
|
||||
* 零样本目标检测模型
|
||||
* @author dwj
|
||||
*/
|
||||
|
||||
public interface ZeroDetModel extends AutoCloseable{
|
||||
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
void loadModel(ZeroDetConfig config);
|
||||
|
||||
/**
|
||||
* 零样本目标检测
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
default R<DetectionResponse> detect(Image image, String[] candidates){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
default DetectedObjects detectCore(VisionLanguageInput input){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
default R<DetectionResponse> detectAndDraw(Image image, String[] candidates){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
default R<DetectionResponse> detectAndDraw(String[] candidates, String imagePath, String outputPath){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
default void setFromFactory(boolean fromFactory){
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package cn.smartjavaai.zeroshot.model;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.objectdetection.exception.DetectionException;
|
||||
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 零样本目标检测 模型工厂
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZeroDetModelFactory {
|
||||
|
||||
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
|
||||
private static volatile ZeroDetModelFactory instance;
|
||||
|
||||
private static final ConcurrentHashMap<ZeroDetModelEnum, ZeroDetModel> modelMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 模型注册表
|
||||
*/
|
||||
private static final Map<ZeroDetModelEnum, Class<? extends ZeroDetModel>> registry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
// 私有构造函数,防止外部创建实例
|
||||
private ZeroDetModelFactory() {}
|
||||
|
||||
// 双重检查锁定的单例方法
|
||||
public static ZeroDetModelFactory getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (ZeroDetModelFactory.class) {
|
||||
if (instance == null) {
|
||||
instance = new ZeroDetModelFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型(通过配置)
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public ZeroDetModel getModel(ZeroDetConfig config) {
|
||||
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
|
||||
throw new DetectionException("未配置模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||
return createModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用ModelConfig创建模型
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private ZeroDetModel createModel(ZeroDetConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum());
|
||||
if(clazz == null){
|
||||
throw new DetectionException("Unsupported model");
|
||||
}
|
||||
ZeroDetModel model = null;
|
||||
try {
|
||||
model = (ZeroDetModel) clazz.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new DetectionException(e);
|
||||
}
|
||||
model.loadModel(config);
|
||||
model.setFromFactory(true);
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册模型
|
||||
* @param modelEnum
|
||||
* @param clazz
|
||||
*/
|
||||
private static void registerAlgorithm(ZeroDetModelEnum modelEnum, Class<? extends ZeroDetModel> clazz) {
|
||||
registry.put(modelEnum, clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除缓存的模型
|
||||
* @param modelEnum
|
||||
*/
|
||||
public static void removeFromCache(ZeroDetModelEnum modelEnum) {
|
||||
modelMap.remove(modelEnum);
|
||||
}
|
||||
|
||||
|
||||
// 初始化默认算法
|
||||
static {
|
||||
registerAlgorithm(ZeroDetModelEnum.YOLOV8S_WORLDV2, CommonZeroDetModel.class);
|
||||
registerAlgorithm(ZeroDetModelEnum.OWLV2_BASE_PATCH16, CommonZeroDetModel.class);
|
||||
log.debug("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user