From efce16834b4af63167ed0870c889424f4aaa9408 Mon Sep 17 00:00:00 2001
From: dengwenjie <775747758@qq.com>
Date: Wed, 26 Nov 2025 18:51:59 +0800
Subject: [PATCH 1/2] =?UTF-8?q?1=E3=80=81=E3=80=90=E6=A0=B8=E5=BF=83?=
=?UTF-8?q?=E5=8D=87=E7=BA=A7=E3=80=91=E5=8D=87=E7=BA=A7DJL=E7=89=88?=
=?UTF-8?q?=E6=9C=AC=E5=88=B00.34.0=202=E3=80=81=E3=80=90=E5=B9=B3?=
=?UTF-8?q?=E5=8F=B0=E6=94=AF=E6=8C=81=E3=80=91=E6=96=B0=E5=A2=9E=E5=AF=B9?=
=?UTF-8?q?=20Linux=20ARM64=20=E6=9E=B6=E6=9E=84=E7=9A=84=E5=85=A8?=
=?UTF-8?q?=E9=9D=A2=E6=94=AF=E6=8C=81=203=E3=80=81=E3=80=90=E9=80=9A?=
=?UTF-8?q?=E7=94=A8=E8=A7=86=E8=A7=89=E3=80=91=E9=9B=86=E6=88=90=E9=9B=B6?=
=?UTF-8?q?=E6=A0=B7=E6=9C=AC=E7=9B=AE=E6=A0=87=E6=A3=80=E6=B5=8B=E6=A8=A1?=
=?UTF-8?q?=E5=9E=8B=204=E3=80=81=E3=80=90=E6=B4=BB=E4=BD=93=E6=A3=80?=
=?UTF-8?q?=E6=B5=8B=E3=80=91=E4=BC=98=E5=8C=96=E8=A7=86=E9=A2=91=E6=A3=80?=
=?UTF-8?q?=E6=B5=8B=E6=B5=81=E7=A8=8B=EF=BC=8C=E5=AE=9E=E7=8E=B0=20Predic?=
=?UTF-8?q?tor=20=E8=A7=86=E9=A2=91=E4=BC=9A=E8=AF=9D=E7=BA=A7=E5=A4=8D?=
=?UTF-8?q?=E7=94=A8=205=E3=80=81=E3=80=90=E4=BA=BA=E8=84=B8=E8=AF=86?=
=?UTF-8?q?=E5=88=AB=E3=80=91SQLite=E4=BA=BA=E8=84=B8=E6=9F=A5=E8=AF=A2?=
=?UTF-8?q?=E6=94=B9=E8=BF=9B=E7=BA=BF=E7=A8=8B=E6=B1=A0=206=E3=80=81?=
=?UTF-8?q?=E3=80=90=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB=E3=80=91=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=20Milvus=20=E5=90=91=E9=87=8F=E5=BA=93=E4=B8=8B=20lis?=
=?UTF-8?q?tFaces=20=E6=8E=A5=E5=8F=A3=E7=9A=84=E8=B0=83=E7=94=A8=E5=BC=82?=
=?UTF-8?q?=E5=B8=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 15 +-
all/pom.xml | 4 +-
bom/pom.xml | 4 +-
common/pom.xml | 2 +-
.../common/executor/GlobalExecutor.java | 53 ++++++
examples/face-example/pom.xml | 47 +++--
examples/ocr-examples/pom.xml | 45 ++++-
examples/speech-examples/pom.xml | 2 +-
examples/translation-example/pom.xml | 18 +-
.../nlp/translation/TranslationDemo.java | 2 +-
examples/vision-example/pom.xml | 60 ++++++-
.../java/smartai/examples/vision/ClsDemo.java | 2 +-
.../vision/ZeroShotObjectDetectionDemo.java | 137 +++++++++++++++
face/pom.xml | 8 +-
.../Seetaface6FaceAttributeModel.java | 8 +-
.../model/facedect/CommonFaceDetModel.java | 25 +++
.../face/model/facedect/FaceDetModel.java | 16 ++
.../model/facedect/FaceDetectManager.java | 99 +++++++++++
.../model/facedect/MtcnnFaceDetModel.java | 140 ++++++++++-----
.../facedect/SeetaFace6FaceDetModel.java | 52 ++++++
.../model/facedect/mtcnn/MtcnnPredictors.java | 31 ++++
.../model/liveness/CommonLivenessModel.java | 70 +++++++-
.../liveness/MiniVisionLivenessModel.java | 149 ++++++++++++++++
.../liveness/Seetaface6LivenessModel.java | 56 +++++-
.../SeetaFace6FaceDetPredictors.java | 27 +++
.../face/vector/core/MilvusClient.java | 3 +-
.../face/vector/core/SQLiteClient.java | 16 +-
ocr/pom.xml | 4 +-
pom.xml | 4 +-
speech/pom.xml | 4 +-
translate/pom.xml | 2 +-
vision/pom.xml | 4 +-
.../action/model/ActionRecModelFactory.java | 4 +-
.../cn/smartjavaai/clip/model/ClipModel.java | 13 +-
.../clip/model/ClipModelFactory.java | 4 +-
.../cls/model/ClsModelFactory.java | 4 +-
.../model/InstanceSegModelFactory.java | 4 +-
.../obb/model/ObbDetModelFactory.java | 4 +-
.../model/person/PersonDetModelFactory.java | 4 +-
.../pose/model/PoseDetModelFactory.java | 4 +-
.../semseg/model/SemSegModelFactory.java | 4 +-
.../zeroshot/config/ZeroDetConfig.java | 48 ++++++
.../criteria/ZeroDetCriteriaFactory.java | 53 ++++++
.../zeroshot/entity/DetectParams.java | 17 ++
.../zeroshot/enums/ZeroDetModelEnum.java | 45 +++++
.../zeroshot/exception/ZeroDetException.java | 29 ++++
.../zeroshot/model/CommonZeroDetModel.java | 162 ++++++++++++++++++
.../zeroshot/model/ZeroDetModel.java | 50 ++++++
.../zeroshot/model/ZeroDetModelFactory.java | 109 ++++++++++++
49 files changed, 1517 insertions(+), 150 deletions(-)
create mode 100644 common/src/main/java/cn/smartjavaai/common/executor/GlobalExecutor.java
create mode 100644 examples/vision-example/src/main/java/smartai/examples/vision/ZeroShotObjectDetectionDemo.java
create mode 100644 face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetectManager.java
create mode 100644 face/src/main/java/cn/smartjavaai/face/model/facedect/mtcnn/MtcnnPredictors.java
create mode 100644 face/src/main/java/cn/smartjavaai/face/seetaface/SeetaFace6FaceDetPredictors.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/config/ZeroDetConfig.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/criteria/ZeroDetCriteriaFactory.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/entity/DetectParams.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/enums/ZeroDetModelEnum.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/exception/ZeroDetException.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/model/CommonZeroDetModel.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModel.java
create mode 100644 vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModelFactory.java
diff --git a/README.md b/README.md
index c718f5f..56aac77 100644
--- a/README.md
+++ b/README.md
@@ -242,6 +242,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
+
@@ -483,7 +496,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
cn.smartjavaai
all
- 1.0.27
+ 1.1.0
```
diff --git a/all/pom.xml b/all/pom.xml
index be65c94..63c7d14 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -6,11 +6,11 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
all
- 1.0.27
+ 1.1.0
${project.artifactId}
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/bom/pom.xml b/bom/pom.xml
index c50ce45..815bba0 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -6,10 +6,10 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
- 1.0.27
+ 1.1.0
bom
bom
统一版本管理的 BOM 包,同时支持 import 和全量依赖
diff --git a/common/pom.xml b/common/pom.xml
index 23ad847..e8d4e2c 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -6,7 +6,7 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
common
diff --git a/common/src/main/java/cn/smartjavaai/common/executor/GlobalExecutor.java b/common/src/main/java/cn/smartjavaai/common/executor/GlobalExecutor.java
new file mode 100644
index 0000000..035f8b5
--- /dev/null
+++ b/common/src/main/java/cn/smartjavaai/common/executor/GlobalExecutor.java
@@ -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();
+ }
+ }
+ }
+
+}
diff --git a/examples/face-example/pom.xml b/examples/face-example/pom.xml
index ba03477..78c8918 100644
--- a/examples/face-example/pom.xml
+++ b/examples/face-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.27
+ 1.1.0
smartai.examples.face.facedet.FaceDetDemo
@@ -88,11 +88,10 @@
face
-
ai.djl.pytorch
pytorch-jni
- 2.5.1-0.32.0
+ 2.7.1-0.34.0
runtime
@@ -129,7 +128,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.windows-x86_64}
- 2.5.1
+ 2.7.1
runtime
@@ -167,19 +166,45 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.linux-x86_64}
- 2.5.1
+ 2.7.1
runtime
+
+
+
+ org.bytedeco
+ javacpp
+ ${javacv.version}
+ ${javacv.platform.linux-arm64}
+
- ai.djl.pytorch
- pytorch-native-cpu-precxx11
- ${djl.platform.linux-x86_64}
- 2.5.1
- runtime
+ org.bytedeco
+ ffmpeg
+ 6.1.1-1.5.10
+ ${javacv.platform.linux-arm64}
+
+ org.bytedeco
+ openblas
+ 0.3.26-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+ org.bytedeco
+ opencv
+ 4.9.0-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+ ai.djl.pytorch
+ pytorch-native-cpu
+ ${djl.platform.linux-aarch64}
+ 2.7.1
+ runtime
+
@@ -213,7 +238,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.osx-aarch64}
- 2.5.1
+ 2.7.1
runtime
diff --git a/examples/ocr-examples/pom.xml b/examples/ocr-examples/pom.xml
index ce4ec26..b444855 100644
--- a/examples/ocr-examples/pom.xml
+++ b/examples/ocr-examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.27
+ 1.1.0
smartai.examples.ocr.common.OcrRecognizeDemo
@@ -95,7 +95,7 @@
ai.djl.pytorch
pytorch-jni
- 2.5.1-0.32.0
+ 2.7.1-0.34.0
runtime
@@ -132,7 +132,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.windows-x86_64}
- 2.5.1
+ 2.7.1
runtime
@@ -170,14 +170,43 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.linux-x86_64}
- 2.5.1
+ 2.7.1
runtime
+
+
+
+ org.bytedeco
+ javacpp
+ ${javacv.version}
+ ${javacv.platform.linux-arm64}
+
+
+ org.bytedeco
+ ffmpeg
+ 6.1.1-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+
+ org.bytedeco
+ openblas
+ 0.3.26-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+
+ org.bytedeco
+ opencv
+ 4.9.0-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
ai.djl.pytorch
- pytorch-native-cpu-precxx11
- ${djl.platform.linux-x86_64}
- 2.5.1
+ pytorch-native-cpu
+ ${djl.platform.linux-aarch64}
+ 2.7.1
runtime
@@ -214,7 +243,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.osx-aarch64}
- 2.5.1
+ 2.7.1
runtime
diff --git a/examples/speech-examples/pom.xml b/examples/speech-examples/pom.xml
index e077903..cc0c81c 100644
--- a/examples/speech-examples/pom.xml
+++ b/examples/speech-examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.27
+ 1.1.0
smartai.examples.speech.asr.common.OcrRecognizeDemo
diff --git a/examples/translation-example/pom.xml b/examples/translation-example/pom.xml
index d0012b1..b4f85e4 100644
--- a/examples/translation-example/pom.xml
+++ b/examples/translation-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.27
+ 1.1.0
smartai.examples.nlp.translation.TranslationDemo
@@ -93,7 +93,7 @@
ai.djl.pytorch
pytorch-jni
- 2.5.1-0.32.0
+ 2.7.1-0.34.0
runtime
@@ -104,7 +104,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.windows-x86_64}
- 2.5.1
+ 2.7.1
runtime
@@ -116,25 +116,25 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.linux-x86_64}
- 2.5.1
+ 2.7.1
runtime
+
ai.djl.pytorch
- pytorch-native-cpu-precxx11
- ${djl.platform.linux-x86_64}
- 2.5.1
+ pytorch-native-cpu
+ ${djl.platform.linux-aarch64}
+ 2.7.1
runtime
-
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.osx-aarch64}
- 2.5.1
+ 2.7.1
runtime
diff --git a/examples/translation-example/src/main/java/smartai/examples/nlp/translation/TranslationDemo.java b/examples/translation-example/src/main/java/smartai/examples/nlp/translation/TranslationDemo.java
index b667123..e12b62f 100644
--- a/examples/translation-example/src/main/java/smartai/examples/nlp/translation/TranslationDemo.java
+++ b/examples/translation-example/src/main/java/smartai/examples/nlp/translation/TranslationDemo.java
@@ -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);
}
diff --git a/examples/vision-example/pom.xml b/examples/vision-example/pom.xml
index 54d2a27..db39394 100644
--- a/examples/vision-example/pom.xml
+++ b/examples/vision-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.27
+ 1.1.0
smartai.examples.vision.ObjectDetectionDemo
@@ -92,7 +92,7 @@
ai.djl.pytorch
pytorch-jni
- 2.5.1-0.32.0
+ 2.7.1-0.34.0
runtime
@@ -129,7 +129,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.windows-x86_64}
- 2.5.1
+ 2.7.1
runtime
@@ -179,12 +179,11 @@
${javacv.platform.linux-x86_64}
-
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.linux-x86_64}
- 2.5.1
+ 2.7.1
runtime
@@ -203,14 +202,57 @@
1.9.1
+
+
+ org.bytedeco
+ javacpp
+ ${javacv.version}
+ ${javacv.platform.linux-arm64}
+
+
+ org.bytedeco
+ ffmpeg
+ 6.1.1-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+
+ org.bytedeco
+ openblas
+ 0.3.26-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
+
+ org.bytedeco
+ opencv
+ 4.9.0-1.5.10
+ ${javacv.platform.linux-arm64}
+
+
ai.djl.pytorch
- pytorch-native-cpu-precxx11
- ${djl.platform.linux-x86_64}
- 2.5.1
+ pytorch-native-cpu
+ ${djl.platform.linux-aarch64}
+ 2.7.1
runtime
+
+ ai.djl.tensorflow
+ tensorflow-native-cpu
+ ${javacv.platform.linux-arm64}
+ runtime
+ 2.16.1
+
+
+ ai.djl.mxnet
+ mxnet-native-mkl
+ ${javacv.platform.linux-arm64}
+ runtime
+ 1.9.1
+
+
@@ -244,7 +286,7 @@
ai.djl.pytorch
pytorch-native-cpu
${djl.platform.osx-aarch64}
- 2.5.1
+ 2.7.1
runtime
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/ClsDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/ClsDemo.java
index 955f7ee..c6a93b0 100644
--- a/examples/vision-example/src/main/java/smartai/examples/vision/ClsDemo.java
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/ClsDemo.java
@@ -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");
diff --git a/examples/vision-example/src/main/java/smartai/examples/vision/ZeroShotObjectDetectionDemo.java b/examples/vision-example/src/main/java/smartai/examples/vision/ZeroShotObjectDetectionDemo.java
new file mode 100644
index 0000000..529cf8b
--- /dev/null
+++ b/examples/vision-example/src/main/java/smartai/examples/vision/ZeroShotObjectDetectionDemo.java
@@ -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 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 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 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();
+ }
+
+ }
+
+}
diff --git a/face/pom.xml b/face/pom.xml
index 189c135..e5ea7ce 100644
--- a/face/pom.xml
+++ b/face/pom.xml
@@ -6,11 +6,11 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
face
- 1.0.27
+ 1.1.0
face
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
@@ -26,7 +26,7 @@
UTF-8
true
- 1.5.8
+ 1.5.10
5.1.2-1.5.8
@@ -57,6 +57,8 @@
+
+
diff --git a/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java b/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
index 29f6bfa..c5e43f3 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/attribute/Seetaface6FaceAttributeModel.java
@@ -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()];
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
index a1dc181..fcb1dd9 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/CommonFaceDetModel.java
@@ -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 borrowPredictor() throws Exception {
+ if(predictorPool == null){
+ throw new FaceException("请先加载模型");
+ }
+ return predictorPool.borrowObject();
+ }
+
+ @Override
+ public void returnPredictor(Predictor 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);
+ }
+ }
+ }
+ }
+
/**
* 加载模型
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
index 228c4b3..5d3768f 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetModel.java
@@ -131,4 +131,20 @@ public interface FaceDetModel extends AutoCloseable{
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
+
+ /**
+ * 获取Predictor
+ * @return
+ */
+ default Predictor borrowPredictor() throws Exception{
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ /**
+ * 归还Predictor
+ * @param predictor
+ */
+ default void returnPredictor(Predictor predictor){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetectManager.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetectManager.java
new file mode 100644
index 0000000..588d237
--- /dev/null
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/FaceDetectManager.java
@@ -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 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 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);
+ }
+ }
+}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
index e878b12..ecf9ed5 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/MtcnnFaceDetModel.java
@@ -221,52 +221,51 @@ public class MtcnnFaceDetModel extends CommonFaceDetModel{
}
+ /**
+ * 使用MtcnnPredictors进行人脸检测
+ * @param image
+ * @param predictors
+ * @return
+ */
+ public DetectedObjects detectCoreByPredictors(Image image, MtcnnPredictors predictors){
+ Predictor pNetPredictor = predictors.pNetPredictor;
+ Predictor rNetPredictor = predictors.rNetPredictor;
+ Predictor 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 detectionInfoList = new ArrayList();
-//
-// 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 keyPoints = new ArrayList();
-// 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 p = pnetPredictorPool.borrowObject();
+ Predictor r = rnetPredictorPool.borrowObject();
+ Predictor o = onetPredictorPool.borrowObject();
+ return new MtcnnPredictors(p, r, o, this);
+ }
+
+ public void returnPredictor(Predictor pNetPredictor, Predictor rNetPredictor, Predictor 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) {
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
index ef275c4..32b3bce 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/SeetaFace6FaceDetModel.java
@@ -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 seetaPointFSList = new ArrayList();
+ 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 detectAndDraw(Image image) {
R 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);
+ }
+ }
+ }
+
diff --git a/face/src/main/java/cn/smartjavaai/face/model/facedect/mtcnn/MtcnnPredictors.java b/face/src/main/java/cn/smartjavaai/face/model/facedect/mtcnn/MtcnnPredictors.java
new file mode 100644
index 0000000..f051c48
--- /dev/null
+++ b/face/src/main/java/cn/smartjavaai/face/model/facedect/mtcnn/MtcnnPredictors.java
@@ -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 pNetPredictor;
+ public Predictor rNetPredictor;
+ public Predictor oNetPredictor;
+
+ // 标记是否由外部借用,用于控制 close 行为
+ private MtcnnFaceDetModel model;
+
+ public MtcnnPredictors(Predictor p, Predictor r, Predictor 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);
+ }
+}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java b/face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java
index 9a73be3..854cefb 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/liveness/CommonLivenessModel.java
@@ -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 detectVideo(FFmpegFrameGrabber grabber) {
- try {
+ protected R detectVideo(FFmpegFrameGrabber grabber) {
+ Predictor predictor = null;
+ try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
+ //初始化predictors
+ faceDetectManager.borrowPredictors();
+ predictor = predictorPool.borrowObject();
//滑动窗口
Deque scoreWindow = new ArrayDeque<>();
grabber.start();
@@ -147,7 +158,8 @@ public class CommonLivenessModel implements LivenessDetModel{
converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
}
Mat mat = converterToMat.convert(frame);
- R livenessScore = detectTopFace(SmartImageFactory.getInstance().fromMat(mat));
+ Image image = SmartImageFactory.getInstance().fromMat(mat);
+ R 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 detectVideoFrame(FaceDetectManager faceDetectManager, Image image, Predictor predictor) {
+ //预处理图片
+ Image processedImage = null;
+ try {
+ //检测人脸
+ R 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 detectTopFace(Image image) {
R faceDetectionResponse = config.getDetectModel().detect(image);
diff --git a/face/src/main/java/cn/smartjavaai/face/model/liveness/MiniVisionLivenessModel.java b/face/src/main/java/cn/smartjavaai/face/model/liveness/MiniVisionLivenessModel.java
index 247a6c9..5013961 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/liveness/MiniVisionLivenessModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/liveness/MiniVisionLivenessModel.java
@@ -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> sePredictorPool;
+ private OpenCVFrameConverter.ToOrgOpenCvCoreMat converterToMat = null;
+
/**
* 模型策略
@@ -224,6 +232,147 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
}
}
+
+ protected R detectVideo(FFmpegFrameGrabber grabber) {
+ Predictor predictor = null;
+ Predictor sePredictor = null;
+ try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
+ //初始化predictors
+ faceDetectManager.borrowPredictors();
+ predictor = predictorPool.borrowObject();
+ sePredictor = sePredictorPool.borrowObject();
+ //滑动窗口
+ Deque 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 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 detectVideoFrame(FaceDetectManager faceDetectManager, Image image, Predictor predictor, Predictor sePredictor) {
+ try {
+ //检测人脸
+ R 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> getPredictorPool() {
return predictorPool;
}
diff --git a/face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java b/face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java
index ec1ae5e..87fa5b7 100644
--- a/face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java
+++ b/face/src/main/java/cn/smartjavaai/face/model/liveness/Seetaface6LivenessModel.java
@@ -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 detectVideoFrame(Image image, FaceDetectManager faceDetectManager, FaceAntiSpoofing faceAntiSpoofing) {
+ //检测人脸
+ R 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 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 livenessStatus = detectTopFace(image, false);
+ R 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 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()];
diff --git a/face/src/main/java/cn/smartjavaai/face/seetaface/SeetaFace6FaceDetPredictors.java b/face/src/main/java/cn/smartjavaai/face/seetaface/SeetaFace6FaceDetPredictors.java
new file mode 100644
index 0000000..5e5bda3
--- /dev/null
+++ b/face/src/main/java/cn/smartjavaai/face/seetaface/SeetaFace6FaceDetPredictors.java
@@ -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);
+ }
+}
diff --git a/face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java b/face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java
index 39595ff..ea915f1 100644
--- a/face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java
+++ b/face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java
@@ -637,7 +637,8 @@ public class MilvusClient implements VectorDBClient {
List 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>) {
diff --git a/face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java b/face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java
index f1f28ae..95ba8bf 100644
--- a/face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java
+++ b/face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java
@@ -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 memoryIndex = new CopyOnWriteArrayList<>();
private final ConcurrentHashMap 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
diff --git a/ocr/pom.xml b/ocr/pom.xml
index d5d1cef..67ad098 100644
--- a/ocr/pom.xml
+++ b/ocr/pom.xml
@@ -6,7 +6,7 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
ocr
@@ -42,7 +42,7 @@
- 1.0.27
+ 1.1.0
ocr
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/pom.xml b/pom.xml
index 0ca7dfd..c07ea22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
SmartJavaAI
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
pom
SmartJavaAI
@@ -26,7 +26,7 @@
8
8
UTF-8
- 0.32.0
+ 0.34.0
diff --git a/speech/pom.xml b/speech/pom.xml
index ee84d79..bfaf7c7 100644
--- a/speech/pom.xml
+++ b/speech/pom.xml
@@ -6,7 +6,7 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
speech
@@ -57,7 +57,7 @@
- 1.0.27
+ 1.1.0
speech
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/translate/pom.xml b/translate/pom.xml
index 9d8746d..2469f02 100644
--- a/translate/pom.xml
+++ b/translate/pom.xml
@@ -25,7 +25,7 @@
- 1.0.27
+ 1.1.0
translate
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/vision/pom.xml b/vision/pom.xml
index 7dfc497..ed6baf0 100644
--- a/vision/pom.xml
+++ b/vision/pom.xml
@@ -6,11 +6,11 @@
cn.smartjavaai
smartjavaai-parent
- 1.0.27
+ 1.1.0
vision
- 1.0.27
+ 1.1.0
vision
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
diff --git a/vision/src/main/java/cn/smartjavaai/action/model/ActionRecModelFactory.java b/vision/src/main/java/cn/smartjavaai/action/model/ActionRecModelFactory.java
index 27d43cf..5312f14 100644
--- a/vision/src/main/java/cn/smartjavaai/action/model/ActionRecModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/action/model/ActionRecModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/clip/model/ClipModel.java b/vision/src/main/java/cn/smartjavaai/clip/model/ClipModel.java
index c5c8718..7b8f169 100644
--- a/vision/src/main/java/cn/smartjavaai/clip/model/ClipModel.java
+++ b/vision/src/main/java/cn/smartjavaai/clip/model/ClipModel.java
@@ -66,8 +66,9 @@ public interface ClipModel extends AutoCloseable{
/**
* 图片特征比较
- * @param image1 图1
- * @param image2 图2
+ * @param image1
+ * @param image2
+ * @param scale
* @return
*/
default R 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 compareFeatures(float[] feature1, float[] feature2, float scale){
diff --git a/vision/src/main/java/cn/smartjavaai/clip/model/ClipModelFactory.java b/vision/src/main/java/cn/smartjavaai/clip/model/ClipModelFactory.java
index e87b796..b9cab11 100644
--- a/vision/src/main/java/cn/smartjavaai/clip/model/ClipModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/clip/model/ClipModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/cls/model/ClsModelFactory.java b/vision/src/main/java/cn/smartjavaai/cls/model/ClsModelFactory.java
index 03953e8..0863a3b 100644
--- a/vision/src/main/java/cn/smartjavaai/cls/model/ClsModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/cls/model/ClsModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/instanceseg/model/InstanceSegModelFactory.java b/vision/src/main/java/cn/smartjavaai/instanceseg/model/InstanceSegModelFactory.java
index ca52395..e5549e6 100644
--- a/vision/src/main/java/cn/smartjavaai/instanceseg/model/InstanceSegModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/instanceseg/model/InstanceSegModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/obb/model/ObbDetModelFactory.java b/vision/src/main/java/cn/smartjavaai/obb/model/ObbDetModelFactory.java
index 2cb4f79..16d01a4 100644
--- a/vision/src/main/java/cn/smartjavaai/obb/model/ObbDetModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/obb/model/ObbDetModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/objectdetection/model/person/PersonDetModelFactory.java b/vision/src/main/java/cn/smartjavaai/objectdetection/model/person/PersonDetModelFactory.java
index d480cba..af0eab0 100644
--- a/vision/src/main/java/cn/smartjavaai/objectdetection/model/person/PersonDetModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/objectdetection/model/person/PersonDetModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/pose/model/PoseDetModelFactory.java b/vision/src/main/java/cn/smartjavaai/pose/model/PoseDetModelFactory.java
index 8b5cc18..13172cd 100644
--- a/vision/src/main/java/cn/smartjavaai/pose/model/PoseDetModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/pose/model/PoseDetModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/semseg/model/SemSegModelFactory.java b/vision/src/main/java/cn/smartjavaai/semseg/model/SemSegModelFactory.java
index 285ad1c..7da0849 100644
--- a/vision/src/main/java/cn/smartjavaai/semseg/model/SemSegModelFactory.java
+++ b/vision/src/main/java/cn/smartjavaai/semseg/model/SemSegModelFactory.java
@@ -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");
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/config/ZeroDetConfig.java b/vision/src/main/java/cn/smartjavaai/zeroshot/config/ZeroDetConfig.java
new file mode 100644
index 0000000..30ae953
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/config/ZeroDetConfig.java
@@ -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;
+ }
+
+
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/criteria/ZeroDetCriteriaFactory.java b/vision/src/main/java/cn/smartjavaai/zeroshot/criteria/ZeroDetCriteriaFactory.java
new file mode 100644
index 0000000..d953eb6
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/criteria/ZeroDetCriteriaFactory.java
@@ -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 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 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;
+ }
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/entity/DetectParams.java b/vision/src/main/java/cn/smartjavaai/zeroshot/entity/DetectParams.java
new file mode 100644
index 0000000..a297396
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/entity/DetectParams.java
@@ -0,0 +1,17 @@
+package cn.smartjavaai.zeroshot.entity;
+
+import lombok.Data;
+
+/**
+ * 检测参数
+ * @author dwj
+ */
+@Data
+public class DetectParams {
+
+ /**
+ * 置信度阈值
+ */
+ private float threshold = 0.3f;
+
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/enums/ZeroDetModelEnum.java b/vision/src/main/java/cn/smartjavaai/zeroshot/enums/ZeroDetModelEnum.java
new file mode 100644
index 0000000..140397e
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/enums/ZeroDetModelEnum.java
@@ -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;
+ }
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/exception/ZeroDetException.java b/vision/src/main/java/cn/smartjavaai/zeroshot/exception/ZeroDetException.java
new file mode 100644
index 0000000..e13918e
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/exception/ZeroDetException.java
@@ -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);
+ }
+
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/model/CommonZeroDetModel.java b/vision/src/main/java/cn/smartjavaai/zeroshot/model/CommonZeroDetModel.java
new file mode 100644
index 0000000..0d2c1f9
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/model/CommonZeroDetModel.java
@@ -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 model;
+
+ private GenericObjectPool> predictorPool;
+
+ @Override
+ public void loadModel(ZeroDetConfig config) {
+ if(Objects.isNull(config.getModelEnum())){
+ throw new DetectionException("未配置模型枚举");
+ }
+ Criteria 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 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 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 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 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);
+ }
+ }
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModel.java b/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModel.java
new file mode 100644
index 0000000..9cd83fe
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModel.java
@@ -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 detect(Image image, String[] candidates){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ default DetectedObjects detectCore(VisionLanguageInput input){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ default R detectAndDraw(Image image, String[] candidates){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ default R detectAndDraw(String[] candidates, String imagePath, String outputPath){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ default void setFromFactory(boolean fromFactory){
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+
+}
diff --git a/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModelFactory.java b/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModelFactory.java
new file mode 100644
index 0000000..ead9603
--- /dev/null
+++ b/vision/src/main/java/cn/smartjavaai/zeroshot/model/ZeroDetModelFactory.java
@@ -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 modelMap = new ConcurrentHashMap<>();
+
+ /**
+ * 模型注册表
+ */
+ private static final Map> 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());
+ }
+}
+
From b0379bb2e54039f8b1b6c1305690f79b5294b18d Mon Sep 17 00:00:00 2001
From: dengwenjie <775747758@qq.com>
Date: Thu, 27 Nov 2025 10:53:35 +0800
Subject: [PATCH 2/2] =?UTF-8?q?readme=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/README.md b/README.md
index 56aac77..8694a68 100644
--- a/README.md
+++ b/README.md
@@ -420,6 +420,8 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 支持KINETICS400数据集中400个人类动作识别
- **姿态估计**
- 集成YOLOv8-pose、YOLOv11-pose等模型
+- **零样本目标检测**
+ - 集成YOLOv8s_worldv2、owlv2_base_patch16模型
- **CLIP**
- 支持提取图片及文本特征
- 支持文搜图、图搜文、图搜图
@@ -695,6 +697,14 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
| YOLOV11-OBB | OnnxRuntime | Ultralytics在DOTAv1 数据集 上训练的模型、通过引入一个额外的角度来更准确地定位图像中的对象 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
---
+#### 零样本目标检测模型
+
+| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
+|-------------|---------|--------------------------------|----------------------------------------------------------|
+| YOLOv8s-worldv2 | PyTorch | 可根据描述性文本检测图像中的任何物体 | [官网](https://docs.ultralytics.com/zh/models/yolo-world/) |
+| owlv2-base-patch16 | PyTorch | OWLv2是一种多模态模型,通过结合CLIP的骨干和ViT样的Transformer,实现零样本文本对象检测| [官网](https://huggingface.co/google/owlv2-base-patch16) |
+---
+
#### 行人检测模型
| 模型名称 | 引擎 | 模型开源网站 |
|