22 Commits
v1.0.27 ... dev

Author SHA1 Message Date
geekwenjie
dea0673026 Merge pull request #26 from geekwenjie/master
合并
2025-12-31 11:37:17 +08:00
geekwenjie
22a9121a46 Merge pull request #25 from geekwenjie/dev
Merge pull request #22 from geekwenjie/master
2025-12-31 11:35:43 +08:00
dengwenjie
1fbd8779b9 更新版本号:v1.1.1 2025-12-31 11:28:06 +08:00
dengwenjie
bc2a9f11ad 升级lombok版本 2025-12-29 17:21:04 +08:00
dengwenjie
afcf7d1903 新增android商业版本简介 2025-12-29 16:14:50 +08:00
dengwenjie
7b3426f231 新增Dockerfile及代码示例 2025-12-24 11:23:04 +08:00
dengwenjie
a707e41d9d 解决vosk中文乱码问题 2025-12-23 14:24:21 +08:00
dengwenjie
b0736c6494 优化视频目标检测 2025-12-16 16:52:33 +08:00
dengwenjie
06aee850cf 修复onnxruntime问题 2025-12-15 19:17:13 +08:00
dengwenjie
861e6c128d 修复目标检测YOLOV11枚举无法使用问题 2025-12-10 15:17:45 +08:00
dengwenjie
69c553058b 修复maven引用问题 2025-12-10 15:01:31 +08:00
dengwenjie
fefc36bc30 Merge branch 'master' of https://github.com/geekwenjie/SmartJavaAI 2025-12-08 21:13:54 +08:00
Adam
7917423190 !12 feat(cv): 新增通过URL创建Image的方法
Merge pull request !12 from 黄政棋/dev
2025-12-04 10:06:56 +00:00
hyczq
035d8919f8 feat(cv): 新增通过URL创建Image的方法
- 添加fromUrl(URL url)方法,支持通过URL对象加载图片
- 添加fromUrl(String urlString)方法,支持通过URL字符串加载图片
- 增加对URL为空参数的参数校验并抛出异常
- 实现了使用URL打开输入流并转换为Image的功能
- 提升了SmartImageFactory的灵活性和易用性
2025-12-03 17:54:46 +08:00
geekwenjie
9e8d8bb48f Merge pull request #22 from geekwenjie/master
合并
2025-11-28 09:05:12 +08:00
geekwenjie
4a7471923e Merge pull request #21 from geekwenjie/dev
Merge pull request #20 from geekwenjie/master
2025-11-28 09:02:24 +08:00
dengwenjie
b0379bb2e5 readme更新 2025-11-27 10:53:35 +08:00
dengwenjie
efce16834b 1、【核心升级】升级DJL版本到0.34.0
2、【平台支持】新增对 Linux ARM64 架构的全面支持
3、【通用视觉】集成零样本目标检测模型
4、【活体检测】优化视频检测流程,实现 Predictor 视频会话级复用
5、【人脸识别】SQLite人脸查询改进线程池
6、【人脸识别】修复 Milvus 向量库下 listFaces 接口的调用异常
2025-11-26 18:51:59 +08:00
geekwenjie
a957516a5b Merge pull request #20 from geekwenjie/master
合并最新更新
2025-11-26 16:08:01 +08:00
dengwenjie
06cb54bbf7 更新readme 2025-10-29 17:07:46 +08:00
dengwenjie
07d625d83b 更新readme 2025-10-29 17:05:07 +08:00
dengwenjie
d834008bd4 更新readme 2025-10-28 23:02:35 +08:00
69 changed files with 2110 additions and 388 deletions

114
README.md
View File

@@ -39,6 +39,22 @@
SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 JAVA AI算法工具包致力于帮助JAVA开发者零门槛使用各种AI算法模型开发者无需深入了解底层实现即可轻松在 Java 代码中调用人脸识别、目标检测、OCR 等功能。底层支持包括基于 DJL (Deep Java Library) 封装的深度学习模型,以及通过 JNI 接入的 C++/Python 算法,兼容多种主流深度学习框架如 PyTorch、TensorFlow、ONNX、Paddle 等,屏蔽复杂的模型部署与调用细节,开发者无需了解 AI 底层实现即可直接在 Java 项目中集成使用,后续将持续扩展更多算法,目标是构建一个“像 Hutool 一样简单易用”的 JAVA AI 通用工具箱 SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 JAVA AI算法工具包致力于帮助JAVA开发者零门槛使用各种AI算法模型开发者无需深入了解底层实现即可轻松在 Java 代码中调用人脸识别、目标检测、OCR 等功能。底层支持包括基于 DJL (Deep Java Library) 封装的深度学习模型,以及通过 JNI 接入的 C++/Python 算法,兼容多种主流深度学习框架如 PyTorch、TensorFlow、ONNX、Paddle 等,屏蔽复杂的模型部署与调用细节,开发者无需了解 AI 底层实现即可直接在 Java 项目中集成使用,后续将持续扩展更多算法,目标是构建一个“像 Hutool 一样简单易用”的 JAVA AI 通用工具箱
## 📱 SmartJavaAI Android 商业版
**SmartJavaAI 现已支持 Android 移动端!**
如果您有移动端离线人脸识别的需求,我们推出了基于 SmartJavaAI 的 Android SDK 及演示 APP。
* 🚀 **核心能力**:毫秒级离线人脸检测、比对、注册与 1:N 搜索。
* 📦 **开箱即用**:提供标准 SDK 接口与完整 Demo APK。
* 💼 **商业授权**Android 版本为商业授权版本。
👉 **[点击查看 Android 版演示截图、APK 下载及获取方式](./android.md)**
## 🚀 能力展示 ## 🚀 能力展示
<div align="center"> <div align="center">
@@ -242,6 +258,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
</div> </div>
</td> </td>
</tr> </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> <tr>
<td> <td>
<div align="left"> <div align="left">
@@ -407,6 +436,8 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 支持KINETICS400数据集中400个人类动作识别 - 支持KINETICS400数据集中400个人类动作识别
- **姿态估计** - **姿态估计**
- 集成YOLOv8-pose、YOLOv11-pose等模型 - 集成YOLOv8-pose、YOLOv11-pose等模型
- **零样本目标检测**
- 集成YOLOv8s_worldv2、owlv2_base_patch16模型
- **CLIP** - **CLIP**
- 支持提取图片及文本特征 - 支持提取图片及文本特征
- 支持文搜图、图搜文、图搜图 - 支持文搜图、图搜文、图搜图
@@ -483,7 +514,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<dependency> <dependency>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>all</artifactId> <artifactId>all</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</dependency> </dependency>
``` ```
@@ -491,6 +522,30 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
[示例代码](https://gitee.com/dengwenjie/SmartJavaAI/tree/master/examples) [示例代码](https://gitee.com/dengwenjie/SmartJavaAI/tree/master/examples)
本项目在 `examples` 文件夹下提供了多个示例工程,用于演示各功能模块的使用方法:
* `face-example`:人脸检测、人脸识别等功能示例
* `vision-example`:通用视觉检测示例:目标检测、目标分割、图像分类等
* `ocr-example`OCR文字识别、车牌识别等功能示例
* `translate-example`:机器翻译功能示例
* `speech-example`:语音识别、语音合成功能示例
#### 运行方式
如果你只想运行某个示例,请按以下方式操作:
1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/face-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
### 4、文档地址 ### 4、文档地址
[开发文档](http://doc.smartjavaai.cn) [开发文档](http://doc.smartjavaai.cn)
@@ -658,6 +713,14 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
| YOLOV11-OBB | OnnxRuntime | Ultralytics在DOTAv1 数据集 上训练的模型、通过引入一个额外的角度来更准确地定位图像中的对象 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) | | 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) |
---
#### 行人检测模型 #### 行人检测模型
| 模型名称 | 引擎 | 模型开源网站 | | 模型名称 | 引擎 | 模型开源网站 |
@@ -812,54 +875,5 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
6、等待维护者合并 6、等待维护者合并
## 近期更新日志
## [v1.0.27] - 2025-10-24
- 【通用视觉】集成 OpenAI CLIP 模型,支持以图搜图、以文搜图、以图搜文等功能
- 【通用视觉】新增 YOLO 图像分类模型支持
- 【ASR/TTS】集成 Sherpa TTS语音合成与 ASR语音识别模块支持中文、粤语、方言、英文等多种语言
- 【目标检测】优化视频目标检测功能
## [v1.0.25] - 2025-10-02
- 【人脸识别】 新增多种人脸识别模型
- 【底层优化】 支持自由选择 OpenCV 或 BufferedImage 作为图像引擎
- 【通用图像】 全部模型启用 Image 输入,支持各类图片格式与 Image 的互转
- 【模型管理】 优化模型生命周期,关闭后可重新创建
- 【人脸识别】 支持在人脸查询结果中绘制姓名标注
- 【人脸检测】 新增人脸裁剪功能
- 【修复】 修复若干已知问题,提升系统稳定性
## [v1.0.24] - 2025-09-07
- 【人脸检测】新增6个模型(MTCNN、YOLOV5、RetinaFace小尺寸版),大幅提升性能
- 【人脸识别】新增Seetaface6轻量模型
- 【目标检测】支持视频流目标检测rtsp、视频文件等
- 【目标检测】支持tensorflow2目标检测模型
- 【目标检测】新增行人检测模型(yolo-person)
- 【通用视觉】新增4个动作识别模型
- 【通用视觉】新增语义分割模型
- 【通用视觉】新增5个实例分割模型(含yolov8-seg、yolov11-seg)
- 【通用视觉】新增yolo-obb11旋转框检测(含yolov11-obb)
- 【通用视觉】新增5个姿态估计模型(含yolov8-pose、yolov11-pose)
## [v1.0.23] - 2025-08-09
- 新增 语音识别模块,集成 OpenAI 开源的 Whisper 和 Vosk
- 修复 质量评估模型的 Bug
- 修复 OCR 模块 recognizeAndDraw 方法的 Bug
- 修复 车牌识别在未检测到车牌时的报错问题
- 优化 OCR 表格识别功能,新增导出方式
## [v1.0.22] - 2025-07-28
- 新增 Milvus 身份验证支持
- 集成车牌识别模型,支持车牌检测与识别
- 目标检测功能升级可指定类别及topk
- 支持自定义线程池线程数量
## [v1.0.20] - 2025-07-18
- OCR新增表格识别模型
- OCR新增9个通用模型
- OCR支持批量检测识别
- OCR新增更多参数使用更加灵活
- 人脸识别支持ID查询及分页获取人脸信息
- 活体检测:视频检测支持设置最大帧数

View File

@@ -6,11 +6,11 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<artifactId>all</artifactId> <artifactId>all</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>

68
android.md Normal file
View File

@@ -0,0 +1,68 @@
# SmartJavaAI Android 商业版 (SDK & App)
欢迎关注 SmartJavaAI Android 版本。这是专为移动设备打造的高性能、离线人脸识别解决方案。
## ✨ 核心功能
Android 版本包含以下四大核心模块,支持完全**离线运行**,无需联网:
1. **人脸检测 (Face Detection)**
2. **人脸注册 (Face Registration)**
3. **人脸比对 (Face Comparison 1:1)**
4. **人脸查询 (Face Search 1:N)**
---
## 📸 效果演示
所见即所得,以下是 Demo App 在真机上的运行实拍:
| **人脸检测** | **人脸比对** |
|:----------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------:|
| <img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/android/android1.jpg"/> | <img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/android/android2.jpg"/> |
---
## 🚀 免费试用申请 (Free Trial)
我们为开发者提供 **3天全功能免费试用 License**,以便您充分评估 SDK 的性能与效果。请按照以下步骤获取试用:
### 第一步:下载演示 APK
* **文件名**SmartJavaAI_Face_Demo_v1.0.apk
* **下载地址**[点击这里下载 APK](https://pan.baidu.com/s/1cJhY9q6HcRiLX7orv6CoXQ?pwd=1234)
### 第二步:邮件申请 License
请发送邮件至 **775747758@qq.com** 申请试用授权License。
* **邮件标题**SmartJavaAI Android 试用申请
* **邮件内容**
* **申请类型**:企业 / 个人
* **公司名称/个人姓名**
* **联系电话**(选填)
* **用途简述**(例如:门禁项目测试)
> 💡 **小贴士**:您可以直接复制上方内容。收到邮件后,我们通常会在 24 小时内将 License 文件回复给您。
### 第三步:激活使用
下载 APK 并安装后,将邮件附件中的 `秘钥` 粘贴到 App中即可开启 3 天免费试用。
---
## 🤝 正式商业授权与咨询
试用满意后,如需购买正式商业授权,请联系我们:
* **SDK 形式**:提供标准 AAR/Jar 包,方便集成。
* **源码支持**:提供完整的 Demo 工程源码。
---
**咨询微信 (WeChat)[deng775747758]**
*(添加时请备注android)*
---
[🔙 返回项目主页](./README.md)

View File

@@ -6,10 +6,10 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<version>1.0.27</version> <version>1.1.1</version>
<artifactId>bom</artifactId> <artifactId>bom</artifactId>
<name>bom</name> <name>bom</name>
<description>统一版本管理的 BOM 包,同时支持 import 和全量依赖</description> <description>统一版本管理的 BOM 包,同时支持 import 和全量依赖</description>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<name>common</name> <name>common</name>

View File

@@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.nio.file.Path; import java.nio.file.Path;
@@ -147,6 +148,19 @@ public class SmartImageFactory {
return ImageFactory.getInstance().fromInputStream(inputStream); return ImageFactory.getInstance().fromInputStream(inputStream);
} }
public Image fromUrl(URL url) throws IOException {
if (url == null) {
throw new IllegalArgumentException("URL 不能为空");
}
try (InputStream inputStream = url.openStream()) {
return ImageFactory.getInstance().fromInputStream(inputStream);
}
}
public Image fromUrl(String urlString) throws IOException {
return this.fromUrl(new URL(urlString));
}
} }

View File

@@ -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();
}
}
}
}

View File

@@ -1,44 +1,40 @@
# SmartJavaAI 示例项目说明 # SmartJavaAI 示例项目说明
本项目包含多个基于 SmartJavaAI 平台的人脸识别、活体检测及目标检测的示例代码
## 项目结构 ## 项目结构
``` ```
src/main/java/smartai/examples/ src/main/java/smartai/examples/
├── face/ 人脸相关示例 ├── face-example/ 人脸检测、人脸识别等功能示例
│ ├── attribute/ 人脸属性检测模块 ├── vision-example/ 通用视觉检测示例:目标检测、目标分割、图像分类等
└── FaceAttributeDetDemo.java 示例:检测性别、年龄等人脸属性 ├── ocr-example/ OCR文字识别、车牌识别等功能示例
│ ├── facerec/ 人脸识别模块1:1、1:N ├── translate-example/ 机器翻译功能示例
│ │ ├── FaceNetDemo.java 示例:使用 FaceNet 算法做人脸识别 ├── speech-example/ 语音识别、语音合成功能示例
│ │ ├── GpuFaceDemo.java 示例:使用 GPU 加速的人脸识别
│ │ ├── LightFaceDemo.java 示例:轻量级人脸识别模型(适用于嵌入式场景)
│ │ ├── RetinaFaceDemo.java 示例:使用 RetinaFace 进行人脸检测
│ │ └── SeetaFace6Demo.java 示例:集成 SeetaFace6 的人脸识别
│ └── liveness/ 活体检测模块
│ ├── LivenessDetDemo.java 示例:基于图像进行活体检测
├── objectdetection/ 目标检测模块
│ └── ObjectDetection.java 示例:使用目标检测模型识别图像中的目标
└── ocr/ OCR文字识别模块
│ ├── OcrDetectionDemo.java 示例OCR通用文字检测示例
│ ├── OcrDirectionDetDemo.java 示例OCR方向检测示例
│ └── OcrRecognizeDemo.java 示例OCR通用文字识别示例
├── nlp/ NLP
│ ├── translate/ 机器翻译
│ └── TranslationDemo.java 示例:机器翻译
``` ```
## 快速开始 本项目在 `examples` 文件夹下提供了多个示例工程,用于演示各功能模块的使用方法:
1. 克隆本项目 * `face-example`:人脸检测、人脸识别等功能示例
2. 导入 IDE推荐 IntelliJ IDEA * `vision-example`:通用视觉检测示例:目标检测、目标分割、图像分类等
3. 运行对应 demo 文件即可测试功能(确保模型文件已准备好) * `ocr-example`OCR文字识别、车牌识别等功能示例
* `translate-example`:机器翻译功能示例
* `speech-example`:语音识别、语音合成功能示例
## 模型说明 ## 运行方式
如果你只想运行某个示例,请按以下方式操作:
1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/face-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
- 本示例项目配合 `smartjavaai` 平台使用,模型加载及使用方式已封装好。
- 支持 CPU 和 GPU 两种运行模式。
- 所有模型均可通过 Maven 或本地加载方式接入。
## 联系与支持 ## 联系与支持

View File

@@ -0,0 +1,35 @@
# 使用官方 Ubuntu 22.04 作为基础
FROM ubuntu:22.04
# 设置工作目录(可选,但推荐)
WORKDIR /app
# 更新 apt 仓库并安装 OpenJDK 11
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-11-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# (可选) 验证 Java 安装 (可以选择添加你的应用程序,并编译运行,或者只是执行 java -version)
RUN java -version
# 设置默认的模型路径环境变量
ENV SMART_MODEL_PATH=/app/models
# 创建该目录
RUN mkdir -p /app/models
# 挂载
VOLUME ["/app/models"]
# UTF-8
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 声明服务运行在8080端口
EXPOSE 8080
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]

View File

@@ -24,52 +24,7 @@ src/main/java/smartai/examples/face/
--- ---
## 🧩 功能模块说明
### 1. 人脸属性检测 ([FaceAttributeDetDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java))
- **功能**:识别性别、年龄、眼镜佩戴状态、种族等属性。
- **使用模型**SeetaFace6 等。
---
### 2. 表情识别 ([ExpressionRecDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java))
- **功能**:识别 7 种面部表情:中性、高兴、悲伤、惊讶、恐惧、厌恶、愤怒。
- **支持模式**:单人、多人、摄像头实时检测。
---
### 3. 人脸检测 ([FaceDetDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java))
- **功能**:识别图像或视频中的人脸区域,并返回人脸边界框。
- **支持模型**RetinaFace、SeetaFace6。
---
### 4. 人脸识别 ([FaceRecDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java))
- **功能**提取人脸特征、进行人脸比对1:1、人脸搜索1:N、人脸注册管理。
- **支持数据库**SQLite、Milvus 向量数据库。
---
### 5. 活体检测 ([LivenessDetDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java))
- **功能**:判断输入图像中人脸是否为真实人脸(非照片、视频伪造)。
- **支持模型**IIC-FL、MiniVision双模型融合
---
### 6. 人脸质量评估 ([FaceQualityDetDemo.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java))
- **功能**:评估人脸图像的质量指标,包括:
- 亮度 (Brightness)
- 完整度 (Completeness)
- 清晰度 (Clarity)
- 姿态 (Pose)
- 分辨率 (Resolution)
---
### 7. 工具类 ([ViewerFrame.java](file:///Users/xxx/Documents/idea_workplace/SmartJavaAI/examples/face-example/src/main/java/smartai/examples/face/ViewerFrame.java))
- **功能**GUI 显示组件,用于展示图像处理结果(如人脸框、表情、活体状态等)。
- **用途**:支持摄像头实时检测时的结果可视化。
---
## ⚙️ 配置要求 ## ⚙️ 配置要求
@@ -83,15 +38,21 @@ src/main/java/smartai/examples/face/
--- ---
## 🚀 快速开始
1. 克隆项目到本地: ## 运行方式
2. 导入项目至 IntelliJ IDEA。 如果你只想运行某个示例,请按以下方式操作:
3. 根据需要修改模型路径(见各 demo 中注释)。 1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/face-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
4. 运行对应的 JUnit 测试类方法即可体验各项功能。
--- ---

View File

@@ -12,9 +12,9 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version> <smartjavaai.version>1.1.1</smartjavaai.version>
<!--如果打包运行需要替换成你的main--> <!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.face.facedet.FaceDetDemo</exec.mainClass> <exec.mainClass>smartai.examples.face.FaceDemo</exec.mainClass>
<javacv.version>1.5.10</javacv.version> <javacv.version>1.5.10</javacv.version>
@@ -86,13 +86,26 @@
<dependency> <dependency>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>face</artifactId> <artifactId>face</artifactId>
<exclusions>
<exclusion>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
<version>1.20.0</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-jni</artifactId> <artifactId>pytorch-jni</artifactId>
<version>2.5.1-0.32.0</version> <version>2.7.1-0.34.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -129,7 +142,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier> <classifier>${djl.platform.windows-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -167,19 +180,45 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>org.bytedeco</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId> <artifactId>ffmpeg</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <version>6.1.1-1.5.10</version>
<version>2.5.1</version> <classifier>${javacv.platform.linux-arm64}</classifier>
<scope>runtime</scope>
</dependency> </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系列 平台 (保留对应平台的配置,可以减小包大小)--> <!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency> <dependency>
@@ -213,7 +252,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.osx-aarch64}</classifier> <classifier>${djl.platform.osx-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>

View File

@@ -0,0 +1,73 @@
package smartai.examples.face;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
/**
* @author dwj
* @date 2025/12/23
*/
@Slf4j
public class FaceDemo {
public static String MODEL_PATH = "";
/**
* 获取人脸检测模型
* 注意事项:极速模型,识别准确度低,速度快
* @return
*/
public static FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.YOLOV5_FACE_320);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath(MODEL_PATH + "/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
public static void main(String[] args) {
try {
MODEL_PATH = System.getenv("SMART_MODEL_PATH");
log.info("MODEL_PATH:" + MODEL_PATH);
Config.setCachePath("/app/smartjavaai_cache");
FaceDetModel faceModel = getFaceDetModel();
InputStream is = FaceDemo.class
.getClassLoader()
.getResourceAsStream("iu_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromInputStream(is);
R<DetectionResponse> detectedResult = faceModel.detect(image);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -31,9 +31,6 @@ import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV; import nu.pattern.OpenCV;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.opencv.core.Mat; import org.opencv.core.Mat;

View File

@@ -0,0 +1,35 @@
# 使用官方 Ubuntu 22.04 作为基础
FROM ubuntu:22.04
# 设置工作目录(可选,但推荐)
WORKDIR /app
# 更新 apt 仓库并安装 OpenJDK 11
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-11-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# (可选) 验证 Java 安装 (可以选择添加你的应用程序,并编译运行,或者只是执行 java -version)
RUN java -version
# 设置默认的模型路径环境变量
ENV SMART_MODEL_PATH=/app/models
# 创建该目录
RUN mkdir -p /app/models
# 挂载
VOLUME ["/app/models"]
# UTF-8
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 声明服务运行在8080端口
EXPOSE 8080
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]

View File

@@ -9,11 +9,16 @@ src
├── main ├── main
│ ├── java │ ├── java
│ │ └── smartai/examples/ocr │ │ └── smartai/examples/ocr
│ │ ├── OcrDetectionDemo.java # 文本检测示例 │ │ ├── common
│ │ ├── OcrDirectionDetDemo.java # 文本方向检测示例 │ │ ├── OcrDetectionDemo.java # 文本检测示例
│ │ ── OcrRecognizeDemo.java # 文本识别示例 │ │ │ ├── OcrDirectionDetDemo.java # 文本方向检测示例
│ │ │ └── OcrRecognizeDemo.java # 文本识别示例
│ │ └── table
│ │ └── TableRecDemo.java # 表格识别示例
│ │ └── plate
│ │ └── PlateRecDemo.java # 车牌识别示例
│ └── resources │ └── resources
│ ├── logback.xml # 日志配置文件 │ ├── logback.xml # 日志配置文件
└── test └── test
@@ -22,24 +27,6 @@ src
--- ---
## 🧩 功能说明
### 1. 文本检测 - [OcrDetectionDemo]
- **功能**:检测图像中的文本区域,仅返回文本框位置,不识别文字内容。
### 2. 文本方向检测 - [OcrDirectionDetDemo]
- **功能**在文本检测基础上判断文本整体方向0°, 90°, 180°, 270°
### 3. 文本识别 - [OcrRecognizeDemo]
- **功能**:对检测到的文本区域进行文字识别,支持简体中文、繁体中文、英文、日文等。
- **流程**
- 文本检测 → 文本识别(或加上方向矫正)
---
## ⚙️ 配置要求 ## ⚙️ 配置要求
@@ -56,13 +43,18 @@ src
## 🚀 快速开始 ## 🚀 快速开始
1. 克隆项目到本地 如果你只想运行某个示例,请按以下方式操作
2. 导入项目至 IntelliJ IDEA。 1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
3. 根据需要修改模型路径(见各 demo 中注释)。 ```
examples/ocr-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
4. 运行对应的 JUnit 测试类方法即可体验各项功能。
--- ---

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version> <smartjavaai.version>1.1.1</smartjavaai.version>
<!--如果打包运行需要替换成你的main--> <!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass> <exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass>
@@ -89,13 +89,26 @@
<dependency> <dependency>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>ocr</artifactId> <artifactId>ocr</artifactId>
<exclusions>
<exclusion>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
<version>1.20.0</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-jni</artifactId> <artifactId>pytorch-jni</artifactId>
<version>2.5.1-0.32.0</version> <version>2.7.1-0.34.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -132,7 +145,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier> <classifier>${djl.platform.windows-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -170,14 +183,43 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </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> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -214,7 +256,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.osx-aarch64}</classifier> <classifier>${djl.platform.osx-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>

View File

@@ -0,0 +1,35 @@
# 使用官方 Ubuntu 22.04 作为基础
FROM ubuntu:22.04
# 设置工作目录(可选,但推荐)
WORKDIR /app
# 更新 apt 仓库并安装 OpenJDK 11
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-11-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# (可选) 验证 Java 安装 (可以选择添加你的应用程序,并编译运行,或者只是执行 java -version)
RUN java -version
# 设置默认的模型路径环境变量
ENV SMART_MODEL_PATH=/app/models
# 创建该目录
RUN mkdir -p /app/models
# 挂载
VOLUME ["/app/models"]
# UTF-8
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 声明服务运行在8080端口
EXPOSE 8080
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]

View File

@@ -1,11 +1,4 @@
# OCR文字识别示例 # 语音识别ASR+语音合成TTS示例
我来为你写一个 `speech-examples` 项目的 README.md 文件。
## 🎤 语音识别示例
本项目展示了如何使用 SmartJavaAI SDK 进行语音识别,支持 Whisper 和 Vosk 两种语音识别引擎。
## 📁 项目结构 ## 📁 项目结构
@@ -13,25 +6,27 @@
src src
├── main ├── main
│ ├── java │ ├── java
│ │ └── smartai/examples/speech/asr │ │ └── smartai/examples/speech
│ │ ── SpeechRecognizeDemo.java # 语音识别示例 │ │ ── asr
└── resources │ │ ├── SherpaAsrDemo.java # sherpa-onnx 语音识别ASR 示例
├── logback.xml # 日志配置文件 │ ├── SpeechRecognizeDemo.java # 其他模型的 语音识别ASR 示例
├── speech_zh.mp3 # 中文测试音频 │ └── tts
├── lff_zh.mp3 # 中文测试音频 └── TtsDemo.java # tts语音合成示例
│ └── jfk_en.wav # 英文测试音频
└── test
``` ```
## 🚀 快速开始 ## 🚀 快速开始
1. 克隆项目到本地 如果你只想运行某个示例,请按以下方式操作
2. 导入项目至 IntelliJ IDEA。 1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
3. 根据需要修改模型路径(见各 demo 中注释)。 ```
examples/speech-example
4. 运行对应的 JUnit 测试类方法即可体验各项功能。 ```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
--- ---

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version> <smartjavaai.version>1.1.1</smartjavaai.version>
<!--如果打包运行需要替换成你的main--> <!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.speech.asr.common.OcrRecognizeDemo</exec.mainClass> <exec.mainClass>smartai.examples.speech.asr.common.OcrRecognizeDemo</exec.mainClass>

View File

@@ -234,6 +234,8 @@ public class SpeechRecognizeDemo {
@Test @Test
public void testVosk() { public void testVosk() {
try { try {
//解决中文乱码问题
System.setProperty("jna.encoding","utf-8");
SpeechRecognizer recognizer = geVoskRecognizer(); SpeechRecognizer recognizer = geVoskRecognizer();
//建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度 //建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度
R<AsrResult> result = recognizer.recognize("src/main/resources/lff_zh.mp3"); R<AsrResult> result = recognizer.recognize("src/main/resources/lff_zh.mp3");
@@ -289,6 +291,8 @@ public class SpeechRecognizeDemo {
@Test @Test
public void testVoskAdvanced() { public void testVoskAdvanced() {
try { try {
//解决中文乱码问题
System.setProperty("jna.encoding","utf-8");
VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer(); VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer();
//使用vosk内部接口需要指定识别音频的采样率 //使用vosk内部接口需要指定识别音频的采样率
Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000); Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000);
@@ -318,6 +322,8 @@ public class SpeechRecognizeDemo {
@Test @Test
public void testVoskRealTime() { public void testVoskRealTime() {
try { try {
//解决中文乱码问题
System.setProperty("jna.encoding","utf-8");
VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer(); VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer();
//使用vosk内部接口需要指定识别音频的采样率 //使用vosk内部接口需要指定识别音频的采样率
Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000); Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000);

View File

@@ -0,0 +1,35 @@
# 使用官方 Ubuntu 22.04 作为基础
FROM ubuntu:22.04
# 设置工作目录(可选,但推荐)
WORKDIR /app
# 更新 apt 仓库并安装 OpenJDK 11
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-11-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# (可选) 验证 Java 安装 (可以选择添加你的应用程序,并编译运行,或者只是执行 java -version)
RUN java -version
# 设置默认的模型路径环境变量
ENV SMART_MODEL_PATH=/app/models
# 创建该目录
RUN mkdir -p /app/models
# 挂载
VOLUME ["/app/models"]
# UTF-8
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 声明服务运行在8080端口
EXPOSE 8080
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]

View File

@@ -22,13 +22,18 @@
## 🚀 快速开始 ## 🚀 快速开始
1. 克隆项目到本地 如果你只想运行某个示例,请按以下方式操作
2. 导入项目至 IntelliJ IDEA。 1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
3. 根据需要修改模型路径(见各 demo 中注释)。 ```
examples/translation-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
4. 运行对应的 JUnit 测试类方法即可体验各项功能。
--- ---

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version> <smartjavaai.version>1.1.1</smartjavaai.version>
<!--如果打包运行需要替换成你的main--> <!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.nlp.translation.TranslationDemo</exec.mainClass> <exec.mainClass>smartai.examples.nlp.translation.TranslationDemo</exec.mainClass>
@@ -93,7 +93,7 @@
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-jni</artifactId> <artifactId>pytorch-jni</artifactId>
<version>2.5.1-0.32.0</version> <version>2.7.1-0.34.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -104,7 +104,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier> <classifier>${djl.platform.windows-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -116,25 +116,25 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)--> <!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.osx-aarch64}</classifier> <classifier>${djl.platform.osx-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>

View File

@@ -44,7 +44,7 @@ public class TranslationDemo {
//指定翻译模型NLLB,切换模型需同时修改modelEnum及modelPath //指定翻译模型NLLB,切换模型需同时修改modelEnum及modelPath
config.setModelEnum(TranslationModeEnum.NLLB_MODEL); 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); config.setDevice(DeviceEnum.CPU);
return TranslationModelFactory.getInstance().getModel(config); return TranslationModelFactory.getInstance().getModel(config);
} }

View File

@@ -0,0 +1,35 @@
# 使用官方 Ubuntu 22.04 作为基础
FROM ubuntu:22.04
# 设置工作目录(可选,但推荐)
WORKDIR /app
# 更新 apt 仓库并安装 OpenJDK 11
RUN apt-get update && \
apt-get install -y --no-install-recommends openjdk-11-jdk && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# (可选) 验证 Java 安装 (可以选择添加你的应用程序,并编译运行,或者只是执行 java -version)
RUN java -version
# 设置默认的模型路径环境变量
ENV SMART_MODEL_PATH=/app/models
# 创建该目录
RUN mkdir -p /app/models
# 挂载
VOLUME ["/app/models"]
# UTF-8
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 声明服务运行在8080端口
EXPOSE 8080
# 指定docker容器启动时运行jar包
ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]

View File

@@ -1,27 +1,63 @@
# 目标检测示例 # 计算机视觉示例
本项目包含了多个计算机视觉相关的示例代码,展示了如何使用 SmartJavaAI SDK 进行各种视觉任务。
## 📁 项目结构 ## 📁 项目结构
``` ```
vision-example/
objectdetection-example/
├── src/ ├── src/
│ ├── main/ │ ├── main/
│ │ ├── java/ │ │ ├── java/
│ │ │ └── smartai/examples/objectdetection/ │ │ │ └── smartai/examples/vision/
│ │ │ ├── ObjectDetection.java │ │ │ ├── ActionRecognizeDemo.java # 动作识别示例
│ │ │ ── ViewerFrame.java │ │ │ ── ClipDemo.java # CLIP模型示例图文匹配
│ │ │ ├── ClsDemo.java # 图像分类示例
│ │ │ ├── InstanceSegDemo.java # 实例分割示例
│ │ │ ├── ObbDetDemo.java # 旋转框检测示例
│ │ │ ├── ObjectDetectionDemo.java # 目标检测示例
│ │ │ ├── PersonDetectDemo.java # 行人检测示例
│ │ │ ├── PoseDetDemo.java # 姿态检测示例
│ │ │ ├── SemSegDemo.java # 语义分割示例
``` ```
---
## 🧩 功能模块说明 ## 🧩 功能模块说明
### 1. 目标检测 [ObjectDetection.java] ### 1. 目标检测 [ObjectDetectionDemo.java]
- **功能**核心目标检测类,包含多个测试方法,展示了如何使用不同的模型进行目标检测 - **功能**:展示了如何使用目标检测模型进行物体检测
- **特点**:支持图片检测、视频流检测、本地摄像头检测等多种场景
### 2. 动作识别 [ActionRecognizeDemo.java]
- **功能**:展示了如何使用动作识别模型识别图片或视频中的人物动作
- **特点**:支持多种动作类别的识别
### 3. CLIP模型 [ClipDemo.java]
- **功能**展示了如何使用CLIP模型进行图文匹配相关任务
- **特点**:支持图片特征提取、文本特征提取、相似度计算、图文匹配等功能
### 4. 实例分割 [InstanceSegDemo.java]
- **功能**:展示了如何使用实例分割模型进行物体分割
- **特点**:可以精确分割出图片中的每个物体实例
### 5. 旋转框检测 [ObbDetDemo.java]
- **功能**:展示了如何使用旋转框检测模型检测任意角度的物体
- **特点**:适用于航拍图片等场景中的物体检测
### 6. 行人检测 [PersonDetectDemo.java]
- **功能**:展示了如何使用专门的行人检测模型进行人体检测
- **特点**:针对行人检测场景优化,准确率更高
### 7. 姿态检测 [PoseDetDemo.java]
- **功能**:展示了如何使用姿态检测模型检测人体关键点
- **特点**:可以识别人体的各个关节点位置
### 8. 语义分割 [SemSegDemo.java]
- **功能**:展示了如何使用语义分割模型进行场景分割
- **特点**:可以对图片中的每个像素进行分类
### 9. 图像分类 [ClsDemo.java]
- **功能**:展示了如何使用分类模型对图片进行分类
- **特点**:支持多种分类模型和类别
--- ---
@@ -40,13 +76,20 @@ objectdetection-example/
## 🚀 快速开始 ## 🚀 快速开始
1. 克隆项目到本地: ## 运行方式
2. 导入项目至 IntelliJ IDEA。 如果你只想运行某个示例,请按以下方式操作:
3. 根据需要修改模型路径(见各 demo 中注释)。 1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/vison-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
4. 运行对应的 JUnit 测试类方法即可体验各项功能。
--- ---

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version> <smartjavaai.version>1.1.1</smartjavaai.version>
<!--如果打包运行需要替换成你的main--> <!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.vision.ObjectDetectionDemo</exec.mainClass> <exec.mainClass>smartai.examples.vision.ObjectDetectionDemo</exec.mainClass>
@@ -86,13 +86,26 @@
<dependency> <dependency>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>vision</artifactId> <artifactId>vision</artifactId>
<exclusions>
<exclusion>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
<version>1.20.0</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-jni</artifactId> <artifactId>pytorch-jni</artifactId>
<version>2.5.1-0.32.0</version> <version>2.7.1-0.34.0</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -129,7 +142,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier> <classifier>${djl.platform.windows-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -179,12 +192,11 @@
<classifier>${javacv.platform.linux-x86_64}</classifier> <classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency> </dependency>
<!--PyTorch离线平台依赖-->
<dependency> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
@@ -203,14 +215,50 @@
<version>1.9.1</version> <version>1.9.1</version>
</dependency> </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> <dependency>
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier> <classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>ai.djl.tensorflow</groupId>
<artifactId>tensorflow-native-cpu</artifactId>
<classifier>${djl.platform.linux-aarch64}</classifier>
<scope>runtime</scope>
<version>2.16.1</version>
</dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)--> <!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency> <dependency>
@@ -244,7 +292,7 @@
<groupId>ai.djl.pytorch</groupId> <groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId> <artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.osx-aarch64}</classifier> <classifier>${djl.platform.osx-aarch64}</classifier>
<version>2.5.1</version> <version>2.7.1</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>

View File

@@ -46,7 +46,7 @@ public class ClsDemo {
public ClsModel getModel(){ public ClsModel getModel(){
ClsModelConfig config = new ClsModelConfig(); ClsModelConfig config = new ClsModelConfig();
//实例分割模型,切换模型需要同时修改modelEnum及modelPath //切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(ClsModelEnum.YOLOV8); config.setModelEnum(ClsModelEnum.YOLOV8);
//模型所在路径synset.txt也需要放在同目录下 //模型所在路径synset.txt也需要放在同目录下
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/cls/yolo11m-cls.onnx"); config.setModelPath("/Users/wenjie/Documents/develop/model/vision/cls/yolo11m-cls.onnx");

View File

@@ -205,13 +205,16 @@ public class ObjectDetectionDemo {
*/ */
@Test @Test
public void testStream(){ public void testStream(){
StreamDetector detector = new StreamDetector.Builder() StreamDetector detector = new StreamDetector.Builder()
//视频源类型:支持视频流、本地摄像头、视频文件 //视频源类型:支持视频流、本地摄像头、视频文件
.sourceType(VideoSourceType.STREAM) .sourceType(VideoSourceType.STREAM)
//视频流地址支持rtsp、rtmp、http等常见视频流 //视频流地址支持rtsp、rtmp、http等常见视频流
.streamUrl("rtsp://username:password@ip:port/Streaming/Channels/101") .streamUrl("rtsp://127.0.0.1:8554/stream")
//每隔多少帧检测一次(需要根据模型检测速度决定) //每隔多少帧检测一次(需要根据模型检测速度决定)
.frameDetectionInterval(10) .frameDetectionInterval(10)
//是否打印调试日志
.enableDebugLog(false)
//目标检测模型 //目标检测模型
.detectorModel(getModel()) .detectorModel(getModel())
//回调函数检测到指定目标时触发getModel中可指定模型检测的物体 //回调函数检测到指定目标时触发getModel中可指定模型检测的物体
@@ -246,6 +249,7 @@ public class ObjectDetectionDemo {
} }
}).build(); }).build();
detector.startDetection(); detector.startDetection();
//阻塞主线程 //阻塞主线程
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
try { try {
@@ -319,8 +323,6 @@ public class ObjectDetectionDemo {
.frameDetectionInterval(5) .frameDetectionInterval(5)
//目标检测模型 //目标检测模型
.detectorModel(getModel()) .detectorModel(getModel())
//同物体重复检测时间间隔单位s
.repeatGap(5)
//回调函数检测到指定目标时触发getModel中可指定模型检测的物体 //回调函数检测到指定目标时触发getModel中可指定模型检测的物体
.listener(new StreamDetectionListener() { .listener(new StreamDetectionListener() {
@Override @Override

View File

@@ -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();
}
}
}

View File

@@ -6,11 +6,11 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<artifactId>face</artifactId> <artifactId>face</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
<name>face</name> <name>face</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>
@@ -26,7 +26,7 @@
<!-- <maven.compiler.target>11</maven.compiler.target>--> <!-- <maven.compiler.target>11</maven.compiler.target>-->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.skip>true</maven.test.skip> <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> <javacv.ffmpeg.version>5.1.2-1.5.8</javacv.ffmpeg.version>
</properties> </properties>
@@ -57,6 +57,8 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -176,7 +176,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
imageData.data = BufferedImageUtils.getMatrixBGR(image); imageData.data = BufferedImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
throw new FaceException("无人脸数据"); throw new FaceException("无人脸数据");
} }
for(SeetaRect seetaRect : seetaResult){ for(SeetaRect seetaRect : seetaResult){
@@ -456,7 +456,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
imageData.data = BufferedImageUtils.getMatrixBGR(image); imageData.data = BufferedImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
throw new FaceException("无人脸数据"); throw new FaceException("无人脸数据");
} }
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()]; SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];
@@ -510,7 +510,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
imageData.data = ImageUtils.getMatrixBGR(image); imageData.data = ImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
throw new FaceException("无人脸数据"); throw new FaceException("无人脸数据");
} }
for(SeetaRect seetaRect : seetaResult){ for(SeetaRect seetaRect : seetaResult){
@@ -642,7 +642,7 @@ public class Seetaface6FaceAttributeModel implements FaceAttributeModel {
imageData.data = ImageUtils.getMatrixBGR(image); imageData.data = ImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
throw new FaceException("无人脸数据"); throw new FaceException("无人脸数据");
} }
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()]; SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];

View File

@@ -9,6 +9,7 @@ import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel; import ai.djl.repository.zoo.ZooModel;
import ai.djl.translate.TranslateException;
import cn.smartjavaai.common.cv.SmartImageFactory; import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse; import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R; import cn.smartjavaai.common.entity.R;
@@ -50,6 +51,30 @@ public class CommonFaceDetModel implements FaceDetModel{
private FaceDetConfig config; 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);
}
}
}
}
/** /**
* 加载模型 * 加载模型

View File

@@ -131,4 +131,20 @@ public interface FaceDetModel extends AutoCloseable{
default void setFromFactory(boolean fromFactory){ default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能"); 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("默认不支持该功能");
}
} }

View File

@@ -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);
}
}
}

View File

@@ -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);
if(CollectionUtils.isEmpty(outputPnet)){
// /** return DJLCommonUtils.buildEmptyDetectedObjects();
// * 转换为FaceDetectedResult }
// * @param mtcnnBatchResult NDArray boxes = outputPnet.get(0);
// * @return NDArray image_inds = outputPnet.get(1);
// */ NDArray imgs = outputPnet.get(2);
// public static DetectionResponse convertToDetectionResponse(MtcnnBatchResult mtcnnBatchResult){ if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_inds) || DJLCommonUtils.isNDArrayEmpty(imgs)){
// if(Objects.isNull(mtcnnBatchResult) || CollectionUtils.isEmpty(mtcnnBatchResult.boxes) return DJLCommonUtils.buildEmptyDetectedObjects();
// || CollectionUtils.isEmpty(mtcnnBatchResult.points) }
// || CollectionUtils.isEmpty(mtcnnBatchResult.probs)){ NDList pad = MtcnnUtils.pad(boxes, w, h);
// return null; //第二阶段
// } NDList outputRnet = RNetModel.secondStage(manager, rNetPredictor, imgs, boxes, pad, image_inds);
// DetectionResponse detectionResponse = new DetectionResponse(); if(CollectionUtils.isEmpty(outputRnet)){
// List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>(); return DJLCommonUtils.buildEmptyDetectedObjects();
// }
// NDArray boxes = mtcnnBatchResult.boxes.get(0); NDArray image_indsFiltered = outputRnet.get(0);
// NDArray probs = mtcnnBatchResult.probs.get(0); NDArray scoresFiltered = outputRnet.get(1);
// NDArray points = mtcnnBatchResult.points.get(0); boxes = outputRnet.get(2);
// if(DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(image_indsFiltered) || DJLCommonUtils.isNDArrayEmpty(scoresFiltered)){
// if (DJLCommonUtils.isNDArrayEmpty(boxes) || DJLCommonUtils.isNDArrayEmpty(probs) || DJLCommonUtils.isNDArrayEmpty(points)){ return DJLCommonUtils.buildEmptyDetectedObjects();
// return null; }
// } //第三阶段
// long numBoxes = boxes.getShape().get(0); MtcnnBatchResult oNetResult = ONetModel.thirdStage(manager, oNetPredictor, imgs, boxes, w, h, scoresFiltered, image_indsFiltered);
// for (int i = 0; i < numBoxes; i++) { return FaceUtils.toDetectedObjects(oNetResult, w, h);
// float[] boxCoords = boxes.get(i).toFloatArray(); // [x1, y1, x2, y2] } catch (Exception e) {
// float score = probs.getFloat(i); e.printStackTrace();
// NDArray pointND = points.get(i); // shape [5,2] throw new RuntimeException(e);
// 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;
// }
@@ -293,6 +292,57 @@ public class MtcnnFaceDetModel extends CommonFaceDetModel{
return fromFactory; 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 @Override
public void close() { public void close() {
if (fromFactory) { if (fromFactory) {

View File

@@ -1,7 +1,9 @@
package cn.smartjavaai.face.model.facedect; package cn.smartjavaai.face.model.facedect;
import ai.djl.engine.Engine; import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image; import ai.djl.modality.cv.Image;
import ai.djl.ndarray.NDList;
import cn.smartjavaai.common.cv.SmartImageFactory; import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse; import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R; 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.config.FaceDetConfig;
import cn.smartjavaai.face.exception.FaceException; import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory; 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.NativeLoader;
import cn.smartjavaai.face.seetaface.SeetaFace6FaceDetPredictors;
import cn.smartjavaai.face.utils.FaceUtils; import cn.smartjavaai.face.utils.FaceUtils;
import com.seeta.pool.*; import com.seeta.pool.*;
import com.seeta.sdk.*; 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 @Override
public R<DetectionResponse> detectAndDraw(Image image) { public R<DetectionResponse> detectAndDraw(Image image) {
R<DetectionResponse> result = detect(image); R<DetectionResponse> result = detect(image);
@@ -276,6 +301,33 @@ public class SeetaFace6FaceDetModel implements FaceDetModel{
return R.ok(drawnImage); 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -6,6 +6,7 @@ import ai.djl.engine.Engine;
import ai.djl.inference.Predictor; import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image; import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel; 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.exception.FaceException;
import cn.smartjavaai.face.factory.FaceDetModelFactory; import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.LivenessModelFactory; 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.criterial.LivenessCriteriaFactory;
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator; 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 com.seeta.sdk.FaceAntiSpoofing;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV; import nu.pattern.OpenCV;
@@ -124,8 +131,12 @@ public class CommonLivenessModel implements LivenessDetModel{
return detectVideo(new FFmpegFrameGrabber(videoPath)); return detectVideo(new FFmpegFrameGrabber(videoPath));
} }
private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) { protected R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
try { Predictor<Image, Float> predictor = null;
try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
//初始化predictors
faceDetectManager.borrowPredictors();
predictor = predictorPool.borrowObject();
//滑动窗口 //滑动窗口
Deque<Float> scoreWindow = new ArrayDeque<>(); Deque<Float> scoreWindow = new ArrayDeque<>();
grabber.start(); grabber.start();
@@ -147,7 +158,8 @@ public class CommonLivenessModel implements LivenessDetModel{
converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat(); converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
} }
Mat mat = converterToMat.convert(frame); 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(); mat.release();
if(!livenessScore.isSuccess()){ if(!livenessScore.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessScore.getMessage()); log.debug("" + frameIndex + "帧处理失败:" + livenessScore.getMessage());
@@ -175,6 +187,24 @@ public class CommonLivenessModel implements LivenessDetModel{
} }
} catch (Exception e) { } catch (Exception e) {
throw new FaceException(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); 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 @Override
public R<LivenessResult> detectTopFace(Image image) { public R<LivenessResult> detectTopFace(Image image) {
R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image); R<DetectionResponse> faceDetectionResponse = config.getDetectModel().detect(image);

View File

@@ -10,6 +10,7 @@ import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ZooModel; import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar; import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*; import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.FaceInfo; import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.entity.face.LivenessResult; 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.common.utils.*;
import cn.smartjavaai.face.config.LivenessConfig; import cn.smartjavaai.face.config.LivenessConfig;
import cn.smartjavaai.face.constant.MiniVisionConstant; import cn.smartjavaai.face.constant.MiniVisionConstant;
import cn.smartjavaai.face.enums.LivenessModelEnum;
import cn.smartjavaai.face.exception.FaceException; import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.LivenessModelFactory; import cn.smartjavaai.face.factory.LivenessModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetectManager;
import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator; import cn.smartjavaai.face.model.liveness.translator.MiniVisionTranslator;
import com.seeta.sdk.*; import com.seeta.sdk.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool; 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 org.opencv.core.Mat;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@@ -59,6 +65,8 @@ public class MiniVisionLivenessModel extends CommonLivenessModel{
private GenericObjectPool<Predictor<Image, float[]>> sePredictorPool; 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() { public GenericObjectPool<Predictor<Image, float[]>> getPredictorPool() {
return predictorPool; return predictorPool;
} }

View File

@@ -1,7 +1,9 @@
package cn.smartjavaai.face.model.liveness; package cn.smartjavaai.face.model.liveness;
import ai.djl.engine.Engine; import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image; import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.output.DetectedObjects;
import cn.smartjavaai.common.cv.SmartImageFactory; import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*; import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.face.FaceInfo; 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.constant.LivenessConstant;
import cn.smartjavaai.face.exception.FaceException; import cn.smartjavaai.face.exception.FaceException;
import cn.smartjavaai.face.factory.LivenessModelFactory; 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.seetaface.NativeLoader;
import cn.smartjavaai.face.utils.FaceUtils; import cn.smartjavaai.face.utils.FaceUtils;
import cn.smartjavaai.face.utils.Seetaface6Utils; import cn.smartjavaai.face.utils.Seetaface6Utils;
@@ -23,6 +27,7 @@ import com.seeta.sdk.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV; import nu.pattern.OpenCV;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.bytedeco.javacv.FFmpegFrameGrabber; import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame; import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils; import org.bytedeco.javacv.Java2DFrameUtils;
@@ -60,6 +65,9 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
if(StringUtils.isBlank(config.getModelPath())){ if(StringUtils.isBlank(config.getModelPath())){
throw new FaceException("modelPath is null"); throw new FaceException("modelPath is null");
} }
if(Objects.isNull(config.getDetectModel())){
throw new FaceException("未指定人脸检测模型");
}
this.config = config; this.config = config;
//加载依赖库 //加载依赖库
NativeLoader.loadNativeLibraries(config.getDevice()); 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) { private R<LivenessResult> detectVideo(FFmpegFrameGrabber grabber) {
FaceAntiSpoofing faceAntiSpoofing = null; FaceAntiSpoofing faceAntiSpoofing = null;
try { try (FaceDetectManager faceDetectManager = new FaceDetectManager(config.getDetectModel())){
//初始化predictors
faceDetectManager.borrowPredictors();
faceAntiSpoofing = faceAntiSpoofingPool.borrowObject(); faceAntiSpoofing = faceAntiSpoofingPool.borrowObject();
//重置视频 //重置视频
faceAntiSpoofing.ResetVideo(); faceAntiSpoofing.ResetVideo();
@@ -194,14 +233,14 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
// 逐帧处理视频 // 逐帧处理视频
for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) { for (int frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
if(frameIndex >= config.getMaxVideoDetectFrames()){ if(frameIndex >= config.getMaxVideoDetectFrames()){
return R.fail(10002, "超出最大检测帧数:" + config.getMaxVideoDetectFrames()); return R.fail(10002, "视频中未检测到人脸,超出最大检测帧数:" + config.getMaxVideoDetectFrames());
} }
// 获取当前帧 // 获取当前帧
Frame frame = grabber.grabImage(); Frame frame = grabber.grabImage();
if (frame != null) { if (frame != null) {
BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame); BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(frame);
Image image = SmartImageFactory.getInstance().fromBufferedImage(bufferedImage); Image image = SmartImageFactory.getInstance().fromBufferedImage(bufferedImage);
R<LivenessResult> livenessStatus = detectTopFace(image, false); R<LivenessResult> livenessStatus = detectVideoFrame(image, faceDetectManager, faceAntiSpoofing);
if(!livenessStatus.isSuccess()){ if(!livenessStatus.isSuccess()){
log.debug("" + frameIndex + "帧处理失败:" + livenessStatus.getMessage()); log.debug("" + frameIndex + "帧处理失败:" + livenessStatus.getMessage());
continue; continue;
@@ -225,10 +264,17 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
log.warn("归还Predictor失败", e); log.warn("归还Predictor失败", e);
} }
} }
try {
grabber.release();
} catch (FFmpegFrameGrabber.Exception e) {
throw new RuntimeException(e);
}
} }
return R.fail(1000, "有效帧数量不足,无法完成活体检测"); return R.fail(1000, "有效帧数量不足,无法完成活体检测");
} }
@Override @Override
public R<DetectionResponse> detect(Image image) { public R<DetectionResponse> detect(Image image) {
FaceAntiSpoofing faceAntiSpoofing = null; FaceAntiSpoofing faceAntiSpoofing = null;
@@ -246,7 +292,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
imageData.data = ImageUtils.getMatrixBGR(image); imageData.data = ImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return R.fail(R.Status.NO_FACE_DETECTED); return R.fail(R.Status.NO_FACE_DETECTED);
} }
for(SeetaRect seetaRect : seetaResult){ for(SeetaRect seetaRect : seetaResult){
@@ -346,7 +392,7 @@ public class Seetaface6LivenessModel implements LivenessDetModel{
imageData.data = ImageUtils.getMatrixBGR(image); imageData.data = ImageUtils.getMatrixBGR(image);
//检测人脸 //检测人脸
SeetaRect[] seetaResult = detectPredictor.Detect(imageData); SeetaRect[] seetaResult = detectPredictor.Detect(imageData);
if(Objects.isNull(seetaResult)){ if(Objects.isNull(seetaResult) || seetaResult.length == 0){
return R.fail(R.Status.NO_FACE_DETECTED); return R.fail(R.Status.NO_FACE_DETECTED);
} }
SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()]; SeetaPointF[] landmarks = new SeetaPointF[faceLandmarker.number()];

View File

@@ -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);
}
}

View File

@@ -637,7 +637,8 @@ public class MilvusClient implements VectorDBClient {
List<FaceVector> result = new ArrayList<>(); List<FaceVector> result = new ArrayList<>();
for (QueryResultsWrapper.RowRecord row : records) { 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); Object vectorObj = row.get(VectorDBConstants.FieldNames.VECTOR_FIELD);
float[] vector = null; float[] vector = null;
if (vectorObj instanceof List<?>) { if (vectorObj instanceof List<?>) {

View File

@@ -2,6 +2,7 @@ package cn.smartjavaai.face.vector.core;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.smartjavaai.common.config.Config; import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.executor.GlobalExecutor;
import cn.smartjavaai.common.utils.SimilarityUtil; import cn.smartjavaai.common.utils.SimilarityUtil;
import cn.smartjavaai.face.dao.FaceDao; import cn.smartjavaai.face.dao.FaceDao;
import cn.smartjavaai.face.entity.FaceSearchParams; import cn.smartjavaai.face.entity.FaceSearchParams;
@@ -23,12 +24,9 @@ import java.util.stream.Collectors;
public class SQLiteClient implements VectorDBClient { public class SQLiteClient implements VectorDBClient {
private final FaceDao faceDao; private final FaceDao faceDao;
//private final List<FaceVector> memoryIndex = new CopyOnWriteArrayList<>();
private final ConcurrentHashMap<String, FaceVector> memoryIndex = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, FaceVector> memoryIndex = new ConcurrentHashMap<>();
private int featureDimension; // 维度 private int featureDimension; // 维度
private final ExecutorService executor = Executors.newFixedThreadPool(4);
private SQLiteConfig config; private SQLiteConfig config;
/** /**
@@ -162,7 +160,7 @@ public class SQLiteClient implements VectorDBClient {
return similarity >= faceSearchParams.getThreshold() ? return similarity >= faceSearchParams.getThreshold() ?
new FaceSearchResult(vector.getId(), similarity, vector.getMetadata()) : new FaceSearchResult(vector.getId(), similarity, vector.getMetadata()) :
null; null;
}, executor)) }, GlobalExecutor.getExecutor()))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 收集结果并过滤null // 收集结果并过滤null
@@ -185,15 +183,7 @@ public class SQLiteClient implements VectorDBClient {
@Override @Override
public void close() { public void close() {
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
} }
@Override @Override

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<artifactId>ocr</artifactId> <artifactId>ocr</artifactId>
@@ -42,7 +42,7 @@
</dependency> </dependency>
</dependencies> </dependencies>
<version>1.0.27</version> <version>1.1.1</version>
<name>ocr</name> <name>ocr</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -7,7 +7,7 @@
<name>SmartJavaAI</name> <name>SmartJavaAI</name>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<modules> <modules>
@@ -26,7 +26,7 @@
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<djl.version>0.32.0</djl.version> <djl.version>0.34.0</djl.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -109,7 +109,7 @@
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.4</version> <version>1.18.34</version>
</dependency> </dependency>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<artifactId>speech</artifactId> <artifactId>speech</artifactId>
@@ -57,7 +57,7 @@
</dependencies> </dependencies>
<version>1.0.27</version> <version>1.1.1</version>
<name>speech</name> <name>speech</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -56,6 +56,8 @@ public class VoskRecognizer implements SpeechRecognizer{
@Override @Override
public void loadModel(AsrModelConfig config) { public void loadModel(AsrModelConfig config) {
this.config = config; this.config = config;
//防止中文乱码
System.setProperty("jna.encoding","utf-8");
if(StringUtils.isBlank(config.getModelPath())){ if(StringUtils.isBlank(config.getModelPath())){
throw new AsrException("modelPath is null"); throw new AsrException("modelPath is null");
} }

View File

@@ -25,7 +25,7 @@
</dependency> </dependency>
</dependencies> </dependencies>
<version>1.0.27</version> <version>1.1.1</version>
<name>translate</name> <name>translate</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -6,11 +6,11 @@
<parent> <parent>
<groupId>cn.smartjavaai</groupId> <groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId> <artifactId>smartjavaai-parent</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
</parent> </parent>
<artifactId>vision</artifactId> <artifactId>vision</artifactId>
<version>1.0.27</version> <version>1.1.1</version>
<name>vision</name> <name>vision</name>
<description>SmartJavaAI</description> <description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url> <url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -55,7 +55,7 @@ public class ActionRecModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -64,7 +64,7 @@ public class ActionRecModelFactory {
* @param config * @param config
* @return * @return
*/ */
private ActionRecModel createFaceDetModel(ActionRecModelConfig config) { private ActionRecModel createModel(ActionRecModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -66,8 +66,9 @@ public interface ClipModel extends AutoCloseable{
/** /**
* 图片特征比较 * 图片特征比较
* @param image1 图1 * @param image1
* @param image2 图2 * @param image2
* @param scale
* @return * @return
*/ */
default R<Float> compareImage(Image image1, Image image2, float scale){ default R<Float> compareImage(Image image1, Image image2, float scale){
@@ -115,10 +116,12 @@ public interface ClipModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能"); throw new UnsupportedOperationException("默认不支持该功能");
} }
/** /**
* 文本特征比较 * 特征比较
* @param feature1 文本1 * @param feature1
* @param feature2 文本2 * @param feature2
* @param scale
* @return * @return
*/ */
default R<Float> compareFeatures(float[] feature1, float[] feature2, float scale){ default R<Float> compareFeatures(float[] feature1, float[] feature2, float scale){

View File

@@ -56,7 +56,7 @@ public class ClipModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -65,7 +65,7 @@ public class ClipModelFactory {
* @param config * @param config
* @return * @return
*/ */
private ClipModel createFaceDetModel(ClipModelConfig config) { private ClipModel createModel(ClipModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -55,7 +55,7 @@ public class ClsModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -64,7 +64,7 @@ public class ClsModelFactory {
* @param config * @param config
* @return * @return
*/ */
private ClsModel createFaceDetModel(ClsModelConfig config) { private ClsModel createModel(ClsModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -54,7 +54,7 @@ public class InstanceSegModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -63,7 +63,7 @@ public class InstanceSegModelFactory {
* @param config * @param config
* @return * @return
*/ */
private InstanceSegModel createFaceDetModel(InstanceSegModelConfig config) { private InstanceSegModel createModel(InstanceSegModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -54,7 +54,7 @@ public class ObbDetModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -63,7 +63,7 @@ public class ObbDetModelFactory {
* @param config * @param config
* @return * @return
*/ */
private ObbDetModel createFaceDetModel(ObbDetModelConfig config) { private ObbDetModel createModel(ObbDetModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -29,10 +29,14 @@ public class CriteriaBuilderFactory {
switch (config.getModelEnum()) { switch (config.getModelEnum()) {
case YOLOV8_OFFICIAL_ONNX: case YOLOV8_OFFICIAL_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config); return new YoloCriteriaBuilder().buildCriteria(config);
case YOLOV11_OFFICIAL_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config);
case YOLOV12_OFFICIAL_ONNX: case YOLOV12_OFFICIAL_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config); return new YoloCriteriaBuilder().buildCriteria(config);
case YOLOV8_CUSTOM_ONNX: case YOLOV8_CUSTOM_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config); return new YoloCriteriaBuilder().buildCriteria(config);
case YOLOV11_CUSTOM_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config);
case YOLOV12_CUSTOM_ONNX: case YOLOV12_CUSTOM_ONNX:
return new YoloCriteriaBuilder().buildCriteria(config); return new YoloCriteriaBuilder().buildCriteria(config);
case TENSORFLOW2_OFFICIAL: case TENSORFLOW2_OFFICIAL:

View File

@@ -56,7 +56,7 @@ public class PersonDetModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -65,7 +65,7 @@ public class PersonDetModelFactory {
* @param config * @param config
* @return * @return
*/ */
private PersonDetModel createFaceDetModel(PersonDetModelConfig config) { private PersonDetModel createModel(PersonDetModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -49,7 +49,6 @@ public class StreamDetector implements AutoCloseable{
//回调线程池 //回调线程池
ExecutorService callbackExecutor; ExecutorService callbackExecutor;
private int frameDetectionInterval = 1; private int frameDetectionInterval = 1;
private long repeatGap = 5; // 秒
private volatile boolean isRunning; private volatile boolean isRunning;
private FrameGrabber grabber; private FrameGrabber grabber;
private StreamDetectionListener listener; private StreamDetectionListener listener;
@@ -57,6 +56,8 @@ public class StreamDetector implements AutoCloseable{
private VideoSourceType sourceType = VideoSourceType.STREAM; // 默认流 private VideoSourceType sourceType = VideoSourceType.STREAM; // 默认流
private int cameraIndex = 0; // 默认第一个摄像头 private int cameraIndex = 0; // 默认第一个摄像头
private boolean enableDebugLog; //是否开启debug log
private Map<String, Long> lastDetectTime = new ConcurrentHashMap<>(); private Map<String, Long> lastDetectTime = new ConcurrentHashMap<>();
private BlockingQueue<Frame> frameQueue = new LinkedBlockingQueue<>(100); private BlockingQueue<Frame> frameQueue = new LinkedBlockingQueue<>(100);
@@ -84,7 +85,8 @@ public class StreamDetector implements AutoCloseable{
this.listener = builder.listener; this.listener = builder.listener;
this.sourceType = builder.sourceType; this.sourceType = builder.sourceType;
this.cameraIndex = builder.cameraIndex; this.cameraIndex = builder.cameraIndex;
this.repeatGap = builder.repeatGap; // this.repeatGap = builder.repeatGap;
this.enableDebugLog = builder.enableDebugLog;
this.converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat(); this.converterToMat = new OpenCVFrameConverter.ToOrgOpenCvCoreMat();
} }
@@ -160,7 +162,7 @@ public class StreamDetector implements AutoCloseable{
* 负责抓取视频帧到队列 * 负责抓取视频帧到队列
*/ */
private void processFrames() { private void processFrames() {
long frameCount = 0; long detectCounter = 0;
while (!grabberFinished && isRunning) { while (!grabberFinished && isRunning) {
try { try {
Frame frame = grabber.grabFrame(); Frame frame = grabber.grabFrame();
@@ -192,11 +194,16 @@ public class StreamDetector implements AutoCloseable{
continue; continue;
} }
} }
frameCount++; detectCounter++;
if (frameCount % frameDetectionInterval != 0) continue; if (detectCounter < frameDetectionInterval) {
continue;
}
detectCounter = 0;
Frame currentFrame = frame.clone(); Frame currentFrame = frame.clone();
frameQueue.offer(currentFrame); frameQueue.offer(currentFrame);
// log.debug("正在抓取第{}帧,当前帧数:{}", frameCount, frameQueue.size()); if (enableDebugLog){
log.debug("当前未处理帧数:{}", frameQueue.size());
}
} catch (Exception e) { } catch (Exception e) {
log.error("抓取视频帧异常", e); log.error("抓取视频帧异常", e);
} }
@@ -242,16 +249,15 @@ public class StreamDetector implements AutoCloseable{
Image image = SmartImageFactory.getInstance().fromMat(mat); Image image = SmartImageFactory.getInstance().fromMat(mat);
DetectedObjects detectedObjects = predictor.predict(image); DetectedObjects detectedObjects = predictor.predict(image);
// log.info("内部检测结果:{}", detectedObjects.toString()); if (enableDebugLog){
log.debug("帧检测结果:{}", detectedObjects.toString());
}
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image); DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image);
if(Objects.isNull(detectionResponse)){ if(Objects.isNull(detectionResponse)){
return; return;
} }
List<DetectionInfo> filtered = filterRepeatedObjects(detectionResponse); Image copyImage = image.duplicate();
if (!filtered.isEmpty() && listener != null) { callbackExecutor.submit(() -> listener.onObjectDetected(detectionResponse.getDetectionInfoList(), copyImage));
Image copyImage = image.duplicate();
callbackExecutor.submit(() -> listener.onObjectDetected(filtered, copyImage));
}
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
log.error("单帧处理异常", e); log.error("单帧处理异常", e);
@@ -260,21 +266,6 @@ public class StreamDetector implements AutoCloseable{
} }
} }
private List<DetectionInfo> filterRepeatedObjects(DetectionResponse response) {
List<DetectionInfo> result = new ArrayList<>();
long now = System.currentTimeMillis();
for (DetectionInfo info : response.getDetectionInfoList()) {
String name = info.getObjectDetInfo().getClassName();
Long last = lastDetectTime.get(name);
if (last == null || (now - last) > repeatGap * 1000) {
lastDetectTime.put(name, now);
result.add(info);
}
}
return result;
}
/** /**
* 开始检测下一个视频文件 * 开始检测下一个视频文件
*/ */
@@ -322,10 +313,6 @@ public class StreamDetector implements AutoCloseable{
@Override @Override
public void close() { public void close() {
// if(isRunning){
// System.out.println("--isRunning" + isRunning);
// stopDetection();
// }
if (grabberExecutor != null){ if (grabberExecutor != null){
grabberExecutor.shutdownNow(); grabberExecutor.shutdownNow();
} }
@@ -345,15 +332,11 @@ public class StreamDetector implements AutoCloseable{
private VideoSourceType sourceType = VideoSourceType.STREAM; // 默认流 private VideoSourceType sourceType = VideoSourceType.STREAM; // 默认流
private int cameraIndex = 0; // 默认第一个摄像头 private int cameraIndex = 0; // 默认第一个摄像头
private long repeatGap = 5;//同物体重复检测间隔 private boolean enableDebugLog; //是否开启debug log
public Builder detectorModel(DetectorModel m) { this.detectorModel = m; return this; } public Builder detectorModel(DetectorModel m) { this.detectorModel = m; return this; }
public Builder streamUrl(String url) { this.streamUrl = url; return this; } public Builder streamUrl(String url) { this.streamUrl = url; return this; }
public Builder listener(StreamDetectionListener listener) { this.listener = listener; return this; } public Builder listener(StreamDetectionListener listener) { this.listener = listener; return this; }
public Builder repeatGap(long repeatGap) {
this.repeatGap = repeatGap;
return this;
}
public Builder sourceType(VideoSourceType sourceType) { public Builder sourceType(VideoSourceType sourceType) {
this.sourceType = sourceType; this.sourceType = sourceType;
return this; return this;
@@ -368,6 +351,11 @@ public class StreamDetector implements AutoCloseable{
return this; return this;
} }
public Builder enableDebugLog(boolean enableDebugLog) {
this.enableDebugLog = enableDebugLog;
return this;
}
public StreamDetector build() { public StreamDetector build() {
if (detectorModel == null) { if (detectorModel == null) {
throw new DetectionException("detectorModel 不能为空"); throw new DetectionException("detectorModel 不能为空");

View File

@@ -55,7 +55,7 @@ public class PoseDetModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -64,7 +64,7 @@ public class PoseDetModelFactory {
* @param config * @param config
* @return * @return
*/ */
private PoseModel createFaceDetModel(PoseModelConfig config) { private PoseModel createModel(PoseModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -54,7 +54,7 @@ public class SemSegModelFactory {
throw new DetectionException("未配置模型"); throw new DetectionException("未配置模型");
} }
return modelMap.computeIfAbsent(config.getModelEnum(), k -> { return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
return createFaceDetModel(config); return createModel(config);
}); });
} }
@@ -63,7 +63,7 @@ public class SemSegModelFactory {
* @param config * @param config
* @return * @return
*/ */
private SemSegModel createFaceDetModel(SemSegModelConfig config) { private SemSegModel createModel(SemSegModelConfig config) {
Class<?> clazz = registry.get(config.getModelEnum()); Class<?> clazz = registry.get(config.getModelEnum());
if(clazz == null){ if(clazz == null){
throw new DetectionException("Unsupported model"); throw new DetectionException("Unsupported model");

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,17 @@
package cn.smartjavaai.zeroshot.entity;
import lombok.Data;
/**
* 检测参数
* @author dwj
*/
@Data
public class DetectParams {
/**
* 置信度阈值
*/
private float threshold = 0.3f;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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("默认不支持该功能");
}
}

View File

@@ -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());
}
}