mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-12 20:58:51 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dea0673026 | ||
|
|
22a9121a46 | ||
|
|
1fbd8779b9 | ||
|
|
bc2a9f11ad | ||
|
|
afcf7d1903 | ||
|
|
7b3426f231 | ||
|
|
a707e41d9d | ||
|
|
b0736c6494 | ||
|
|
06aee850cf | ||
|
|
861e6c128d | ||
|
|
69c553058b | ||
|
|
fefc36bc30 | ||
|
|
7917423190 | ||
|
|
035d8919f8 | ||
|
|
9e8d8bb48f | ||
|
|
4a7471923e | ||
|
|
b0379bb2e5 | ||
|
|
efce16834b | ||
|
|
a957516a5b | ||
|
|
06cb54bbf7 | ||
|
|
07d625d83b | ||
|
|
d834008bd4 |
114
README.md
114
README.md
@@ -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查询及分页获取人脸信息
|
|
||||||
- 活体检测:视频检测支持设置最大帧数
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
68
android.md
Normal 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)
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 或本地加载方式接入。
|
|
||||||
|
|
||||||
## 联系与支持
|
## 联系与支持
|
||||||
|
|
||||||
|
|||||||
35
examples/face-example/Dockerfile
Normal file
35
examples/face-example/Dockerfile
Normal 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"]
|
||||||
@@ -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 测试类方法即可体验各项功能。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
35
examples/ocr-examples/Dockerfile
Normal file
35
examples/ocr-examples/Dockerfile
Normal 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"]
|
||||||
@@ -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 测试类方法即可体验各项功能。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
35
examples/speech-examples/Dockerfile
Normal file
35
examples/speech-examples/Dockerfile
Normal 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"]
|
||||||
@@ -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 文件功能的说明,运行相应的测试方法进行体验。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
35
examples/translation-example/Dockerfile
Normal file
35
examples/translation-example/Dockerfile
Normal 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"]
|
||||||
@@ -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 测试类方法即可体验各项功能。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
35
examples/vision-example/Dockerfile
Normal file
35
examples/vision-example/Dockerfile
Normal 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"]
|
||||||
@@ -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 测试类方法即可体验各项功能。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package smartai.examples.vision;
|
||||||
|
|
||||||
|
import ai.djl.modality.cv.Image;
|
||||||
|
|
||||||
|
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||||
|
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||||
|
import cn.smartjavaai.common.entity.R;
|
||||||
|
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||||
|
import cn.smartjavaai.common.utils.ImageUtils;
|
||||||
|
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||||
|
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||||
|
import cn.smartjavaai.zeroshot.model.ZeroDetModel;
|
||||||
|
import cn.smartjavaai.zeroshot.model.ZeroDetModelFactory;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ZeroShotObjectDetectionDemo {
|
||||||
|
|
||||||
|
|
||||||
|
//设备类型
|
||||||
|
public static DeviceEnum device = DeviceEnum.CPU;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeAll() throws IOException {
|
||||||
|
//修改缓存路径
|
||||||
|
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取零样本目标检测模型
|
||||||
|
*/
|
||||||
|
public ZeroDetModel getModel(){
|
||||||
|
ZeroDetConfig config = new ZeroDetConfig();
|
||||||
|
//零样本目标检测模型,切换模型需要同时修改modelEnum及modelPath
|
||||||
|
config.setModelEnum(ZeroDetModelEnum.OWLV2_BASE_PATCH16);
|
||||||
|
//模型所在路径
|
||||||
|
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/zero/owlv2-base-patch16");
|
||||||
|
config.setDevice(device);
|
||||||
|
//置信度阈值
|
||||||
|
config.setThreshold(0.5f);
|
||||||
|
return ZeroDetModelFactory.getInstance().getModel(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测
|
||||||
|
* 特性:
|
||||||
|
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||||
|
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||||
|
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void zeroDetection(){
|
||||||
|
try {
|
||||||
|
ZeroDetModel detectorModel = getModel();
|
||||||
|
//创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
|
||||||
|
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/zero/000000039769.jpg"));
|
||||||
|
//输入图片以及条件
|
||||||
|
R<DetectionResponse> result = detectorModel.detect(image, new String[]{"cat","remote control"});
|
||||||
|
if(result.isSuccess()){
|
||||||
|
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||||
|
}else{
|
||||||
|
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测并绘制检测结果
|
||||||
|
* 特性:
|
||||||
|
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||||
|
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||||
|
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void zeroDetectionAndDraw() {
|
||||||
|
try {
|
||||||
|
ZeroDetModel detectorModel = getModel();
|
||||||
|
String[] candidates = new String[]{"cat","remote control"};
|
||||||
|
//保存绘制后图片以及返回检测结果
|
||||||
|
R<DetectionResponse> result = detectorModel.detectAndDraw(candidates, "src/main/resources/zero/000000039769.jpg","output/cat_detected.png");
|
||||||
|
if(result.isSuccess()){
|
||||||
|
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||||
|
}else{
|
||||||
|
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测并绘制检测结果
|
||||||
|
* 特性:
|
||||||
|
* 1、零样本检测能力:无需针对特定类别进行训练,可直接通过文本查询检测新类别物体
|
||||||
|
* 2、开放词汇识别:能够识别训练时未见过的类别名称,突破传统检测模型的类别限制
|
||||||
|
* 3、多查询支持:支持同时使用多个文本查询进行目标检测,提高检测效率
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void zeroDetectionAndDraw2(){
|
||||||
|
try {
|
||||||
|
ZeroDetModel detectorModel = getModel();
|
||||||
|
//创建Image对象,可以从文件、url、InputStream创建、BufferedImage、Base64创建,具体使用方法可以查看文档
|
||||||
|
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/zero/000000039769.jpg"));
|
||||||
|
String[] candidates = new String[]{"cat","remote control"};
|
||||||
|
R<DetectionResponse> result = detectorModel.detectAndDraw(image, candidates);
|
||||||
|
if(result.isSuccess()){
|
||||||
|
log.info("零样本目标检测结果:{}", JSONObject.toJSONString(result.getData()));
|
||||||
|
//保存图片
|
||||||
|
ImageUtils.save(result.getData().getDrawnImage(), "output/cat_detected.png");
|
||||||
|
}else{
|
||||||
|
log.info("零样本目标检测失败:{}", result.getMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
<parent>
|
<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>
|
||||||
|
|||||||
@@ -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()];
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载模型
|
* 加载模型
|
||||||
|
|||||||
@@ -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("默认不支持该功能");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package cn.smartjavaai.face.model.facedect;
|
||||||
|
|
||||||
|
import ai.djl.inference.Predictor;
|
||||||
|
import ai.djl.modality.cv.Image;
|
||||||
|
import ai.djl.modality.cv.output.DetectedObjects;
|
||||||
|
import cn.smartjavaai.common.entity.DetectionInfo;
|
||||||
|
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||||
|
import cn.smartjavaai.common.entity.R;
|
||||||
|
import cn.smartjavaai.face.exception.FaceException;
|
||||||
|
import cn.smartjavaai.face.model.facedect.mtcnn.MtcnnPredictors;
|
||||||
|
import cn.smartjavaai.face.seetaface.SeetaFace6FaceDetPredictors;
|
||||||
|
import cn.smartjavaai.face.utils.FaceUtils;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dwj
|
||||||
|
* @date 2025/11/24
|
||||||
|
*/
|
||||||
|
public class FaceDetectManager implements AutoCloseable{
|
||||||
|
|
||||||
|
|
||||||
|
private FaceDetModel faceDetModel;
|
||||||
|
|
||||||
|
public FaceDetectManager(FaceDetModel faceDetModel) {
|
||||||
|
this.faceDetModel = faceDetModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MtcnnPredictors mtcnnPredictors;
|
||||||
|
|
||||||
|
private SeetaFace6FaceDetPredictors seetaFace6FaceDetPredictors;
|
||||||
|
|
||||||
|
private Predictor<Image, DetectedObjects> commonPredictor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void borrowPredictors(){
|
||||||
|
try {
|
||||||
|
//mtcnn
|
||||||
|
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||||
|
MtcnnFaceDetModel mtcnnFaceDetModel = (MtcnnFaceDetModel) faceDetModel;
|
||||||
|
mtcnnPredictors = mtcnnFaceDetModel.borrowPredictors();
|
||||||
|
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||||
|
//SeetaFace6
|
||||||
|
SeetaFace6FaceDetModel seetaFace6FaceDetModel = (SeetaFace6FaceDetModel) faceDetModel;
|
||||||
|
seetaFace6FaceDetPredictors = seetaFace6FaceDetModel.borrowPredictors();
|
||||||
|
}else{
|
||||||
|
//其他通用模型
|
||||||
|
commonPredictor = faceDetModel.borrowPredictor();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FaceException("获取predictors异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public R<DetectionInfo> detectTopFace(Image image){
|
||||||
|
DetectionResponse detectionResponse = null;
|
||||||
|
try {
|
||||||
|
//mtcnn
|
||||||
|
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||||
|
MtcnnFaceDetModel mtcnnFaceDetModel = (MtcnnFaceDetModel) faceDetModel;
|
||||||
|
DetectedObjects detections = mtcnnFaceDetModel.detectCoreByPredictors(image, mtcnnPredictors);
|
||||||
|
detectionResponse = FaceUtils.convertToDetectionResponse(detections, image);
|
||||||
|
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||||
|
//SeetaFace6
|
||||||
|
SeetaFace6FaceDetModel seetaFace6FaceDetModel = (SeetaFace6FaceDetModel) faceDetModel;
|
||||||
|
detectionResponse = seetaFace6FaceDetModel.detectByPredictors(image, seetaFace6FaceDetPredictors);
|
||||||
|
}else{
|
||||||
|
DetectedObjects detections = commonPredictor.predict(image);
|
||||||
|
detectionResponse = FaceUtils.convertToDetectionResponse(detections, image);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FaceException("获取predictors异常", e);
|
||||||
|
}
|
||||||
|
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
|
||||||
|
return R.fail(R.Status.NO_FACE_DETECTED);
|
||||||
|
}
|
||||||
|
DetectionInfo detectionInfo = detectionResponse.getDetectionInfoList().get(0);
|
||||||
|
return R.ok(detectionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(){
|
||||||
|
try {
|
||||||
|
//mtcnn
|
||||||
|
if(faceDetModel instanceof MtcnnFaceDetModel){
|
||||||
|
mtcnnPredictors.close();
|
||||||
|
}else if(faceDetModel instanceof SeetaFace6FaceDetModel){
|
||||||
|
//SeetaFace6
|
||||||
|
seetaFace6FaceDetPredictors.close();
|
||||||
|
}else{
|
||||||
|
faceDetModel.getPool().returnObject(commonPredictor);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FaceException("归还predictors异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -221,52 +221,51 @@ public class MtcnnFaceDetModel extends CommonFaceDetModel{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用MtcnnPredictors进行人脸检测
|
||||||
|
* @param image
|
||||||
|
* @param predictors
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public DetectedObjects detectCoreByPredictors(Image image, MtcnnPredictors predictors){
|
||||||
|
Predictor<NDList, NDList> pNetPredictor = predictors.pNetPredictor;
|
||||||
|
Predictor<NDList, NDList> rNetPredictor = predictors.rNetPredictor;
|
||||||
|
Predictor<NDList, NDList> oNetPredictor = predictors.oNetPredictor;
|
||||||
|
try (NDManager manager = pNetModel.getNDManager().newSubManager();){
|
||||||
|
int h = image.getHeight();
|
||||||
|
int w = image.getWidth();
|
||||||
|
//第一阶段
|
||||||
|
NDList outputPnet = PNetModel.firstStage(manager, pNetPredictor, image);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package cn.smartjavaai.face.model.facedect.mtcnn;
|
||||||
|
|
||||||
|
import ai.djl.inference.Predictor;
|
||||||
|
import ai.djl.ndarray.NDList;
|
||||||
|
import cn.smartjavaai.face.model.facedect.MtcnnFaceDetModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dwj
|
||||||
|
* @date 2025/11/24
|
||||||
|
*/
|
||||||
|
public class MtcnnPredictors implements AutoCloseable{
|
||||||
|
|
||||||
|
public Predictor<NDList, NDList> pNetPredictor;
|
||||||
|
public Predictor<NDList, NDList> rNetPredictor;
|
||||||
|
public Predictor<NDList, NDList> oNetPredictor;
|
||||||
|
|
||||||
|
// 标记是否由外部借用,用于控制 close 行为
|
||||||
|
private MtcnnFaceDetModel model;
|
||||||
|
|
||||||
|
public MtcnnPredictors(Predictor<NDList, NDList> p, Predictor<NDList, NDList> r, Predictor<NDList, NDList> o, MtcnnFaceDetModel m) {
|
||||||
|
this.pNetPredictor = p;
|
||||||
|
this.rNetPredictor = r;
|
||||||
|
this.oNetPredictor = o;
|
||||||
|
this.model = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
model.returnPredictor(pNetPredictor, rNetPredictor, oNetPredictor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import ai.djl.engine.Engine;
|
|||||||
import ai.djl.inference.Predictor;
|
import ai.djl.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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()];
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.smartjavaai.face.seetaface;
|
||||||
|
|
||||||
|
import cn.smartjavaai.face.model.facedect.SeetaFace6FaceDetModel;
|
||||||
|
import com.seeta.sdk.FaceDetector;
|
||||||
|
import com.seeta.sdk.FaceLandmarker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SeetaFace6 人脸检测Detector
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
public class SeetaFace6FaceDetPredictors implements AutoCloseable{
|
||||||
|
|
||||||
|
public FaceDetector faceDetector;
|
||||||
|
public FaceLandmarker faceLandmarker;
|
||||||
|
public SeetaFace6FaceDetModel model;
|
||||||
|
|
||||||
|
public SeetaFace6FaceDetPredictors(FaceDetector faceDetector, FaceLandmarker faceLandmarker, SeetaFace6FaceDetModel model) {
|
||||||
|
this.faceDetector = faceDetector;
|
||||||
|
this.faceLandmarker = faceLandmarker;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(){
|
||||||
|
model.returnPredictor(faceDetector, faceLandmarker);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -637,7 +637,8 @@ public class MilvusClient implements VectorDBClient {
|
|||||||
|
|
||||||
List<FaceVector> result = new ArrayList<>();
|
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<?>) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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){
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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 不能为空");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.config;
|
||||||
|
|
||||||
|
import cn.smartjavaai.common.config.ModelConfig;
|
||||||
|
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||||
|
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测模型参数配置
|
||||||
|
*
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ZeroDetConfig extends ModelConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型
|
||||||
|
*/
|
||||||
|
private ZeroDetModelEnum modelEnum;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型路径
|
||||||
|
*/
|
||||||
|
private String modelPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 置信度阈值
|
||||||
|
*/
|
||||||
|
private float threshold = 0.3f;
|
||||||
|
|
||||||
|
|
||||||
|
public ZeroDetConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetConfig(ZeroDetModelEnum modelEnum, DeviceEnum device) {
|
||||||
|
this.modelEnum = modelEnum;
|
||||||
|
setDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetConfig(ZeroDetModelEnum modelEnum) {
|
||||||
|
this.modelEnum = modelEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.criteria;
|
||||||
|
|
||||||
|
import ai.djl.Device;
|
||||||
|
import ai.djl.huggingface.translator.ZeroShotObjectDetectionTranslatorFactory;
|
||||||
|
import ai.djl.modality.cv.Image;
|
||||||
|
import ai.djl.modality.cv.VisionLanguageInput;
|
||||||
|
import ai.djl.modality.cv.output.DetectedObjects;
|
||||||
|
import ai.djl.modality.cv.translator.YoloWorldTranslatorFactory;
|
||||||
|
import ai.djl.repository.zoo.Criteria;
|
||||||
|
import ai.djl.training.util.ProgressBar;
|
||||||
|
import ai.djl.translate.TranslatorFactory;
|
||||||
|
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||||
|
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||||
|
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测Criteria工厂
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
public class ZeroDetCriteriaFactory {
|
||||||
|
|
||||||
|
|
||||||
|
public static Criteria<VisionLanguageInput, DetectedObjects> createCriteria(ZeroDetConfig config) {
|
||||||
|
Device device = null;
|
||||||
|
if(!Objects.isNull(config.getDevice())){
|
||||||
|
device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(config.getGpuId());
|
||||||
|
}
|
||||||
|
TranslatorFactory translatorFactory = null;
|
||||||
|
if(config.getModelEnum() == ZeroDetModelEnum.OWLV2_BASE_PATCH16){
|
||||||
|
translatorFactory = new ZeroShotObjectDetectionTranslatorFactory();
|
||||||
|
}else if(config.getModelEnum() == ZeroDetModelEnum.YOLOV8S_WORLDV2){
|
||||||
|
translatorFactory = new YoloWorldTranslatorFactory();
|
||||||
|
}
|
||||||
|
Criteria<VisionLanguageInput, DetectedObjects> criteria =
|
||||||
|
Criteria.builder()
|
||||||
|
.setTypes(VisionLanguageInput.class, DetectedObjects.class)
|
||||||
|
.optModelUrls(StringUtils.isNotBlank(config.getModelPath()) ? null :
|
||||||
|
config.getModelEnum().getModelUri())
|
||||||
|
.optModelPath(StringUtils.isNotBlank(config.getModelPath()) ? Paths.get(config.getModelPath()) : null)
|
||||||
|
.optDevice(device)
|
||||||
|
.optEngine(config.getModelEnum().getEngine())
|
||||||
|
.optTranslatorFactory(translatorFactory)
|
||||||
|
.optProgress(new ProgressBar())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测参数
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DetectParams {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 置信度阈值
|
||||||
|
*/
|
||||||
|
private float threshold = 0.3f;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测模型枚举
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
public enum ZeroDetModelEnum {
|
||||||
|
|
||||||
|
YOLOV8S_WORLDV2("PyTorch", "djl://ai.djl.pytorch/yolov8s-worldv2"),
|
||||||
|
OWLV2_BASE_PATCH16("PyTorch", "djl://ai.djl.huggingface.pytorch/google/owlv2-base-patch16");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称获取枚举 (忽略大小写和下划线变体)
|
||||||
|
*/
|
||||||
|
public static ZeroDetModelEnum fromName(String name) {
|
||||||
|
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
|
||||||
|
for (ZeroDetModelEnum model : values()) {
|
||||||
|
if (model.name().replaceAll("_", "").equals(formatted)) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("未知模型名称: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String modelUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型引擎
|
||||||
|
*/
|
||||||
|
private final String engine;
|
||||||
|
|
||||||
|
ZeroDetModelEnum(String engine, String modelUri) {
|
||||||
|
this.modelUri = modelUri;
|
||||||
|
this.engine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModelUri() {
|
||||||
|
return modelUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEngine() {
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测异常
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
public class ZeroDetException extends RuntimeException{
|
||||||
|
|
||||||
|
public ZeroDetException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZeroDetException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.model;
|
||||||
|
|
||||||
|
import ai.djl.MalformedModelException;
|
||||||
|
import ai.djl.engine.Engine;
|
||||||
|
import ai.djl.inference.Predictor;
|
||||||
|
import ai.djl.modality.cv.Image;
|
||||||
|
import ai.djl.modality.cv.VisionLanguageInput;
|
||||||
|
import ai.djl.modality.cv.output.DetectedObjects;
|
||||||
|
import ai.djl.repository.zoo.Criteria;
|
||||||
|
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||||
|
import ai.djl.repository.zoo.ZooModel;
|
||||||
|
import cn.smartjavaai.common.cv.SmartImageFactory;
|
||||||
|
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||||
|
import cn.smartjavaai.common.entity.R;
|
||||||
|
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||||
|
import cn.smartjavaai.objectdetection.exception.DetectionException;
|
||||||
|
import cn.smartjavaai.vision.utils.DetectedObjectsFilter;
|
||||||
|
import cn.smartjavaai.vision.utils.DetectorUtils;
|
||||||
|
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||||
|
import cn.smartjavaai.zeroshot.criteria.ZeroDetCriteriaFactory;
|
||||||
|
import cn.smartjavaai.zeroshot.exception.ZeroDetException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测模型
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CommonZeroDetModel implements ZeroDetModel {
|
||||||
|
|
||||||
|
|
||||||
|
private ZeroDetConfig config;
|
||||||
|
|
||||||
|
private ZooModel<VisionLanguageInput, DetectedObjects> model;
|
||||||
|
|
||||||
|
private GenericObjectPool<Predictor<VisionLanguageInput, DetectedObjects>> predictorPool;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadModel(ZeroDetConfig config) {
|
||||||
|
if(Objects.isNull(config.getModelEnum())){
|
||||||
|
throw new DetectionException("未配置模型枚举");
|
||||||
|
}
|
||||||
|
Criteria<VisionLanguageInput, DetectedObjects> criteria = ZeroDetCriteriaFactory.createCriteria(config);
|
||||||
|
this.config = config;
|
||||||
|
try {
|
||||||
|
model = criteria.loadModel();
|
||||||
|
// 创建池子:每个线程独享 Predictor
|
||||||
|
this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(model));
|
||||||
|
int predictorPoolSize = config.getPredictorPoolSize();
|
||||||
|
if(config.getPredictorPoolSize() <= 0){
|
||||||
|
predictorPoolSize = Runtime.getRuntime().availableProcessors(); // 默认等于CPU核心数
|
||||||
|
}
|
||||||
|
predictorPool.setMaxTotal(predictorPoolSize);
|
||||||
|
log.debug("当前设备: " + model.getNDManager().getDevice());
|
||||||
|
log.debug("当前引擎: " + Engine.getInstance().getEngineName());
|
||||||
|
log.debug("模型推理器线程池最大数量: " + predictorPoolSize);
|
||||||
|
} catch (IOException | ModelNotFoundException | MalformedModelException e) {
|
||||||
|
throw new DetectionException("模型加载失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<DetectionResponse> detect(Image image, String[] candidates) {
|
||||||
|
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(image, candidates));
|
||||||
|
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image);
|
||||||
|
return R.ok(detectionResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型核心推理方法
|
||||||
|
* @param input
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DetectedObjects detectCore(VisionLanguageInput input) {
|
||||||
|
Predictor<VisionLanguageInput, DetectedObjects> predictor = null;
|
||||||
|
try {
|
||||||
|
predictor = predictorPool.borrowObject();
|
||||||
|
DetectedObjects detectedObjects = predictor.predict(input);
|
||||||
|
//过滤
|
||||||
|
if(Objects.nonNull(detectedObjects) && detectedObjects.getNumberOfObjects() > 0){
|
||||||
|
DetectedObjectsFilter detectedObjectsFilter = new DetectedObjectsFilter(null, config.getThreshold());
|
||||||
|
detectedObjects = detectedObjectsFilter.filter(detectedObjects);
|
||||||
|
}
|
||||||
|
return detectedObjects;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DetectionException("零样本目标检测错误", e);
|
||||||
|
}finally {
|
||||||
|
if (predictor != null) {
|
||||||
|
try {
|
||||||
|
predictorPool.returnObject(predictor); //归还
|
||||||
|
log.debug("释放资源");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("归还Predictor失败", e);
|
||||||
|
try {
|
||||||
|
predictor.close(); // 归还失败才销毁
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("关闭Predictor失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<DetectionResponse> detectAndDraw(Image image, String[] candidates) {
|
||||||
|
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(image, candidates));
|
||||||
|
image.drawBoundingBoxes(detectedObjects);
|
||||||
|
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, image);
|
||||||
|
detectionResponse.setDrawnImage(image);
|
||||||
|
return R.ok(detectionResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<DetectionResponse> detectAndDraw(String[] candidates, String imagePath, String outputPath) {
|
||||||
|
try {
|
||||||
|
Image img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||||
|
DetectedObjects detectedObjects = detectCore(new VisionLanguageInput(img, candidates));
|
||||||
|
img.drawBoundingBoxes(detectedObjects);
|
||||||
|
img.save(Files.newOutputStream(Paths.get(outputPath)), "png");
|
||||||
|
DetectionResponse detectionResponse = DetectorUtils.convertToDetectionResponse(detectedObjects, img);
|
||||||
|
return R.ok(detectionResponse);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ZeroDetException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fromFactory = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFromFactory(boolean fromFactory) {
|
||||||
|
this.fromFactory = fromFactory;
|
||||||
|
}
|
||||||
|
public boolean isFromFactory() {
|
||||||
|
return fromFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
try {
|
||||||
|
if (predictorPool != null) {
|
||||||
|
predictorPool.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("关闭 predictorPool 失败", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (model != null) {
|
||||||
|
model.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("关闭 model 失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.model;
|
||||||
|
|
||||||
|
import ai.djl.modality.cv.Image;
|
||||||
|
import ai.djl.modality.cv.VisionLanguageInput;
|
||||||
|
import ai.djl.modality.cv.output.DetectedObjects;
|
||||||
|
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||||
|
import cn.smartjavaai.common.entity.R;
|
||||||
|
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测模型
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface ZeroDetModel extends AutoCloseable{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载模型
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
void loadModel(ZeroDetConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测
|
||||||
|
* @param image
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default R<DetectionResponse> detect(Image image, String[] candidates){
|
||||||
|
throw new UnsupportedOperationException("默认不支持该功能");
|
||||||
|
}
|
||||||
|
|
||||||
|
default DetectedObjects detectCore(VisionLanguageInput input){
|
||||||
|
throw new UnsupportedOperationException("默认不支持该功能");
|
||||||
|
}
|
||||||
|
|
||||||
|
default R<DetectionResponse> detectAndDraw(Image image, String[] candidates){
|
||||||
|
throw new UnsupportedOperationException("默认不支持该功能");
|
||||||
|
}
|
||||||
|
|
||||||
|
default R<DetectionResponse> detectAndDraw(String[] candidates, String imagePath, String outputPath){
|
||||||
|
throw new UnsupportedOperationException("默认不支持该功能");
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setFromFactory(boolean fromFactory){
|
||||||
|
throw new UnsupportedOperationException("默认不支持该功能");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package cn.smartjavaai.zeroshot.model;
|
||||||
|
|
||||||
|
import cn.smartjavaai.common.config.Config;
|
||||||
|
import cn.smartjavaai.objectdetection.exception.DetectionException;
|
||||||
|
import cn.smartjavaai.zeroshot.config.ZeroDetConfig;
|
||||||
|
import cn.smartjavaai.zeroshot.enums.ZeroDetModelEnum;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 零样本目标检测 模型工厂
|
||||||
|
* @author dwj
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ZeroDetModelFactory {
|
||||||
|
|
||||||
|
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
|
||||||
|
private static volatile ZeroDetModelFactory instance;
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<ZeroDetModelEnum, ZeroDetModel> modelMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型注册表
|
||||||
|
*/
|
||||||
|
private static final Map<ZeroDetModelEnum, Class<? extends ZeroDetModel>> registry =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
// 私有构造函数,防止外部创建实例
|
||||||
|
private ZeroDetModelFactory() {}
|
||||||
|
|
||||||
|
// 双重检查锁定的单例方法
|
||||||
|
public static ZeroDetModelFactory getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (ZeroDetModelFactory.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ZeroDetModelFactory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模型(通过配置)
|
||||||
|
* @param config
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ZeroDetModel getModel(ZeroDetConfig config) {
|
||||||
|
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
|
||||||
|
throw new DetectionException("未配置模型");
|
||||||
|
}
|
||||||
|
return modelMap.computeIfAbsent(config.getModelEnum(), k -> {
|
||||||
|
return createModel(config);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用ModelConfig创建模型
|
||||||
|
* @param config
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ZeroDetModel createModel(ZeroDetConfig config) {
|
||||||
|
Class<?> clazz = registry.get(config.getModelEnum());
|
||||||
|
if(clazz == null){
|
||||||
|
throw new DetectionException("Unsupported model");
|
||||||
|
}
|
||||||
|
ZeroDetModel model = null;
|
||||||
|
try {
|
||||||
|
model = (ZeroDetModel) clazz.newInstance();
|
||||||
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
|
throw new DetectionException(e);
|
||||||
|
}
|
||||||
|
model.loadModel(config);
|
||||||
|
model.setFromFactory(true);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册模型
|
||||||
|
* @param modelEnum
|
||||||
|
* @param clazz
|
||||||
|
*/
|
||||||
|
private static void registerAlgorithm(ZeroDetModelEnum modelEnum, Class<? extends ZeroDetModel> clazz) {
|
||||||
|
registry.put(modelEnum, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除缓存的模型
|
||||||
|
* @param modelEnum
|
||||||
|
*/
|
||||||
|
public static void removeFromCache(ZeroDetModelEnum modelEnum) {
|
||||||
|
modelMap.remove(modelEnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 初始化默认算法
|
||||||
|
static {
|
||||||
|
registerAlgorithm(ZeroDetModelEnum.YOLOV8S_WORLDV2, CommonZeroDetModel.class);
|
||||||
|
registerAlgorithm(ZeroDetModelEnum.OWLV2_BASE_PATCH16, CommonZeroDetModel.class);
|
||||||
|
log.debug("缓存目录:{}", Config.getCachePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user