Merge pull request #20 from geekwenjie/master

合并最新更新
This commit is contained in:
geekwenjie
2025-11-26 16:08:01 +08:00
committed by GitHub
433 changed files with 25832 additions and 4992 deletions

516
README.md
View File

@@ -46,7 +46,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>人脸检测</p>
<p>人脸检测(Face Detection)</p>
- 5点人脸关键点定位 <br>
</div>
</td>
@@ -59,7 +59,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>人脸比对11</p>
<p>人脸比对11<br>(Face Recognition)</p>
- 人脸对齐 <br>
</div>
</td>
@@ -72,7 +72,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>人证核验</p>
<p>人证核验<br>(Face ID Verification)</p>
</div>
</td>
<td>
@@ -84,7 +84,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>人脸比对1N</p>
<p>人脸比对1N<br>(Face Recognition)</p>
- 人脸对齐 <br>
- 人脸注册 <br>
- 人脸库查询<br>
@@ -101,11 +101,11 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<td>
<div align="left">
<p>人脸属性检测</p>
- 性别检测 <br>
- 年龄检测 <br>
- 口罩检测<br>
- 眼睛状态检测<br>
- 脸部姿态检测<br>
- 性别检测(GenderDetection) <br>
- 年龄检测(AgeDetection) <br>
- 口罩检测(Face Mask Detection)<br>
- 眼睛状态检测(EyeClosenessDetection)<br>
- 脸部姿态检测(FacePoseEstimation)<br>
</div>
</td>
<td>
@@ -117,7 +117,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>活体检测</p>
<p>活体检测<br>(FaceLivenessDetection)</p>
- 图片和视频活体检测 <br>
</div>
</td>
@@ -130,7 +130,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>人脸表情识别</p>
<p>人脸表情识别<br>(FacialExpressionRecognition)</p>
- 7种表情检测 <br>
</div>
</td>
@@ -140,10 +140,23 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>图像分类<br>(Image Classification)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/vision/cls.png" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>目标检测</p>
<p>目标检测(Object Detection)</p>
- 视频流目标检测rtsp、摄像头、视频文件等 <br>
</div>
</td>
<td>
@@ -151,11 +164,40 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/object_detect_1.jpeg" width = "500px"/>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/object_detection_detected.jpg" width = "500px"/>
</div>
</td>
</tr>
<tr>
<tr>
<td>
<div align="left">
<p>自定义目标训练+检测</p>
<p>语义分割<br>(Semantic Segmentation)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/dog_bike_car_semseg.png" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>实例分割<br>(Instance Segmentation)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/dog_bike_car_instance.png" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>自定义目标训练+检测<br>(Object Detection)</p>
</div>
</td>
<td>
@@ -167,7 +209,55 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>OCR文字识别</p>
<p>行人检测(Person Detection)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/person_detected.png" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>人类动作识别<br>(Action Recognition)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/calling.jpg" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>OBB旋转框检测<br>(Oriented Bounding Boxes Object Detection)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/boats_detected.jpg" height = "200px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>姿态估计<br>(Pose Estimation)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/objectdect/pose_detected.png" height = "300px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>OCR文字识别<br>(Text Recognition)</p>
- 支持任意角度文字识别 <br>
- 支持印刷体识别 <br>
- 支持手写字识别<br>
@@ -183,7 +273,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<td>
<div align="left">
<p>OCR文字识别</p>
- 表格识别 <br>
- 表格识别<br>(Table Structure Recognition) <br>
</div>
</td>
<td>
@@ -195,7 +285,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>车牌识别</p>
<p>车牌识别<br>(License Plate Recognition)</p>
- 单层/双层检测 <br>
- 车牌颜色识别 <br>
</div>
@@ -205,7 +295,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/ocr/plate_recognized.jpg" width = "500px"/>
</div>
</td>
<td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/ocr/plate_recognized2.jpg" width = "500px"/>
</div>
@@ -214,7 +304,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
<tr>
<td>
<div align="left">
<p>机器翻译</p>
<p>机器翻译<br>(Machine Translation)</p>
- 200多种语言互相翻译
</div>
</td>
@@ -224,47 +314,102 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>语音识别<br>(Speech Recognition)</p>
- 支持100种语言<br>
- 支持实时语音识别
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/speech/asr.png" width = "500px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>语音合成(ASR)</p>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/speech/tts.jpg" width = "500px"/>
</div>
</td>
</tr>
<tr>
<td>
<div align="left">
<p>CLIP</p>
- 文搜图 <br>
- 图搜图 <br>
- 图搜文<br>
</div>
</td>
<td>
<div align="center">
<img src="https://cdn.jsdelivr.net/gh/geekwenjie/SmartJavaAI-Site/images/vision/clip.png" width = "500px"/>
</div>
</td>
</tr>
</table>
</div>
## 🚀 核心亮点
## 🚀 SmartJavaAI 解决了什么问题?
针对 Java 开发者在集成智能算法时常见的两大痛点:
- 🐍 主流AI深度学习框架PyTorch/TensorFlow的Python生态与Java工程体系割裂
- ⚙️ 现有算法方案分散杂乱,封装不统一,使用门槛高,不易直接服务于 Java 业务开发
- ⚙️ 现有AI集成方案分散杂乱,封装不统一,使用门槛高,不易直接服务于 Java 业务开发
我们实现了:
**开箱即用** - 两行代码即可调用算法
**开箱即用** - 两行代码即可调用算法
**支持多种深度学习引擎** - Pytorch、Tensorflow、MXNet、ONNX Runtime
**功能丰富** - 当前支持人脸识别目标检测,未来将陆续支持 OCR、图像分类、NLP 等多个 AI 领域任务,构建全面的智能算法体系。
**功能丰富** - 当前支持人脸识别目标检测、OCR、NLP 等多个 AI 领域任务,构建全面的智能算法体系。
**跨平台兼容** - 支持Windows/Linux/macOS系统x86 & ARM架构
**跨平台兼容** - 支持Windows/Linux/macOS系统x86 & ARM架构
## 📌 支持功能
### ✅ 已实现功能
- **人脸识别**
- 人脸检测5点人脸关键点定位
- **人脸识别**
- 人脸检测人脸区域检测、5点人脸关键点定位
- 人脸识别人脸512维特征提取、人脸对齐、1:1 人脸比对、1:N 人脸识别
- 人脸库:人脸注册、更新、查询、删除(支持向量数据库milvus/sqlite)
- 人脸属性检测:性别、年龄、口罩、眼睛状态、脸部姿态
- 静默活体检测:图片、视频活体检测
- 人脸表情识别7种表情识别
- 人脸质量评估:亮度评估、清晰度评估、完整度评估、姿态评估、遮挡评估
- **图像分类**
- 支持多种主流模型:集成 YOLOv8、YOLOv11 等分类模型
- 支持自定义模型加载:可无缝加载并部署用户自行训练的分类模型
- **目标检测**
- 支持多种主流模型:兼容 YOLOv3、YOLOv5、YOLOv8、YOLOv11、YOLOv12、SSD 等目标检测算法
- 支持多种主流模型:集成 YOLOv5、YOLOv8、YOLOv11、YOLOv12、Tensorflow Object Detection 等目标检测模型
- 支持自定义模型加载:可无缝加载并部署用户自行训练的目标检测模型
- 集成行人检测模型
- **语义分割**
- 集成DeepLabV3模型
- **实例分割**
- 集成YOLOv8-seg、YOLOv11-seg、Mask R-CNN等模型
- **OBB旋转框目标检测**
- 集成YOLOv11-obb模型
- **动作识别**
- 支持KINETICS400数据集中400个人类动作识别
- **姿态估计**
- 集成YOLOv8-pose、YOLOv11-pose等模型
- **CLIP**
- 支持提取图片及文本特征
- 支持文搜图、图搜文、图搜图
- **OCR文字识别**
- 支持PaddleOCR 3.0模型集成最新PP-OCRv5、PP-OCRv4、表格结构识别模型(SLANet_plus)、文本行方向分类模型
- 支持任意角度识别,方向校准
@@ -273,33 +418,41 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 支持中文车牌识别:单层/双层检测颜色识别支持12种中文车牌
- **机器翻译**
- 集成NLLB-200模型支持200+语言互相翻译
- **语音识别(ASR)**
- 集成openai的whisper模型支持100种语言
- 集成vosk语音识别
- 集成sherpa-onnx语音识别
- **语音合成(TTS)**
- 集成sherpa-onnx支持中文、方言、粤语、英文、德语等多种语言
## 🌟 AI集成方式对比
| 方案 | 技术特点 | 优点 | 缺点 |
|---------------------|--------------------------------------------------------------------------|---------------------------------------------------------------------|-----------------------------------------------|
| **OpenCV** | 传统图像处理方案 | ✅ 提供java接口<br>✅ 轻量级部署<br>✅ 社区资源丰富 | ❌ 基于传统算法精度低(60%-75%)<br>❌ 需本地安装环境 |
| 方案 | 技术特点 | 优点 | 缺点 |
|---------------------|--------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------|
| **OpenCV** | 传统图像处理方案 | ✅ 提供java接口<br>✅ 轻量级部署<br>✅ 社区资源丰富 | ❌ 基于传统算法精度低(60%-75%)<br>❌ 需本地安装环境 |
| &zwnj;**商业闭源SDK如虹软等**&zwnj; | 商业级闭源解决方案 | ✅ 开箱即用<br>✅ 提供完整文档和SDK<br>✅ 支持离线活体检测 | ❌ 免费版需年度授权更新<br>❌ 商业授权费用高<br>❌ 代码不可控 |
| &zwnj;**云API(阿里云)**&zwnj; | SaaS化云端服务 | ✅ 零部署成本<br>✅ 支持高并发<br>✅ 自带模型迭代 | ❌ 网络延迟风险(200-800ms)<br>❌ 按调用量计费<br>❌ 有数据安全风险 |
| &zwnj;**Python混合调用**&zwnj; | 跨语言调用方案 | ✅ 可集成PyTorch/TF等框架<br>✅ 支持自定义算法<br>✅ 识别精度高 | ❌ 需维护双语言环境<br>❌ 进程通信性能损耗(30%+)<br>❌ 异常处理复杂度翻倍 |
| &zwnj;**JNI/JNA**&zwnj; | 跨语言底层调用方案 | ✅ 直接调用 C/C++ 高性能算法库✅ 支持调用各种原生成熟库✅ 可封装成通用工具Jar | ❌ 开发成本高JNI更复杂❌ 跨平台兼容性差 |
| &zwnj;**JNI/JNA**&zwnj; | 跨语言底层调用方案 | ✅ 直接调用 C/C++ 高性能算法库<br>✅ 支持调用各种原生成熟库<br>✅ 可封装成通用工具Jar | ❌ 开发成本高JNI更复杂<br>❌ 跨平台兼容性差 |
| &zwnj;**DJL框架**&zwnj; | 深度学习框架 | ✅ 纯Java实现<br>✅ 支持主流深度学习框架<br>✅ 可加载预训练模型(99%+) | ❌ 需掌握DL知识<br>❌ 需处理模型加载、预处理、后处理等复杂技术细节 |
| &zwnj;**SmartJavaAI**&zwnj; | java深度学习工具包 | ✅ 支持主流深度学习框架<br>✅ 提供丰富、开箱即用API<br> ✅ 上手简单单一Jar包集成 | 无 |
| &zwnj;**SmartJavaAI**&zwnj; | java深度学习工具包 | ✅ 支持主流深度学习框架<br>✅ 提供丰富、开箱即用API<br>✅ 上手简单单一Jar包集成 | 无 |
## 🛠️包含组件
| 模块 | 介绍 |
|-----------------------------|---------------------------|
| smartjavaai-common | 基础通用模块,封装了公共功能,供各算法模块共享使用 |
| smartjavaai-face | 人脸功能模块 |
| smartjavaai-objectdetection | 目标检测模块 |
| smartjavaai-ocr | OCR文字识别模块 |
| smartjavaai-translate | 机器翻译模块 |
| 模块 | 介绍 |
|-----------------------|---------------------------|
| common | 基础通用模块,封装了公共功能,供各算法模块共享使用 |
| bom | 依赖管理模块 |
| face | 人脸功能模块 |
| vision | 通用视觉模块(目标检测等功能) |
| ocr | OCR文字识别模块 |
| translate | 机器翻译模块 |
| speech | 语音功能模块,包含 ASR 和 TTS |
可以根据需求对每个模块单独引入,也可以通过引入`smartjavaai-all`方式引入所有模块。
可以根据需求对每个模块单独引入,也可以通过引入`all`方式引入所有模块。
-------------------------------------------------------------------------------
@@ -316,22 +469,52 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
### 1、环境要求
- Java 版本:**JDK 8或更高版本**
- 操作系统:不同模型支持的系统不一样,具体请查看文档
- 操作系统:不同模型支持的系统不一样,具体请查看[文档](http://doc.smartjavaai.cn)
### 2、Maven
在项目的pom.xml的dependencies中加入以下内容全部功能也可以根据需求对每个模块单独引入:
在项目的 `pom.xml``dependencies` 中可以一次性引入全部功能(如下所示)。
⚠️ **注意:不推荐直接引入全部依赖**,更推荐根据实际需求,按功能模块单独引入,避免引入不必要的包。
详细引入方式请查看 [文档](http://doc.smartjavaai.cn/install.html)、或查看[示例代码](https://gitee.com/dengwenjie/SmartJavaAI/tree/master/examples)
```xml
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-all</artifactId>
<version>1.0.22</version>
<artifactId>all</artifactId>
<version>1.0.27</version>
</dependency>
```
### 3、完整示例代码
[示例代码](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、文档地址
[开发文档](http://doc.smartjavaai.cn)
@@ -344,39 +527,37 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
**人脸检测模型(FaceDetection、FaceLandmarkExtraction)**
支持功能:
- 人脸检测
- 5点人脸关键点定位
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
| ----------- |---------|-------------------------|----------------------------------------------------------------------------------------|
| RetinaFace | PyTorch | 高精度人脸检测模型 | [Github](https://github.com/biubug6/Pytorch_Retinaface) |
| UltraLightFastGenericFace | PyTorch | 针对边缘计算设备设计的轻量人脸检测模型 | [Github](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB) |
| SeetaFace6 | C++ | 中科视拓最新开放的开源免费的全栈人脸识别工具包 | [Github](https://github.com/seetafaceengine/SeetaFace6) |
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|------------------------------|-------------|------------------------------|---------------------------------------------------------------------------------|
| MTCNN(均衡) | PyTorch | 使用最广泛的模型之一,经典多阶段人脸检测,速度与精度均衡 | [Github](https://github.com/timesler/facenet-pytorch) |
| SeetaFace6(均衡模型) | C++ | 中科视拓最新开放的开源免费的全栈人脸识别工具包 | [Github](https://github.com/seetafaceengine/SeetaFace6) |
| RetinaFace(高精度) | PyTorch | 基于单阶段检测的多尺度人脸检测 | [Github](https://github.com/biubug6/Pytorch_Retinaface) |
| RetinaFace_1080x720(高精度) | OnnxRuntime | 针对高分辨率图像优化的 RetinaFace 模型 | [Github](https://github.com/biubug6/Pytorch_Retinaface) |
| RetinaFace_640x640(高精度) | OnnxRuntime | 针对低分辨率图像优化的 RetinaFace 模型 | [Github](https://github.com/biubug6/Pytorch_Retinaface) |
| yolov5face_n_0.5_320x320(极速) | OnnxRuntime | YOLOv5 小型人脸检测模型,设计轻量,适合快速推理 | 无 |
| yolov5face_m_640x640(极速) | OnnxRuntime | YOLOv5 中型人脸检测模型 | 无 |
| UltraLightFastGenericFace(极速) | PyTorch | 针对边缘计算设备设计的轻量人脸检测模型 | [Github](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB) |
---
**人脸识别模型(FaceRecognition)**
支持功能:
- 人脸512维特征提取
- 人脸对齐(人脸矫正)
- 人脸特征比对(内积[IP]、欧氏距离[L2]、余弦相似度[COSINE]
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------|---------|---------------------------------------------------------|----------------------------------------------------------------------------------------|
| InsightFace_IR-SE50| PyTorch | (高精度)这是对 ArcFace论文和 InsightFaceGitHub的 PyTorch 重新实现 | [Github](https://github.com/TreB1eN/InsightFace_Pytorch) |
| InsightFace_Mobilefacenet | PyTorch | (轻量级)这是对 ArcFace论文和 InsightFaceGitHub的 PyTorch 重新实现 | [Github](https://github.com/TreB1eN/InsightFace_Pytorch) |
| FaceNet | PyTorch | 基于 PyTorch 的 Inception ResNetV1模型仓库 | [Github](https://github.com/timesler/facenet-pytorch) |
| ElasticFace| PyTorch | 基于 CVPRW2022 论文《ElasticFace: Elastic Margin Loss for Deep Face Recognition》实现的人脸识别模型| [Github](https://github.com/fdbtrs/ElasticFace) |
| SeetaFace6 | C++ | 中科视拓最新开放的开源免费的全栈人脸识别工具包 | [Github](https://github.com/seetafaceengine/SeetaFace6) |
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------------------------------|---------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
| InsightFace_IR-SE50(高精度) | PyTorch | 这是对 ArcFace论文和 InsightFaceGitHub的 PyTorch 重新实现 | [Github](https://github.com/TreB1eN/InsightFace_Pytorch) |
| InsightFace_Mobilefacenet(极速) | PyTorch | (轻量级)这是对 ArcFace论文和 InsightFaceGitHub的 PyTorch 重新实现 | [Github](https://github.com/TreB1eN/InsightFace_Pytorch) |
| FaceNet | PyTorch | 基于 PyTorch 的 Inception ResNetV1模型仓库 | [Github](https://github.com/timesler/facenet-pytorch) |
| ElasticFace | PyTorch | 基于 CVPRW2022 论文《ElasticFace: Elastic Margin Loss for Deep Face Recognition》实现的人脸识别模型 | [Github](https://github.com/fdbtrs/ElasticFace) |
| SeetaFace6 | C++ | 中科视拓最新开放的开源免费的全栈人脸识别工具包 | [Github](https://github.com/seetafaceengine/SeetaFace6) |
| SphereFace-20A | PyTorch+OnnxRuntime | SphereFace:Deep Hypersphere Embedding for Face Recognition | [Github](https://github.com/wy1iu/sphereface) |
| DREAM_IJBA_RES18 | OnnxRuntime | DREAM block for Pose-Robust Face Recognition | [Github](https://github.com/penincillin/DREAM) |
| face.evoLVe | PyTorch | High-Performance Face Recognition Library based on PaddlePaddle & PyTorch | [Github](https://github.com/ZhaoJ9014/face.evoLVe) |
| VGGFace2 | PyTorch | PyTorch Face Recognizer based on 'VGGFace2: A dataset for recognising faces across pose and age'. | [Github](https://github.com/cydonia999/VGGFace2-pytorch) |
**静态活体检测(RGB)模型Silent face-anti-spoofing、FaceLivenessDetection**
支持功能:
- 检测图片中的人脸是否为来自认证设备端的近距离裸拍活体人脸对象(裸拍活体正面人脸是指真人未经重度PS、风格化、人工合成等后处理的含正面人脸)
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-----------|----------------|-------------------------|----------------------------------------------------------------------|
| MiniVision| OnnxRuntime | 小视科技的静默活体检测 | [Github](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing) |
@@ -425,24 +606,36 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
---
#### 目标检测模型
支持功能:
- 自训练模型推理
- yolov3~yolov12 系列
#### 图像分类模型(CLS)
**YOLO 系列**
|模型名称 | 引擎 | 版本 | 大小Backbone | <div style="width: 60pt">数据集</div> | <div style="width: 50pt">精度</div> | <div style="width: 50pt">速度</div> | <div style="width: 150pt">适用场景</div> |
| :---------- |-------------| -------| ----------- |----------------------------------------|-----------------------------------|-------|--------------------------------------|
| YOLO12N | OnnxRuntime | v12 | 极轻量 | COCO | 高 | 极快 | YOLO 系列最新版本,精度与速度进一步优化,适合高实时性要求场景 |
|YOLO11N | PyTorch | v11 | 极轻量 | COCO | 中等偏上 | 极快 | 与 v8n 类似,版本更新点在兼容性与 API |
|YOLOV8N | PyTorch | v8 | 极轻量 | COCO | 中等偏上 | 极快 | 对实时性要求极高的应用 |
|YOLOV5S | PyTorch | v5 | 小型 | COCO | 较高 | 非常快 | 常见通用场景,算力资源有限时优选 |
|YOLOV5S_ONNXRUNTIME| OnnxRuntime | v5 | 小型 | COCO | 较高 | 加速(需 ONNX 支持) | Windows/Linux 通用加速部署 |
|YOLO (MXNet / 通用模型) | MXNet | v3 | DarkNet53 | COCO | 较高 | 快 | 需要 MXNet 生态或复现老项目时使用 |
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------------|----------------|------------|----------------------------------------------------------------------|
| YOLOV11-cls | OnnxRuntime | 最流行的图像分类模型 | [Github](https://docs.ultralytics.com/zh/models/) |
| YOLOV8-cls | OnnxRuntime | 最流行的图像分类模型 | [Github](https://docs.ultralytics.com/zh/models/) |
#### 目标检测模型
**YOLO 系列**
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|---------|----------------|-------------------------|----------------------------------------------------------------------|
| YOLOV12 | OnnxRuntime | 最流行的目标检测模型 | [Github](https://docs.ultralytics.com/zh/models/) |
| YOLOV11 | OnnxRuntime | 最流行的目标检测模型 | [Github](https://docs.ultralytics.com/zh/models/) |
| YOLOV8 | OnnxRuntime | 最流行的目标检测模型 | [Github](https://docs.ultralytics.com/zh/models/) |
**Tensorflow系列**
仅测试了以下Tensorflow目标检测模型
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|---------|-------------|----------------|----------------------------------------------------------------------|
| EfficientDet | Tensorflow | Tensorflow目标检测 | [Github](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md) |
| SSD MobileNet V2 | Tensorflow | Tensorflow目标检测 | [Github](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md) |
| Faster RCNN Inception Resnet V2 | Tensorflow | Tensorflow目标检测 | [Github](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md) |
**SSD 系列**
| 模型名称 | 引擎 | 骨干网络 | 输入尺寸 | <div style="width: 60pt">训练数据集</div> | 精度mAP | <div style="width: 50pt">推理速度</div> | <div style="width: 150pt">适用场景</div>|
@@ -453,21 +646,68 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
|SSD_300_VGG16_ATROUS_VOC| MXNet | VGG16 | 300×300 | Pascal VOC | 中等偏上 | 快 | VOC 数据集同类任务;资源受限时使用|
|SSD_512_MOBILENET1_VOC| MXNet | MobileNet1.0 | 512×512 | Pascal VOC | 中等 | 快 | 嵌入式/移动端设备;算力和内存都很有限|
---
**YOLOv3 变体系列**
#### 语义分割模型
|模型名称| 引擎 | 骨干网络 | <div style="width: 60pt">数据集</div> | 输入尺寸 | <div style="width: 50pt">精度</div> | <div style="width: 50pt">速度</div> | <div style="width: 200pt">适用场景</div> |
| :-----|---------- | -------| ----------- |------------| ---------|-------|--------------------------------------|
|YOLO3_DARKNET_VOC_416|MXNet | DarkNet53 | VOC | 416×416 | 高 | 中等 | VOC 任务复现;精度优先 |
|YOLO3_DARKNET_COCO_320 |MXNet| DarkNet53 | COCO | 320×320 | 中等 | 快 | COCO 小模型测试;资源受限 |
|YOLO3_DARKNET_COCO_416 |MXNet| DarkNet53 | COCO | 416×416 | 高 | 中等 | 通用 COCO 部署;精度优先 |
|YOLO3_DARKNET_COCO_608 |MXNet| DarkNet53 | COCO | 608×608 | 很高 | 慢| 批量离线推理;精度要求极高 |
|YOLO3_MOBILENET_VOC_320 |MXNet| MobileNetV1 | VOC | 320×320 | 中等 | 非常快| 嵌入式设备VOC 小目标任务 |
|YOLO3_MOBILENET_VOC_416 |MXNet| MobileNetV1 | VOC | 416×416 | 高 | 快| 移动端 VOC 部署 |
|YOLO3_MOBILENET_COCO_320 |MXNet| MobileNetV1 | COCO | 320×320 | 中等 | 非常快 | 嵌入式设备COCO 小目标任务 |
|YOLO3_MOBILENET_COCO_416 |MXNet| MobileNetV1 | COCO | 416×416 | 高 | 快 | 移动端 COCO 部署 |
|YOLO3_MOBILENET_COCO_608 |MXNet| MobileNetV1 | COCO | 608×608 | 很高 | 中等 | 对精度要求较高的移动端任务 |
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|---------|---------|-------------------------|--------|
| DeepLabv3 | PyTorch | DeepLabv3 是一种图像分割模型,可以把图片里的每个像素分类出来 | 无 |
---
#### 实例分割模型
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------------|---------|------------------------------------|------------|
| YOLOV8-SEG | OnnxRuntime | Ultralytics在COCO 数据集 上训练的模型 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
| YOLOV11-SEG | OnnxRuntime | Ultralytics在COCO 数据集 上训练的模型 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
| Mask R-CNN | MXNet | Mask R-CNN 是一种在目标检测基础上,同时为每个物体生成像素级分割区域的深度学习模型 | 无 |
#### CLIP模型(Connecting text and images)
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------------|-------------|--------------------|------------|
| clip-vit-base-patch32 | PyTorch | openai的图片及文本特征提取模型 | [Github](https://github.com/openai/CLIP) |
---
#### OBB旋转框目标检测模型
| 模型名称 | 引擎 | 模型简介 | 模型开源网站 |
|-------------|---------|--------------------------------|------------|
| YOLOV11-OBB | OnnxRuntime | Ultralytics在DOTAv1 数据集 上训练的模型、通过引入一个额外的角度来更准确地定位图像中的对象 | [Github](https://docs.ultralytics.com/zh/tasks/segment/) |
---
#### 行人检测模型
| 模型名称 | 引擎 | 模型开源网站 |
|-------------|---------|-----|
| YOLOV8_PERSON | OnnxRuntime | [Github](https://github.com/YapaLab/yolo-face) |
---
#### 人类动作识别模型
| 模型名称 | 引擎 |
|-------------|---------|
| VIT_BASE_PATCH16 | PyTorch |
| INCEPTIONV3_KINETICS400 | OnnxRuntime |
| INCEPTIONV1_KINETICS400 | OnnxRuntime |
| RESNET_V1B_KINETICS400 | OnnxRuntime |
---
#### 姿态估计模型
| 模型名称 | 引擎 | 模型开源网站 |
|--------------|-------------|----------------------------------------------------------|
| YOLO11N-POSE | OnnxRuntime | [Github](https://docs.ultralytics.com/zh/tasks/pose/) |
| YOLO8N-POSE | OnnxRuntime | [Github](https://docs.ultralytics.com/zh/tasks/pose/) |
| SIMPLE_POSE | MXNet | 无 |
---
#### OCR 模型
@@ -536,9 +776,32 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
支持功能
- 200多语言互相翻译
| 模型名称 | 模型简介 | 模型官网 |
| ------------------------------ | ------------------------ |-----------------------------------------------|
| NLLB-200 | Meta AI 开发的一个先进的单一多语言机器翻译模型 | [Github](https://github.com/facebookresearch/fairseq/tree/nllb) |
| 模型名称 | 模型简介 | 模型官网 |
|---------------|-----------------------------------------|-----------------------------------------------|
| NLLB-200 | Meta AI 开发的一个先进的单一多语言机器翻译模型 | [Github](https://github.com/facebookresearch/fairseq/tree/nllb) |
| OPUS_MT_ZH_EN | Helsinki-NLP团队开发的中英翻译模型基于Transformer架构 | [huggingface](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en) |
| OPUS_MT_EN_ZH | Helsinki-NLP团队开发的英中翻译模型基于Transformer架构 | [huggingface](https://huggingface.co/Helsinki-NLP/opus-mt-en-zh) |
---
#### 语音识别模型(ASR)
这里仅介绍模型的开源项目,每个开源项目通常包含多个具体模型,本文不逐一列出。
| 模型名称 | 模型简介 | 模型官网 |
|---------|-------------------------------------------------------------------------|-----------------------------------------------|
| Whisper | OpenAI 开源的通用语音识别ASR模型支持多语言转写和翻译具有较高的识别精度尤其在嘈杂环境中表现良好适合离线和批量音频处理。 | [Github](https://github.com/ggml-org/whisper.cpp) |
| Vosk | 一个轻量级离线语音识别工具包,支持多种语言和平台(包括移动端与嵌入式设备),可在低资源环境中运行,适合实时语音识别场景。 | [Github](https://github.com/alphacep/vosk-api) |
| sherpa-onnx | Sherpa-ONNX 是一个基于 ONNX Runtime 的ASR 及 TTS 推理框架 | [Github](https://github.com/k2-fsa/sherpa-onnx) |
#### 语音合成模型(TTS)
这里仅介绍模型的开源项目,每个开源项目通常包含多个具体模型,本文不逐一列出。
| 模型名称 | 模型简介 | 模型官网 |
|---------|-------------------------------------------------------------------------|-----------------------------------------------|
| sherpa-onnx | Sherpa-ONNX 是一个基于 ONNX Runtime 的ASR 及 TTS 推理框架 | [Github](https://github.com/k2-fsa/sherpa-onnx) |
---
@@ -551,7 +814,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
## 联系方式
如您在使用过程中有任何问题建议,欢迎添加微信与我们交流并加入用户交流群
如您在使用过程中有任何问题建议,或希望进行技术交流与合作,欢迎添加微信与我联系,并加入用户交流群
- **微信**: deng775747758 请备注SmartJavaAI
- **Email**: 775747758@qq.com
@@ -559,52 +822,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
🚀 **如果这个项目对你有帮助,别忘了点个 Star ⭐!你的支持是我持续优化升级的动力!** ❤️
## 献代码的步骤
## 近期更新日志
1、在Gitee或者Github/Gitcode上fork项目到自己的repo
## [v1.0.22] - 2025-07-28
- 新增 Milvus 身份验证支持
- 集成车牌识别模型,支持车牌检测与识别
- 目标检测功能升级可指定类别及topk
- 支持自定义线程池线程数量
2、把fork过去的项目也就是你的项目clone到你的本地
3、修改代码记得一定要修改dev分支
4、commit后push到自己的库dev分支
5、登录Gitee或Github/Gitcode在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
6、等待维护者合并
## [v1.0.20] - 2025-07-18
- OCR新增表格识别模型
- OCR新增9个通用模型
- OCR支持批量检测识别
- OCR新增更多参数使用更加灵活
- 人脸识别支持ID查询及分页获取人脸信息
- 活体检测:视频检测支持设置最大帧数
## [v1.0.19] - 2025-07-06
- 人脸模块新增小视科技MiniVision活体检测模型
- 人脸模块:新增阿里通义工作室活体检测模型
- 人脸模块:新增 2 个表情识别模型
- 人脸模块:新增 InsightFace 和 ElasticFace 人脸识别模型
- 人脸模块:新增 Seetaface6 质量评估模型
- 目标检测模块:支持更多自定义模型参数配置
- 人脸模块:支持 Base64 编码图片输入
- 通用功能:实现 AutoCloseable 接口,支持资源自动释放
- OCR 模块:修复加方向矫正后无法连续识别的问题
- 人脸模块:修复人脸更新后的缓存异常问题
- 其他:优化部分功能与细节体验
## [v1.0.17] - 2025-06-18
- 新增机器翻译模块:支持 200+ 种语言之间的相互翻译
- 人脸识别模块:修复批量删除人脸数据时的异常问题
- 人脸识别模块:修复人脸检索 Top大于 1 时报异常问题
## [v1.0.16] - 2025-06-09
- 人脸模块:人脸查询支持 Milvus 和 SQLite
- 人脸模块FaceNet人脸模型也支持人脸注册查询等功能
- 人脸模块Seetaface6 自动下载人脸库
- 人脸模块Seetaface6解决依赖库重复下载问题
- 人脸模块:支持手动加载人脸库
- 人脸模块:人脸识别相关功能支持更多参数
## [v1.0.15] - 2025-05-17
- 新增OCR文字识别模块支持最新 PP-OCRv5
- OCR文本识别支持文字方向检测与自动校正

View File

@@ -6,11 +6,11 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.22</version>
<version>1.0.27</version>
</parent>
<artifactId>smartjavaai-all</artifactId>
<version>1.0.22</version>
<artifactId>all</artifactId>
<version>1.0.27</version>
<name>${project.artifactId}</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>
@@ -33,25 +33,31 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-face</artifactId>
<artifactId>face</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-objectdetection</artifactId>
<artifactId>vision</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-ocr</artifactId>
<artifactId>ocr</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-translate</artifactId>
<artifactId>translate</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>speech</artifactId>
<version>${project.version}</version>
</dependency>

View File

@@ -0,0 +1,47 @@
import ai.djl.Application;
import ai.djl.Model;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelZoo;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.enums.CommonDetModelEnum;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameUtils;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
/**
* @author dwj
* @date 2025/4/24
*/
@Slf4j
public class Test {
public static String savePath = "/Users/wenjie/Downloads/";
//public static String image1Path = "/Users/wenjie/Documents/idea_workplace/SmartJavaAI-Demo/src/main/resources/5.jpg";
public static String image1Path = "/Users/wenjie/Documents/idea_workplace/SmartJavaAI-Demo/src/main/resources/MJ_20250226_172200.png";
public static String image2Path = "/Users/wenjie/Documents/idea_workplace/SmartJavaAI-Demo/src/main/resources/MJ_20250226_172222.png";
public static void main(String[] args) throws IOException {
// // 加载模型
// Model model = ModelZoo.loadModel(Criteria.builder()
// .optApplication(Application.NLP.ANY)
// .optEngine("PyTorch")
// .optModelName("Llama 3")
// .optTranslatorFactory(new Llama3TranslatorFactory())
// .optTranslatorProvider(() -> new Llama3Translator())
// .build());
}
}

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.22</version>
<version>1.0.27</version>
</parent>
<version>1.0.22</version>
<artifactId>smartjavaai-bom</artifactId>
<name>smartjavaai-bom</name>
<version>1.0.27</version>
<artifactId>bom</artifactId>
<name>bom</name>
<description>统一版本管理的 BOM 包,同时支持 import 和全量依赖</description>
<properties>
@@ -25,22 +25,27 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-face</artifactId>
<artifactId>face</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-objectdetection</artifactId>
<artifactId>vision</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-ocr</artifactId>
<artifactId>ocr</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-translate</artifactId>
<artifactId>translate</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>speech</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>

View File

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

View File

@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.nio.file.Paths;
/**
* 全局配置
@@ -68,19 +69,29 @@ public class Config {
String osName = SystemUtil.getOsInfo().getName();
log.info("当前操作系统:{}", osName);
if(osName.toLowerCase().contains("windows")){
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
cachePath = Paths.get(
SystemUtil.getUserInfo().getHomeDir(),
"smartjavaai_cache"
).toString();
FileUtil.mkdir(cachePath);
}else if(osName.toLowerCase().contains("linux")){
cachePath = "/root/" + CACHE_DIR;
FileUtil.mkdir(cachePath);
}else if(osName.toLowerCase().contains("mac")){
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
cachePath = Paths.get(
SystemUtil.getUserInfo().getHomeDir(),
"smartjavaai_cache"
).toString();
FileUtil.mkdir(cachePath);
}else{
cachePath = SystemUtil.getUserInfo().getHomeDir() + CACHE_DIR;
cachePath = Paths.get(
SystemUtil.getUserInfo().getHomeDir(),
"smartjavaai_cache"
).toString();
FileUtil.mkdir(cachePath);
}
}
}

View File

@@ -45,6 +45,12 @@ public class ModelConfig {
return clazz.cast(value);
}
public <T> T getCustomParam(String key, Class<T> clazz, T defaultValue) {
Object value = customParams.getOrDefault(key, defaultValue);
return clazz.cast(value);
}
/**
* 添加个性化配置项
*/

View File

@@ -0,0 +1,152 @@
package cn.smartjavaai.common.cv;
import ai.djl.modality.cv.BufferedImageFactory;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.types.DataType;
import ai.djl.ndarray.types.Shape;
import ai.djl.opencv.OpenCVImageFactory;
import ai.djl.util.Utils;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import nu.pattern.OpenCV;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 图片处理工厂类
* @author dwj
*/
public class SmartImageFactory {
public enum Engine {
BUFFEREDIMAGE,
OPENCV
}
private static volatile Engine currentEngine = Engine.BUFFEREDIMAGE;
private static volatile SmartImageFactory instance;
public static synchronized void setEngine(Engine engine) {
if (engine == null || engine == currentEngine) {
return;
}
currentEngine = engine;
// 只在切换时注册全局
switch (currentEngine) {
case OPENCV:
ImageFactory.setImageFactory(new OpenCVImageFactory());
break;
case BUFFEREDIMAGE:
default:
ImageFactory.setImageFactory(new BufferedImageFactory());
}
}
public static synchronized SmartImageFactory getInstance() {
if (instance == null) {
instance = new SmartImageFactory();
// 初始化全局 Engine
switch (currentEngine) {
case OPENCV:
ImageFactory.setImageFactory(new OpenCVImageFactory());
break;
case BUFFEREDIMAGE:
default:
ImageFactory.setImageFactory(new BufferedImageFactory());
}
}
return instance;
}
public Image fromBufferedImage(BufferedImage sourceImage){
if (sourceImage == null) {
throw new IllegalArgumentException("BufferedImage 不能为空");
}
Image image = null;
switch (currentEngine) {
case BUFFEREDIMAGE:
image = ImageFactory.getInstance().fromImage(sourceImage);
break;
case OPENCV:
// 先转 Mat
Mat mat = OpenCVUtils.image2Mat(sourceImage);
image = ImageFactory.getInstance().fromImage(mat);
break;
default:
throw new IllegalStateException("未知 Engine: " + currentEngine);
}
return image;
}
public Image fromMat(Mat mat){
if (mat == null) {
throw new IllegalArgumentException("mat 不能为空");
}
Image image = null;
switch (currentEngine) {
case OPENCV:
image = ImageFactory.getInstance().fromImage(mat);
break;
case BUFFEREDIMAGE:
// 先转 Mat
BufferedImage sourceImage = OpenCVUtils.mat2Image(mat);
image = ImageFactory.getInstance().fromImage(sourceImage);
break;
default:
throw new IllegalStateException("未知 Engine: " + currentEngine);
}
return image;
}
public Image fromBase64(String base64Image) throws IOException {
return ImageFactory.getInstance().fromUrl(base64Image);
}
public Image fromBytes(byte[] imageData) throws IOException {
return ImageFactory.getInstance().fromInputStream(new ByteArrayInputStream(imageData));
}
public Image fromFile(File file) throws IOException {
return ImageFactory.getInstance().fromFile(file.toPath());
}
public Image fromFile(Path path) throws IOException {
return ImageFactory.getInstance().fromFile(path);
}
public Image fromFile(String filePath) throws IOException {
if (filePath == null || filePath.trim().isEmpty()) {
throw new IllegalArgumentException("filePath 不能为空");
}
return fromFile(Paths.get(filePath));
}
public Image fromPixels(int[] pixels, int width, int height){
return ImageFactory.getInstance().fromPixels(pixels, width, height);
}
public Image fromInputStream(InputStream inputStream) throws IOException {
return ImageFactory.getInstance().fromInputStream(inputStream);
}
}

View File

@@ -32,6 +32,16 @@ public class DetectionInfo {
*/
private ObjectDetInfo objectDetInfo;
/**
* 目标分割信息
*/
private InstanceSegInfo instanceSegInfo;
/**
* 旋转框信息
*/
private ObbDetInfo obbDetInfo;
public DetectionInfo() {

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.common.entity;
import ai.djl.modality.cv.Image;
import lombok.Data;
import java.util.List;
@@ -7,13 +8,14 @@ import java.util.List;
/**
* 检测结果
* @author dwj
* @date 2025/4/12
*/
@Data
public class DetectionResponse {
private List<DetectionInfo> detectionInfoList;
private transient Image drawnImage;
public DetectionResponse() {
}

View File

@@ -0,0 +1,29 @@
package cn.smartjavaai.common.entity;
import lombok.Data;
/**
* 目标分割信息
* @author dwj
*/
@Data
public class InstanceSegInfo {
/**
* 类别名称
*/
private String className;
/**
* 遮罩
*/
private float[][] mask;
public InstanceSegInfo() {
}
public InstanceSegInfo(String className, float[][] mask) {
this.className = className;
this.mask = mask;
}
}

View File

@@ -0,0 +1,138 @@
package cn.smartjavaai.common.entity;
/**
* 语言枚举
* @author dwj
*/
public enum Language {
EN("en", "English", "英语"),
ZH("zh", "Chinese", "中文"),
DE("de", "German", "德语"),
ES("es", "Spanish", "西班牙语"),
RU("ru", "Russian", "俄语"),
KO("ko", "Korean", "韩语"),
FR("fr", "French", "法语"),
JA("ja", "Japanese", "日语"),
PT("pt", "Portuguese", "葡萄牙语"),
TR("tr", "Turkish", "土耳其语"),
PL("pl", "Polish", "波兰语"),
CA("ca", "Catalan", "加泰罗尼亚语"),
NL("nl", "Dutch", "荷兰语"),
AR("ar", "Arabic", "阿拉伯语"),
SV("sv", "Swedish", "瑞典语"),
IT("it", "Italian", "意大利语"),
ID("id", "Indonesian", "印尼语"),
HI("hi", "Hindi", "印地语"),
FI("fi", "Finnish", "芬兰语"),
VI("vi", "Vietnamese", "越南语"),
HE("he", "Hebrew", "希伯来语"),
UK("uk", "Ukrainian", "乌克兰语"),
EL("el", "Greek", "希腊语"),
MS("ms", "Malay", "马来语"),
CS("cs", "Czech", "捷克语"),
RO("ro", "Romanian", "罗马尼亚语"),
DA("da", "Danish", "丹麦语"),
HU("hu", "Hungarian", "匈牙利语"),
TA("ta", "Tamil", "泰米尔语"),
NO("no", "Norwegian", "挪威语"),
TH("th", "Thai", "泰语"),
UR("ur", "Urdu", "乌尔都语"),
HR("hr", "Croatian", "克罗地亚语"),
BG("bg", "Bulgarian", "保加利亚语"),
LT("lt", "Lithuanian", "立陶宛语"),
LA("la", "Latin", "拉丁语"),
MI("mi", "Maori", "毛利语"),
ML("ml", "Malayalam", "马拉雅拉姆语"),
CY("cy", "Welsh", "威尔士语"),
SK("sk", "Slovak", "斯洛伐克语"),
TE("te", "Telugu", "泰卢固语"),
FA("fa", "Persian", "波斯语"),
LV("lv", "Latvian", "拉脱维亚语"),
BN("bn", "Bengali", "孟加拉语"),
SR("sr", "Serbian", "塞尔维亚语"),
AZ("az", "Azerbaijani", "阿塞拜疆语"),
SL("sl", "Slovenian", "斯洛文尼亚语"),
KN("kn", "Kannada", "卡纳达语"),
ET("et", "Estonian", "爱沙尼亚语"),
MK("mk", "Macedonian", "马其顿语"),
BR("br", "Breton", "布列塔尼语"),
EU("eu", "Basque", "巴斯克语"),
IS("is", "Icelandic", "冰岛语"),
HY("hy", "Armenian", "亚美尼亚语"),
NE("ne", "Nepali", "尼泊尔语"),
MN("mn", "Mongolian", "蒙古语"),
BS("bs", "Bosnian", "波斯尼亚语"),
KK("kk", "Kazakh", "哈萨克语"),
SQ("sq", "Albanian", "阿尔巴尼亚语"),
SW("sw", "Swahili", "斯瓦希里语"),
GL("gl", "Galician", "加利西亚语"),
MR("mr", "Marathi", "马拉地语"),
PA("pa", "Punjabi", "旁遮普语"),
SI("si", "Sinhala", "僧伽罗语"),
KM("km", "Khmer", "高棉语"),
SN("sn", "Shona", "修纳语"),
YO("yo", "Yoruba", "约鲁巴语"),
SO("so", "Somali", "索马里语"),
AF("af", "Afrikaans", "南非荷兰语"),
OC("oc", "Occitan", "奥克语"),
KA("ka", "Georgian", "格鲁吉亚语"),
BE("be", "Belarusian", "白俄罗斯语"),
TG("tg", "Tajik", "塔吉克语"),
SD("sd", "Sindhi", "信德语"),
GU("gu", "Gujarati", "古吉拉特语"),
AM("am", "Amharic", "阿姆哈拉语"),
YI("yi", "Yiddish", "意第绪语"),
LO("lo", "Lao", "老挝语"),
UZ("uz", "Uzbek", "乌兹别克语"),
FO("fo", "Faroese", "法罗语"),
HT("ht", "Haitian Creole", "海地克里奥尔语"),
PS("ps", "Pashto", "普什图语"),
TK("tk", "Turkmen", "土库曼语"),
NN("nn", "Nynorsk", "新挪威语"),
MT("mt", "Maltese", "马耳他语"),
SA("sa", "Sanskrit", "梵语"),
LB("lb", "Luxembourgish", "卢森堡语"),
MY("my", "Myanmar", "缅甸语"),
BO("bo", "Tibetan", "藏语"),
TL("tl", "Tagalog", "他加禄语"),
MG("mg", "Malagasy", "马尔加什语"),
AS("as", "Assamese", "阿萨姆语"),
TT("tt", "Tatar", "鞑靼语"),
HAW("haw", "Hawaiian", "夏威夷语"),
LN("ln", "Lingala", "林加拉语"),
HA("ha", "Hausa", "豪萨语"),
BA("ba", "Bashkir", "巴什基尔语"),
JW("jw", "Javanese", "爪哇语"),
SU("su", "Sundanese", "巽他语"),
YUE("yue", "Cantonese", "粤语");
private final String code; // Whisper语言代码
private final String englishName; // 英文名称
private final String chineseName; // 中文名称
Language(String code, String englishName, String chineseName) {
this.code = code;
this.englishName = englishName;
this.chineseName = chineseName;
}
public String getCode() {
return code;
}
public String getEnglishName() {
return englishName;
}
public String getChineseName() {
return chineseName;
}
@Override
public String toString() {
return code;
}
}

View File

@@ -0,0 +1,32 @@
package cn.smartjavaai.common.entity;
import lombok.Data;
import java.util.List;
/**
* 定向边界框 检测结果
* @author dwj
*/
@Data
public class ObbDetInfo {
/**
* 类别名称
*/
private String className;
/**
* 检测框坐标
*/
private RotatedBox rotatedBox;
public ObbDetInfo() {
}
public ObbDetInfo(String className, RotatedBox rotatedBox) {
this.className = className;
this.rotatedBox = rotatedBox;
}
}

View File

@@ -0,0 +1,28 @@
package cn.smartjavaai.common.entity;
import lombok.Data;
import java.util.List;
/**
* 多边形
* @author dwj
*/
@Data
public class PolygonLabel {
private List<Point> points;
private String text;
public PolygonLabel(List<Point> points, String text) {
this.points = points;
this.text = text;
}
public PolygonLabel() {
}
public PolygonLabel(List<Point> points) {
this.points = points;
}
}

View File

@@ -53,7 +53,7 @@ public class R<T> {
public enum Status {
SUCCESS(0, "成功"),
INVALID_IMAGE(1, "图像无效"),
FILE_NOT_FOUND(2, "图像文件不存在"),
FILE_NOT_FOUND(2, "文件不存在"),
NO_FACE_DETECTED(3, "未检测到人脸"),
PARAM_ERROR(4, "参数错误"),
INVALID_VIDEO(5, "视频无效"),

View File

@@ -0,0 +1,41 @@
package cn.smartjavaai.common.entity;
import lombok.Data;
/**
* 旋转框
* @author dwj
*/
@Data
public class RotatedBox {
/**
* 左上角
*/
private Point topLeft;
/**
* 右上角
*/
private Point topRight;
/**
* 右下角
*/
private Point bottomRight;
/**
* 左下角
*/
private Point bottomLeft;
public RotatedBox(Point topLeft, Point topRight, Point bottomRight, Point bottomLeft) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomRight = bottomRight;
this.bottomLeft = bottomLeft;
}
public RotatedBox() {
}
}

View File

@@ -1,4 +1,4 @@
package cn.smartjavaai.face.enums;
package cn.smartjavaai.common.enums;
/**
* @author dwj

View File

@@ -0,0 +1,12 @@
package cn.smartjavaai.common.enums;
/**
* 视频源类型枚举
* @author dwj
* @date 2025/8/27
*/
public enum VideoSourceType {
STREAM, // RTSP 或 HTTP 流
FILE, // 本地视频文件
CAMERA; // 本地摄像头
}

View File

@@ -10,7 +10,7 @@ import java.awt.image.BufferedImage;
* @author dwj
* @date 2025/6/27
*/
public class BufferedImagePreprocessor {
public class BufferedImagePreprocessor implements ImagePreprocessor<BufferedImage>{
private BufferedImage image;
private DetectionRectangle rect;

View File

@@ -0,0 +1,84 @@
package cn.smartjavaai.common.preprocess;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
import org.opencv.core.Mat;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* @author dwj
*/
public class DJLImagePreprocessor implements ImagePreprocessor<Image>{
private final ImagePreprocessor<?> delegate;
private Image input = null;
public DJLImagePreprocessor(Image image, DetectionRectangle rect) {
this.input = image;
if (input.getWrappedImage() instanceof BufferedImage) {
this.delegate = new BufferedImagePreprocessor((BufferedImage) input.getWrappedImage(), rect);
} else if (input.getWrappedImage() instanceof Mat) {
this.delegate = new OpenCVPreprocessor((Mat) input.getWrappedImage(), rect);
} else {
throw new IllegalArgumentException("Unsupported input type");
}
}
@Override
public DJLImagePreprocessor setExtendRatio(float ratio) {
delegate.setExtendRatio(ratio);
return this;
}
@Override
public DJLImagePreprocessor setTargetSize(int size) {
delegate.setTargetSize(size);
return this;
}
@Override
public DJLImagePreprocessor setCenterCropSize(int size) {
delegate.setCenterCropSize(size);
return this;
}
@Override
public DJLImagePreprocessor enableSquarePadding(boolean enable) {
delegate.enableSquarePadding(enable);
return this;
}
@Override
public DJLImagePreprocessor enableScaling(boolean enable) {
delegate.enableScaling(enable);
return this;
}
@Override
public DJLImagePreprocessor enableCenterCrop(boolean enable) {
delegate.enableCenterCrop(enable);
return this;
}
@Override
public ImagePreprocessor setPaddingColor(Color color) {
return delegate.setPaddingColor(color);
}
@Override
public Image process() {
Object result = delegate.process();
if (result instanceof BufferedImage) {
return SmartImageFactory.getInstance().fromBufferedImage((BufferedImage) result);
} else if (result instanceof Mat) {
return SmartImageFactory.getInstance().fromMat((Mat) result);
}
throw new IllegalStateException("Unsupported process result: " + result.getClass());
}
}

View File

@@ -0,0 +1,28 @@
package cn.smartjavaai.common.preprocess;
import java.awt.*;
/**
* 图片预处理
* @author dwj
*/
public interface ImagePreprocessor<T> {
ImagePreprocessor<T> setExtendRatio(float ratio);
ImagePreprocessor<T> setTargetSize(int size);
ImagePreprocessor<T> setCenterCropSize(int size);
ImagePreprocessor<T> enableSquarePadding(boolean enable);
ImagePreprocessor<T> enableScaling(boolean enable);
ImagePreprocessor<T> enableCenterCrop(boolean enable);
ImagePreprocessor<T> setPaddingColor(Color color);
T process();
}

View File

@@ -0,0 +1,154 @@
package cn.smartjavaai.common.preprocess;
import cn.smartjavaai.common.entity.DetectionRectangle;
import org.opencv.core.Mat;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
/**
* @author dwj
*/
public class OpenCVPreprocessor implements ImagePreprocessor<Mat> {
private Mat image;
private DetectionRectangle rect;
private float extendRatio = 1;
private int targetSize = 128;
private int centerCropSize = 80;
private Scalar paddingColor = new Scalar(127, 127, 127); // 默认灰色
private boolean enableSquarePadding = true;
private boolean enableScaling = true;
private boolean enableCenterCrop = false;
public OpenCVPreprocessor(Mat image, DetectionRectangle rect) {
this.image = image;
this.rect = rect;
}
@Override
public OpenCVPreprocessor setExtendRatio(float ratio) {
this.extendRatio = ratio;
return this;
}
@Override
public OpenCVPreprocessor setTargetSize(int size) {
this.targetSize = size;
return this;
}
@Override
public OpenCVPreprocessor setCenterCropSize(int size) {
this.centerCropSize = size;
return this;
}
@Override
public OpenCVPreprocessor enableSquarePadding(boolean enable) {
this.enableSquarePadding = enable;
return this;
}
@Override
public OpenCVPreprocessor enableScaling(boolean enable) {
this.enableScaling = enable;
return this;
}
@Override
public OpenCVPreprocessor enableCenterCrop(boolean enable) {
this.enableCenterCrop = enable;
return this;
}
@Override
public OpenCVPreprocessor setPaddingColor(java.awt.Color color) {
this.paddingColor = new Scalar(color.getBlue(), color.getGreen(), color.getRed());
return this;
}
@Override
public Mat process() {
// Step 1: 裁剪 + 扩展
Mat cropped = cropAndExtend();
// Step 2: 填充正方形
Mat squared = enableSquarePadding ? squarePadding(cropped) : cropped;
// Step 3: 缩放
Mat scaled = enableScaling ? scaleToTarget(squared) : squared;
// Step 4: CenterCrop
Mat finalResult = enableCenterCrop ? centerCrop(scaled) : scaled;
return finalResult;
}
/**
* 检测框扩展及裁剪
*/
private Mat cropAndExtend() {
int x = rect.x;
int y = rect.y;
int width = rect.width;
int height = rect.height;
int extendX = Math.round(width * extendRatio);
int extendY = Math.round(height * extendRatio);
int left = Math.max(0, x - extendX);
int right = Math.min(image.width(), x + width + extendX);
int top = Math.max(0, y - extendY);
int bottom = Math.min(image.height(), y + height + extendY);
int origRoiWidth = right - left;
int origRoiHeight = bottom - top;
int longSide = Math.max(origRoiWidth, origRoiHeight);
// 计算可扩展空间(不超出原图边界)
int extendLeft = Math.min(left, (longSide - origRoiWidth) / 2);
int extendRight = Math.min(image.width() - right, (longSide - origRoiWidth + 1) / 2);
int extendTop = Math.min(top, (longSide - origRoiHeight) / 2);
int extendBottom = Math.min(image.height() - bottom, (longSide - origRoiHeight + 1) / 2);
int expandedLeft = left - extendLeft;
int expandedRight = right + extendRight;
int expandedTop = top - extendTop;
int expandedBottom = bottom + extendBottom;
Rect roi = new Rect(expandedLeft, expandedTop, expandedRight - expandedLeft, expandedBottom - expandedTop);
return new Mat(image, roi).clone(); // clone 避免与原图共享内存
}
/**
* 填充为正方形
*/
private Mat squarePadding(Mat src) {
int longSide = Math.max(src.width(), src.height());
Mat squared = new Mat(new Size(longSide, longSide), src.type(), paddingColor);
int xOffset = (longSide - src.width()) / 2;
int yOffset = (longSide - src.height()) / 2;
src.copyTo(squared.submat(yOffset, yOffset + src.height(), xOffset, xOffset + src.width()));
return squared;
}
/**
* 缩放到目标大小
*/
private Mat scaleToTarget(Mat src) {
Mat result = new Mat();
Imgproc.resize(src, result, new Size(targetSize, targetSize), 0, 0, Imgproc.INTER_AREA);
return result;
}
/**
* CenterCrop
*/
private Mat centerCrop(Mat src) {
int startX = (src.width() - centerCropSize) / 2;
int startY = (src.height() - centerCropSize) / 2;
Rect roi = new Rect(startX, startY, centerCropSize, centerCropSize);
return new Mat(src, roi).clone();
}
}

View File

@@ -0,0 +1,608 @@
package cn.smartjavaai.common.utils;
import ai.djl.ndarray.NDArray;
import ai.djl.util.RandomUtils;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.FaceSearchResult;
import cn.smartjavaai.common.entity.face.HeadPose;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentSampleModel;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @author dwj
*/
public class BufferedImageUtils {
/**
* 拷贝图片
* @param src
* @return
*/
public static BufferedImage copyBufferedImage(BufferedImage src) {
BufferedImage copy = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
Graphics2D g = copy.createGraphics();
g.drawImage(src, 0, 0, null);
g.dispose();
return copy;
}
/**
* 对图像解码返回BGR格式矩阵数据
*
* @param image
* @return
*/
public static byte[] getMatrixBGR(BufferedImage image) {
byte[] matrixBGR;
if (isBGR3Byte(image)) {
matrixBGR = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
} else {
// ARGB格式图像数据
int intrgb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
// ARGB转BGR格式
for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
matrixBGR[j] = (byte) (intrgb[i] & 0xff);
matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
}
}
return matrixBGR;
}
/**
* 推断图像是否为BGR格式
*
* @return
*/
public static boolean isBGR3Byte(BufferedImage image) {
return equalBandOffsetWith3Byte(image, new int[]{0, 1, 2});
}
/**
* @param image
* @param bandOffset 用于推断通道顺序
* @return
*/
private static boolean equalBandOffsetWith3Byte(BufferedImage image, int[] bandOffset) {
if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
if (image.getData().getSampleModel() instanceof ComponentSampleModel) {
ComponentSampleModel sampleModel = (ComponentSampleModel) image.getData().getSampleModel();
if (Arrays.equals(sampleModel.getBandOffsets(), bandOffset)) {
return true;
}
}
}
return false;
}
public static BufferedImage bgrToBufferedImage(byte[] data, int width, int height) {
int type = BufferedImage.TYPE_3BYTE_BGR;
// bgr to rgb
byte b;
for (int i = 0; i < data.length; i = i + 3) {
b = data[i];
data[i] = data[i + 2];
data[i + 2] = b;
}
BufferedImage image = new BufferedImage(width, height, type);
image.getRaster().setDataElements(0, 0, width, height, data);
return image;
}
/**
* 检查图像是否有效
* @param image
* @return
*/
public static boolean isImageValid(BufferedImage image) {
// 检查是否为 null 或尺寸异常如宽高为0
return image != null && image.getWidth() > 0 && image.getHeight() > 0;
}
/**
* 画检测框
*
* @param image
* @param x
* @param y
* @param width
* @param height
*/
public static void drawRect(BufferedImage image, int x, int y, int width, int height) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(new Color(0, 255, 0));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);
} finally {
g.dispose();
}
}
/**
* 保存BufferedImage图片
* @param image
* @param outputPath
* @param formatName
* @throws IOException
*/
public static void saveBufferedImage(BufferedImage image, String outputPath, String formatName) throws IOException {
if (image == null) {
throw new IllegalArgumentException("BufferedImage 不能为空");
}
if (outputPath == null || outputPath.isEmpty()) {
throw new IllegalArgumentException("输出路径不能为空");
}
if (formatName == null || formatName.isEmpty()) {
throw new IllegalArgumentException("格式不能为空");
}
Path path = Paths.get(outputPath);
Path parent = path.getParent();
if (parent != null && !Files.exists(parent)) {
Files.createDirectories(parent); // 自动创建父目录
}
File outFile = path.toFile();
boolean result = ImageIO.write(image, formatName, outFile);
if (!result) {
throw new IOException("保存图片失败,不支持的格式: " + formatName);
}
}
/**
* 默认保存图片格式为png
* @param image
* @param outputPath
* @throws IOException
*/
public static void saveImage(BufferedImage image, String outputPath) throws IOException {
saveBufferedImage(image, outputPath, "png");
}
/**
* 画检测框(有倾斜角)
*
* @param image
* @param box
*/
public static void drawRect(BufferedImage image, NDArray box) {
float[] points = box.toFloatArray();
int[] xPoints = new int[5];
int[] yPoints = new int[5];
for (int i = 0; i < 4; i++) {
xPoints[i] = (int) points[2 * i];
yPoints[i] = (int) points[2 * i + 1];
}
xPoints[4] = xPoints[0];
yPoints[4] = yPoints[0];
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(new Color(0, 255, 0));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
} finally {
g.dispose();
}
}
/**
* 画检测框(有倾斜角)和文本
*
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(BufferedImage image, NDArray box, String text) {
float[] points = box.toFloatArray();
int[] xPoints = new int[5];
int[] yPoints = new int[5];
for (int i = 0; i < 4; i++) {
xPoints[i] = (int) points[2 * i];
yPoints[i] = (int) points[2 * i + 1];
}
xPoints[4] = xPoints[0];
yPoints[4] = yPoints[0];
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
int fontSize = 32;
Font font = new Font("楷体", Font.PLAIN, fontSize);
g.setFont(font);
g.setColor(new Color(0, 0, 255));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
g.drawString(text, xPoints[0], yPoints[0]);
} finally {
g.dispose();
}
}
/**
* 显示文字
*
* @param image
* @param text
* @param x
* @param y
*/
public static void drawImageText(BufferedImage image, String text, int x, int y) {
Graphics graphics = image.getGraphics();
int fontSize = 32;
Font font = new Font("楷体", Font.PLAIN, fontSize);
try {
graphics.setFont(font);
graphics.setColor(new Color(0, 0, 255));
int strWidth = graphics.getFontMetrics().stringWidth(text);
graphics.drawString(text, x, y);
} finally {
graphics.dispose();
}
}
/**
* 画检测框(有倾斜角)和文本
*
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(BufferedImage image, DetectionRectangle box, String text, Color color) {
// 将绘制图像转换为Graphics2D
Graphics2D graphics = (Graphics2D) image.getGraphics();
try {
drawRectAndText(graphics, box, text, color);
} finally {
graphics.dispose();
}
}
/**
* 画检测框(有倾斜角)和文本
*
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(BufferedImage image, DetectionRectangle box, String text, int fontSize) {
Color color = new Color(255, 0, 0);
// 将绘制图像转换为Graphics2D
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setFont(new Font("楷体", Font.PLAIN, fontSize));
try {
drawRectAndText(graphics, box, text, color);
} finally {
graphics.dispose();
}
}
/**
* 画检测框(有倾斜角)和文本
*
* @param graphics
* @param box
* @param text
*/
public static void drawRectAndText(Graphics2D graphics, DetectionRectangle box, String text, Color color) {
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
graphics.setColor(color);// 边框颜色
graphics.drawRect(box.getX(), box.getY(), box.getWidth(), box.getHeight());
Graphics2DUtils.drawText(graphics, text, box.getX(), box.getY(), stroke, 4);
}
/**
* 绘制检测框
* @param sourceImage
* @param detectionResponse
* @throws IOException
*/
public static void drawFaceSearchResult(BufferedImage sourceImage, DetectionResponse detectionResponse, String displayField) {
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new IllegalArgumentException("图像无效");
}
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
throw new IllegalArgumentException("无目标数据");
}
Graphics2D graphics = sourceImage.createGraphics();
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
for(DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()){
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
//人脸查询结果
if(detectionInfo.getFaceInfo().getFaceSearchResults() != null){
for (FaceSearchResult faceSearchResult : detectionInfo.getFaceInfo().getFaceSearchResults()){
if(StringUtils.isNotBlank(faceSearchResult.getMetadata())){
JsonObject metadata = GsonUtils.parseToJsonObject(faceSearchResult.getMetadata());
JsonElement nameElement = metadata.get("name");
if(metadata.has("name")){
Graphics2DUtils.drawText(graphics, nameElement.getAsString(), rectangle.getX(), rectangle.getY(), stroke, 4);
}
}
}
}
}
}
graphics.dispose();
}
/**
* 绘制检测框
* @param sourceImage
* @param detectionResponse
* @throws IOException
*/
public static void drawBoundingBoxes(BufferedImage sourceImage, DetectionResponse detectionResponse) {
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new IllegalArgumentException("图像无效");
}
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
throw new IllegalArgumentException("无目标数据");
}
Graphics2D graphics = sourceImage.createGraphics();
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
for(DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()){
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
// Graphics2DUtils.drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
//人脸属性
if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
FaceAttribute faceAttribute = detectionInfo.getFaceInfo().getFaceAttribute();
drawFaceAttribute(faceAttribute, rectangle, graphics);
}
}
//绘制目标检测信息
if(detectionInfo.getObjectDetInfo() != null){
String className = detectionInfo.getObjectDetInfo().getClassName();
Graphics2DUtils.drawText(graphics, className, rectangle.getX(), rectangle.getY(), stroke, 4);
}
}
graphics.dispose();
}
/**
* 绘制矩形框和文字
*
* @param sourceImage
* @param detectionInfo
*/
public static void drawRectAndText(BufferedImage sourceImage, DetectionInfo detectionInfo) {
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new IllegalArgumentException("图像无效");
}
if(Objects.isNull(detectionInfo)){
throw new IllegalArgumentException("无目标数据");
}
Graphics2D graphics = sourceImage.createGraphics();
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
//绘制人脸关键点
if(detectionInfo.getFaceInfo() != null && detectionInfo.getFaceInfo().getKeyPoints() != null &&
!detectionInfo.getFaceInfo().getKeyPoints().isEmpty()){
Graphics2DUtils.drawLandmarks(graphics, detectionInfo.getFaceInfo().getKeyPoints());
// Graphics2DUtils.drawText(graphics, "face", rectangle.getX(), rectangle.getY(), stroke, 4);
//人脸属性
if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
FaceAttribute faceAttribute = detectionInfo.getFaceInfo().getFaceAttribute();
drawFaceAttribute(faceAttribute, rectangle, graphics);
}
}
//绘制目标检测信息
if(detectionInfo.getObjectDetInfo() != null){
String className = detectionInfo.getObjectDetInfo().getClassName();
Graphics2DUtils.drawText(graphics, className, rectangle.getX(), rectangle.getY(), stroke, 4);
}
graphics.dispose();
}
/**
* 绘制人脸属性
* @param faceAttribute
* @param rectangle
* @param graphics
*/
public static void drawFaceAttribute(FaceAttribute faceAttribute, DetectionRectangle rectangle, Graphics2D graphics){
List<String> lines = new ArrayList<>();
if (faceAttribute.getGenderType() != null) {
lines.add("性别: " + faceAttribute.getGenderType().name());
}
if (faceAttribute.getAge() != null) {
lines.add("年龄: " + faceAttribute.getAge());
}
if (faceAttribute.getWearingMask() != null) {
lines.add("口罩: " + (faceAttribute.getWearingMask() ? "" : ""));
}
if (faceAttribute.getLeftEyeStatus() != null && faceAttribute.getRightEyeStatus() != null) {
lines.add("眼睛: " + faceAttribute.getLeftEyeStatus().name() + "/" + faceAttribute.getRightEyeStatus().name());
}
if (faceAttribute.getHeadPose() != null) {
HeadPose pose = faceAttribute.getHeadPose();
String pitch = pose.getPitch() != null ? String.valueOf(pose.getPitch().intValue()) : "-";
String yaw = pose.getYaw() != null ? String.valueOf(pose.getYaw().intValue()) : "-";
String roll = pose.getRoll() != null ? String.valueOf(pose.getRoll().intValue()) : "-";
lines.add("姿态: P=" + pitch + " Y=" + yaw + " R=" + roll);
}
if (!lines.isEmpty()) {
Graphics2DUtils.drawMultilineTextWithBackground(graphics, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
}
}
public static void drawPolygonWithText(BufferedImage image,List<PolygonLabel> polygonLabelList, int fontSize) {
Font font = new Font("楷体", Font.PLAIN, fontSize);
Stroke stroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
Color color = new Color(0, 0, 255);
Graphics2D g = (Graphics2D) image.getGraphics();
for (PolygonLabel polygonLabel : polygonLabelList){
drawPolygonWithText(g, polygonLabel.getPoints(), polygonLabel.getText(), font, color, stroke);
}
}
/**
* 绘制多边形及文字
* @param g Graphics2D
* @param points 多边形顶点
* @param text 绘制的文字(可为空)
* @param font 字体
* @param color 颜色
* @param stroke 画笔样式
*/
public static void drawPolygonWithText(Graphics2D g, List<Point> points,
String text, int fontSize) {
Font font = new Font("楷体", Font.PLAIN, fontSize);
Stroke stroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
Color color = new Color(0, 0, 255);
drawPolygonWithText(g, points, text, font, color, stroke);
}
/**
* 绘制多边形及文字
* @param g Graphics2D
* @param points 多边形顶点
* @param text 绘制的文字(可为空)
* @param font 字体
* @param color 颜色
* @param stroke 画笔样式
*/
public static void drawPolygonWithText(Graphics2D g, List<Point> points,
String text, Font font,
Color color, Stroke stroke) {
if (points == null || points.size() < 3) {
return;
}
int[] xPoints = points.stream().mapToInt(p -> (int) p.getX()).toArray();
int[] yPoints = points.stream().mapToInt(p -> (int) p.getY()).toArray();
g.setFont(font);
g.setColor(color);
g.setStroke(stroke);
// 绘制多边形
g.drawPolygon(xPoints, yPoints, points.size());
// 绘制文字(默认放在第一个点)
if (text != null && !text.isEmpty()) {
g.drawString(text, xPoints[0], yPoints[0]);
}
}
/**
* 绘制关键点
* @param graphics
* @param points
* @param color
*/
public static void drawKeyPoints(Graphics2D graphics, List<Point> points, Color color){
if(points == null || points.isEmpty()){
return;
}
for (Point point : points){
//绘制关键点
graphics.setColor(color);
graphics.drawRect((int)point.getX(), (int)point.getY(), 2, 2);
}
}
/**
* 绘制检测框
* @param sourceImage
* @param detectionResponse
* @throws IOException
*/
public static void drawFaceSearchResult(Graphics2D graphics, DetectionInfo detectionInfo, String text) {
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
DetectionRectangle rectangle = detectionInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
//绘制人脸关键点
//人脸查询结果
if(detectionInfo.getFaceInfo().getFaceSearchResults() != null){
for (FaceSearchResult faceSearchResult : detectionInfo.getFaceInfo().getFaceSearchResults()){
if(StringUtils.isNotBlank(faceSearchResult.getMetadata())){
JsonObject metadata = GsonUtils.parseToJsonObject(faceSearchResult.getMetadata());
JsonElement nameElement = metadata.get("name");
if(metadata.has("name")){
Graphics2DUtils.drawText(graphics, nameElement.getAsString(), rectangle.getX(), rectangle.getY(), stroke, 4);
}
}
}
}
graphics.dispose();
}
}

View File

@@ -1,37 +1,66 @@
package cn.smartjavaai.ocr.opencv;
package cn.smartjavaai.common.utils;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.Point;
import ai.djl.ndarray.NDArray;
import cn.smartjavaai.common.entity.R;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* NDArray Utils
*
* @author dwj
*/
public class OcrNDArrayUtils {
public class DJLCommonUtils {
private static final List<String> SUPPORTED_PROTOCOLS = Arrays.asList(
"file://",
"http://",
"https://",
"jar://",
"djl://",
"s3://",
"hdfs://"
);
/**
* Mat To MatOfPoint
* @param mat
* 检查模型目录中是否存在 "serving.properties" 文件
*
* @param modelPath 模型目录路径
* @return true 表示存在false 表示不存在
*/
public static boolean isServingPropertiesExists(Path modelPath) {
if (modelPath == null || !Files.exists(modelPath)) {
return false;
}
// 确定目录路径
Path dirPath = Files.isDirectory(modelPath) ? modelPath : modelPath.getParent();
if (dirPath == null) {
return false; // 可能是根目录的文件
}
// 判断目录下的 serving.properties 是否存在
Path servingFile = dirPath.resolve("serving.properties");
return Files.exists(servingFile);
}
/**
* 判断 NDArray 是否为空
* @param ndArray
* @return
*/
public static MatOfPoint matToMatOfPoint(Mat mat) {
int rows = mat.rows();
MatOfPoint matOfPoint = new MatOfPoint();
List<Point> list = new ArrayList<>();
for (int i = 0; i < rows; i++) {
Point point = new Point((float) mat.get(i, 0)[0], (float) mat.get(i, 1)[0]);
list.add(point);
}
matOfPoint.fromList(list);
return matOfPoint;
public static boolean isNDArrayEmpty(NDArray ndArray){
return Objects.isNull(ndArray) || ndArray.size() == 0;
}
/**
* float NDArray To float[][] Array
* @param ndArray
@@ -51,65 +80,6 @@ public class OcrNDArrayUtils {
return arr;
}
/**
* Mat To double[][] Array
* @param mat
* @return
*/
public static double[][] matToDoubleArray(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
double[][] doubles = new double[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
doubles[i][j] = mat.get(i, j)[0];
}
}
return doubles;
}
/**
* Mat To float[][] Array
* @param mat
* @return
*/
public static float[][] matToFloatArray(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
float[][] floats = new float[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
floats[i][j] = (float) mat.get(i, j)[0];
}
}
return floats;
}
/**
* Mat To byte[][] Array
* @param mat
* @return
*/
public static byte[][] matToUint8Array(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
byte[][] bytes = new byte[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
bytes[i][j] = (byte) mat.get(i, j)[0];
}
}
return bytes;
}
/**
* float NDArray To float[][] Array
@@ -172,57 +142,44 @@ public class OcrNDArrayUtils {
return mat;
}
/**
* float[][] Array To Mat
* @param arr
* @return
*/
public static Mat floatArrayToMat(float[][] arr) {
int rows = arr.length;
int cols = arr[0].length;
Mat mat = new Mat(rows, cols, CvType.CV_32F);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mat.put(i, j, arr[i][j]);
}
}
return mat;
}
/**
* byte[][] Array To Mat
* @param arr
* @return
*/
public static Mat uint8ArrayToMat(byte[][] arr) {
int rows = arr.length;
int cols = arr[0].length;
Mat mat = new Mat(rows, cols, CvType.CV_8U);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mat.put(i, j, arr[i][j]);
}
}
return mat;
}
/**
* List To Mat
* @param points
* @return
*/
public static Mat toMat(List<ai.djl.modality.cv.output.Point> points) {
public static Mat toMat(List<Point> points) {
Mat mat = new Mat(points.size(), 2, CvType.CV_32F);
for (int i = 0; i < points.size(); i++) {
ai.djl.modality.cv.output.Point point = points.get(i);
mat.put(i, 0, (float) point.getX());
mat.put(i, 1, (float) point.getY());
}
return mat;
}
/**
* 构建一个空的 DetectedObjects 对象
* @return
*/
public static DetectedObjects buildEmptyDetectedObjects(){
List<String> classNames = new ArrayList<>();
List<Double> probabilities = new ArrayList<>();
List<BoundingBox> boxes = new ArrayList<>();
return new DetectedObjects(classNames, probabilities, boxes);
}
/**
* 判断路径是否以已知协议开头
* @param path 模型路径
* @return 是否以支持的协议开头
*/
public static boolean hasSupportedProtocol(String path) {
if (path == null || path.isEmpty()) {
return false;
}
return SUPPORTED_PROTOCOLS.stream().anyMatch(path::startsWith);
}
}

View File

@@ -0,0 +1,162 @@
package cn.smartjavaai.common.utils;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件操作工具类
* @author dwj
* @date 2025/4/4
*/
public class FileUtils {
/**
* 检查文件是否存在
* @param filePath
* @return
*/
public static boolean isFileExists(String filePath) {
File file = new File(filePath);
return file.exists() && !file.isDirectory(); // 确保是文件且存在
}
/**
* 检查目录是否存在
* @param path
* @return
*/
public static boolean isValidDirectory(String path) {
File file = new File(path);
return file.exists() && file.isDirectory();
}
/**
* 查找指定目录下指定后缀的文件
*
* @param dir 目录
* @param suffix 文件后缀,例如 ".txt"、".wav"
* @param recursive 是否递归子目录
* @return 文件列表
*/
public static List<File> findFilesWithSuffix(File dir, String suffix, boolean recursive) {
List<File> result = new ArrayList<>();
if (dir == null || !dir.exists() || !dir.isDirectory()) {
return result;
}
searchFiles(dir, suffix, recursive, result);
return result;
}
// 搜索方法
private static void searchFiles(File dir, String suffix, boolean recursive, List<File> result) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
if(file.getName().endsWith(suffix)){
result.add(file);
continue;
}
if (recursive) {
searchFiles(file, suffix, true, result);
}
} else if (file.isFile() && file.getName().endsWith(suffix)) {
result.add(file);
}
}
}
/**
* 查找指定目录下指定文件名的文件
*
* @param dir 目录
* @param fileName 文件名(精确匹配)
* @param recursive 是否递归子目录
* @return 文件列表
*/
public static List<File> findFilesByName(File dir, String fileName, boolean recursive) {
List<File> result = new ArrayList<>();
if (dir == null || !dir.exists() || !dir.isDirectory() || fileName == null) {
return result;
}
searchByName(dir, fileName, recursive, result);
return result;
}
// 递归搜索方法
private static void searchByName(File dir, String fileName, boolean recursive, List<File> result) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
if (file.getName().equals(fileName)){
result.add(file);
continue;
}
if (recursive) {
searchByName(file, fileName, true, result);
}
} else if (file.isFile() && file.getName().equals(fileName)) {
result.add(file);
}
}
}
/**
* 将文件列表转换为绝对路径字符串
*
* @param files 文件列表
* @return 绝对路径字符串,用逗号分隔
*/
public static String joinAbsolutePaths(List<File> files) {
if (files == null || files.isEmpty()) {
return "";
}
return files.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(","));
}
/**
* 在指定目录中查找文件名包含指定关键字的文件,可选指定后缀。
*
* @param dirPath 要搜索的目录路径
* @param keyword 文件名包含的关键字(可为 null
* @param extension 文件后缀名(例如 ".wav",可为 null
* @param recursive 是否递归搜索子目录
* @return 匹配的文件列表
*/
public static List<File> searchFiles(String dirPath, String keyword, String extension, boolean recursive) {
List<File> result = new ArrayList<>();
File dir = new File(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
System.err.println("目录不存在或不是目录:" + dirPath);
return result;
}
File[] files = dir.listFiles();
if (files == null) return result;
for (File file : files) {
if (file.isDirectory() && recursive) {
// 递归子目录
result.addAll(searchFiles(file.getAbsolutePath(), keyword, extension, true));
} else if (file.isFile()) {
String name = file.getName().toLowerCase();
boolean matchKeyword = (keyword == null || name.contains(keyword.toLowerCase()));
boolean matchExt = (extension == null || name.endsWith(extension.toLowerCase()));
if (matchKeyword && matchExt) {
result.add(file);
}
}
}
return result;
}
}

View File

@@ -0,0 +1,74 @@
package cn.smartjavaai.common.utils;
import cn.smartjavaai.common.entity.Point;
import java.awt.*;
import java.util.List;
/**
* @author dwj
*/
public class Graphics2DUtils {
/**
* 绘制文本
* @param g
* @param text
* @param x
* @param y
* @param stroke
* @param padding
*/
public static void drawText(Graphics2D g, String text, int x, int y, int stroke, int padding) {
FontMetrics metrics = g.getFontMetrics();
x += stroke / 2;
y += stroke / 2;
int width = metrics.stringWidth(text) + padding * 2 - stroke / 2;
int height = metrics.getHeight() + metrics.getDescent();
int ascent = metrics.getAscent();
y = Math.max(0, y - height);
java.awt.Rectangle background = new java.awt.Rectangle(x, y, width, height);
g.fill(background);
g.setPaint(Color.WHITE);
g.drawString(text, x + padding, y + ascent);
}
/**
* 绘制人脸关键点
* @param g
* @param keyPoints
*/
public static void drawLandmarks(Graphics2D g, List<cn.smartjavaai.common.entity.Point> keyPoints) {
g.setColor(new Color(246, 96, 0));
BasicStroke bStroke = new BasicStroke(4.0F, 0, 0);
g.setStroke(bStroke);
for (Point point : keyPoints){
g.drawRect((int)point.getX(), (int)point.getY(), 2, 2);
}
}
public static void drawMultilineTextWithBackground(Graphics2D g, List<String> lines, int x, int y) {
Font font = new Font("SansSerif", Font.PLAIN, 14);
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
int lineHeight = fm.getHeight();
int maxWidth = lines.stream().mapToInt(fm::stringWidth).max().orElse(0);
int padding = 4;
int boxWidth = maxWidth + padding * 2;
int boxHeight = lineHeight * lines.size() + padding * 2;
// 背景矩形
g.setColor(new Color(0, 0, 0, 128));
g.fillRoundRect(x, y, boxWidth, boxHeight, 8, 8);
// 绘制每一行文字
g.setColor(Color.WHITE);
for (int i = 0; i < lines.size(); i++) {
g.drawString(lines.get(i), x + padding, y + padding + (i + 1) * lineHeight - 4);
}
}
}

View File

@@ -0,0 +1,69 @@
package cn.smartjavaai.common.utils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
/**
* @author dwj
*/
public class GsonUtils {
private static final Gson GSON = new Gson();
private GsonUtils() {
// 私有构造,防止实例化
}
/**
* 将 JSON 字符串安全转换为 JsonObject
*
* @param jsonStr JSON 字符串
* @return JsonObject如果解析失败返回 null
*/
public static JsonObject parseToJsonObject(String jsonStr) {
if (jsonStr == null || jsonStr.isEmpty()) {
return null;
}
try {
return JsonParser.parseString(jsonStr).getAsJsonObject();
} catch (JsonSyntaxException | IllegalStateException e) {
// 解析失败返回 null
return null;
}
}
/**
* 将 JSON 字符串转换为指定类型对象
*
* @param jsonStr JSON 字符串
* @param clazz 目标类型
* @param <T> 类型参数
* @return 对象实例,如果解析失败返回 null
*/
public static <T> T fromJson(String jsonStr, Class<T> clazz) {
if (jsonStr == null || jsonStr.isEmpty()) {
return null;
}
try {
return GSON.fromJson(jsonStr, clazz);
} catch (JsonSyntaxException e) {
return null;
}
}
/**
* 将对象转换为 JSON 字符串
*
* @param obj 对象
* @return JSON 字符串,如果对象为 null 返回 null
*/
public static String toJson(Object obj) {
if (obj == null) {
return null;
}
return GSON.toJson(obj);
}
}

View File

@@ -0,0 +1,498 @@
package cn.smartjavaai.common.utils;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.CategoryMask;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.opencv.OpenCVImageFactory;
import ai.djl.util.RandomUtils;
import cn.hutool.core.codec.Base64;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.PolygonLabel;
import org.opencv.core.Mat;
import java.awt.image.BufferedImage;
//import java.awt.image.ColorConvertOp;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* 图片处理工具类
*/
public class ImageUtils {
/**
* 保存DJL图片
*
* @param img
* @param name
* @param path
*/
public static void save(Image img, String name, String path) {
Path outputDir = Paths.get(path);
Path imagePath = outputDir.resolve(name);
// OpenJDK 不能保存 jpg 图片的 alpha channel
try {
img.save(Files.newOutputStream(imagePath), "png");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取图片矩阵BGR
*
* @param img
* @return
*/
public static byte[] getMatrixBGR(Image img) {
if (img.getWrappedImage() instanceof BufferedImage){
return BufferedImageUtils.getMatrixBGR((BufferedImage)img.getWrappedImage());
}else if (img.getWrappedImage() instanceof Mat){
return OpenCVUtils.getMatrixBGR((Mat)img.getWrappedImage());
}else {
throw new RuntimeException("不支持的图片类型");
}
}
/**
* 保存图片,含检测框
*
* @param img
* @param detection
* @param name
* @param path
* @throws IOException
*/
public static void saveBoundingBoxImage(
Image img, DetectedObjects detection, String name, String path) throws IOException {
// Make image copy with alpha channel because original image was jpg
img.drawBoundingBoxes(detection);
Path outputDir = Paths.get(path);
Files.createDirectories(outputDir);
Path imagePath = outputDir.resolve(name);
// OpenJDK can't save jpg with alpha channel
img.save(Files.newOutputStream(imagePath), "png");
}
/**
* 计算左上角,右下角坐标 x0,y0,x1,y1
* Get absolute coordinations
*
* @param rect
* @param width
* @param height
* @return
*/
public static int[] rectXYXY(ai.djl.modality.cv.output.Rectangle rect, int width, int height) {
int left = Math.max((int) (width * rect.getX()), 0);
int top = Math.max((int) (height * rect.getY()), 0);
int right = Math.min((int) (width * (rect.getX() + rect.getWidth())), width - 1);
int bottom = Math.min((int) (height * (rect.getY() + rect.getHeight())), height - 1);
return new int[] {left, top, right, bottom};
}
/**
* 列出文件夹下的所有图片文件
* List all image files under the folder
*
* @param folderPath
* @return
*/
public static List<File> listImageFiles(String folderPath) {
File folder = new File(folderPath);
List<File> imageFiles = new ArrayList<>();
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files == null) {
return imageFiles;
}
for (File file : files) {
if (file.isFile()) {
String name = file.getName().toLowerCase();
if (name.endsWith(".jpg") || name.endsWith(".jpeg") ||
name.endsWith(".png") || name.endsWith(".bmp") ||
name.endsWith(".gif") || name.endsWith(".tiff") ||
name.endsWith(".webp")) {
imageFiles.add(file);
}
}
}
}
return imageFiles;
}
/**
* 读取指定目录下所有图片,返回 List<Image>DJL 格式)
*
* @param folderPath 图片文件夹路径
* @return List<Image>
* @throws IOException
*/
public static List<Image> readImagesFromFolder(String folderPath) throws IOException {
File folder = new File(folderPath);
List<Image> imageList = new ArrayList<>();
if (folder.exists() && folder.isDirectory()) {
File[] files = folder.listFiles();
if (files == null) {
return imageList;
}
for (File file : files) {
if (file.isFile()) {
String name = file.getName().toLowerCase();
if (name.endsWith(".jpg") || name.endsWith(".jpeg") ||
name.endsWith(".png") || name.endsWith(".bmp") ||
name.endsWith(".gif") || name.endsWith(".tiff") ||
name.endsWith(".webp")) {
Image img = SmartImageFactory.getInstance().fromInputStream(Files.newInputStream(file.toPath()));
imageList.add(img);
}
}
}
}
return imageList;
}
/**
* 判断所有图片尺寸是否一致
*
* @param images 图片列表
*/
public static boolean isAllImageSizeEqual(List<Image> images) {
if (images == null || images.isEmpty()) {
return true; // 空集合视为一致
}
int width = images.get(0).getWidth();
int height = images.get(0).getHeight();
for (Image img : images) {
if (img.getWidth() != width || img.getHeight() != height) {
return false;
}
}
return true;
}
/**
* 拷贝图片
* @param src
* @return
*/
public static Image copy(Image src) {
Object srcData = src.getWrappedImage();
//当图片是BufferedImageDJL的duplicate会有问题
if (srcData instanceof BufferedImage) {
return SmartImageFactory.getInstance().fromBufferedImage(BufferedImageUtils.copyBufferedImage((BufferedImage) srcData));
}else{
return src.duplicate();
}
}
/**
* 为不同分类生成不同颜色
* @param background
* @param opacity
* @param classes
* @return
*/
public static int[] generateColors(int background, int opacity, List<String> classes) {
int[] colors = new int[classes.size()];
colors[0] = background;
for (int i = 1; i < classes.size(); i++) {
int red = RandomUtils.nextInt(256);
int green = RandomUtils.nextInt(256);
int blue = RandomUtils.nextInt(256);
colors[i] = opacity << 24 | red << 16 | green << 8 | blue;
}
return colors;
}
/**
* 生成不同颜色遮罩
* @param colors
* @param mask
* @return
*/
public static Image getColorOverlay(int[] colors,int[][] mask) {
int height = mask.length;
int width = mask[0].length;
int[] pixels = new int[width * height];
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int index = mask[h][w];
pixels[h * width + w] = colors[index];
}
}
return SmartImageFactory.getInstance().fromPixels(pixels, width, height);
}
/**
* 绘制遮罩
* @param categoryMask
* @param image
* @param opacity
* @param background
*/
public static void drawMask(CategoryMask categoryMask, Image image, int opacity, int background) {
int[] colors = generateColors(background, opacity, categoryMask.getClasses());
Image maskImage = getColorOverlay(colors, categoryMask.getMask());
image.drawImage(maskImage, true);
}
/**
* 保存 Image 到指定路径,格式根据后缀自动推断
*/
public static void save(Image image, Path path) throws IOException {
String fileName = path.getFileName().toString().toLowerCase();
String format = "png"; // 默认 png
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
format = "jpg";
} else if (fileName.endsWith(".bmp")) {
format = "bmp";
} else if (fileName.endsWith(".webp")) {
format = "webp";
}
Files.createDirectories(path.getParent());
try (OutputStream os = Files.newOutputStream(path)) {
image.save(os, format);
}
}
/**
* 保存 Image 到指定路径,格式根据后缀自动推断
*/
public static void save(Image image, Path path, String format) throws IOException {
Files.createDirectories(path.getParent());
try (OutputStream os = Files.newOutputStream(path)) {
image.save(os, format);
}
}
/**
* 保存 Image 到指定路径
*/
public static void save(Image image, String imagePath) throws IOException {
Path path = Paths.get(imagePath);
Files.createDirectories(path.getParent());
try (OutputStream os = Files.newOutputStream(path)) {
image.save(os, "png");
}
}
/**
* 转换为 BufferedImage
*/
public static BufferedImage toBufferedImage(Image image) {
Object wrapped = image.getWrappedImage();
if (wrapped instanceof BufferedImage) {
return (BufferedImage) wrapped;
} else if (wrapped instanceof Mat) {
Mat mat = (Mat) wrapped;
return OpenCVUtils.mat2Image(mat);
} else {
throw new IllegalArgumentException("Unsupported wrapped image type: " + wrapped.getClass());
}
}
/**
* 转换为 Mat
*/
public static Mat toMat(Image image) {
Object wrapped = image.getWrappedImage();
if (wrapped instanceof BufferedImage) {
return OpenCVUtils.image2Mat((BufferedImage) wrapped);
} else if (wrapped instanceof Mat) {
return (Mat) wrapped;
} else {
throw new IllegalArgumentException("Unsupported wrapped image type: " + wrapped.getClass());
}
}
/**
* Image 转 byte[] (默认 png 格式)
*/
public static byte[] toBytes(Image image, String format) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
image.save(baos, format);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to convert Image to byte[]", e);
}
}
/**
* 保存 Image 到 OutputStream
*
* @param image 图像对象
* @param os 输出流(需要调用方负责关闭)
* @param format 保存格式png/jpg/webp
*/
public static void toOutputStream(Image image, OutputStream os, String format) {
try {
image.save(os, format);
} catch (IOException e) {
throw new RuntimeException("Failed to write image to OutputStream", e);
}
}
/**
* 转换 Image 为 Base64 字符串
*
* @param image 图像对象
* @param format 输出格式png/jpg/webp
* @return Base64 编码的字符串
*/
public static String toBase64(Image image, String format) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
image.save(baos, format);
return Base64.encode(baos.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Failed to convert image to Base64", e);
}
}
/**
* 释放 OpenCV Mat
* @param image
*/
public static void releaseOpenCVMat(Image image){
if (image != null && image.getWrappedImage() instanceof Mat){
((Mat)image.getWrappedImage()).release();
}
}
/**
* 绘制检测结果
* @param sourceImage
* @param detectionResponse
* @return
*/
public static Image drawBoundingBoxes(Image sourceImage, DetectionResponse detectionResponse){
Object srcData = sourceImage.getWrappedImage();
if (srcData instanceof BufferedImage) {
BufferedImage copyBufferedImage = BufferedImageUtils.copyBufferedImage((BufferedImage) srcData);
BufferedImageUtils.drawBoundingBoxes(copyBufferedImage, detectionResponse);
return SmartImageFactory.getInstance().fromBufferedImage(copyBufferedImage);
}else if (srcData instanceof Mat) {
Mat srcMat = ((Mat) srcData).clone();
OpenCVUtils.drawBoundingBoxes(srcMat, detectionResponse);
return SmartImageFactory.getInstance().fromMat(srcMat);
}else {
throw new IllegalArgumentException("Unsupported wrapped image type: " + srcData.getClass());
}
}
/**
* 逆时针旋转图片
*
* @param image
* @param times
* @return
*/
public static Image rotateImg(Image image, int times) {
try (NDManager manager = NDManager.newBaseManager()) {
NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), times);
return OpenCVImageFactory.getInstance().fromNDArray(rotated);
}
}
/**
* 图片旋转
*
* @param manager
* @param image
* @return
*/
public static Image rotateImg(NDManager manager, Image image) {
NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), 1);
return ImageFactory.getInstance().fromNDArray(rotated);
}
public static void drawPolygonWithText(Image image, List<PolygonLabel> polygonLabelList, int fontSize) {
Object srcData = image.getWrappedImage();
if (srcData instanceof BufferedImage) {
BufferedImageUtils.drawPolygonWithText((BufferedImage) srcData, polygonLabelList, fontSize);
}else if (srcData instanceof Mat) {
Mat srcMat = (Mat) srcData;
OpenCVUtils.drawPolygonWithText(srcMat, polygonLabelList, fontSize);
}else {
throw new IllegalArgumentException("Unsupported wrapped image type: " + srcData.getClass());
}
}
/**
* 绘制矩形框
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(Image image, DetectionRectangle box, String text) {
Object srcData = image.getWrappedImage();
if (srcData instanceof BufferedImage) {
BufferedImageUtils.drawRectAndText((BufferedImage) srcData, box, text,12);
}else if (srcData instanceof Mat) {
Mat srcMat = (Mat) srcData;
OpenCVUtils.drawRectAndText(srcMat, box, text, 0.5);
}else {
throw new IllegalArgumentException();
}
}
/**
* 绘制矩形框
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(Image image, DetectionRectangle box, String text, double fontSize) {
Object srcData = image.getWrappedImage();
if (srcData instanceof BufferedImage) {
BufferedImageUtils.drawRectAndText((BufferedImage) srcData, box, text, (int)fontSize);
}else if (srcData instanceof Mat) {
Mat srcMat = (Mat) srcData;
OpenCVUtils.drawRectAndText(srcMat, box, text, fontSize);
}else {
throw new IllegalArgumentException();
}
}
public static void drawRectAndText(Image image, DetectionInfo detectionInfo){
Object srcData = image.getWrappedImage();
if (srcData instanceof BufferedImage) {
BufferedImageUtils.drawRectAndText((BufferedImage) srcData, detectionInfo);
}else if (srcData instanceof Mat) {
Mat srcMat = (Mat) srcData;
OpenCVUtils.drawRectAndText(srcMat, detectionInfo);
}else {
throw new IllegalArgumentException();
}
}
public static void drawRectAndText(Image image, List<DetectionInfo> detectionInfoList){
for(DetectionInfo detectionInfo : detectionInfoList){
drawRectAndText(image, detectionInfo);
}
}
}

View File

@@ -1,5 +1,8 @@
package cn.smartjavaai.common.utils;
import ai.djl.modality.cv.output.Landmark;
import ai.djl.modality.cv.output.Point;
import ai.djl.modality.cv.output.Rectangle;
import ai.djl.modality.cv.util.NDImageUtils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
@@ -7,7 +10,9 @@ import ai.djl.ndarray.index.NDIndex;
import ai.djl.ndarray.types.DataType;
import ai.djl.ndarray.types.Shape;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 按比例缩放剩余空间用指定颜色填充
@@ -60,7 +65,7 @@ public class LetterBoxUtils {
// NDArray paddingImg = manager
// .full(new Shape(targetW, targetH, 3), padColor, DataType.UINT8);
NDArray paddingImg = manager.zeros(new Shape(targetW, targetH, 3), DataType.FLOAT32);
NDArray paddingImg = manager.zeros(new Shape(targetH, targetW, 3), DataType.FLOAT32);
paddingImg = paddingImg.add(114);
int padW = targetW - newW;
@@ -121,4 +126,88 @@ public class LetterBoxUtils {
return boxes;
}
/**
* 恢复缩放后的 box(左上角坐标)
* @param rectangle
* @param scale
* @param origImageWidth
* @param origImageHeight
*/
public static Rectangle restoreBox(Rectangle rectangle, float scale, int origImageWidth, int origImageHeight, int inputWidth, int inputHeight){
double paddingWidth = (inputWidth - origImageWidth * scale) / 2;
double paddingHeight = (inputHeight - origImageHeight * scale) / 2;
// 去掉 padding
double x_noPad = rectangle.getX() - paddingWidth;
double y_noPad = rectangle.getY() - paddingHeight;
//模型输出就是原图坐标
double x1 = x_noPad / scale / origImageWidth;
double y1 = y_noPad / scale / origImageHeight;
double boxW = rectangle.getWidth() / scale / origImageWidth ;
double boxH = rectangle.getHeight() / scale / origImageHeight;
return new Rectangle(x1, y1, boxW, boxH);
}
/**
* 恢复缩放后的 box(左上角坐标)
* @param landmark
* @param scale
* @param origImageWidth
* @param origImageHeight
*/
public static Landmark restoreBox(Landmark landmark, float scale, int origImageWidth, int origImageHeight, int inputWidth, int inputHeight, boolean isNormalized){
double x = 0;
double y = 0;
double width = 0;
double height = 0;
if(isNormalized){
x = landmark.getX() * inputWidth;
y = landmark.getY() * inputHeight;
width = landmark.getWidth() * inputWidth;
height = landmark.getHeight() * inputHeight;
}else{
x = landmark.getX();
y = landmark.getY();
width = landmark.getWidth();
height = landmark.getHeight();
}
double paddingWidth = (inputWidth - origImageWidth * scale) / 2;
double paddingHeight = (inputHeight - origImageHeight * scale) / 2;
// 去掉 padding
double x_noPad = x - paddingWidth;
double y_noPad = y - paddingHeight;
//模型输出就是原图坐标
double x1 = x_noPad / scale / origImageWidth;
double y1 = y_noPad / scale / origImageHeight;
double boxW = width / scale / origImageWidth ;
double boxH = height / scale / origImageHeight;
List<Point> points = new ArrayList<>();
// 要求关键点未归一化
landmark.getPath().forEach(point -> {
double pointX = (point.getX() - paddingWidth) / scale;
double pointY = (point.getY() - paddingHeight) / scale;
points.add(new Point(pointX, pointY));
});
return new Landmark(x1, y1, boxW, boxH, points);
}
/**
* 获取缩放后的图片大小
* @param origW 原始图片宽度
* @param origH 原始图片高度
* @param targetWidth 目标图片宽度
* @param targetHeight 目标图片高度
* @return
*/
public static int[] getResizeSize(int origW, int origH, int targetWidth, int targetHeight){
float r = Math.min(targetWidth / (float) origW, targetHeight / (float) origH);
int newW = Math.round(origW * r);
int newH = Math.round(origH * r);
return new int[]{newW, newH};
}
}

View File

@@ -0,0 +1,177 @@
package cn.smartjavaai.common.utils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDArrays;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.ndarray.types.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* @author dwj
* @date 2025/7/23
*/
public class NMSUtils {
/**
* 通用 NMS 方法,输入 NDArray 形式的 boxes 和 scores返回保留的索引列表
*
* @param boxes NDArray 形状为 (N, 4),格式为 [x1, y1, x2, y2]
* @param scores NDArray 形状为 (N,) 或 (N,1),每个 box 的置信度
* @param iouThreshold IOU 阈值,超过该阈值则认为有重叠
* @return 保留框的索引列表
*/
public static int[] nms(NDArray boxes, NDArray scores, float iouThreshold) {
if (boxes.isEmpty()) {
return new int[0];
}
NDArray x1 = boxes.get(":, 0");
NDArray y1 = boxes.get(":, 1");
NDArray x2 = boxes.get(":, 2");
NDArray y2 = boxes.get(":, 3");
NDArray areas = x2.sub(x1).add(1).mul(y2.sub(y1).add(1));
// 按照置信度降序排序
NDArray order = scores.argSort().flip(0);
List<Integer> keep = new ArrayList<>();
while (order.size() > 0) {
int idx = (int)order.getLong(0);
keep.add(idx);
if (order.size() == 1) break;
NDArray currentBox = boxes.get(idx);
NDArray others = boxes.get(order);
NDArray xx1 = x1.get(order).maximum(x1.get(idx));
NDArray yy1 = y1.get(order).maximum(y1.get(idx));
NDArray xx2 = x2.get(order).minimum(x2.get(idx));
NDArray yy2 = y2.get(order).minimum(y2.get(idx));
NDArray w = xx2.sub(xx1).add(1).maximum(0);
NDArray h = yy2.sub(yy1).add(1).maximum(0);
NDArray inter = w.mul(h);
NDArray remAreas = areas.get(order);
NDArray union = remAreas.add(areas.get(idx)).sub(inter);
NDArray iou = inter.div(union);
NDArray mask = iou.lte(iouThreshold);
order = order.get(mask);
}
return keep.stream().mapToInt(i -> i).toArray();
}
/**
* 批量执行 NMS输入 NDArray 形式的 boxes、scores 和 idxs返回保留的索引列表
*
* @param boxes NDArray 形状为 (N, 4),格式为 [x1, y1, x2, y2]
* @param scores NDArray 形状为 (N,) 或 (N,1),每个 box 的置信度
* @param idxs NDArray 形状为 (N,),每个 box 对应的 batch id
* @param iouThreshold IOU 阈值,超过该阈值则认为有
* @return 批量保留框的索引列表
*
*/
public static NDArray batchedNms(NDArray boxes, NDArray scores, NDArray idxs, float iouThreshold, NDManager manager) {
// System.out.println("---------------boxes:" + Arrays.toString(boxes.toFloatArray()));
List<NDArray> keepList = new ArrayList<>();
// 获取唯一 batch id
NDArray uniqueIdxs = idxs.unique().get(0);
for (long batchId : uniqueIdxs.toLongArray()) {
// 找出当前 batch 的框
NDArray mask = idxs.eq(batchId);
NDArray batchBoxes = boxes.get(mask);
NDArray batchScores = scores.get(mask);
// 执行单 batch NMS
int[] keepIndices = mtcnnNms(batchBoxes, batchScores, iouThreshold);
if (keepIndices.length > 0) {
// 将局部索引映射回全局索引
NDArray globalIndices = manager.arange(boxes.getShape().get(0))
.get(mask)
.toType(DataType.INT64, false)
.get(manager.create(keepIndices));
keepList.add(globalIndices);
}
}
if (keepList.isEmpty()) {
return manager.create(new long[0]);
}
return NDArrays.concat(new NDList(keepList));
}
public static int[] mtcnnNms(NDArray boxes, NDArray scores, float iouThreshold) {
if (boxes.isEmpty()) {
return new int[0];
}
NDArray x1 = boxes.get(":, 0");
NDArray y1 = boxes.get(":, 1");
NDArray x2 = boxes.get(":, 2");
NDArray y2 = boxes.get(":, 3");
// 面积
NDArray areas = x2.sub(x1).add(1).mul(y2.sub(y1).add(1));
// scores 降序索引
NDArray order = scores.argSort();
//System.out.println("order" + order.getShape());
//System.out.println("order" + Arrays.toString(order.toLongArray()));
List<Integer> keep = new ArrayList<>();
while (order.size() > 0) {
int i = (int) order.getLong(-1);
keep.add(i);
if (order.size() == 1) break; // 没框了就退出
// 剩余框
NDArray idx = order.get("0:-1");
NDArray xx1 = x1.get(i).maximum(x1.get(idx));
NDArray yy1 = y1.get(i).maximum(y1.get(idx));
NDArray xx2 = x2.get(i).minimum(x2.get(idx));
NDArray yy2 = y2.get(i).minimum(y2.get(idx));
NDArray w = xx2.sub(xx1).add(1).maximum(0);
NDArray h = yy2.sub(yy1).add(1).maximum(0);
NDArray inter = w.mul(h);
NDArray union = areas.get(i).minimum(areas.get(idx));
NDArray iou = inter.div(union);
// System.out.println("Max IoU: " + iou.max().getFloat());
// System.out.println("Min IoU: " + iou.min().getFloat());
// System.out.println("Mean IoU: " + iou.mean().getFloat());
// System.out.println("Before: " + order.size());
// 保留 IoU <= 阈值的框
NDArray mask = iou.lte(iouThreshold);
// System.out.println("Mask size: " + mask.size() + " True count: " + mask.sum());
// 更新 order
order = idx.get(mask);
// System.out.println("After: " + order.size());
}
return keep.stream().mapToInt(Integer::intValue).toArray();
}
}

View File

@@ -0,0 +1,751 @@
package cn.smartjavaai.common.utils;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.opencv.OpenCVImageFactory;
import ai.djl.util.RandomUtils;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.PolygonLabel;
import cn.smartjavaai.common.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.HeadPose;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* OpenCV 工具类
*/
public class OpenCVUtils {
/**
* canny算法边缘检测
*
* @param src
* @return
*/
public static Mat canny(Mat src) {
Mat mat = src.clone();
Imgproc.Canny(src, mat, 100, 200);
return mat;
}
/**
* 画线
*
* @param mat
* @param point1
* @param point2
*/
public static void line(Mat mat, Point point1, Point point2) {
Imgproc.line(mat, point1, point2, new Scalar(255, 255, 255), 1);
}
/**
* NDArray to opencv_core.Mat
*
* @param manager
* @param srcPoints
* @param dstPoints
* @return
*/
public static Mat toOpenCVMat(NDManager manager, NDArray srcPoints, NDArray dstPoints) {
NDArray svdMat = SVDUtils.transformationFromPoints(manager, srcPoints, dstPoints);
double[] doubleArray = svdMat.toDoubleArray();
Mat newSvdMat = new Mat(2, 3, CvType.CV_64F);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
newSvdMat.put(i, j, doubleArray[i * 3 + j]);
}
}
return newSvdMat;
}
/**
* double[][] points array to Mat
* @param points
* @return
*/
public static Mat toOpenCVMat(double[][] points) {
Mat mat = new Mat(5, 2, CvType.CV_64F);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 2; j++) {
mat.put(i, j, points[i * 5 + j]);
}
}
return mat;
}
/**
* 变换矩阵的逆矩阵
*
* @param src
* @return
*/
public static Mat invertAffineTransform(Mat src) {
Mat dst = src.clone();
Imgproc.invertAffineTransform(src, dst);
return dst;
}
/**
* Mat to BufferedImage
*
* @param mat
* @return
*/
public static BufferedImage mat2Image(Mat mat) {
int width = mat.width();
int height = mat.height();
byte[] data = new byte[width * height * (int) mat.elemSize()];
Imgproc.cvtColor(mat, mat, 4);
mat.get(0, 0, data);
BufferedImage ret = new BufferedImage(width, height, 5);
ret.getRaster().setDataElements(0, 0, width, height, data);
return ret;
}
/**
* BufferedImage to Mat
*
* @param img
* @return
*/
public static Mat image2Mat(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
// 强制转换为 TYPE_3BYTE_BGR自动去除透明通道
BufferedImage convertedImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2d = convertedImg.createGraphics();
g2d.drawImage(img, 0, 0, null);
g2d.dispose();
byte[] data = ((DataBufferByte) convertedImg.getRaster().getDataBuffer()).getData();
Mat mat = new Mat(height, width, CvType.CV_8UC3);
mat.put(0, 0, data);
return mat;
}
/**
* 透视变换
*
* @param src
* @param srcPoints
* @param dstPoints
* @return
*/
public static Mat perspectiveTransform(Mat src, Mat srcPoints, Mat dstPoints) {
Mat dst = src.clone();
Mat warp_mat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints);
Imgproc.warpPerspective(src, dst, warp_mat, dst.size());
warp_mat.release();
return dst;
}
/**
* 绘制矩形框和文字
*
* @param image
* @param detectionInfoList
*/
public static void drawRectAndText(Mat image, List<DetectionInfo> detectionInfoList) {
if(CollectionUtils.isEmpty(detectionInfoList))
return;
for(DetectionInfo detectionInfo : detectionInfoList){
drawRectAndText(image, detectionInfo);
}
}
/**
* 绘制矩形框和文字
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(Mat image, DetectionRectangle box, String text) {
if(image.empty())
return;
int x = box.getX();
int y = box.getY();
int width = box.getWidth();
int height = box.getHeight();
Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
// 绘制矩形框
Point pt1 = new Point(x, y);
Point pt2 = new Point(x + width, y + height);
Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
Scalar textColor = new Scalar(255.0, 255.0, 255.0);
putTextWithBackground(image, text, pt1, textColor, rectangleColor, 1);
}
/**
* 绘制矩形框和文字
* @param image
* @param box
* @param text
*/
public static void drawRectAndText(Mat image, DetectionRectangle box, String text, double fontSize) {
if(image.empty())
return;
int x = box.getX();
int y = box.getY();
int width = box.getWidth();
int height = box.getHeight();
Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
// 绘制矩形框
Point pt1 = new Point(x, y);
Point pt2 = new Point(x + width, y + height);
Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
Scalar textColor = new Scalar(255.0, 255.0, 255.0);
putTextWithBackground(image, text, pt1, textColor, rectangleColor, 1, fontSize);
}
/**
* 绘制矩形框和文字
*
* @param image
* @param detectionInfo
*/
public static void drawRectAndText(Mat image, DetectionInfo detectionInfo) {
if (image == null) return;
int x = detectionInfo.getDetectionRectangle().getX();
int y = detectionInfo.getDetectionRectangle().getY();
int width = detectionInfo.getDetectionRectangle().getWidth();
int height = detectionInfo.getDetectionRectangle().getHeight();
Scalar rectangleColor = new Scalar((double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178), (double)RandomUtils.nextInt(178));
// 绘制矩形框
Point pt1 = new Point(x, y);
Point pt2 = new Point(x + width, y + height);
Imgproc.rectangle(image, pt1, pt2, rectangleColor, 2);
// 绘制文字
String className = null;
Scalar textColor = new Scalar(255.0, 255.0, 255.0);
//目标检测信息
if(detectionInfo.getObjectDetInfo() != null){
className = detectionInfo.getObjectDetInfo().getClassName();
putTextWithBackground(image, className, pt1, textColor, rectangleColor, 1);
}
//人脸
if(detectionInfo.getFaceInfo() != null){
className = "face";
putTextWithBackground(image, className, pt1, textColor, rectangleColor, 1);
//绘制关键点
drawLandmarks(image, detectionInfo.getFaceInfo().getKeyPoints());
//绘制人脸属性
if(detectionInfo.getFaceInfo().getFaceAttribute() != null){
drawFaceAttribute(detectionInfo.getFaceInfo().getFaceAttribute(), detectionInfo.getDetectionRectangle(), image);
}
}
}
/**
* 在Mat上绘制矩形框和文字
*
* @param mat 待绘制的Mat
* @param x 矩形左上角X
* @param y 矩形左上角Y
* @param width 矩形宽度
* @param height 矩形高度
* @param color 框的颜色,例如 new Scalar(0, 255, 0) 绿色
* @param thickness 框线宽度
* @param text 需要绘制的文字可以为null或空
* @param fontScale 文字缩放比例
* @param textColor 文字颜色
*/
public static void drawRectAndText(Mat mat,
int x, int y, int width, int height,
Scalar color, int thickness,
String text, double fontScale, Scalar textColor) {
if (mat == null || mat.empty()) return;
// 绘制矩形框
Point pt1 = new Point(x, y);
Point pt2 = new Point(x + width, y + height);
Imgproc.rectangle(mat, pt1, pt2, color, thickness);
// 绘制文字
if (text != null && !text.isEmpty()) {
int baseline[] = new int[1];
Size textSize = Imgproc.getTextSize(text, Imgproc.FONT_HERSHEY_SIMPLEX, fontScale, thickness, baseline);
// 保证文字不超出矩形
Point textOrg = new Point(x, y - 5 < 0 ? y + textSize.height + 5 : y - 5);
Imgproc.putText(mat, text, textOrg, Imgproc.FONT_HERSHEY_SIMPLEX, fontScale, textColor, thickness);
}
}
/**
* 将 Bytedeco 的 Mat 转换为 OpenCV 官方的 Mat
* @param src Bytedeco Mat (BGR 或 BGRA)
* @return OpenCV Mat (BGR 或 BGRA)
*/
// public static org.opencv.core.Mat convertToOpenCVMat(org.bytedeco.opencv.opencv_core.Mat bMat) {
//
//
// try {
// int width = bMat.cols();
// int height = bMat.rows();
// int channels = bMat.channels();
//
// // 创建 OpenCV Mat
// org.opencv.core.Mat cvMat = new org.opencv.core.Mat(height, width, channels == 3 ? CvType.CV_8UC3 : CvType.CV_8UC1);
//
// // 从 bytedeco Mat 获取像素数据
// byte[] data = new byte[width * height * channels];
// bMat.data().get(data);
//
// // 填充到 OpenCV Mat
// cvMat.put(0, 0, data);
// return cvMat;
// } catch (Throwable e) {
// e.printStackTrace();
// }
// return null;
// }
/**
* 从 OpenCV Mat 中获取 BGR 格式矩阵数据
*
* @param mat OpenCV Mat需为 CV_8UC3 或可转换为 BGR 格式
* @return BGR 格式字节数组,按行连续存储
*/
public static byte[] getMatrixBGR(Mat mat) {
if (mat == null || mat.empty()) {
throw new IllegalArgumentException("Mat 不能为空");
}
// 确保是三通道 BGR 格式
Mat bgrMat = new Mat();
if (mat.channels() == 3) {
mat.copyTo(bgrMat);
} else if (mat.channels() == 4) {
// RGBA 转 BGR
Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_RGBA2BGR);
} else if (mat.channels() == 1) {
// 灰度转 BGR
Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_GRAY2BGR);
} else {
throw new IllegalArgumentException("不支持的通道数: " + mat.channels());
}
int size = (int) (bgrMat.total() * bgrMat.channels());
byte[] data = new byte[size];
bgrMat.get(0, 0, data);
bgrMat.release(); // 释放临时 Mat
return data;
}
/**
* 在图像上绘制带白色背景、黑色文字的文本
*/
public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding) {
// 默认字体
int font = Imgproc.FONT_HERSHEY_SIMPLEX;
// 默认字体缩放大小
double fontScale = 0.6;
//线条粗细
int thickness = 2;
//获取文字大小
int[] baseLine = new int[1];
Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
int textWidth = (int) textSize.width;
int textHeight = (int) textSize.height;
//计算带padding的背景框
org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
//绘制背景矩形
Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
//绘制文字(黑色)
Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
}
/**
* 在图像上绘制带白色背景、黑色文字的文本
*/
public static void putTextWithBackground(Mat image, String text, org.opencv.core.Point origin, Scalar textColor, Scalar backgroundColor, int padding, double fontScale) {
// 默认字体
int font = Imgproc.FONT_HERSHEY_SIMPLEX;
//线条粗细
int thickness = 1;
//获取文字大小
int[] baseLine = new int[1];
Size textSize = Imgproc.getTextSize(text, font, fontScale, thickness, baseLine);
int textWidth = (int) textSize.width;
int textHeight = (int) textSize.height;
//计算带padding的背景框
org.opencv.core.Point bgTopLeft = new org.opencv.core.Point(origin.x - padding, origin.y - textHeight - padding);
org.opencv.core.Point bgBottomRight = new org.opencv.core.Point(origin.x + textWidth + padding, origin.y + baseLine[0] + padding);
//绘制背景矩形
Imgproc.rectangle(image, bgTopLeft, bgBottomRight, backgroundColor, Imgproc.FILLED);
//绘制文字(黑色)
Imgproc.putText(image, text, origin, font, fontScale, textColor, thickness);
}
/**
* 绘制检测结果
* @param image 待绘制的图片
* @param detectionResponse 检测结果
* @return 绘制后的图片
*/
public static void drawBoundingBoxes(Mat image, DetectionResponse detectionResponse){
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
throw new IllegalArgumentException("无目标数据");
}
drawRectAndText(image, detectionResponse.getDetectionInfoList());
}
/**
* 在 Mat 上绘制关键点
* @param mat OpenCV 图像
* @param keyPoints 人脸关键点列表
*/
public static void drawLandmarks(Mat mat, List<cn.smartjavaai.common.entity.Point> keyPoints) {
// 设置颜色 (BGR 格式),这里是橙色 (0,96,246)
Scalar color = new Scalar(0, 96, 246);
// 遍历关键点,用圆来表示 (比矩形更自然)
for (cn.smartjavaai.common.entity.Point p : keyPoints) {
Point cvPoint = new Point(p.getX(), p.getY());
Imgproc.circle(mat, cvPoint, 2, color, 2, Imgproc.LINE_AA, 0);
}
}
/**
* 在 Mat 上绘制多行文字,并带背景
* @param mat OpenCV Mat
* @param lines 文字行
* @param x 起始 x
* @param y 起始 y
*/
public static void drawMultilineTextWithBackground(Mat mat, List<String> lines, int x, int y) {
int fontFace = Imgproc.FONT_HERSHEY_SIMPLEX;
double fontScale = 0.5; // 字体大小
int thickness = 1;
int baseline[] = {0};
// 逐行计算最大宽度 & 总高度
int maxWidth = 0;
int lineHeight = 0;
for (String line : lines) {
Size textSize = Imgproc.getTextSize(line, fontFace, fontScale, thickness, baseline);
maxWidth = Math.max(maxWidth, (int) textSize.width);
lineHeight = Math.max(lineHeight, (int) (textSize.height + baseline[0]));
}
int padding = 4;
int boxWidth = maxWidth + padding * 2;
int boxHeight = lineHeight * lines.size() + padding * 2;
// 绘制背景矩形 (半透明黑色在 OpenCV 里不好直接实现,只能画实色或用 addWeighted 合成)
Scalar bgColor = new Scalar(0, 0, 0); // BGR = 黑色
Point topLeft = new Point(x, y);
Point bottomRight = new Point(x + boxWidth, y + boxHeight);
Imgproc.rectangle(mat, topLeft, bottomRight, bgColor, -1); // -1 表示填充
// 逐行绘制文字 (白色)
Scalar textColor = new Scalar(255, 255, 255);
for (int i = 0; i < lines.size(); i++) {
int textY = y + padding + (i + 1) * lineHeight;
Imgproc.putText(mat, lines.get(i),
new Point(x + padding, textY),
fontFace, fontScale, textColor, thickness, Imgproc.LINE_AA, false);
}
}
/**
* 绘制人脸属性
* @param faceAttribute
* @param rectangle
* @param mat
*/
public static void drawFaceAttribute(FaceAttribute faceAttribute, DetectionRectangle rectangle, Mat mat){
List<String> lines = new ArrayList<>();
if (faceAttribute.getGenderType() != null) {
lines.add("gender: " + faceAttribute.getGenderType());
}
if (faceAttribute.getAge() != null) {
lines.add("age: " + faceAttribute.getAge());
}
if (faceAttribute.getWearingMask() != null) {
lines.add("mask: " + (faceAttribute.getWearingMask() ? "yes" : "no"));
}
if (faceAttribute.getLeftEyeStatus() != null && faceAttribute.getRightEyeStatus() != null) {
lines.add("eyes: " + faceAttribute.getLeftEyeStatus() + "/" + faceAttribute.getRightEyeStatus());
}
if (faceAttribute.getHeadPose() != null) {
HeadPose pose = faceAttribute.getHeadPose();
String pitch = pose.getPitch() != null ? String.valueOf(pose.getPitch().intValue()) : "-";
String yaw = pose.getYaw() != null ? String.valueOf(pose.getYaw().intValue()) : "-";
String roll = pose.getRoll() != null ? String.valueOf(pose.getRoll().intValue()) : "-";
lines.add("head pose: P=" + pitch + " Y=" + yaw + " R=" + roll);
}
if (!lines.isEmpty()) {
drawMultilineTextWithBackground(mat, lines, rectangle.getX(), rectangle.getY()); // 适当偏移
}
}
/**
* 透视变换 + 裁剪
* @param srcMat
* @param landMarks
* @return
*/
public static Image transformAndCrop(Mat srcMat, List<ai.djl.modality.cv.output.Point> landMarks){
if (landMarks == null || landMarks.size() != 4) {
throw new IllegalArgumentException("必须提供4个关键点");
}
// 步骤 1排序为 左上、右上、右下、左下
List<ai.djl.modality.cv.output.Point> ordered = PointUtils.orderPoints(landMarks);
ai.djl.modality.cv.output.Point lt = ordered.get(0);
ai.djl.modality.cv.output.Point rt = ordered.get(1);
ai.djl.modality.cv.output.Point rb = ordered.get(2);
ai.djl.modality.cv.output.Point lb = ordered.get(3);
// 步骤 2计算目标图像尺寸宽、高
int img_crop_width = (int) Math.max(
PointUtils.distance(lt, rt),
PointUtils.distance(rb, lb)
);
int img_crop_height = (int) Math.max(
PointUtils.distance(lt, lb),
PointUtils.distance(rt, rb)
);
// 步骤 3构造目标坐标点
List<ai.djl.modality.cv.output.Point> dstPoints = Arrays.asList(
new ai.djl.modality.cv.output.Point(0, 0),
new ai.djl.modality.cv.output.Point(img_crop_width, 0),
new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height),
new ai.djl.modality.cv.output.Point(0, img_crop_height)
);
// 步骤 4透视变换
Mat srcPoint2f = DJLCommonUtils.toMat(ordered);
Mat dstPoint2f = DJLCommonUtils.toMat(dstPoints);
Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f);
// 步骤 5转为 DJL Image + 裁剪
Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat);
subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height);
// 释放资源
cvMat.release();
srcPoint2f.release();
dstPoint2f.release();
return subImg;
}
/**
* Mat To MatOfPoint
* @param mat
* @return
*/
public static MatOfPoint matToMatOfPoint(Mat mat) {
int rows = mat.rows();
MatOfPoint matOfPoint = new MatOfPoint();
List<Point> list = new ArrayList<>();
for (int i = 0; i < rows; i++) {
Point point = new Point((float) mat.get(i, 0)[0], (float) mat.get(i, 1)[0]);
list.add(point);
}
matOfPoint.fromList(list);
return matOfPoint;
}
/**
* Mat To double[][] Array
* @param mat
* @return
*/
public static double[][] matToDoubleArray(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
double[][] doubles = new double[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
doubles[i][j] = mat.get(i, j)[0];
}
}
return doubles;
}
/**
* Mat To float[][] Array
* @param mat
* @return
*/
public static float[][] matToFloatArray(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
float[][] floats = new float[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
floats[i][j] = (float) mat.get(i, j)[0];
}
}
return floats;
}
/**
* Mat To byte[][] Array
* @param mat
* @return
*/
public static byte[][] matToUint8Array(Mat mat) {
int rows = mat.rows();
int cols = mat.cols();
byte[][] bytes = new byte[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
bytes[i][j] = (byte) mat.get(i, j)[0];
}
}
return bytes;
}
/**
* float[][] Array To Mat
* @param arr
* @return
*/
public static Mat floatArrayToMat(float[][] arr) {
int rows = arr.length;
int cols = arr[0].length;
Mat mat = new Mat(rows, cols, CvType.CV_32F);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mat.put(i, j, arr[i][j]);
}
}
return mat;
}
/**
* byte[][] Array To Mat
* @param arr
* @return
*/
public static Mat uint8ArrayToMat(byte[][] arr) {
int rows = arr.length;
int cols = arr[0].length;
Mat mat = new Mat(rows, cols, CvType.CV_8U);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mat.put(i, j, arr[i][j]);
}
}
return mat;
}
/**
* 将自定义 Point 列表转换为 OpenCV Point 列表
* @param pointList 自定义 Point 列表
* @return OpenCV Point 列表
*/
public static List<Point> toCvPointList(List<cn.smartjavaai.common.entity.Point> pointList) {
if (pointList == null) {
return null;
}
return pointList.stream()
.map(p -> new Point(p.getX(), p.getY()))
.collect(Collectors.toList());
}
public static void drawPolygonWithText(Mat mat, List<PolygonLabel> polygonLabelList, int fontSize) {
for (PolygonLabel polygonLabel : polygonLabelList){
List<Point> cvPointList = toCvPointList(polygonLabel.getPoints());
drawPolygonWithText(mat, cvPointList, polygonLabel.getText(), new Scalar(0, 255, 0), 2);
}
}
/**
* 在图像上绘制多边形(任意边数)
*
* @param mat 图像
* @param points 点的列表至少2个点
* @param color 颜色
* @param thickness 线宽
*/
public static void drawPolygonWithText(Mat mat, List<Point> points, String text, Scalar color, int thickness) {
if (points == null || points.size() < 2) {
return;
}
// 连线
for (int i = 0; i < points.size(); i++) {
Point p1 = points.get(i);
Point p2 = points.get((i + 1) % points.size()); // 最后一个点连回第一个
Imgproc.line(mat, p1, p2, color, thickness);
}
if(StringUtils.isNotBlank(text)){
Scalar textScalar = new Scalar(0,0,0);
// 保证文字不超出矩形
Imgproc.putText(mat, text, points.get(0), Imgproc.FONT_HERSHEY_SIMPLEX, 1, textScalar, thickness);
}
}
public static Mat getSubImage(Mat image, int x, int y, int w, int h) {
return image.submat(new Rect(x, y, w, h));
}
/**
* 从本地路径读取图片并转为 Mat
*
* @param path 图片路径
* @return Mat 对象
*/
public static Mat loadImage(String path) {
// 使用 imread 读取
Mat mat = Imgcodecs.imread(path);
// 判空,避免后续处理时报错
if (mat.empty()) {
throw new IllegalArgumentException("无法加载图片: " + path);
}
return mat;
}
}

View File

@@ -1,6 +1,7 @@
package cn.smartjavaai.face.utils;
package cn.smartjavaai.common.utils;
import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.common.enums.SimilarityType;
/**
* 特征相似度计算工具类

View File

@@ -1,44 +1,40 @@
# SmartJavaAI 示例项目说明
本项目包含多个基于 SmartJavaAI 平台的人脸识别、活体检测及目标检测的示例代码
## 项目结构
```
src/main/java/smartai/examples/
├── face/ 人脸相关示例
│ ├── attribute/ 人脸属性检测模块
└── FaceAttributeDetDemo.java 示例:检测性别、年龄等人脸属性
│ ├── facerec/ 人脸识别模块1:1、1:N
│ │ ├── FaceNetDemo.java 示例:使用 FaceNet 算法做人脸识别
│ │ ├── 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 示例:机器翻译
├── face-example/ 人脸检测、人脸识别等功能示例
├── vision-example/ 通用视觉检测示例:目标检测、目标分割、图像分类等
├── ocr-example/ OCR文字识别、车牌识别等功能示例
├── translate-example/ 机器翻译功能示例
├── speech-example/ 语音识别、语音合成功能示例
```
## 快速开始
本项目在 `examples` 文件夹下提供了多个示例工程,用于演示各功能模块的使用方法:
1. 克隆本项目
2. 导入 IDE推荐 IntelliJ IDEA
3. 运行对应 demo 文件即可测试功能(确保模型文件已准备好)
* `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 文件功能的说明,运行相应的测试方法进行体验。
- 本示例项目配合 `smartjavaai` 平台使用,模型加载及使用方式已封装好。
- 支持 CPU 和 GPU 两种运行模式。
- 所有模型均可通过 Maven 或本地加载方式接入。
## 联系与支持

View File

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

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.22</smartjavaai.version>
<smartjavaai.version>1.0.27</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.face.facedet.FaceDetDemo</exec.mainClass>
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-bom</artifactId>
<artifactId>bom</artifactId>
<version>${smartjavaai.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
@@ -55,11 +55,7 @@
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -73,11 +69,6 @@
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
@@ -94,17 +85,9 @@
<!--人脸识别模块-->
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-face</artifactId>
<version>1.0.22</version>
<artifactId>face</artifactId>
</dependency>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-all</artifactId>
<version>1.0.22</version>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
@@ -187,6 +170,15 @@
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
@@ -226,42 +218,7 @@
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
@@ -292,18 +249,26 @@
</plugins>
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>aliyunmaven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>

View File

@@ -1,25 +0,0 @@
package smartai.examples.face;
import cn.smartjavaai.common.utils.VideoUtils;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
/**
* 视频预处理
* @author dwj
* @date 2025/7/17
*/
public class VideoDemo {
public static void main(String[] args) {
try {
VideoUtils.rotateVideo("/Users/wenjie/Downloads/girl.mp4", "/Users/wenjie/Downloads/girl_rotate.mp4", 180,"mp4", avcodec.AV_CODEC_ID_H264);
} catch (FFmpegFrameRecorder.Exception e) {
throw new RuntimeException(e);
} catch (FFmpegFrameGrabber.Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,10 +1,14 @@
package smartai.examples.face.attribute;
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.entity.face.FaceAttribute;
import cn.smartjavaai.common.entity.face.FaceInfo;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceAttributeConfig;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.enums.FaceAttributeModelEnum;
@@ -16,6 +20,7 @@ import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
@@ -28,23 +33,32 @@ import java.util.List;
/**
* 人脸属性检测demo
* 模型下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class FaceAttributeDetDemo {
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
public FaceAttributeModel getFaceAttributeModel() {
FaceAttributeConfig config = new FaceAttributeConfig();
config.setModelEnum(FaceAttributeModelEnum.SEETA_FACE6_MODEL);
//需替换为实际模型存储路径
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
config.setModelPath("C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models");
return FaceAttributeModelFactory.getInstance().getModel(config);
}
public FaceDetModel getFaceDetModel() {
//需替换为实际模型存储路径
String modelPath = "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models";
String modelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
FaceDetConfig faceDetectModelConfig = new FaceDetConfig();
faceDetectModelConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetectModelConfig.setModelPath(modelPath);
@@ -59,10 +73,12 @@ public class FaceAttributeDetDemo {
public void testFaceAttributeDetect(){
try {
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
DetectionResponse detectionResponse = faceAttributeModel.detect("src/main/resources/iu_1.jpg");
////创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
DetectionResponse detectionResponse = faceAttributeModel.detect(image);
//绘制并导出人脸属性图片,小人脸仅有人脸框
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
FaceUtils.drawBoxesWithFaceAttribute(image, detectionResponse,"C:/Users/Administrator/Downloads/double_person_.png");
BufferedImage bufferedImage = ImageUtils.toBufferedImage(image);
FaceUtils.drawBoxesWithFaceAttribute(bufferedImage, detectionResponse,"C:/Users/Administrator/Downloads/double_person_.png");
log.info("人脸属性检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
@@ -76,30 +92,15 @@ public class FaceAttributeDetDemo {
public void testFaceAttributeDetect2(){
try {
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
FaceAttribute faceAttribute = faceAttributeModel.detectTopFace("src/main/resources/iu_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
FaceAttribute faceAttribute = faceAttributeModel.detectTopFace(image);
log.info("人脸属性检测结果:{}", JSONObject.toJSONString(faceAttribute));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 图片多人脸属性检测(基于已检测出的人脸区域和关键点)
*/
@Test
public void testFaceAttributeDetect3(){
try {
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
FaceAttribute faceAttribute = faceAttributeModel.detectTopFace("src/main/resources/iu_1.jpg");
log.info("人脸属性检测结果:{}", JSONObject.toJSONString(faceAttribute));
} catch (Exception e) {
e.printStackTrace();
}
//人脸检测
}
/**
* 图片单人脸人脸属性检测(基于已检测出的人脸区域和关键点)
@@ -109,8 +110,8 @@ public class FaceAttributeDetDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
FaceAttributeModel faceAttributeModel = getFaceAttributeModel();
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));

View File

@@ -2,6 +2,8 @@ package smartai.examples.face.expression;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -10,6 +12,7 @@ import cn.smartjavaai.common.entity.face.ExpressionResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.face.FacialExpression;
import cn.smartjavaai.common.enums.face.LivenessStatus;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
@@ -26,6 +29,7 @@ import cn.smartjavaai.face.model.liveness.LivenessDetModel;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opencv.core.Mat;
import org.opencv.core.Size;
@@ -46,6 +50,8 @@ import java.util.List;
/**
* 表情识别demo
* 支持识别7种表情neutral中性、happy高兴、sad悲伤、surprise惊讶、fear恐惧、disgust厌恶、anger愤怒
* 模型下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
@@ -54,16 +60,30 @@ public class ExpressionRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取人脸检测模型
* 获取人脸检测模型(均衡模型)
* 均衡模型:兼顾速度和精度
* 注意事项SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
* @return
*/
public FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setDevice(device);
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
@@ -75,7 +95,7 @@ public class ExpressionRecDemo {
public ExpressionModel getExpressionModel(){
FaceExpressionConfig config = new FaceExpressionConfig();
config.setModelEnum(ExpressionModelEnum.FrEmotion);
config.setModelPath("/Users/xxx/Documents/develop/model/emotion/fr_expression.onnx");
config.setModelPath("/Users/wenjie/Documents/develop/model/emotion/fr_expression.onnx");
config.setDevice(device);
config.setAlign(true);
config.setDetectModel(getFaceDetModel());
@@ -90,7 +110,9 @@ public class ExpressionRecDemo {
public void testExpressionDetect() {
try {
ExpressionModel model = getExpressionModel();
R<ExpressionResult> result = model.detectTopFace("src/main/resources/emotion/happy.png");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R<ExpressionResult> result = model.detectTopFace(image);
if(result.isSuccess()){
log.info("识别结果:{}", JSONObject.toJSONString(result.getData().getExpression().getDescription()));
}else{
@@ -109,7 +131,9 @@ public class ExpressionRecDemo {
public void testExpressionDetect2() {
try {
ExpressionModel model = getExpressionModel();
R<DetectionResponse> result = model.detect("src/main/resources/emotion/happy.png");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R<DetectionResponse> result = model.detect(image);
if(result.isSuccess()){
//log.info("识别结果:{}", JSONObject.toJSONString(result.getData()));
for (DetectionInfo detectionInfo : result.getData().getDetectionInfoList()) {
@@ -133,7 +157,8 @@ public class ExpressionRecDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
ExpressionModel model = getExpressionModel();
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/happy.png").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R<DetectionResponse> detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
R<List<ExpressionResult>> result = model.detect(image, detResult.getData());
@@ -162,7 +187,8 @@ public class ExpressionRecDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
ExpressionModel model = getExpressionModel();
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/happy.png").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/happy.png");
R<DetectionResponse> detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
for (DetectionInfo detectionInfo : detResult.getData().getDetectionInfoList()) {
@@ -188,15 +214,16 @@ public class ExpressionRecDemo {
public void testExpressionDetectAndDraw(){
try {
ExpressionModel model = getExpressionModel();
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/emotion/surprise.png").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/emotion/surprise.png");
R<DetectionResponse> result = model.detect(image);
if(result.isSuccess()){
//log.info("识别结果:{}", JSONObject.toJSONString(result.getData()));
for (DetectionInfo detectionInfo : result.getData().getDetectionInfoList()) {
log.info("识别结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription()));
ImageUtils.drawImageRectWithText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription(), Color.red);
ImageUtils.drawRectAndText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription());
}
ImageUtils.saveImage(image, "output/detect.jpg");
ImageUtils.save(image, "output/detect.jpg");
}else{
log.info("识别失败:{}", result.getMessage());
}
@@ -209,7 +236,7 @@ public class ExpressionRecDemo {
* 摄像头表情识别
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
*/
@Test
// @Test
public void testExpressionDetectCamera(){
try {
ExpressionModel expressionModel = getExpressionModel();
@@ -248,7 +275,7 @@ public class ExpressionRecDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
ImageFactory factory = ImageFactory.getInstance();
SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -257,19 +284,18 @@ public class ExpressionRecDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
Image img = factory.fromImage(resizeImage);
BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
R<DetectionResponse> detectedResult = expressionModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> detectedResult = expressionModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
}
for(DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()){
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
String text = detectionInfo.getFaceInfo().getExpressionResult().getExpression().getDescription() + ":" + detectionInfo.getFaceInfo().getExpressionResult().getScore();
ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.red);
String text = detectionInfo.getFaceInfo().getExpressionResult().getExpression().getLabel() + ":" + detectionInfo.getFaceInfo().getExpressionResult().getScore();
ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
frame.showImage(bufferedImage);
frame.showImage(ImageUtils.toBufferedImage(img));
}
capture.release();

View File

@@ -2,6 +2,9 @@ package smartai.examples.face.facedet;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -16,9 +19,11 @@ import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.model.liveness.LivenessDetModel;
import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opencv.core.Mat;
import org.opencv.core.Size;
@@ -32,13 +37,13 @@ import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
/**
* 人脸检测模型demo
* 支持系统windows 64位linux 64位, macos M系列
* 支持功能:人脸检测
* 模型下载地址https://pan.baidu.com/s/1d2YlJ2YOdGn3Y-AegyAhmQ?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
@@ -47,17 +52,68 @@ public class FaceDetDemo {
public static String imgPath = "src/main/resources/iu_1.jpg";
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取人脸检测模型
* 注意事项:高精度模型,速度较慢
* 获取人脸检测模型(均衡模型)
* 均衡模型:兼顾速度和精度
* 注意事项SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
* @return
*/
public FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸检测模型(高精度模型)
* 注意事项:高精度模型,识别准确度高,速度慢
* @return
*/
public FaceDetModel getProFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/face_model/retinaface.pt");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸检测模型(极速模型)
* 注意事项:极速模型,识别准确度低,速度快
* @return
*/
public FaceDetModel getFastFaceDetModel(){
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("/Users/wenjie/Documents/develop/model/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
@@ -70,42 +126,32 @@ public class FaceDetDemo {
FaceDetConfig config = new FaceDetConfig();
//指定模型
config.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
//指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(模型下载地址请查看文档
//指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
config.setConfidenceThreshold(0.9);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 人脸检测(默认配置)
* 使用默认模型参数检测默认模型retinaface需联网会自动下载模型
* 图片参数:图片路径
* 人脸检测
* 注意事项:
* 1、此用例使用均衡模型可以切换高精度模型或极速模型
*/
@Test
public void testFaceDetect(){
try {
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel();
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 人脸检测(自定义模型参数)
* 图片参数:图片路径
*/
@Test
public void testFaceDetectCustomConfig(){
try {
FaceDetModel faceModel = getFaceDetModel();
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> detectedResult = faceModel.detect(image);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
//裁剪人脸保存
for (DetectionInfo detectionInfo : detectedResult.getData().getDetectionInfoList()) {
Image faceImage = FaceUtils.cropFace(image, detectionInfo.getDetectionRectangle());
ImageUtils.save(faceImage, "output/face_" + detectionInfo.getDetectionRectangle().getX() + "_" + detectionInfo.getDetectionRectangle().getY() + ".jpg");
}
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
@@ -122,7 +168,12 @@ public class FaceDetDemo {
public void testFaceDetectAndDraw(){
try {
FaceDetModel faceModel = getFaceDetModel();
faceModel.detectAndDraw("src/main/resources/largest_selfie.jpg","output/largest_selfie_detected.png");
R<DetectionResponse> detectedResult = faceModel.detectAndDraw("src/main/resources/largest_selfie.jpg","output/largest_selfie_detected.png");
if(detectedResult.isSuccess()){
log.info("人脸检测成功:{}", JsonUtils.toJson(detectedResult.getData()));
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -136,15 +187,14 @@ public class FaceDetDemo {
public void testFaceDetectAndDraw2(){
try {
FaceDetModel faceModel = getFaceDetModel();
BufferedImage image = null;
String imagePath = "src/main/resources/largest_selfie.jpg";
image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
//可以根据后续业务场景使用detectedImage
R<BufferedImage> detectedImage = faceModel.detectAndDraw(image);
if(detectedImage.isSuccess()){
log.info("人脸检测成功");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> detectionResponseR = faceModel.detectAndDraw(image);
if(detectionResponseR.isSuccess()){
log.info("人脸检测成功:{}", JsonUtils.toJson(detectionResponseR.getData()));
ImageUtils.save(detectionResponseR.getData().getDrawnImage(), "output/iu_1_detect.png");
}else{
log.info("人脸检测失败:{}", detectedImage.getMessage());
log.info("人脸检测失败:{}", detectionResponseR.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
@@ -153,58 +203,18 @@ public class FaceDetDemo {
}
/**
* 人脸检测(离线模型)
*/
@Test
public void testDetectFaceOffine(){
try {
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸模型
//模型路径,不同模型下载路径请参看文档
config.setModelPath("/Users/xxx/Documents/develop/face_model/retinaface.pt");
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 人脸检测GPU模式
*/
@Test
public void testDetectFaceGPU(){
try {
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸模型
config.setDevice(DeviceEnum.GPU);
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
log.info("人脸检测失败:{}", detectedResult.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 人脸检测(Seetaface6)
* 图片参数:图片路径
* 注意事项不支持macos
*/
@Test
public void testFaceDetectSeetaface6(){
try {
FaceDetModel faceModel = getSeetaface6DetModel();
R<DetectionResponse> detectedResult = faceModel.detect(imgPath);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imgPath);
R<DetectionResponse> detectedResult = faceModel.detect(image);
if(detectedResult.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
}else{
@@ -218,12 +228,12 @@ public class FaceDetDemo {
/**
* 摄像头人脸检测
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
* 注意事项:实时检测,需要使用极速模型
*/
@Test
public void testDetectCamera(){
try {
FaceDetModel faceModel = getFaceDetModel();
FaceDetModel faceModel = getFastFaceDetModel();
OpenCV.loadShared();
VideoCapture capture = new VideoCapture(0);
if (!capture.isOpened()) {
@@ -259,7 +269,7 @@ public class FaceDetDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
ImageFactory factory = ImageFactory.getInstance();
SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -268,9 +278,8 @@ public class FaceDetDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
Image img = factory.fromImage(resizeImage);
BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
R<DetectionResponse> detectedResult = faceModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> detectedResult = faceModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
@@ -281,11 +290,10 @@ public class FaceDetDemo {
if(detectionInfo.getScore() > 0){
text = detectionInfo.getScore() + "";
}
ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.red);
ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
frame.showImage(bufferedImage);
frame.showImage(ImageUtils.toBufferedImage(img));
}
capture.release();
System.exit(0);
} catch (Exception e) {

View File

@@ -1,9 +1,14 @@
package smartai.examples.face.facerec;
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.entity.face.FaceSearchResult;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.SimilarityType;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.config.FaceRecConfig;
import cn.smartjavaai.face.constant.FaceDetectConstant;
@@ -12,29 +17,28 @@ import cn.smartjavaai.face.entity.FaceSearchParams;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.enums.FaceRecModelEnum;
import cn.smartjavaai.face.enums.IdStrategy;
import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.FaceRecModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.model.facerec.FaceRecModel;
import cn.smartjavaai.face.utils.SimilarityUtil;
import cn.smartjavaai.face.vector.config.MilvusConfig;
import cn.smartjavaai.face.vector.config.SQLiteConfig;
import cn.smartjavaai.face.vector.entity.FaceVector;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
/**
* FaceNet人脸算法模型demo
* 支持系统windows 64位linux 64位macOS M系列芯片
* 支持功能人脸特征提取、人脸比对11、人脸比对1N、人脸注册
* 人脸识别模型demo
* 模型下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
* @date 2025/4/11
*/
@Slf4j
public class FaceRecDemo {
@@ -42,29 +46,87 @@ public class FaceRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取人脸检测模型
* 获取人脸检测模型(均衡模型)
* 均衡模型:兼顾速度和精度
* 注意事项SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
* @return
*/
public FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setDevice(device);
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸识别模型
* 获取人脸检测模型(高精度模型
* 注意事项:
* 1、高精度模型识别准确度高速度慢
* 2、具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
* @return
*/
public FaceDetModel getProFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/face_model/retinaface.pt");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸检测模型(极速模型)
* 注意事项:
* 1、极速模型识别准确度低速度快
* 2、具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
* @return
*/
public FaceDetModel getFastFaceDetModel(){
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("/Users/wenjie/Documents/develop/face_model/yolo-face/yolov5face-n-0.5-320x320.onnx");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸识别模型(高精度,速度慢)
* 追求准确度可以使用
* 也可以使用其他模型具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
* @return
*/
public FaceRecModel getFaceRecModel(){
FaceRecConfig config = new FaceRecConfig();
config.setModelEnum(FaceRecModelEnum.FACENET_MODEL);
// config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
// config.setModelPath("/Users/xxx/Documents/develop/model/InsightFace/model_mobilefacenet.pt");
//高精度模型,速度慢
config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
//模型路径请下载模型并替换为本地路径https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸如果图片已经是裁剪过的则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
@@ -75,19 +137,43 @@ public class FaceRecDemo {
return FaceRecModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸识别模型(高速模型,精度一般)
* 追求速度可以使用
* 也可以使用其他模型具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
* @return
*/
public FaceRecModel getHighSpeedFaceRecModel(){
FaceRecConfig config = new FaceRecConfig();
//模型枚举
config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_MOBILE_FACENET_MODEL);
//模型路径请下载模型并替换为本地路径https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_mobilefacenet.pt");
//裁剪人脸如果图片已经是裁剪过的则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(false);
config.setDevice(device);
//指定人脸检测模型
config.setDetectModel(getFastFaceDetModel());
return FaceRecModelFactory.getInstance().getModel(config);
}
/**
* 获取人脸识别模型(带向量数据库配置)
* @return
*/
public FaceRecModel getFaceRecModelWithDbConfig(){
FaceRecConfig config = new FaceRecConfig();
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸检测模型
config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
//高精度模型,速度慢,追求速度请更换高速模型具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
//模型路径请下载模型并替换为本地路径https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸如果图片已经是裁剪过的则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
//指定人脸检测模型
//指定人脸检测模型可切换人脸检测模型极速getFastFaceDetModel高精度getProFaceDetModel具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
config.setDetectModel(getFaceDetModel());
config.setDevice(device);
@@ -95,9 +181,9 @@ public class FaceRecDemo {
MilvusConfig vectorDBConfig = new MilvusConfig();
vectorDBConfig.setHost("127.0.0.1");
vectorDBConfig.setPort(19530);
//vectorDBConfig.setUsername("root");
//vectorDBConfig.setPassword("Milvus");
//vectorDBConfig.setCollectionName("face5");
// vectorDBConfig.setUsername("root");
// vectorDBConfig.setPassword("Milvus");
// vectorDBConfig.setCollectionName("face6");
//ID策略自动生成
vectorDBConfig.setIdStrategy(IdStrategy.AUTO);
//索引类型:内积 (Inner Product) 不建议修改
@@ -112,12 +198,15 @@ public class FaceRecDemo {
*/
public FaceRecModel getFaceRecModelWithSQLiteConfig(){
FaceRecConfig config = new FaceRecConfig();
config.setModelEnum(FaceRecModelEnum.FACENET_MODEL);//人脸检测模型
//高精度模型,速度慢, 追求速度请更换高速模型具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
config.setModelEnum(FaceRecModelEnum.INSIGHT_FACE_IRSE50_MODEL);
//模型路径请下载模型并替换为本地路径https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/recognition/InsightFace/model_ir_se50.pt");
//裁剪人脸如果图片已经是裁剪过的则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
//指定人脸检测模型
//指定人脸检测模型可切换人脸检测模型极速getFastFaceDetModel高精度getProFaceDetModel具体其他模型参数可以查看文档http://doc.smartjavaai.cn/face.html
config.setDetectModel(getFaceDetModel());
config.setDevice(device);
@@ -138,9 +227,12 @@ public class FaceRecDemo {
@Test
public void testExtractFeatures(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
FaceRecModel faceRecModel = getFaceRecModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//提取图片中所有人脸特征
R<DetectionResponse> faceResult = faceRecModel.extractFeatures("src/main/resources/iu_1.jpg");
R<DetectionResponse> faceResult = faceRecModel.extractFeatures(image);
if(faceResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(faceResult.getData()));
}else{
@@ -162,15 +254,60 @@ public class FaceRecDemo {
@Test
public void featureComparison(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
FaceRecModel faceRecModel = getFaceRecModel();
//基于图像直接比对人脸特征
R<Float> similarResult = faceRecModel.featureComparison("src/main/resources/iu_1.jpg","src/main/resources/iu_2.jpg");
if(similarResult.isSuccess()){
//相似度阈值不同模型不同,具体参看文档
log.info("人脸比对相似度:{}", JSONObject.toJSONString(similarResult.getData()));
//不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
if(similarResult.getData() >= 0.62f){
log.info("识别为同一人");
}else{
log.info("识别为不同人");
}
}else{
log.info("人脸比对失败:{}", similarResult.getMessage());
}
}
catch (Exception e){
e.printStackTrace();
}
}
/**
* 人脸比对11基于图像直接比对
* 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。(接口内自动完成)
* 注意事项:
* 1、首次调用接口可能会较慢。只要不关闭程序后续调用会明显加快。若每次重启程序则每次首次调用都将重新加载仍会较慢。
* 2、若人脸朝向不正可开启人脸对齐以提升特征提取准确度。方法参考自定义配置人脸特征提取
* @throws Exception
*/
@Test
public void featureComparison3(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
FaceRecModel faceRecModel = getFaceRecModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image1 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
//基于图像直接比对人脸特征
R<Float> similarResult = faceRecModel.featureComparison(image1, image2);
if(similarResult.isSuccess()){
//相似度阈值不同模型不同,具体参看文档
log.info("人脸比对相似度:{}", JSONObject.toJSONString(similarResult.getData()));
//不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
if(similarResult.getData() >= 0.62f){
log.info("识别为同一人");
}else{
log.info("识别为不同人");
}
}else{
log.info("人脸比对失败:{}", similarResult.getMessage());
}
}
catch (Exception e){
e.printStackTrace();
@@ -188,9 +325,12 @@ public class FaceRecDemo {
@Test
public void featureComparison2(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
FaceRecModel faceRecModel = getFaceRecModel();
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult1 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image1 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<float[]> featureResult1 = faceRecModel.extractTopFaceFeature(image1);
if(featureResult1.isSuccess()){
log.info("图片1人脸特征提取成功{}", JSONObject.toJSONString(featureResult1.getData()));
}else{
@@ -198,7 +338,8 @@ public class FaceRecDemo {
return;
}
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_2.jpg");
Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature(image2);
if(featureResult2.isSuccess()){
log.info("图片2人脸特征提取成功{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -208,6 +349,12 @@ public class FaceRecDemo {
//计算相似度
float similar = faceRecModel.calculSimilar(featureResult1.getData(), featureResult2.getData());
log.info("相似度:{}", similar);
//不同模型的相似度标准不同。当前阈值仅适用于 insight_face 模型,切换模型时请相应调整阈值,详情请参考文档。
if(similar >= 0.62f){
log.info("识别为同一人");
}else{
log.info("识别为不同人");
}
}
catch (Exception e){
e.printStackTrace();
@@ -226,14 +373,17 @@ public class FaceRecDemo {
@Test
public void searchFace(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型
FaceRecModel faceRecModel = getFaceRecModelWithDbConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
Thread.sleep(100);
}
log.info("====================人脸注册==========================");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
R<float[]> featureResult = faceRecModel.extractTopFaceFeature(image);
if(featureResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
}else{
@@ -255,21 +405,23 @@ public class FaceRecDemo {
}else{
log.info("注册失败:{}", registerResult.getMessage());
}
/*log.info("====================人脸更新==========================");
log.info("====================人脸更新==========================");
//更新人脸 只支持自定义IDvectorDBConfig.setIdStrategy(IdStrategy.CUSTOM);
FaceRegisterInfo updateInfo = new FaceRegisterInfo();
//设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
JSONObject metadataJsonUpdate = new JSONObject();
metadataJsonUpdate.put("name", "iu_update");
metadataJsonUpdate.put("age", "25");
updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
//更新必须设置ID,只有
updateInfo.setId(registerResult.getData());
faceRecModel.upsertFace(updateInfo, "src/main/resources/iu_2.jpg");
log.info("更新人脸成功");*/
// FaceRegisterInfo updateInfo = new FaceRegisterInfo();
// //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
// JSONObject metadataJsonUpdate = new JSONObject();
// metadataJsonUpdate.put("name", "iu_update");
// metadataJsonUpdate.put("age", "25");
// updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
// //更新必须设置ID,只有
// updateInfo.setId(registerResult.getData());
// Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
// faceRecModel.upsertFace(updateInfo, image2);
// log.info("更新人脸成功");
log.info("====================人脸查询==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature(image3);
if(featureResult2.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -278,8 +430,7 @@ public class FaceRecDemo {
}
FaceSearchParams faceSearchParams = new FaceSearchParams();
faceSearchParams.setTopK(1);
faceSearchParams.setThreshold(0.8f);
// faceSearchParams.setThreshold(0.62f);
List<FaceSearchResult> faceSearchResults = faceRecModel.search(featureResult2.getData(), faceSearchParams);
// R<DetectionResponse> faceSearchResults = faceModel.search("src/main/resources/face/iu_3.jpg", faceSearchParams);
log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
@@ -303,6 +454,7 @@ public class FaceRecDemo {
@Test
public void searchFace2(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型
FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
@@ -310,7 +462,9 @@ public class FaceRecDemo {
}
log.info("====================人脸注册==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<float[]> featureResult = faceRecModel.extractTopFaceFeature(image);
if(featureResult.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
}else{
@@ -342,11 +496,13 @@ public class FaceRecDemo {
updateInfo.setMetadata(metadataJsonUpdate.toJSONString());
//更新必须设置ID,只有
updateInfo.setId(registerResult.getData());
faceRecModel.upsertFace(updateInfo, "src/main/resources/iu_2.jpg");
Image image2 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_2.jpg");
faceRecModel.upsertFace(updateInfo, image2);
log.info("更新人脸成功");
log.info("====================人脸查询==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_3.jpg");
Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
R<float[]> featureResult2 = faceRecModel.extractTopFaceFeature(image3);
if(featureResult2.isSuccess()){
log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
}else{
@@ -355,7 +511,7 @@ public class FaceRecDemo {
}
FaceSearchParams faceSearchParams = new FaceSearchParams();
faceSearchParams.setTopK(1);
faceSearchParams.setThreshold(0.8f);
//faceSearchParams.setThreshold(0.62f);
List<FaceSearchResult> faceSearchResults = faceRecModel.search(featureResult2.getData(), faceSearchParams);
log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
log.info("====================人脸删除==========================");
@@ -367,6 +523,57 @@ public class FaceRecDemo {
}
}
/**
* 人脸查询及绘制
*
* @throws Exception
*/
@Test
public void searchFace3(){
try {
//高精度模型,速度慢, 追求速度请更换高速模型
FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
Thread.sleep(100);
}
log.info("====================人脸注册==========================");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸注册信息
FaceRegisterInfo faceRegisterInfo = new FaceRegisterInfo();
//设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
JSONObject metadataJson = new JSONObject();
metadataJson.put("name", "iu");
metadataJson.put("age", "25");
faceRegisterInfo.setMetadata(metadataJson.toJSONString());
//可自定义 ID若未设置则自动生成。
//faceRegisterInfo.setId("00001");
//人脸注册返回人脸库ID
R<String> registerResult = faceRecModel.register(faceRegisterInfo, image);
if(registerResult.isSuccess()){
log.info("注册成功ID-{}", registerResult.getData());
}else{
log.info("注册失败:{}", registerResult.getMessage());
}
log.info("====================人脸查询==========================");
//特征提取(提取分数最高人脸特征),适用于单人脸场景
Image image3 = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_3.jpg");
FaceSearchParams faceSearchParams = new FaceSearchParams();
faceSearchParams.setTopK(1);
//faceSearchParams.setThreshold(0.62f);
//图片中只会显示Metadata信息中name的字段
Image drawSearchResult = faceRecModel.drawSearchResult(image3, faceSearchParams, "name");
ImageUtils.save(drawSearchResult, "output/search_result.jpg");
log.info("====================人脸删除==========================");
faceRecModel.removeRegister(registerResult.getData());
log.info("人脸删除成功");
}
catch (Exception e){
e.printStackTrace();
}
}
/**
* 获取人脸信息
@@ -399,7 +606,7 @@ public class FaceRecDemo {
public void listFaces(){
//使用ID获取人脸信息
try {
FaceRecModel faceRecModel = getFaceRecModelWithDbConfig();
FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
Thread.sleep(100);
@@ -419,4 +626,7 @@ public class FaceRecDemo {
}

View File

@@ -3,6 +3,8 @@ package smartai.examples.face.liveness;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import cn.hutool.core.lang.UUID;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -32,6 +34,7 @@ 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.Test;
import org.opencv.core.Mat;
import org.opencv.core.Size;
@@ -52,8 +55,8 @@ import java.util.List;
/**
* 静态活体检测demo
* 模型下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
* @date 2025/5/1
*/
@Slf4j
public class LivenessDetDemo {
@@ -61,6 +64,14 @@ public class LivenessDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
@@ -72,7 +83,7 @@ public class LivenessDetDemo {
config.setModelEnum(LivenessModelEnum.IIC_FL_MODEL);
config.setDevice(device);
//需替换为实际模型存储路径
config.setModelPath("/Users/xxx/Documents/develop/model/anti/IIC_Fl.onnx");
config.setModelPath("/Users/wenjie/Documents/develop/model/anti/IIC_Fl.onnx");
//人脸活体阈值,可选,默认0.8,超过阈值则认为是真人,低于阈值是非活体
config.setRealityThreshold(LivenessConstant.DEFAULT_REALITY_THRESHOLD);
/*视频检测帧数可选默认10输出帧数超过这个number之后就可以输出识别结果。
@@ -96,9 +107,9 @@ public class LivenessDetDemo {
config.setModelEnum(LivenessModelEnum.MINI_VISION_MODEL);
config.setDevice(device);
//模型1路径需替换为实际模型存储路径
config.setModelPath("/Users/xxx/Documents/develop/model/live/2.7_80x80_MiniFASNetV2.onnx");
config.setModelPath("/Users/wenjie/Documents/develop/model/live/2.7_80x80_MiniFASNetV2.onnx");
//SE模型路径需替换为实际模型存储路径
config.putCustomParam("seModelPath", "/Users/xxx/Documents/develop/model/live/4_0_0_80x80_MiniFASNetV1SE.onnx");
config.putCustomParam("seModelPath", "/Users/wenjie/Documents/develop/model/live/4_0_0_80x80_MiniFASNetV1SE.onnx");
//人脸活体阈值,可选,超过阈值则认为是真人,低于阈值是非活体
config.setRealityThreshold(0.5f);
/*视频检测帧数可选默认10输出帧数超过这个number之后就可以输出识别结果。
@@ -114,15 +125,21 @@ public class LivenessDetDemo {
/**
* 获取人脸检测模型
* 获取人脸检测模型(均衡模型)
* 均衡模型:兼顾速度和精度
* 注意事项SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
* @return
*/
public FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setDevice(device);
//人脸检测模型SmartJavaAI提供了多种模型选择(更多模型,请查看文档)切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(FaceDetModelEnum.MTCNN);
//下载模型并替换本地路径下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
config.setModelPath("/Users/wenjie/Documents/develop/model/face_model/mtcnn");
//只返回相似度大于该值的人脸,需要根据实际情况调整,分值越大越严格容易漏检,分值越小越宽松容易误识别
config.setConfidenceThreshold(0.5f);
//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);
return FaceDetModelFactory.getInstance().getModel(config);
}
@@ -135,10 +152,12 @@ public class LivenessDetDemo {
public void testLivenessDetect(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
R<DetectionResponse> response = livenessDetModel.detect("src/main/resources/liveness/1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
R<DetectionResponse> response = livenessDetModel.detect(image);
if(response.isSuccess()){
for (DetectionInfo detectionInfo : response.getData().getDetectionInfoList()){
log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription()));
log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo));
}
}else{
log.info("活体检测失败:{}", response.getMessage());
@@ -155,18 +174,18 @@ public class LivenessDetDemo {
public void testLivenessDetectAndDraw(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
R<DetectionResponse> response = livenessDetModel.detect(image);
if(response.isSuccess()){
for (DetectionInfo detectionInfo : response.getData().getDetectionInfoList()){
log.info("活体检测结果:{}", JSONObject.toJSONString(detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription()));
Color color = detectionInfo.getFaceInfo().getLivenessStatus().getStatus() == LivenessStatus.LIVE ? Color.GREEN : Color.RED;
ImageUtils.drawImageRectWithText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription(), color);
ImageUtils.drawRectAndText(image, detectionInfo.getDetectionRectangle(), detectionInfo.getFaceInfo().getLivenessStatus().getStatus().toString());
ImageUtils.save(image, "output/detect.jpg");
}
}else{
log.info("活体检测失败:{}", response.getMessage());
}
ImageUtils.saveImage(image, "output/detect.jpg");
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -180,10 +199,12 @@ public class LivenessDetDemo {
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
//指定文件夹路径
File dir = new File("face-example/src/main/resources/liveness");
File dir = new File("src/main/resources/liveness");
File[] files = dir.listFiles();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
SmartImageFactory imageFactory = SmartImageFactory.getInstance();
for (File file : files) {
R<LivenessResult> response = livenessDetModel.detectTopFace(ImageIO.read(file));
R<LivenessResult> response = livenessDetModel.detectTopFace(imageFactory.fromFile(file));
if(response.isSuccess()){
log.info("{}活体检测结果:{},分数:{}", file.getName(), response.getData().getStatus().getDescription(), response.getData().getScore());
}else{
@@ -204,8 +225,8 @@ public class LivenessDetDemo {
try {
FaceDetModel faceDetectModel = getFaceDetModel();
LivenessDetModel livenessDetModel = getLivenessDetModel();
// 将图片路径转换为 BufferedImage
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
//人脸检测
R<DetectionResponse> detectionResponse = faceDetectModel.detect(image);
if(detectionResponse.isSuccess()){
@@ -237,8 +258,8 @@ public class LivenessDetDemo {
try {
FaceDetModel faceDetModel = getFaceDetModel();
LivenessDetModel livenessDetModel = getMiniVisionLivenessDetModel();
// 将图片路径转换为 BufferedImage
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/liveness/1.jpg").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/liveness/1.jpg");
R<DetectionResponse> detResult = faceDetModel.detect(image);
if(detResult.isSuccess()){
for (DetectionInfo detectionInfo : detResult.getData().getDetectionInfoList()) {
@@ -267,7 +288,7 @@ public class LivenessDetDemo {
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
//视频路径
R<LivenessResult> livenessStatus = livenessDetModel.detectVideo("video.mp4");
R<LivenessResult> livenessStatus = livenessDetModel.detectVideo("/Users/wenjie/Documents/idea_workplace/SmartJavaAI-Demo/src/main/resources/girl.mp4");
if (livenessStatus.isSuccess()){
log.info("识别结果:{}", JSONObject.toJSONString(livenessStatus.getData()));
}else{
@@ -282,7 +303,7 @@ public class LivenessDetDemo {
* 摄像头活体检测
* 注意事项:如果视频比较卡,可以使用轻量的人脸检测模型
*/
@Test
// @Test
public void testLivenessDetectCamera(){
try {
LivenessDetModel livenessDetModel = getLivenessDetModel();
@@ -321,7 +342,7 @@ public class LivenessDetDemo {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
ImageFactory factory = ImageFactory.getInstance();
SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
@@ -330,9 +351,8 @@ public class LivenessDetDemo {
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
Image img = factory.fromImage(resizeImage);
BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
R<DetectionResponse> detectedResult = livenessDetModel.detect(bufferedImage);
Image img = factory.fromMat(resizeImage);
R<DetectionResponse> detectedResult = livenessDetModel.detect(img);
if(!detectedResult.isSuccess()){
log.debug("识别失败:{}", detectedResult.getMessage());
continue;
@@ -341,11 +361,10 @@ public class LivenessDetDemo {
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
Color color = detectionInfo.getFaceInfo().getLivenessStatus().getStatus() == LivenessStatus.LIVE ? Color.GREEN : Color.RED;
String text = detectionInfo.getFaceInfo().getLivenessStatus().getStatus().getDescription() + ":" + detectionInfo.getFaceInfo().getLivenessStatus().getScore();
ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, color);
ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
frame.showImage(bufferedImage);
frame.showImage(ImageUtils.toBufferedImage(img));
}
capture.release();
System.exit(0);
} catch (Exception e) {

View File

@@ -1,5 +1,8 @@
package smartai.examples.face.quality;
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;
@@ -23,16 +26,19 @@ import cn.smartjavaai.face.model.quality.FaceQualityModel;
import cn.smartjavaai.face.utils.FaceUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
/**
* 人脸质量评估 demo
* 模型下载地址https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
@@ -41,6 +47,14 @@ public class FaceQualityDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
@@ -52,7 +66,7 @@ public class FaceQualityDetDemo {
QualityConfig config = new QualityConfig();
config.setModelEnum(QualityModelEnum.SEETA_FACE6_MODEL);
//需替换为实际模型存储路径
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
config.setModelPath("C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models");
config.setDevice(device);
return FaceQualityModelFactory.getInstance().getModel(config);
}
@@ -64,7 +78,7 @@ public class FaceQualityDetDemo {
*/
public FaceDetModel getFaceDetModel() {
//需替换为实际模型存储路径
String modelPath = "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models";
String modelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
FaceDetConfig faceDetectModelConfig = new FaceDetConfig();
faceDetectModelConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetectModelConfig.setModelPath(modelPath);
@@ -81,8 +95,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -114,8 +129,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -147,8 +163,8 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -180,8 +196,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -214,8 +231,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));
@@ -248,8 +266,9 @@ public class FaceQualityDetDemo {
try {
FaceQualityModel faceQualityModel = getFaceQualityModel();
FaceDetModel faceDetModel = getFaceDetModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
//人脸检测
BufferedImage image = ImageIO.read(new File(Paths.get("src/main/resources/iu_1.jpg").toAbsolutePath().toString()));
R<DetectionResponse> detectionResponse = faceDetModel.detect(image);
if(detectionResponse.isSuccess()){
log.info("人脸检测结果:{}", JSONObject.toJSONString(detectionResponse.getData()));

View File

@@ -0,0 +1,126 @@
#!/usr/bin/env python
#
# Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
# except in compliance with the License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for
# the specific language governing permissions and limitations under the License.
"""
PyTorch resnet18 pre/post processing example.
"""
import json
import logging
import os
from typing import Optional, Any
import sklearn
import torch
import torch.nn.functional as F
from torchvision import transforms
from djl_python import Input
from djl_python import Output
class Processing(object):
def __init__(self):
self.topK = 5
self.image_processing = None
self.mapping = None
self.initialized = False
def initialize(self, properties: dict):
"""
Initialize model.
"""
self.image_processing = transforms.Compose([
transforms.Resize(112),
transforms.CenterCrop(112),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
#self.mapping = self.load_label_mapping("index_to_name.json")
self.initialized = True
def preprocess(self, inputs: Input) -> Output:
outputs = Output()
try:
batch = inputs.get_batches()
images = []
for i, item in enumerate(batch):
image = self.image_processing(item.get_as_image())
images.append(image)
images = torch.stack(images)
outputs.add_as_numpy(images.detach().numpy())
outputs.add_property("content-type", "tensor/ndlist")
except Exception as e:
logging.exception("pre-process failed")
# error handling
outputs = Output().error(str(e))
return outputs
def postprocess(self, inputs: Input) -> Output:
outputs = Output()
try:
data = inputs.get_as_numpy(0)[0]
item = torch.from_numpy(data)
print("data shape:", item.shape)
embedding = sklearn.preprocessing.normalize(item).flatten()
outputs.add(embedding)
except Exception as e:
logging.exception("post-process failed")
# error handling
outputs = Output().error(str(e))
return outputs
@staticmethod
def load_label_mapping(mapping_file_path: Any) -> dict:
if not os.path.isfile(mapping_file_path):
raise Exception('mapping file not found: ' + mapping_file_path)
with open(mapping_file_path) as f:
mapping = json.load(f)
if not isinstance(mapping, dict):
raise Exception('mapping file should be in "class":"label" format')
for key, value in mapping.items():
new_value = value
if isinstance(new_value, list):
new_value = value[-1]
if not isinstance(new_value, str):
raise Exception(
'labels in mapping must be either str or [str]')
mapping[key] = new_value
return mapping
_service = Processing()
def preprocess(inputs: Input) -> Output:
return _service.preprocess(inputs)
def postprocess(inputs: Input) -> Output:
return _service.postprocess(inputs)
def handle(inputs: Input) -> Optional[Output]:
"""
Default handler function
"""
if not _service.initialized:
# stateful model
_service.initialize(inputs.get_properties())
return None

View File

@@ -1,58 +0,0 @@
# 目标检测示例
## 📁 项目结构
```
objectdetection-example/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── smartai/examples/objectdetection/
│ │ │ ├── ObjectDetection.java
│ │ │ └── ViewerFrame.java
```
---
## 🧩 功能模块说明
### 1. 目标检测 [ObjectDetection.java]
- **功能**:核心目标检测类,包含多个测试方法,展示了如何使用不同的模型进行目标检测
---
## ⚙️ 配置要求
- **运行环境**
- JDK 1.8 或更高版本
- IntelliJ IDEA 推荐作为开发 IDE
- **依赖库**
- OpenCV、DJL、SmartJavaAI SDK
- **模型路径**
- 所有模型需下载并配置正确的路径(参考各 demo 注释中的链接)
---
## 🚀 快速开始
1. 克隆项目到本地:
2. 导入项目至 IntelliJ IDEA。
3. 根据需要修改模型路径(见各 demo 中注释)。
4. 运行对应的 JUnit 测试类方法即可体验各项功能。
---
## 📄 文档
有关完整使用说明,请查阅 SmartJavaAI 官方文档:
[http://doc.smartjavaai.cn](http://doc.smartjavaai.cn)
---

View File

@@ -1,260 +0,0 @@
package smartai.examples.objectdetection;
import ai.djl.Application;
import ai.djl.MalformedModelException;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.*;
import ai.djl.modality.cv.output.Rectangle;
import ai.djl.repository.zoo.Criteria;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import ai.djl.training.util.ProgressBar;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
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.common.utils.OpenCVUtils;
import cn.smartjavaai.objectdetection.config.DetectorModelConfig;
import cn.smartjavaai.objectdetection.enums.DetectorModelEnum;
import cn.smartjavaai.objectdetection.exception.DetectionException;
import cn.smartjavaai.objectdetection.model.DetectorModel;
import cn.smartjavaai.objectdetection.model.ObjectDetectionModelFactory;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.Assert;
import org.junit.Test;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 目标检测模型demo
* 支持功能:目标检测
* 模型下载地址https://pan.baidu.com/s/10aTOLBlR6EG-sq6g0OkAWg?pwd=1234 提取码: 1234
* @author dwj
*/
@Slf4j
public class ObjectDetection {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
/**
* 使用默认模型检测YOLO11N
*/
@Test
public void objectDetection(){
//默认cpu
try {
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel();
DetectionResponse detectionResponse = detectorModel.detect("src/main/resources/object_detection.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 指定模型检测(19种模型可选)
*/
@Test
public void objectDetection2(){
try {
DetectorModelConfig config = new DetectorModelConfig();
config.setModelEnum(DetectorModelEnum.SSD_300_RESNET50);//检测模型目前支持19种预置模型
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person"));
//指定返回检测数量
config.setTopK(100);
config.setDevice(device);
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
DetectionResponse detectionResponse = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 人脸检测并绘制检测结果
*/
@Test
public void objectDetectionAndDraw(){
try {
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel();
detectorModel.detectAndDraw("src/main/resources/object_detection.jpg","output/object_detection_detected.png");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 人脸检测并绘制检测结果,返回BufferedImage
*/
@Test
public void objectDetectionAndDraw2(){
try {
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel();
String imagePath = "src/main/resources/object_detection.jpg";
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
//可以根据后续业务场景使用detectedImage
BufferedImage detectedImage = detectorModel.detectAndDraw(image);
Assert.assertNotNull("detectedImage null", detectedImage);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用yolo官方模型检测物品识别
*/
@Test
public void objectDetectionWithOfficialModel(){
try {
DetectorModelConfig config = new DetectorModelConfig();
config.setThreshold(0.3f);
//也支持YoloV8YOLOV8_OFFICIAL 模型可以从文档中提供的地址下载
config.setModelEnum(DetectorModelEnum.YOLOV12_OFFICIAL);//检测模型目前支持19种模型
// 指定模型路径,需要更改为自己的模型路径
config.setModelPath("/Users/xxx/Documents/yolov12n.onnx");
config.setDevice(device);
//一定要将yolo官方的类别文件synset.txt文档中下载放在模型同目录下否则报错
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
DetectionResponse detect = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detect));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用自己训练的模型检测
*/
@Test
public void objectDetectionWithCustomModel(){
try {
DetectorModelConfig config = new DetectorModelConfig();
//也支持YoloV8YOLOV8_CUSTOM 模型需要自己训练,训练教程可以查看文档
config.setModelEnum(DetectorModelEnum.YOLOV12_CUSTOM);//自定义YOLOV12模型
// 指定模型路径,需要更改为自己的模型路径
config.setModelPath("/Users/xxx/Documents/develop/fire_model/best.onnx");
config.putCustomParam("width", 640);//resize 宽
config.putCustomParam("height", 640);// resize 高
config.putCustomParam("nmsThreshold", 0.5f);
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person"));
//指定返回检测数量
config.setTopK(100);
config.setDevice(device);
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
DetectionResponse detect = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detect));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 摄像头目标检测
* 注意事项:如果视频比较卡,可以使用轻量的检测模型
*/
@Test
public void testDetectCamera(){
try {
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel();
OpenCV.loadShared();
VideoCapture capture = new VideoCapture(0);
if (!capture.isOpened()) {
System.out.println("No camera detected");
return;
}
double ratio =
capture.get(Videoio.CAP_PROP_FRAME_WIDTH)
/ capture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int height = (int) (screenSize.height * 0.65f);
int width = (int) (height * ratio);
if (width > screenSize.width) {
width = screenSize.width;
}
Mat image = new Mat();
boolean captured = false;
for (int i = 0; i < 10; ++i) {
captured = capture.read(image);
if (captured) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException ignore) {
// ignore
}
}
if (!captured) {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
ImageFactory factory = ImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
if (!capture.read(image)) {
break;
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
Image img = factory.fromImage(resizeImage);
BufferedImage bufferedImage = OpenCVUtils.mat2Image(resizeImage);
DetectionResponse detectedResult = detectorModel.detect(bufferedImage);
if (Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getDetectionInfoList()) || detectedResult.getDetectionInfoList().size() == 0){
log.debug("未检测到物体");
continue;
}
for(DetectionInfo detectionInfo : detectedResult.getDetectionInfoList()){
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
String text = detectionInfo.getObjectDetInfo().getClassName();
ImageUtils.drawImageRectWithText(bufferedImage, detectionRectangle, text, Color.RED);
}
frame.showImage(bufferedImage);
}
capture.release();
System.exit(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,3 +0,0 @@
Manifest-Version: 1.0
Main-Class: smartai.examples.objectdetection.ObjectDetection

View File

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

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.22</smartjavaai.version>
<smartjavaai.version>1.0.27</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass>
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-bom</artifactId>
<artifactId>bom</artifactId>
<version>${smartjavaai.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
@@ -50,16 +50,13 @@
<artifactId>commons-cli</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -73,11 +70,6 @@
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
@@ -96,7 +88,7 @@
<!--OCR检测模块-->
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-ocr</artifactId>
<artifactId>ocr</artifactId>
</dependency>
@@ -181,6 +173,13 @@
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
@@ -220,45 +219,6 @@
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
@@ -287,16 +247,21 @@
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>aliyunmaven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>

View File

@@ -1,6 +1,8 @@
package smartai.examples.ocr.common;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -14,37 +16,49 @@ import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.utils.OcrUtils;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opencv.core.Mat;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
/**
* OCR 文本检测 示例
* 模型下载地址https://pan.baidu.com/s/15Noz2xHQzqMQSl1B19BobQ?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class OcrDetectionDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取文本检测模型
* @return
*/
public OcrCommonDetModel getDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型
//指定检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -61,7 +75,9 @@ public class OcrDetectionDemo {
public void detect(){
try {
OcrCommonDetModel model = getDetectionModel();
List<OcrBox> boxes = model.detect("src/main/resources/ocr_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
List<OcrBox> boxes = model.detect(image);
log.info("OCR检测结果{}", JSONObject.toJSONString(boxes));
} catch (Exception e) {
e.printStackTrace();
@@ -85,6 +101,26 @@ public class OcrDetectionDemo {
}
}
/**
* 文本检测并绘制结果
* 检测图像中的文本区域,仅检测文本框位置,不识别文字内容
* 注意事项:
* 1、批量检测时模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。
* 2、模型文件需要放在单独文件夹
*/
@Test
public void detectAndDraw2(){
try {
OcrCommonDetModel model = getDetectionModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
Image resultImage = model.detectAndDraw(image);
ImageUtils.save(resultImage, "output/ocr_1_detected2.jpg");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 批量文本检测:批量检测要求图片宽高一致
@@ -98,7 +134,7 @@ public class OcrDetectionDemo {
try {
OcrCommonDetModel model = getDetectionModel();
//批量检测要求图片宽高一致
String folderPath = "/Users/xxx/Downloads/testing33";
String folderPath = "/Users/wenjie/Downloads/testing33";
//读取文件夹中所有图片
List<Image> images = ImageUtils.readImagesFromFolder(folderPath);
List<List<OcrBox>> ocrResult = model.batchDetectDJLImage(images);

View File

@@ -1,6 +1,10 @@
package smartai.examples.ocr.common;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
@@ -12,15 +16,17 @@ import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
/**
* OCR 行文本方向检测 示例
* 模型下载地址https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
* @date 2025/5/25
*/
@Slf4j
public class OcrDirectionDetDemo {
@@ -28,16 +34,22 @@ public class OcrDirectionDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取方向检测模型
* @return
*/
public OcrDirectionModel getDirectionModel(){
DirectionModelConfig directionModelConfig = new DirectionModelConfig();
//指定行文本方向检测模型
//指定行文本方向检测模型切换模型需要同时修改modelEnum及modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setDevice(device);
directionModelConfig.setTextDetModel(getDetectionModel());
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
@@ -49,10 +61,10 @@ public class OcrDirectionDetDemo {
*/
public OcrCommonDetModel getDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型
//指定检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -69,7 +81,9 @@ public class OcrDirectionDetDemo {
public void detect(){
try {
OcrDirectionModel directionModel = getDirectionModel();
List<OcrItem> itemList = directionModel.detect("src/main/resources/ocr_1.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
List<OcrItem> itemList = directionModel.detect(image);
log.info("OCR方向检测结果1{}", JSONObject.toJSONString(itemList));
} catch (Exception e) {
e.printStackTrace();
@@ -93,6 +107,25 @@ public class OcrDirectionDetDemo {
}
}
/**
* 文本检测并绘制结果
* 流程:文本检测 -> 方向分类
* 检测图像中的文本区域,仅检测文本框位置,不识别文字内容
* 模型需要放在单独文件夹
*/
@Test
public void detectAndDraw2(){
try {
OcrDirectionModel directionModel = getDirectionModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
Image resultImage = directionModel.detectAndDraw(image);
ImageUtils.save(resultImage, "output/ocr_1_detected4.jpg");
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -1,7 +1,13 @@
package smartai.examples.ocr.common;
import ai.djl.modality.cv.Image;
import ai.djl.util.JsonUtils;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
@@ -18,16 +24,22 @@ import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel;
import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* OCR 文本识别 示例
* 模型下载地址https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
* @date 2025/5/25
*/
@Slf4j
public class OcrRecognizeDemo {
@@ -35,31 +47,74 @@ public class OcrRecognizeDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
//Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取通用识别模型(不带方向矫正
* 获取通用识别模型(高精确度模型
* 注意事项:高精度模型,识别准确度高,速度慢
* @return
*/
public OcrCommonRecModel getRecModel(){
public OcrCommonRecModel getProRecModel(){
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
//指定文本识别模型
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
//指定文本识别模型切换模型需要同时修改modelEnum及modelPath
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_SERVER_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
recModelConfig.setDevice(device);
recModelConfig.setTextDetModel(getDetectionModel());
recModelConfig.setTextDetModel(getProDetectionModel());
recModelConfig.setDirectionModel(getDirectionModel());
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
* 获取文本检测模型
* 获取通用识别模型(极速模型)
* 注意事项:极速模型,识别准确度低,速度快
* @return
*/
public OcrCommonDetModel getDetectionModel() {
public OcrCommonRecModel getFastRecModel(){
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
//指定文本识别模型切换模型需要同时修改modelEnum及modelPath
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
recModelConfig.setDevice(device);
recModelConfig.setTextDetModel(getFastDetectionModel());
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
* 获取文本检测模型(极速模型)
* 注意事项:极速模型,识别准确度低,速度快
* @return
*/
public OcrCommonDetModel getFastDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型
//指定检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
/**
* 获取文本检测模型(高精确度模型)
* 注意事项:高精度模型,识别准确度高,速度慢
* @return
*/
public OcrCommonDetModel getProDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_SERVER_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -70,31 +125,15 @@ public class OcrRecognizeDemo {
*/
public OcrDirectionModel getDirectionModel(){
DirectionModelConfig directionModelConfig = new DirectionModelConfig();
//指定行文本方向检测模型
//指定行文本方向检测模型切换模型需要同时修改modelEnum及modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setDevice(device);
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
}
/**
* 获取通用识别模型(带方向矫正)
* @return
*/
public OcrCommonRecModel getRecModelWithDirection() {
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
//指定文本识别模型
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
recModelConfig.setDevice(device);
recModelConfig.setTextDetModel(getDetectionModel());
recModelConfig.setDirectionModel(getDirectionModel());
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
* 文本识别
@@ -107,10 +146,12 @@ public class OcrRecognizeDemo {
@Test
public void recognize(){
try {
OcrCommonRecModel recModel = getRecModel();
OcrCommonRecModel recModel = getFastRecModel();
//不带方向矫正,分行返回文本
OcrRecOptions options = new OcrRecOptions(false, true);
OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_2.jpg",options);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_1.jpg");
OcrInfo ocrInfo = recModel.recognize(image, options);
log.info("OCR识别结果{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -129,8 +170,10 @@ public class OcrRecognizeDemo {
@Test
public void recognizeHandWriting(){
try {
OcrCommonRecModel recModel = getRecModel();
OcrInfo ocrInfo = recModel.recognize("src/main/resources/handwriting_1.jpg",new OcrRecOptions());
OcrCommonRecModel recModel = getFastRecModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/handwriting_1.jpg");
OcrInfo ocrInfo = recModel.recognize(image, new OcrRecOptions());
log.info("OCR识别结果{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -149,10 +192,12 @@ public class OcrRecognizeDemo {
@Test
public void recognize2(){
try {
OcrCommonRecModel recModel = getRecModelWithDirection();
OcrCommonRecModel recModel = getFastRecModel();
//带方向矫正,分行返回文本
OcrRecOptions options = new OcrRecOptions(true, true);
OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_3.jpg",options);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/ocr_3.jpg");
OcrInfo ocrInfo = recModel.recognize(image, options);
log.info("OCR识别结果{}", JSONObject.toJSONString(ocrInfo));
} catch (Exception e) {
e.printStackTrace();
@@ -172,7 +217,7 @@ public class OcrRecognizeDemo {
@Test
public void recognizeAndDraw(){
try {
OcrCommonRecModel recModel = getRecModelWithDirection();
OcrCommonRecModel recModel = getFastRecModel();
int fontSize = 18;
recModel.recognizeAndDraw("src/main/resources/general_ocr_002.png", "output/ocr_4_recognized.jpg", fontSize, new OcrRecOptions());
} catch (Exception e) {
@@ -180,6 +225,28 @@ public class OcrRecognizeDemo {
}
}
@Test
public void recognizeAndDraw2(){
try {
OcrCommonRecModel recModel = getFastRecModel();
int fontSize = 18;
//创建保存路径
Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
Path imageOutputPath = Paths.get("output/ocr_5_recognized.jpg");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(inputImagePath);
OcrInfo ocrInfo = recModel.recognizeAndDraw(image, fontSize, new OcrRecOptions());
log.info("OCR识别结果{}", JSONObject.toJSONString(ocrInfo));
//保存绘制结果
if(ocrInfo != null && ocrInfo.getDrawnImage() != null){
ImageUtils.save(ocrInfo.getDrawnImage(), imageOutputPath.toAbsolutePath().toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 批量识别
* 注意事项:
@@ -189,7 +256,7 @@ public class OcrRecognizeDemo {
@Test
public void batchRecognize(){
try {
OcrCommonRecModel recModel = getRecModelWithDirection();
OcrCommonRecModel recModel = getFastRecModel();
//批量检测要求图片宽高一致
String folderPath = "/Users/xxx/Downloads/testing33";
//读取文件夹中所有图片

View File

@@ -1,6 +1,9 @@
package smartai.examples.ocr.plate;
import ai.djl.modality.cv.Image;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -13,12 +16,20 @@ import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.model.plate.PlateDetModel;
import cn.smartjavaai.ocr.model.plate.PlateRecModel;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
/**
* 车牌识别demo
* 模型下载地址https://pan.baidu.com/s/1YEP56UqYcL-Op80M6JAreA?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
@@ -27,14 +38,23 @@ public class PlateRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取车牌检测模型
* @return
*/
public PlateDetModel getPlateDetModel() {
PlateDetModelConfig config = new PlateDetModelConfig();
//车牌检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(PlateDetModelEnum.YOLOV5);
config.setModelPath("/Users/xxx/Documents/develop/model/plate/yolov5_plate_detect.onnx");
//下载模型并替换本地路径
config.setModelPath("/Users/wenjie/Documents/develop/model/plate/yolov5_plate_detect.onnx");
config.setDevice(device);
return PlateModelFactory.getInstance().getDetModel(config);
}
@@ -45,16 +65,26 @@ public class PlateRecDemo {
*/
public PlateRecModel getPlateRecModel() {
PlateRecModelConfig recModelConfig = new PlateRecModelConfig();
//车牌识别模型切换模型需要同时修改modelEnum及modelPath
recModelConfig.setModelEnum(PlateRecModelEnum.PLATE_REC_CRNN);
recModelConfig.setModelPath("/Users/xxx/Documents/develop/model/plate/plate_rec_color.onnx");
//下载模型并替换本地路径
recModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/plate/plate_rec_color.onnx");
//指定车牌检测模型
recModelConfig.setPlateDetModel(getPlateDetModel());
recModelConfig.setDevice(device);
return PlateModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
* 车牌识别
*/
@Test
public void testDetect() {
public void testDetect() throws IOException {
PlateRecModel plateRecModel = getPlateRecModel();
R<List<PlateInfo>> result = plateRecModel.recognize("src/main/resources/plate/Quicker_20220930_180856.png");
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/plate/Quicker_20220930_180856.png");
//识别车号
R<List<PlateInfo>> result = plateRecModel.recognize(image);
if(result.isSuccess()){
log.info("车牌识别结果:{}", JsonUtils.toJson(result.getData()));
}else{
@@ -62,9 +92,13 @@ public class PlateRecDemo {
}
}
/**
* 车牌识别及绘制结果
*/
@Test
public void recognizeAndDraw() {
PlateRecModel plateRecModel = getPlateRecModel();
//识别车号并绘制结果
R<Void> result = plateRecModel.recognizeAndDraw("src/main/resources/plate/single_green.jpg", "output/plate_recognized2.jpg");
if(result.isSuccess()){
log.info("车牌识别成功");
@@ -74,5 +108,28 @@ public class PlateRecDemo {
}
/**
* 车牌识别及绘制结果
*/
@Test
public void recognizeAndDraw2() {
try {
PlateRecModel plateRecModel = getPlateRecModel();
String imagePath = "src/main/resources/plate/Quicker_20220930_180856.png";
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imagePath);
//可以根据后续业务场景使用detectedImage
R<Image> detectedImage = plateRecModel.recognizeAndDraw(image);
if(detectedImage.isSuccess()){
log.info("车牌识别成功");
ImageUtils.save(detectedImage.getData(), "output/plate_recognized3.jpg");
}else{
log.error("车牌识别失败:{}", detectedImage.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -2,6 +2,8 @@ package smartai.examples.ocr.table;
import ai.djl.modality.cv.Image;
import cn.hutool.core.io.FileUtil;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -21,24 +23,36 @@ import cn.smartjavaai.ocr.model.table.TableRecognizer;
import cn.smartjavaai.ocr.model.table.TableStructureModel;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
/**
* OCR 文本识别 示例
* OCR 表格识别 示例
* 模型下载地址https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
* @date 2025/5/25
*/
@Slf4j
public class TableRecDemo {
@BeforeClass
public static void beforeAll() throws IOException {
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@@ -48,10 +62,10 @@ public class TableRecDemo {
*/
public OcrCommonRecModel getRecModel(){
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
//指定文本识别模型
//指定文本识别模型切换模型需要同时修改modelEnum及modelPath
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
recModelConfig.setRecModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_rec_infer/PP-OCRv5_mobile_rec_infer.onnx");
recModelConfig.setDevice(device);
recModelConfig.setTextDetModel(getDetectionModel());
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
@@ -63,11 +77,10 @@ public class TableRecDemo {
*/
public OcrCommonDetModel getDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
//指定检测模型
//指定检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V5_MOBILE_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
// config.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
config.setDetModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_mobile_det_infer/PP-OCRv5_mobile_det_infer.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
@@ -78,10 +91,10 @@ public class TableRecDemo {
*/
public OcrDirectionModel getDirectionModel(){
DirectionModelConfig directionModelConfig = new DirectionModelConfig();
//指定行文本方向检测模型
//指定行文本方向检测模型切换模型需要同时修改modelEnum及modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
directionModelConfig.setModelPath("/Users/xxx/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx");
directionModelConfig.setDevice(device);
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
}
@@ -92,10 +105,10 @@ public class TableRecDemo {
*/
public TableStructureModel getTableStructureModel(){
TableStructureConfig config = new TableStructureConfig();
//指定行文本方向检测模型
//指定行文本方向检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(TableStructureModelEnum.SLANET_PLUS);
//指定行文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
config.setModelPath("/Users/xxx/Documents/develop/model/ocr/slanet-plus/slanet-plus.onnx");
config.setModelPath("/Users/wenjie/Documents/develop/model/ocr/slanet-plus/slanet-plus.onnx");
// config.setModelPath("/Users/xxx/Documents/develop/model/ocr/SLANet_infer/SLANet.onnx");
config.setDevice(device);
return TableRecModelFactory.getInstance().getTableStructureModel(config);
@@ -103,6 +116,7 @@ public class TableRecDemo {
/**
* 表格识别
* 仅支持简单表格
@@ -112,7 +126,7 @@ public class TableRecDemo {
* 2、模型文件需要放在单独文件夹
*/
@Test
public void recognize(){
public void recognize2(){
try {
TableStructureModel tableStructureModel = getTableStructureModel();
OcrCommonDetModel detModel = getDetectionModel();
@@ -125,7 +139,8 @@ public class TableRecDemo {
// .withDirectionModel(getDirectionModel()) //如果表格中存在旋转的文字,可以使用方向分类模型
.withTextRecModel(recModel).build();
String imagePath = "src/main/resources/table/table_ch1.png";
BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imagePath);
R<TableStructureResult> result = tableRecognizer.recognize(image);
if(result.isSuccess()){
log.info("result: {}", result.getData().getHtml());
@@ -133,9 +148,12 @@ public class TableRecDemo {
Path outputPath = Paths.get("output/table_ch2_result.html");
FileUtil.writeUtf8String(result.getData().getHtml(), outputPath.toAbsolutePath().toString());
//绘制表格结构
tableRecognizer.drawTable(result.getData(), image, "output/table_ch2_result.jpg");
Image resultImage = tableRecognizer.drawTable(result.getData(), image);
ImageUtils.save(resultImage, "output/table_ch2_result.jpg");
//导出excel如果导出失败可能是因为表格结果识别的结果是错乱的
tableRecognizer.exportExcel(result.getData().getHtml(), "output/table_ch2_result.xls");
try (OutputStream out = Files.newOutputStream(Paths.get("output/table_ch2_result2.xls"))) {
tableRecognizer.exportExcel(result.getData().getHtml(), out);
}
}
} catch (Exception e) {
e.printStackTrace();

View File

@@ -0,0 +1,38 @@
# 语音识别ASR+语音合成TTS示例
## 📁 项目结构
```
src
├── main
│ ├── java
│ │ └── smartai/examples/speech
│ │ ├── asr
│ │ │ ├── SherpaAsrDemo.java # sherpa-onnx 语音识别ASR 示例
│ │ │ ├── SpeechRecognizeDemo.java # 其他模型的 语音识别ASR 示例
│ │ └── tts
│ │ └── TtsDemo.java # tts语音合成示例
```
## 🚀 快速开始
如果你只想运行某个示例,请按以下方式操作:
1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/speech-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
---
## 📄 文档
有关完整使用说明,请查阅 SmartJavaAI 官方文档:
[http://doc.smartjavaai.cn](http://doc.smartjavaai.cn)
---

View File

@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.smartjavaai</groupId>
<artifactId>examples-speech</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.27</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.speech.asr.common.OcrRecognizeDemo</exec.mainClass>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>bom</artifactId>
<version>${smartjavaai.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!--语音识别模块-->
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>speech</artifactId>
<exclusions>
<exclusion>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</exclusion>
<exclusion>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
</exclusion>
<exclusion>
<groupId>ai.djl.huggingface</groupId>
<artifactId>tokenizers</artifactId>
</exclusion>
<exclusion>
<groupId>ai.djl.ml.xgboost</groupId>
<artifactId>xgboost</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--ffmpeg库 引用所有平台库-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.5.0</version>
</dependency>
<!-- windows平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-win64</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
<!-- linux x86 平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-linux64</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
<!-- linux arm64 平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-linux-arm64</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
<!-- linux arm32 平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-linux-arm32</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
<!-- macOS osx64 平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-osx64</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<!-- <dependency>-->
<!-- <groupId>ws.schild</groupId>-->
<!-- <artifactId>jave-nativebin-osxm1</artifactId>-->
<!-- <version>3.5.0</version>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${exec.mainClass}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,389 @@
package smartai.examples.speech.asr;
import ai.djl.modality.audio.Audio;
import ai.djl.modality.audio.AudioFactory;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.entity.Language;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.AsrResult;
import cn.smartjavaai.speech.asr.entity.WhisperParams;
import cn.smartjavaai.speech.asr.enums.AsrModelEnum;
import cn.smartjavaai.speech.asr.factory.SpeechRecognizerFactory;
import cn.smartjavaai.speech.asr.model.SpeechRecognizer;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.nio.file.Paths;
/**
* 语音识别ASR demo
* sherpa-onnx模型及依赖库下载链接:
* 1、推荐依赖库官网下载https://github.com/k2-fsa/sherpa-onnx/releases
* 2、推荐ASR模型官网下载https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models
* 3、百度网盘下载https://pan.baidu.com/s/19p3WhVEM7dgdkvXFaeeAxg?pwd=1234 提取码: 1234
* @author dwj
* @date 2025/10/23
*/
@Slf4j
public class SherpaAsrDemo {
/**
* 语音识别Sherpa Paraformer中文
*/
@Test
public void testSherpaParaformerAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_PARAFORMER);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-paraformer-zh-2023-09-14");
config.setModelName("model.int8.onnx");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-paraformer-zh-2023-09-14/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Dolphin中文
*/
@Test
public void testSherpaDolphinAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_DOLPHIN);
config.setModelName("model.int8.onnx");
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-dolphin-base-ctc-multi-lang-int8-2025-04-02");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-dolphin-base-ctc-multi-lang-int8-2025-04-02/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa zipformer中文
*/
@Test
public void testSherpaZipformerAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_ZIPFORMERCTC);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-zipformer-ctc-zh-int8-2025-07-03");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
config.setModelName("model.int8.onnx");
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-zipformer-ctc-zh-int8-2025-07-03/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa FireRedAsr中英
*/
@Test
public void testSherpaFireRedAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_FIREREDASR);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16/test_wavs/3.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa SenseVoice (多语言模型)
*/
@Test
public void testSherpaSenseVoiceAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_SENSEVOICE);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17");
config.setModelName("model.onnx");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/test_wavs/zh.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa WenetCtc多语言模型粤语
*/
@Test
public void testSherpaWenetCtcAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_WENETCTC);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10");
config.setModelName("model.int8.onnx");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-wenetspeech-yue-u2pp-conformer-ctc-zh-en-cantonese-int8-2025-09-10/test_wavs/yue-0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa FireRedAsr方言四川、天津、河南
*/
@Test
public void testSherpaFireRedAsrSichuan() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_FIREREDASR);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-fire-red-asr-large-zh_en-2025-02-16/test_wavs/3-sichuan.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Telespeech方言-天津、河南、四川)
*/
@Test
public void testSherpaTelespeechAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_TELESPEECH);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-telespeech-ctc-int8-zh-2024-06-04");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.setModelName("model.int8.onnx");
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-telespeech-ctc-int8-zh-2024-06-04/test_wavs/4-tianjin.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Nemo英文
*/
@Test
public void testSherpaNemoAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_NEMO);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-nemo-ctc-en-citrinet-512");
config.setModelName("model.onnx");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-nemo-ctc-en-citrinet-512/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Moonshine英文
*/
@Test
public void testSherpaMoonshineAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_MOONSHINE);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-moonshine-tiny-en-int8");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 2);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-moonshine-tiny-en-int8/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Whisper英文
*/
@Test
public void testSherpaWhisperAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_WHISPER);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-whisper-tiny");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-whisper-tiny/test_wavs/0.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Transducer英文
*/
@Test
public void testSherpaTransducerAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_TRANSDUCER);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-zipformer-gigaspeech-2023-12-12");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-zipformer-gigaspeech-2023-12-12/test_wavs/1221-135766-0001.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 语音识别Sherpa Canary德语-英文)
*/
@Test
public void testSherpaCanaryAsr() {
try {
//获取模型
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.SHERPA_CANARY);
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8");
config.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
config.putCustomParam("debug", false);
config.putCustomParam("numThreads", 1);
SpeechRecognizer recognizer = SpeechRecognizerFactory.getInstance().getModel(config);
Audio audio = AudioFactory.newInstance().fromFile(Paths.get("/Users/wenjie/Documents/develop/model/speech/sherpa-asr/sherpa-onnx-nemo-canary-180m-flash-en-es-de-fr-int8/test_wavs/de.wav"));
R<AsrResult> result = recognizer.recognize(audio);
if (result.isSuccess()){
log.info("识别成功:{}", result.getData());
}else{
log.error("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,353 @@
package smartai.examples.speech.asr;
import ai.djl.util.JsonUtils;
import cn.hutool.core.io.FileUtil;
import cn.smartjavaai.common.entity.Language;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.speech.asr.config.AsrModelConfig;
import cn.smartjavaai.speech.asr.entity.AsrResult;
import cn.smartjavaai.speech.asr.entity.VoskParams;
import cn.smartjavaai.speech.asr.entity.WhisperParams;
import cn.smartjavaai.speech.asr.enums.AsrModelEnum;
import cn.smartjavaai.speech.asr.factory.SpeechRecognizerFactory;
import cn.smartjavaai.speech.asr.model.SpeechRecognizer;
import cn.smartjavaai.speech.asr.model.VoskRecognizer;
import cn.smartjavaai.speech.asr.model.WhisperRecognizer;
import io.github.givimad.whisperjni.WhisperFullParams;
import io.github.givimad.whisperjni.WhisperGrammar;
import io.github.givimad.whisperjni.WhisperSamplingStrategy;
import io.github.givimad.whisperjni.WhisperState;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.vosk.Recognizer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.Paths;
/**
* 语音识别demoVosk、Whisper
* 模型下载网盘https://pan.baidu.com/s/1kiMF5MF641R7LTn1GpB2lQ?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class SpeechRecognizeDemo {
/**
* 获取Whisper模型
* 模型下载网盘https://pan.baidu.com/s/1kiMF5MF641R7LTn1GpB2lQ?pwd=1234 提取码: 1234
* 更多模型下载地址https://huggingface.co/ggerganov/whisper.cpp/tree/main
* @return
*/
public SpeechRecognizer getWhisperRecognizer() {
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.WHISPER);
//模型下载地址https://huggingface.co/ggerganov/whisper.cpp/tree/main
config.setModelPath("/Users/xxx/Documents/develop/model/speech/ggml-medium.bin");
return SpeechRecognizerFactory.getInstance().getModel(config);
}
/**
* Whisper 语音识别
* 多语言模型支持100种语言
* 注意事项:
* 1、不支持centos7
* 2、模型越大越准确
* 3、暂不支持GPU使用如需GPU使用需要自行编译https://github.com/ggml-org/whisper.cpp/tree/master?tab=readme-ov-file#nvidia-gpu-support
*/
@Test
public void testWhisper() {
try {
SpeechRecognizer recognizer = getWhisperRecognizer();
WhisperParams params = new WhisperParams();
//语言:中文
params.setLanguage(Language.ZH);
R<AsrResult> result = recognizer.recognize("src/main/resources/speech_zh.mp3", params);
if (result.isSuccess()){
log.info("识别成功:{}", JsonUtils.toJson(result.getData()));
}else{
log.info("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Whisper 语音识别(使用个性化配置)
* 多语言模型支持100种语言
* 注意事项:
* 1、不支持centos7
* 2、模型越大越准确
* 3、暂不支持GPU使用如需GPU使用需要自行编译https://github.com/ggml-org/whisper.cpp/tree/master?tab=readme-ov-file#nvidia-gpu-support
*/
@Test
public void testWhisperWithCustomConfig() {
try {
SpeechRecognizer recognizer = getWhisperRecognizer();
WhisperParams params = new WhisperParams();
//语言:中文
params.setLanguage(Language.ZH);
/**
* 解码搜索策略类型:
* GREEDY - 贪婪解码,逐步选择概率最高的结果;
* BEAN_SEARCH - Beam 搜索,保留多个候选路径以提高准确性。
*/
WhisperFullParams fullParams = new WhisperFullParams(WhisperSamplingStrategy.BEAN_SEARCH);
//语言
fullParams.language = Language.ZH.getCode();
//线程数,设为 0 表示使用最大核心数。
fullParams.nThreads = 0;
//解码器使用的历史文本作为提示的最大 token 数。
fullParams.nMaxTextCtx = 16384;
//解码起始偏移(毫秒)
fullParams.offsetMs = 0;
//解码持续时长(毫秒),超过此长度的音频将被截断
fullParams.durationMs = 0;
//是否翻译为英文
fullParams.translate = false;
// 初始提示,用于提供上下文或样例,帮助模型更准确地理解语音内容
fullParams.initialPrompt = "简体中文";
//禁用上下文链接,不使用前一段解码结果作为上下文
fullParams.noContext = true;
//是否强制仅输出一个段落(适用于短语音)
fullParams.singleSegment = false;
//是否打印特殊标记
fullParams.printSpecial = false;
//是否直接从 whisper.cpp 中打印结果(不推荐,建议使用回调方式替代)
fullParams.printRealtime = false;
//抑制非语音 token输出
fullParams.suppressNonSpeechTokens = false;
//更多参数请查看官网https://github.com/GiviMAD/whisper-jni/blob/33854520b1f0b3697106a7932a2fd64e8191bca9/src/main/java/io/github/givimad/whisperjni/WhisperFullParams.java
params.setParams(fullParams);
//建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度
R<AsrResult> result = recognizer.recognize("src/main/resources/speech_zh.mp3", params);
if (result.isSuccess()){
log.info("识别成功:{}", JsonUtils.toJson(result.getData()));
}else{
log.info("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Whisper 语音识别(使用Grammar语法规则)
* 多语言模型支持100种语言
* 注意事项:
* 1、不支持centos7
* 2、模型越大越准确
* 3、暂不支持GPU使用如需GPU使用需要自行编译https://github.com/ggml-org/whisper.cpp/tree/master?tab=readme-ov-file#nvidia-gpu-support
*/
@Test
public void testWhisperWithGrammar() {
try {
WhisperRecognizer whisperRecognizer = (WhisperRecognizer)getWhisperRecognizer();
//语法规则
String grammarText = "root ::= \" And so, my fellow American, ask not what your country can do for you, ask what you can do for your country.\"";
try (WhisperGrammar grammar = whisperRecognizer.parseGrammar(grammarText)){
WhisperParams params = new WhisperParams();
WhisperFullParams fullParams = new WhisperFullParams(WhisperSamplingStrategy.BEAN_SEARCH);
//语言:英文
fullParams.language = Language.EN.getCode();
fullParams.grammar = grammar;
params.setParams(fullParams);
//建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度
R<AsrResult> result = whisperRecognizer.recognize("src/main/resources/jfk_en.wav", params);
if (result.isSuccess()){
log.info("识别成功:{}", JsonUtils.toJson(result.getData()));
}else{
log.info("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Vosk模型中文
* 模型下载网盘通过网盘分享的文件https://pan.baidu.com/s/1kiMF5MF641R7LTn1GpB2lQ?pwd=1234 提取码: 1234
* 更多模型下载地址https://alphacephei.com/vosk/models
* @return
*/
public SpeechRecognizer geVoskRecognizer() {
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.VOSK);
/**
* 每个模型只支持一种语言请下载对应语音的模型模型下载地址https://alphacephei.com/vosk/models
* 将模型解压后,将模型目录位置填写到此处
*/
config.setModelPath("/Users/wenjie/Documents/develop/model/speech/vosk-model-cn-0.22");
/**
* macos m系列芯片需要手动下载依赖库并指定位置其他平台不需要
* 下载地址https://pan.baidu.com/s/1LZ_EX1XdTTp_f5ruud82MA?pwd=1234 提取码: 1234
*/
config.setLibPath(Paths.get("/Users/wenjie/Downloads/vosk-arrch64-dylib-main/libvosk.dylib"));
return SpeechRecognizerFactory.getInstance().getModel(config);
}
/**
* 获取Vosk模型英文
* 模型下载网盘通过网盘分享的文件https://pan.baidu.com/s/1kiMF5MF641R7LTn1GpB2lQ?pwd=1234 提取码: 1234
* 更多模型下载地址https://alphacephei.com/vosk/models
* @return
*/
public SpeechRecognizer geEnVoskRecognizer() {
AsrModelConfig config = new AsrModelConfig();
config.setModelEnum(AsrModelEnum.VOSK);
/**
* 每个模型只支持一种语言请下载对应语音的模型模型下载地址https://alphacephei.com/vosk/models
* 将模型解压后,将模型目录位置填写到此处
*/
config.setModelPath("/Users/xxx/Documents/develop/model/speech/vosk-model-small-en-us-0.15");
// config.setLibPath(Paths.get("/Users/xxx/Downloads/vosk-arrch64-dylib-main/libvosk.dylib"));
return SpeechRecognizerFactory.getInstance().getModel(config);
}
/**
* Vosk 语音识别
* 支持 20 多种语言和方言——英语、印度英语、德语、法语、西班牙语、葡萄牙语、中文、俄语、土耳其语、越南语、意大利语、荷兰语、加泰罗尼亚语、阿拉伯语、希腊语、波斯语、菲律宾语、乌克兰语、哈萨克语、瑞典语、日语、世界语、印地语、捷克语、波兰语等
* 注意事项:
* 1、每个模型只支持一种语言请下载对应语言的模型
* 2、如果音频中存在多种语言不推荐使用vosk可以使用Whisper
* 3、模型越大越准确
* 4、暂不支持GPU使用如需GPU使用需要自行编译https://alphacephei.com/vosk/install
*/
@Test
public void testVosk() {
try {
SpeechRecognizer recognizer = geVoskRecognizer();
//建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度
R<AsrResult> result = recognizer.recognize("src/main/resources/lff_zh.mp3");
if (result.isSuccess()){
log.info("识别成功:{}", JsonUtils.toJson(result.getData()));
}else{
log.info("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Vosk 语音识别(使用Grammar语法规则)
* 支持 20 多种语言和方言——英语、印度英语、德语、法语、西班牙语、葡萄牙语、中文、俄语、土耳其语、越南语、意大利语、荷兰语、加泰罗尼亚语、阿拉伯语、希腊语、波斯语、菲律宾语、乌克兰语、哈萨克语、瑞典语、日语、世界语、印地语、捷克语、波兰语等
* 注意事项:
* 1、每个模型只支持一种语言请下载对应语言的模型
* 2、如果音频中存在多种语言不推荐使用vosk可以使用Whisper
* 3、模型越大越准确
* 4、暂不支持GPU使用如需GPU使用需要自行编译https://alphacephei.com/vosk/install
*/
@Test
public void testVoskWithGrammar() {
try {
//获取英文模型
SpeechRecognizer recognizer = geEnVoskRecognizer();
VoskParams voskParams = new VoskParams();
//英文
voskParams.setLanguage(Language.EN);
voskParams.setGrammar("[\"one two three four five six seven eight nine zero oh\"]");
//建议上传 WAV 格式音频。其他格式将自动转换为 WAV可能影响处理速度
R<AsrResult> result = recognizer.recognize("src/main/resources/test_en.wav",voskParams);
if (result.isSuccess()){
log.info("识别成功:{}", JsonUtils.toJson(result.getData()));
}else{
log.info("识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Vosk 语音识别(使用Vosk内部识别器)
* 支持 20 多种语言和方言——英语、印度英语、德语、法语、西班牙语、葡萄牙语、中文、俄语、土耳其语、越南语、意大利语、荷兰语、加泰罗尼亚语、阿拉伯语、希腊语、波斯语、菲律宾语、乌克兰语、哈萨克语、瑞典语、日语、世界语、印地语、捷克语、波兰语等
* 注意事项:
* 1、每个模型只支持一种语言请下载对应语言的模型
* 2、如果音频中存在多种语言不推荐使用vosk可以使用Whisper
* 3、模型越大越准确
* 4、暂不支持GPU使用如需GPU使用需要自行编译https://alphacephei.com/vosk/install
*/
@Test
public void testVoskAdvanced() {
try {
VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer();
//使用vosk内部接口需要指定识别音频的采样率
Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000);
voskRecognizer.setWords(true);
voskRecognizer.setPartialWords(true);
// 使用vosk内部接口只支持wav格式
String audioPath = "src/main/resources/lff_zh.wav";
InputStream ais = AudioSystem.getAudioInputStream(new BufferedInputStream(new FileInputStream(audioPath)));
int nbytes;
byte[] b = new byte[4096];
while ((nbytes = ais.read(b)) >= 0) {
if (voskRecognizer.acceptWaveForm(b, nbytes)) {
log.info(voskRecognizer.getResult());
} else {
log.info(voskRecognizer.getPartialResult());
}
}
log.info(voskRecognizer.getFinalResult());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 实时语音识别VOSK
*/
@Test
public void testVoskRealTime() {
try {
VoskRecognizer recognizer = (VoskRecognizer)geVoskRecognizer();
//使用vosk内部接口需要指定识别音频的采样率
Recognizer voskRecognizer = recognizer.createAdvancedRecognizer(16000);
voskRecognizer.setWords(true);
voskRecognizer.setPartialWords(true);
// 设置音频格式: 16kHz, 16bit, 单声道, PCM
AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
// 获取可用的 TargetDataLine
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
if (!AudioSystem.isLineSupported(info)) {
System.out.println("麦克风不支持该格式");
System.exit(0);
}
TargetDataLine microphone = (TargetDataLine) AudioSystem.getLine(info);
microphone.open(format);
microphone.start();
int nbytes;
byte[] b = new byte[4096];
while ((nbytes = microphone.read(b,0,b.length)) >= 0) {
if (voskRecognizer.acceptWaveForm(b, nbytes)) {
log.info(voskRecognizer.getResult());
} else {
log.info(voskRecognizer.getPartialResult());
}
}
log.info(voskRecognizer.getFinalResult());
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,204 @@
package smartai.examples.speech.tts;
import ai.djl.modality.audio.Audio;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.speech.tts.config.TtsModelConfig;
import cn.smartjavaai.speech.tts.entity.SherpaTtsParams;
import cn.smartjavaai.speech.tts.enums.TtsModelEnum;
import cn.smartjavaai.speech.tts.factory.TtsModelFactory;
import cn.smartjavaai.speech.tts.model.SherpaTtsModel;
import cn.smartjavaai.speech.tts.model.TtsModel;
import cn.smartjavaai.speech.utils.AudioUtils;
import com.k2fsa.sherpa.onnx.GeneratedAudio;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 语音合成demoTTS
* sherpa-onnx模型及依赖库下载链接:
* 1、推荐依赖库官网下载https://github.com/k2-fsa/sherpa-onnx/releases
* 2、推荐TTS模型官网下载https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models
* 3、百度网盘下载https://pan.baidu.com/s/186REUf7p1z0HH9AZNnwCUg?pwd=1234 提取码: 1234
* @author dwj
* @date 2025/10/22
*/
@Slf4j
public class TtsDemo {
public static String enText = "Today as always, men fall into two groups: slaves and free men. Whoever does not have"
+ " two-thirds of his day for himself, is a slave, whatever he may be: a statesman, a"
+ " businessman, an official, or a scholar.";
public static String zhText = "有问题请拨打110或者手机18601239876。我们的价值观是真诚热爱";
String znEnText =
"中英文语音合成测试。This is generated by next generation Kaldi using Kokoro without Misaki."
+ " 你觉得中英文说的如何呢?";
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
/**
* 获取Vits中文模型
* @return
*/
public TtsModel getVitsZhModel(){
TtsModelConfig modelConfig = new TtsModelConfig();
modelConfig.setModelEnum(TtsModelEnum.SHERPA_VITS);
modelConfig.setModelPath("/Users/wenjie/Documents/develop/model/speech/tts/vits-zh-hf-keqing");
modelConfig.setModelName("keqing.onnx");
modelConfig.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
modelConfig.setDevice(device);
modelConfig.putCustomParam("debug", false);
modelConfig.putCustomParam("numThreads", 1);
return TtsModelFactory.getInstance().getModel(modelConfig);
}
/**
* 获取Matcha中文模型
* @return
*/
public TtsModel getMatchaZhModel(){
TtsModelConfig modelConfig = new TtsModelConfig();
modelConfig.setModelEnum(TtsModelEnum.SHERPA_MATCHA);
modelConfig.setModelPath("/Users/wenjie/Documents/develop/model/speech/tts/matcha-icefall-zh-baker");
modelConfig.setModelName("model-steps-3.onnx");
modelConfig.putCustomParam("vocoder", "/Users/wenjie/Documents/develop/model/speech/tts/matcha-icefall-zh-baker/vocos-22khz-univ.onnx");
modelConfig.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
modelConfig.setDevice(device);
modelConfig.putCustomParam("debug", false);
modelConfig.putCustomParam("numThreads", 1);
return TtsModelFactory.getInstance().getModel(modelConfig);
}
/**
* 获取Kokoro多语言模型
* @return
*/
public TtsModel getKokoroZhEnModel(){
TtsModelConfig modelConfig = new TtsModelConfig();
modelConfig.setModelEnum(TtsModelEnum.SHERPA_KOKORO);
modelConfig.setModelPath("/Users/wenjie/Documents/develop/model/speech/tts/kokoro-multi-lang-v1_0");
modelConfig.setModelName("model.onnx");
modelConfig.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
modelConfig.setDevice(device);
modelConfig.putCustomParam("debug", false);
modelConfig.putCustomParam("numThreads", 2);
return TtsModelFactory.getInstance().getModel(modelConfig);
}
/**
* 获取Matcha模型英文
* @return
*/
public TtsModel getMatchaEnModel(){
TtsModelConfig modelConfig = new TtsModelConfig();
modelConfig.setModelEnum(TtsModelEnum.SHERPA_MATCHA);
modelConfig.setModelPath("/Users/wenjie/Documents/develop/model/speech/tts/matcha-icefall-en_US-ljspeech");
modelConfig.setModelName("model-steps-3.onnx");
modelConfig.setLibPath(Paths.get("/Users/wenjie/smartjavaai_cache/sherpa-onnx-v1.12.14-osx-arm64-jni/lib"));
modelConfig.putCustomParam("vocoder", "/Users/wenjie/Documents/develop/model/speech/tts/matcha-icefall-zh-baker/vocos-22khz-univ.onnx");
modelConfig.setDevice(device);
modelConfig.putCustomParam("debug", false);
modelConfig.putCustomParam("numThreads", 2);
return TtsModelFactory.getInstance().getModel(modelConfig);
}
/**
* 语音合成Vits中文
*/
@Test
public void testVitsZhTts() throws IOException {
TtsModel ttsModel = getVitsZhModel();
SherpaTtsParams params = new SherpaTtsParams();
//语速
params.setSpeed(1f);
//说话人ID
params.setSpeakerId(100);
//生成音频到本地路径
ttsModel.generate(zhText, params, "/Users/wenjie/Downloads/tts-vits-zh.wav");
}
/**
* 语音合成Matcha中文
*/
@Test
public void testMatchaZhTts() throws IOException {
TtsModel ttsModel = getMatchaZhModel();
SherpaTtsParams params = new SherpaTtsParams();
//语速
params.setSpeed(1f);
//说话人ID
params.setSpeakerId(0);
// ttsModel.generate(zhText, params,"/Users/wenjie/Downloads/tts-matcha-zh.wav");
R<Audio> result = ttsModel.generate(znEnText, params);
if (result.isSuccess()){
Audio audio = result.getData();
AudioUtils.saveToWav(audio,"/Users/wenjie/Downloads/tts-matcha-zh.wav");
}else{
System.out.println(result.getMessage());
}
}
/**
* 语音合成Kokoro中英
*/
@Test
public void testKokoroZhEnTts() throws IOException {
TtsModel ttsModel = getKokoroZhEnModel();
SherpaTtsParams params = new SherpaTtsParams();
//语速
params.setSpeed(1f);
//说话人ID
params.setSpeakerId(3);
// ttsModel.generate(znEnText, params, "/Users/wenjie/Downloads/tts-kokoro-zh-en.wav");
R<Audio> result = ttsModel.generate(znEnText, params);
if (result.isSuccess()){
Audio audio = result.getData();
AudioUtils.saveToWav(audio,"/Users/wenjie/Downloads/tts-kokoro-zh-en.wav");
}else{
System.out.println(result.getMessage());
}
}
/**
* 语音合成Matcha英文
*/
@Test
public void testMatchaEnTts() throws IOException {
TtsModel ttsModel = getMatchaEnModel();
SherpaTtsParams params = new SherpaTtsParams();
//语速
params.setSpeed(1f);
//说话人ID
params.setSpeakerId(0);
// ttsModel.generate(enText, params, "/Users/wenjie/Downloads/tts-kitten-en.wav");
R<Audio> result = ttsModel.generate(enText, params);
if (result.isSuccess()){
Audio audio = result.getData();
AudioUtils.saveToWav(audio,"/Users/wenjie/Downloads/tts-kitten-en.wav");
}else{
System.out.println(result.getMessage());
}
}
}

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: smartai.examples.face.SeetaFace6LinuxDemo

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -12,7 +12,7 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.22</smartjavaai.version>
<smartjavaai.version>1.0.27</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.nlp.translation.TranslationDemo</exec.mainClass>
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-bom</artifactId>
<artifactId>bom</artifactId>
<version>${smartjavaai.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
@@ -55,11 +55,7 @@
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -73,11 +69,7 @@
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
@@ -94,7 +86,7 @@
<!--翻译模块-->
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-translate</artifactId>
<artifactId>translate</artifactId>
</dependency>
@@ -128,6 +120,14 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
@@ -139,15 +139,6 @@
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
@@ -179,16 +170,21 @@
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>aliyunmaven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>

View File

@@ -1,6 +1,7 @@
package smartai.examples.nlp.translation;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.translation.config.TranslationModelConfig;
@@ -11,31 +12,79 @@ import cn.smartjavaai.translation.factory.TranslationModelFactory;
import cn.smartjavaai.translation.model.TranslationModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
/**
* 翻译Demo
* 支持 Meta AI 开源的 NLLB-200 模型,实现 200 多种语言之间的高质量互译。
* NLLB-200官网地址https://github.com/facebookresearch/fairseq/tree/nllb
* 机器翻译Demo
* 模型下载地址https://pan.baidu.com/s/1wf7btnb4cyBFv7DB7baHnw?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class TranslationDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 翻译
* 获取模型(NLLB)
* @return
*/
public TranslationModel getNllbModel() {
TranslationModelConfig config = new TranslationModelConfig();
//指定翻译模型NLLB,切换模型需同时修改modelEnum及modelPath
config.setModelEnum(TranslationModeEnum.NLLB_MODEL);
//指定模型路径,需将模型路径修改为本地的模型路径
config.setModelPath("/Users/xxx/Documents/develop/model/trans/traced_translation_cpu.pt");
config.setDevice(DeviceEnum.CPU);
return TranslationModelFactory.getInstance().getModel(config);
}
/**
* 获取中文模型(OPUS 中文->英文)
* @return
*/
public TranslationModel getOPUSModelZH_EN() {
TranslationModelConfig config = new TranslationModelConfig();
//指定翻译模型,切换模型需同时修改modelEnum及modelPath
config.setModelEnum(TranslationModeEnum.OPUS_MT_ZH_EN);
//指定模型路径,需将模型路径修改为本地的模型路径
config.setModelPath("/Users/wenjie/Documents/develop/model/trans/opus-mt-zh-en/traced_translation.pt");
config.setDevice(DeviceEnum.CPU);
return TranslationModelFactory.getInstance().getModel(config);
}
/**
* 获取中文模型(OPUS 英文->中文)
* @return
*/
public TranslationModel getOPUSModelEN_ZH() {
TranslationModelConfig config = new TranslationModelConfig();
//指定翻译模型,切换模型需同时修改modelEnum及modelPath
config.setModelEnum(TranslationModeEnum.OPUS_MT_EN_ZH);
//指定模型路径,需将模型路径修改为本地的模型路径
config.setModelPath("/Users/wenjie/Documents/develop/model/trans/opus-mt-en-zh/traced_translation.pt");
config.setDevice(DeviceEnum.CPU);
return TranslationModelFactory.getInstance().getModel(config);
}
/**
* 翻译(nllb模型)
*/
@Test
public void translate() {
public void nllbTranslate() {
try {
TranslationModelConfig config = new TranslationModelConfig();
//指定翻译模型NLLB
config.setModelEnum(TranslationModeEnum.NLLB_MODEL);
//指定模型路径,需将模型路径修改为本地的模型路径
config.setModelPath("/Users/xxx/Documents/develop/model/trans/traced_translation_cpu.pt");
TranslationModel translationModel = TranslationModelFactory.getInstance().getModel(config);
TranslationModel translationModel = getNllbModel();
//翻译参数
TranslateParam translateParam = new TranslateParam();
//输入文字
@@ -64,28 +113,16 @@ public class TranslationDemo {
}
/**
* GPU 翻译
* 翻译(opus 中文->英文)
*/
@Test
public void translateGpu() {
public void opusTranslate1() {
try {
TranslationModelConfig config = new TranslationModelConfig();
//指定翻译模型NLLB
config.setModelEnum(TranslationModeEnum.NLLB_MODEL);
//指定设备GPU
config.setDevice(DeviceEnum.GPU);
//指定模型路径,需将模型路径修改为本地的 GPU 模型路径
config.setModelPath("/Users/xxx/Documents/develop/model/trans/traced_translation_gpu.pt");
//获取翻译模型
TranslationModel translationModel = TranslationModelFactory.getInstance().getModel(config);
TranslationModel translationModel = getOPUSModelZH_EN();
//翻译参数
TranslateParam translateParam = new TranslateParam();
//输入文字
translateParam.setInput("你好,欢迎使用SmartJavaAI");
//源语言:中文
translateParam.setSourceLanguage(LanguageCode.ZHO_HANS);
//目标语言:韩语
translateParam.setTargetLanguage(LanguageCode.ENG_LATN);
translateParam.setInput("SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 JAVA AI算法工具包");
R<String> result = translationModel.translate(translateParam);
if(result.isSuccess()){
log.info("翻译结果:{}", result.getData());
@@ -97,4 +134,28 @@ public class TranslationDemo {
}
}
/**
* 翻译(opus 英文->中文)
*/
@Test
public void opusTranslate2() {
try {
TranslationModel translationModel = getOPUSModelEN_ZH();
//翻译参数
TranslateParam translateParam = new TranslateParam();
//输入文字
translateParam.setInput("You don't have to be machine learning expert to get started");
R<String> result = translationModel.translate(translateParam);
if(result.isSuccess()){
log.info("翻译结果:{}", result.getData());
}else{
log.error("翻译失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,101 @@
# 计算机视觉示例
本项目包含了多个计算机视觉相关的示例代码,展示了如何使用 SmartJavaAI SDK 进行各种视觉任务。
## 📁 项目结构
```
vision-example/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── smartai/examples/vision/
│ │ │ ├── ActionRecognizeDemo.java # 动作识别示例
│ │ │ ├── ClipDemo.java # CLIP模型示例图文匹配
│ │ │ ├── ClsDemo.java # 图像分类示例
│ │ │ ├── InstanceSegDemo.java # 实例分割示例
│ │ │ ├── ObbDetDemo.java # 旋转框检测示例
│ │ │ ├── ObjectDetectionDemo.java # 目标检测示例
│ │ │ ├── PersonDetectDemo.java # 行人检测示例
│ │ │ ├── PoseDetDemo.java # 姿态检测示例
│ │ │ ├── SemSegDemo.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]
- **功能**:展示了如何使用分类模型对图片进行分类
- **特点**:支持多种分类模型和类别
---
## ⚙️ 配置要求
- **运行环境**
- JDK 1.8 或更高版本
- IntelliJ IDEA 推荐作为开发 IDE
- **依赖库**
- OpenCV、DJL、SmartJavaAI SDK
- **模型路径**
- 所有模型需下载并配置正确的路径(参考各 demo 注释中的链接)
---
## 🚀 快速开始
## 运行方式
如果你只想运行某个示例,请按以下方式操作:
1. 打开 IDEA或你喜欢的 IDE
2. 选择 **“Open”**,然后仅导入 `examples` 目录下对应的示例项目,例如:
```
examples/vison-example
```
3. IDEA 会自动识别并加载依赖。若首次导入,请等待 Maven 下载依赖完成。
4. 请从我们提供的 百度网盘 中下载模型及其附带文件,并在示例代码中将模型路径修改为您本地的实际路径。
5. 可通过查看每个 Java 文件顶部的注释了解对应功能,或参考 README 文件中对各 Java 文件功能的说明,运行相应的测试方法进行体验。
---
## 📄 文档
有关完整使用说明,请查阅 SmartJavaAI 官方文档:
[http://doc.smartjavaai.cn](http://doc.smartjavaai.cn)
---

View File

@@ -12,9 +12,9 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.0.22</smartjavaai.version>
<smartjavaai.version>1.0.27</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.objectdetection.ObjectDetection</exec.mainClass>
<exec.mainClass>smartai.examples.vision.ObjectDetectionDemo</exec.mainClass>
<javacv.version>1.5.10</javacv.version>
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-bom</artifactId>
<artifactId>bom</artifactId>
<version>${smartjavaai.version}</version>
<type>pom</type>
<!-- 注意这里是import -->
@@ -55,11 +55,7 @@
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -73,11 +69,6 @@
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
@@ -94,7 +85,7 @@
<!--目标检测模块-->
<dependency>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-objectdetection</artifactId>
<artifactId>vision</artifactId>
</dependency>
@@ -142,6 +133,22 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ai.djl.tensorflow</groupId>
<artifactId>tensorflow-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier>
<scope>runtime</scope>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-native-mkl</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier>
<scope>runtime</scope>
<version>1.9.1</version>
</dependency>
<!-- linux x86 平台 (保留对应平台的配置,可以减小包大小)-->
@@ -172,6 +179,7 @@
<classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency>
<!--PyTorch离线平台依赖-->
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId>
@@ -180,6 +188,29 @@
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ai.djl.tensorflow</groupId>
<artifactId>tensorflow-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<scope>runtime</scope>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-native-mkl</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<scope>runtime</scope>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.5.1</version>
<scope>runtime</scope>
</dependency>
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
@@ -217,41 +248,26 @@
<scope>runtime</scope>
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
<groupId>ai.djl.tensorflow</groupId>
<artifactId>tensorflow-native-cpu</artifactId>
<classifier>osx-aarch64</classifier>
<version>2.16.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
<groupId>ai.djl.tensorflow</groupId>
<artifactId>tensorflow-native-cpu</artifactId>
<classifier>osx-aarch64</classifier>
<version>2.16.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-arm64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu-precxx11</artifactId>
<classifier>${djl.platform.linux-aarch64}</classifier>
<version>2.5.1</version>
<groupId>ai.djl.mxnet</groupId>
<artifactId>mxnet-native-mkl</artifactId>
<classifier>osx-x86_64</classifier>
<version>1.9.1</version>
<scope>runtime</scope>
</dependency>
@@ -285,23 +301,23 @@
</build>
<repositories>
<!-- <repository>-->
<!-- <id>aliyunmaven</id>-->
<!-- <name>阿里云公共仓库</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<repository>
<id>aliyunmaven</id>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
</project>

View File

@@ -0,0 +1,94 @@
package smartai.examples.vision;
import ai.djl.modality.Classifications;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.action.config.ActionRecModelConfig;
import cn.smartjavaai.action.enums.ActionRecModelEnum;
import cn.smartjavaai.action.model.ActionRecModel;
import cn.smartjavaai.action.model.ActionRecModelFactory;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
/**
* 动作识别Demo
* 模型下载地址https://pan.baidu.com/s/17doY4pgZM9EbtSIaoCWWCA?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class ActionRecognizeDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取动作识别模型
* 注意事项:
* 1、不同模型支持的动作类别不同请查看文档http://doc.smartjavaai.cn
*/
public ActionRecModel getModel(){
ActionRecModelConfig config = new ActionRecModelConfig();
//动作识别模型切换时,需要同时更新 modelEnum 和 modelPath。其中部分 modelEnum 对应多个模型文件,可通过指定 modelPath 来选择具体的模型。
config.setModelEnum(ActionRecModelEnum.INCEPTIONV3_KINETICS400_ONNX);
//模型所在路径
config.setModelPath("/Users/wenjie/Documents/develop/model/action/gluoncv-inceptionv3_kinetics400-695477a5.onnx");
config.setDevice(device);
//置信度阈值
config.setThreshold(0.5f);
//指定允许的类别
// config.setAllowedClasses(Arrays.asList("dancing_ballet"));
return ActionRecModelFactory.getInstance().getModel(config);
}
/**
* 动作识别
* 注意事项:
* 1、不同模型支持的动作类别不同请查看文档http://doc.smartjavaai.cn
* 2、图片中应该只包含单一动作人物
* 3、动作识别只做图片分类并不做人物定位
*/
@Test
public void actionRecognition(){
try {
ActionRecModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/action/dance.jpg"));
R<Classifications> result = detectorModel.detect(image);
if(result.isSuccess()){
if(CollectionUtils.isNotEmpty(result.getData().getClassNames())){
//分数最高分类
log.info("动作识别结果:{}", result.getData().best().toString());
//按分数排序前5个结果
// log.info("动作识别结果:{}", result.getData().topK(5).toString());
}else{
log.info("未识别到动作");
}
}else{
log.info("动作识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,356 @@
package smartai.examples.vision;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.clip.config.ClipModelConfig;
import cn.smartjavaai.clip.enums.ClipModelEnum;
import cn.smartjavaai.clip.model.ClipModel;
import cn.smartjavaai.clip.model.ClipModelFactory;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.SimilarityType;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.SimilarityUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* clip模型demo
* @author dwj
*/
@Slf4j
public class ClipDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
public ClipModel getModel(){
ClipModelConfig config = new ClipModelConfig();
config.setModelEnum(ClipModelEnum.OPENAI);
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/clip/openai/clip.pt");
//从jar包中加载模型
// config.setModelPath("jar://META-INF/models/clip/openai.zip");
config.setDevice(device);
return ClipModelFactory.getInstance().getModel(config);
}
/**
* 提取图片特征
*/
@Test
public void extractImageFeatures(){
try {
ClipModel model = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/clip/dog.jpg"));
//获取图片特征
R<float[]> features = model.extractImageFeatures(image);
if(features.isSuccess()){
log.info("图片特征:{}", features.getData());
}else{
log.info("图片特征获取失败:{}", features.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提取文本特征
*/
@Test
public void extractTextFeatures() {
try {
ClipModel model = getModel();
// 提取单个文本特征
String text = "a photo of a dog";
R<float[]> features = model.extractTextFeatures(text);
if(features.isSuccess()){
log.info("文本特征:{}", features.getData());
}else{
log.info("文本特征获取失败:{}", features.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 文本搜索图像(基于图像和文本直接比对)
*/
@Test
public void searchImagesByText() {
try {
ClipModel model = getModel();
String text = "a photo of a dog";
// 读取图片列表
List<File> images = ImageUtils.listImageFiles("src/main/resources/clip");
List<Float> similarities = new ArrayList<>();
// 1. 计算每张图片的相似度
for (File imageFile : images) {
Image image = SmartImageFactory.getInstance().fromFile(imageFile.toPath());
R<Float> similarity = model.compareTextAndImage(image, text);
if (similarity.isSuccess()) {
similarities.add(similarity.getData());
log.info("图片:{},相似度:{}", imageFile.getName(), similarity.getData());
} else {
log.warn("图片:{},相似度计算失败:{}", imageFile.getName(), similarity.getMessage());
}
}
if (similarities.isEmpty()) {
log.warn("没有计算到有效的相似度结果");
return;
}
// 2. 计算 Softmax 概率
double total = similarities.stream()
.mapToDouble(Math::exp)
.sum();
List<Double> probabilities = similarities.stream()
.map(v -> Math.exp(v) / total)
.collect(Collectors.toList());
// 3. 找出相似度最高的图片
int maxIndex = IntStream.range(0, similarities.size())
.boxed()
.max(Comparator.comparing(similarities::get))
.orElse(-1);
// 4. 打印结果
log.info("---- 结果统计 ----");
for (int i = 0; i < images.size(); i++) {
log.info("图片:{},相似度:{},概率:{}",
images.get(i).getName(),
similarities.get(i),
String.format("%.4f", probabilities.get(i)));
}
log.info("最匹配的图片:{},相似度:{}Softmax 概率:{}",
images.get(maxIndex).getName(),
similarities.get(maxIndex),
String.format("%.4f", probabilities.get(maxIndex)));
} catch (Exception e) {
log.error("执行 searchImagesByText 异常", e);
}
}
/**
* 文本搜索图像(基于图像和文本的特征值比对)
*/
@Test
public void searchImagesByText2() {
try {
ClipModel model = getModel();
String text = "a photo of a dog";
// 读取图片列表
List<File> images = ImageUtils.listImageFiles("src/main/resources/clip");
List<Float> similarities = new ArrayList<>();
R<float[]> textFeatures = model.extractTextFeatures(text);
float scale = 100f; // 缩放因子,越大 softmax 差异越明显
// 1. 计算每张图片的相似度
for (File imageFile : images) {
Image image = SmartImageFactory.getInstance().fromFile(imageFile.toPath());
R<float[]> imageFeatures = model.extractImageFeatures(image);
if (imageFeatures.isSuccess()) {
float similarity = SimilarityUtil.calculate(
imageFeatures.getData(),
textFeatures.getData(),
SimilarityType.COSINE,
false
);
similarities.add(similarity * scale);
} else {
log.warn("图片:{},特征提取失败:{}", imageFile.getName(), imageFeatures.getMessage());
similarities.add(Float.NEGATIVE_INFINITY); // 特征提取失败,赋极小值
}
}
if (similarities.isEmpty()) {
log.warn("没有计算到有效的相似度结果");
return;
}
// 2. 计算 Softmax 概率
double total = similarities.stream()
.mapToDouble(Math::exp)
.sum();
List<Double> probabilities = similarities.stream()
.map(v -> Math.exp(v) / total)
.collect(Collectors.toList());
// 3. 找出相似度最高的图片
int maxIndex = IntStream.range(0, similarities.size())
.boxed()
.max(Comparator.comparing(similarities::get))
.orElse(-1);
// 4. 打印结果
log.info("---- 结果统计 ----");
for (int i = 0; i < images.size(); i++) {
log.info("图片:{},相似度:{},概率:{}",
images.get(i).getName(),
similarities.get(i),
String.format("%.4f", probabilities.get(i)));
}
log.info("最匹配的图片:{},相似度:{}Softmax 概率:{}",
images.get(maxIndex).getName(),
similarities.get(maxIndex),
String.format("%.4f", probabilities.get(maxIndex)));
} catch (Exception e) {
log.error("执行 searchImagesByText 异常", e);
}
}
/**
* 图像搜索文本
*/
@Test
public void searchTextByImage() {
try {
ClipModel model = getModel();
String[] textArray = {"a diagram", "a dog", "a cat"};
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/clip/dog.jpg"));
//获取图片特征
R<float[]> features = model.extractImageFeatures(image);
List<Float> similarities = new ArrayList<>();
// 1. 计算每张图片的相似度
for (String text : textArray) {
R<Float> similarity = model.compareTextAndImage(image, text);
if (similarity.isSuccess()) {
similarities.add(similarity.getData());
log.info("文本:{},相似度:{}", text, similarity.getData());
} else {
log.warn("文本:{},相似度计算失败:{}", text, similarity.getMessage());
}
}
if (similarities.isEmpty()) {
log.warn("没有计算到有效的相似度结果");
return;
}
// 2. 计算 Softmax 概率
double total = similarities.stream()
.mapToDouble(Math::exp)
.sum();
List<Double> probabilities = similarities.stream()
.map(v -> Math.exp(v) / total)
.collect(Collectors.toList());
// 3. 找出相似度最高的图片
int maxIndex = IntStream.range(0, similarities.size())
.boxed()
.max(Comparator.comparing(similarities::get))
.orElse(-1);
// 4. 打印结果
log.info("---- 结果统计 ----");
for (int i = 0; i < textArray.length; i++) {
log.info("文本:{},相似度:{},概率:{}",
textArray[i],
similarities.get(i),
String.format("%.4f", probabilities.get(i)));
}
log.info("最匹配的文本:{},相似度:{}Softmax 概率:{}",
textArray[maxIndex],
similarities.get(maxIndex),
String.format("%.4f", probabilities.get(maxIndex)));
} catch (Exception e) {
log.error("执行 searchImagesByText 异常", e);
}
}
/**
* 以图搜图
*/
@Test
public void searchImagesByImage() {
try {
ClipModel model = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image1 = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/cat2.jpeg"));
List<Float> similarities = new ArrayList<>();
// 读取图片列表
List<File> images = ImageUtils.listImageFiles("src/main/resources/clip");
// 1. 计算每张图片的相似度
for (File imageFile : images) {
Image image = SmartImageFactory.getInstance().fromFile(imageFile.toPath());
R<Float> similarity = model.compareImage(image1, image, 100);
if (similarity.isSuccess()) {
similarities.add(similarity.getData());
log.info("图片:{},相似度:{}", imageFile.getName(), similarity.getData());
} else {
log.warn("图片:{},相似度计算失败:{}", imageFile.getName(), similarity.getMessage());
}
}
if (similarities.isEmpty()) {
log.warn("没有计算到有效的相似度结果");
return;
}
// 2. 计算 Softmax 概率
double total = similarities.stream()
.mapToDouble(Math::exp)
.sum();
List<Double> probabilities = similarities.stream()
.map(v -> Math.exp(v) / total)
.collect(Collectors.toList());
// 3. 找出相似度最高的图片
int maxIndex = IntStream.range(0, similarities.size())
.boxed()
.max(Comparator.comparing(similarities::get))
.orElse(-1);
// 4. 打印结果
log.info("---- 结果统计 ----");
for (int i = 0; i < images.size(); i++) {
log.info("图片:{},相似度:{},概率:{}",
images.get(i).getName(),
similarities.get(i),
String.format("%.4f", probabilities.get(i)));
}
log.info("最匹配的图片:{},相似度:{}Softmax 概率:{}",
images.get(maxIndex).getName(),
similarities.get(maxIndex),
String.format("%.4f", probabilities.get(maxIndex)));
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,90 @@
package smartai.examples.vision;
import ai.djl.modality.Classifications;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.cls.config.ClsModelConfig;
import cn.smartjavaai.cls.enums.ClsModelEnum;
import cn.smartjavaai.cls.model.ClsModel;
import cn.smartjavaai.cls.model.ClsModelFactory;
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.instanceseg.config.InstanceSegModelConfig;
import cn.smartjavaai.instanceseg.enums.InstanceSegModelEnum;
import cn.smartjavaai.instanceseg.model.InstanceSegModel;
import cn.smartjavaai.instanceseg.model.InstanceSegModelFactory;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
/**
* 图像分类模型demo
* @author dwj
*/
@Slf4j
public class ClsDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
public ClsModel getModel(){
ClsModelConfig config = new ClsModelConfig();
//实例分割模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(ClsModelEnum.YOLOV8);
//模型所在路径synset.txt也需要放在同目录下
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/cls/yolo11m-cls.onnx");
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("dog","car"));
//指定返回检测数量
config.setDevice(device);
//置信度阈值
config.setThreshold(0.5f);
return ClsModelFactory.getInstance().getModel(config);
}
/**
* 实例分割
*/
@Test
public void detect(){
try {
ClsModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/clip/dog.jpg"));
R<Classifications> result = detectorModel.detect(image);
if(result.isSuccess()){
if(CollectionUtils.isNotEmpty(result.getData().getClassNames())){
//分数最高分类
log.info("分类识别结果:{}", result.getData().best().toString());
//按分数排序前5个结果
// log.info("动作识别结果:{}", result.getData().topK(5).toString());
}else{
log.info("未识别到分类");
}
}else{
log.info("分类识别失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,137 @@
package smartai.examples.vision;
import ai.djl.modality.Classifications;
import ai.djl.modality.cv.Image;
import cn.smartjavaai.action.model.ActionRecModel;
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.instanceseg.config.InstanceSegModelConfig;
import cn.smartjavaai.instanceseg.enums.InstanceSegModelEnum;
import cn.smartjavaai.instanceseg.model.InstanceSegModel;
import cn.smartjavaai.instanceseg.model.InstanceSegModelFactory;
import cn.smartjavaai.objectdetection.model.person.PersonDetModel;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
/**
* 实例分割 Demo
* 模型下载地址https://pan.baidu.com/s/12nRRY9JFNDwLeg63jfBerA?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class InstanceSegDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取实例分割模型
* 注意事项:
* 1、更多模型请查看文档http://doc.smartjavaai.cn
*/
public InstanceSegModel getModel(){
InstanceSegModelConfig config = new InstanceSegModelConfig();
//实例分割模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(InstanceSegModelEnum.SEG_YOLO11N_ONNX);
//模型所在路径synset.txt也需要放在同目录下
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/instance/yolo11n-seg-onnx/yolo11n-seg.onnx");
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person","car"));
//指定返回检测数量
config.setDevice(device);
//置信度阈值
config.setThreshold(0.5f);
return InstanceSegModelFactory.getInstance().getModel(config);
}
/**
* 实例分割
*/
@Test
public void instanceSegmentation(){
try {
InstanceSegModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/dog_bike_car.jpg"));
R<DetectionResponse> result = detectorModel.detect(image);
if(result.isSuccess()){
log.info("实例分割结果:{}", JSONObject.toJSONString(result.getData()));
}else{
log.info("实例分割失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 实例分割并绘制检测结果
*/
@Test
public void instanceSegmentationAndDraw(){
try {
InstanceSegModel detectorModel = getModel();
R<DetectionResponse> result = detectorModel.detectAndDraw("src/main/resources/dog_bike_car.jpg","output/dog_bike_car_detected.png");
if(result.isSuccess()){
log.info("实例分割结果:{}", JSONObject.toJSONString(result.getData()));
}else{
log.info("实例分割失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 实例分割并绘制检测结果
*/
@Test
public void instanceSegmentationAndDraw2(){
try {
InstanceSegModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/dog_bike_car.jpg"));
//可以根据后续业务场景使用detectedImage
R<DetectionResponse> result = detectorModel.detectAndDraw(image);
if(result.isSuccess()){
log.info("实例分割结果:{}", JSONObject.toJSONString(result.getData()));
//保存图片
ImageUtils.save(result.getData().getDrawnImage(), "dog_bike_car_detected2.png", "output");
}else{
log.info("实例分割失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,131 @@
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.instanceseg.config.InstanceSegModelConfig;
import cn.smartjavaai.instanceseg.enums.InstanceSegModelEnum;
import cn.smartjavaai.instanceseg.model.InstanceSegModelFactory;
import cn.smartjavaai.obb.config.ObbDetModelConfig;
import cn.smartjavaai.obb.enums.ObbDetModelEnum;
import cn.smartjavaai.obb.model.ObbDetModel;
import cn.smartjavaai.obb.model.ObbDetModelFactory;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
/**
* obb旋转框检测demo
* 模型下载地址https://pan.baidu.com/s/1-tC0u-aha3tnMQwy8FKy1Q?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class ObbDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取旋转框检测模型
* 注意事项:
* 1、更多模型请查看文档http://doc.smartjavaai.cn
* 2、模型可检测物体请查看模型同目录文件synset.txt
*/
public ObbDetModel getModel(){
ObbDetModelConfig config = new ObbDetModelConfig();
//旋转框检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(ObbDetModelEnum.YOLOV11);
//模型所在路径synset.txt也需要放在同目录下
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/obb/yolo11n-obb.onnx");
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("plane","ship"));
//指定返回检测数量
config.setDevice(device);
//置信度阈值
config.setThreshold(0.5f);
return ObbDetModelFactory.getInstance().getModel(config);
}
/**
* 旋转框检测
*/
@Test
public void obbDet(){
try {
ObbDetModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/obb/boats.jpg"));
R<DetectionResponse> result = detectorModel.detect(image);
if(result.isSuccess()){
log.info("旋转框检测结果:{}", JSONObject.toJSONString(result.getData()));
}else{
log.info("旋转框检测失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 旋转框检测并绘制检测结果
*/
@Test
public void obbDetAndDraw(){
try {
ObbDetModel detectorModel = getModel();
R<DetectionResponse> result = detectorModel.detectAndDraw("src/main/resources/obb/boats.jpg","output/boats_detected.png");
if(result.isSuccess()){
log.info("旋转框检测结果:{}", JSONObject.toJSONString(result.getData()));
}else{
log.info("旋转框检测失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 旋转框检测并绘制检测结果
*/
@Test
public void obbDetAndDraw2(){
try {
ObbDetModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(Paths.get("src/main/resources/obb/boats.jpg"));
//可以根据后续业务场景使用detectedImage
R<DetectionResponse> result = detectorModel.detectAndDraw(image);
if(result.isSuccess()){
log.info("旋转框检测结果:{}", JSONObject.toJSONString(result.getData()));
//保存图片
ImageUtils.save(result.getData().getDrawnImage(), "output/boats_obb_detected2.png");
}else{
log.info("旋转框检测失败:{}", result.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,433 @@
package smartai.examples.vision;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.util.JsonUtils;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.UUID;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.enums.VideoSourceType;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.objectdetection.config.DetectorModelConfig;
import cn.smartjavaai.objectdetection.enums.DetectorModelEnum;
import cn.smartjavaai.objectdetection.model.DetectorModel;
import cn.smartjavaai.objectdetection.model.ObjectDetectionModelFactory;
import cn.smartjavaai.objectdetection.stream.StreamDetectionListener;
import cn.smartjavaai.objectdetection.stream.StreamDetector;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 目标检测模型demo
* 模型下载地址https://pan.baidu.com/s/10aTOLBlR6EG-sq6g0OkAWg?pwd=1234 提取码: 1234
* 文档地址http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class ObjectDetectionDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@BeforeClass
public static void beforeAll() throws IOException {
//将图片处理的底层引擎切换为 OpenCV
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
//修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取目标检测模型
* 注意事项:
* 1、更多模型请查看文档http://doc.smartjavaai.cn/objectdetect.html
*/
public DetectorModel getModel(){
DetectorModelConfig config = new DetectorModelConfig();
//目标检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(DetectorModelEnum.YOLOV12_OFFICIAL_ONNX);
//模型所在路径synset.txt也需要放在同目录下
config.setModelPath("/Users/wenjie/Documents/develop/model/vision/object/yolov12/yolov12n.onnx");
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person","car"));
//指定返回检测数量
config.setTopK(100);
config.setDevice(device);
//置信度阈值
config.setThreshold(0.5f);
return ObjectDetectionModelFactory.getInstance().getModel(config);
}
/**
* 目标检测
*/
@Test
public void objectDetection(){
try {
DetectorModel detectorModel = getModel();
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/object_detection.jpg");
DetectionResponse detectionResponse = detectorModel.detect(image);
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 目标检测并绘制检测结果
*/
@Test
public void objectDetectionAndDraw(){
try {
DetectorModel detectorModel = getModel();
detectorModel.detectAndDraw("src/main/resources/object_detection.jpg","output/object_detection_detected.png");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 目标检测并绘制检测结果,返回BufferedImage
*/
@Test
public void objectDetectionAndDraw2(){
try {
DetectorModel detectorModel = getModel();
String imagePath = "src/main/resources/object_detection.jpg";
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile(imagePath);
//可以根据后续业务场景使用detectedImage
DetectionResponse detectionResponse = detectorModel.detectAndDraw(image);
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
if(detectionResponse != null && detectionResponse.getDrawnImage() != null){
ImageUtils.save(detectionResponse.getDrawnImage(), "output/object_detection_detected2.png");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 使用自己训练的模型检测
*/
@Test
public void objectDetectionWithCustomModel(){
try {
DetectorModelConfig config = new DetectorModelConfig();
//目标检测模型切换模型需要同时修改modelEnum及modelPath
config.setModelEnum(DetectorModelEnum.YOLOV12_CUSTOM_ONNX);
//模型所在路径synset.txt也需要放在同目录下(分类文件具体请看文档http://doc.smartjavaai.cn/objectdetect.html#%E4%BD%BF%E7%94%A8%E8%87%AA%E5%B7%B1%E8%AE%AD%E7%BB%83%E7%9A%84%E6%A8%A1%E5%9E%8B%E6%A3%80%E6%B5%8B)
config.setModelPath("/Users/xxx/Documents/develop/fire_model/best.onnx");
//模型训练时图片宽度
config.putCustomParam("width", 640);//resize 宽
//模型训练时图片高度
config.putCustomParam("height", 640);// resize 高
config.putCustomParam("nmsThreshold", 0.5f);
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person"));
//指定返回检测数量
config.setTopK(100);
config.setDevice(device);
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
DetectionResponse detect = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detect));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* tensorflow2目标检测
* 注意事项:
* 1、百度网盘只提供部分模型更多tensorflow模型可以前往官网下载https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2_detection_zoo.md
*/
@Test
public void objectDetection3(){
try {
DetectorModelConfig config = new DetectorModelConfig();
//指定模型枚举可以通过modelPath指定不同tensorflow模型
config.setModelEnum(DetectorModelEnum.TENSORFLOW2_OFFICIAL);
//模型路径需解压模型压缩包可以通过modelPath指定不同tensorflow模型
config.setModelPath("/Users/wenjie/Documents/develop/model/tensorflow/ssd_mobilenet_v2_320x320_coco17_tpu-8");
// config.putCustomParam("synsetUrl", "https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/data/mscoco_label_map.pbtxt");
// config.putCustomParam("synsetPath", "/Users/wenjie/Downloads/mscoco_label_map.pbtxt.txt");
//分类文件,需下载放入模型路径下
config.putCustomParam("synsetFileName", "mscoco.pbtxt");
// 指定允许的类别
// config.setAllowedClasses(Arrays.asList("person"));
//指定返回检测数量
config.setTopK(100);
config.setDevice(device);
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
//创建Image对象可以从文件、url、InputStream创建、BufferedImage、Base64创建具体使用方法可以查看文档
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/dog_bike_car.jpg");
DetectionResponse detectionResponse = detectorModel.detect(image);
log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 视频流目标检测
*/
@Test
public void testStream(){
StreamDetector detector = new StreamDetector.Builder()
//视频源类型:支持视频流、本地摄像头、视频文件
.sourceType(VideoSourceType.STREAM)
//视频流地址支持rtsp、rtmp、http等常见视频流
.streamUrl("rtsp://username:password@ip:port/Streaming/Channels/101")
//每隔多少帧检测一次(需要根据模型检测速度决定)
.frameDetectionInterval(10)
//目标检测模型
.detectorModel(getModel())
//回调函数检测到指定目标时触发getModel中可指定模型检测的物体
.listener(new StreamDetectionListener() {
/**
* 建议把耗时操作放到新线程里执行
* @param detectionInfoList 目标信息列表
* @param image 检测到的图片
*/
@Override
public void onObjectDetected(List<DetectionInfo> detectionInfoList, Image image) {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
if (image != null){
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public void onStreamEnded() {
log.info("视频流检测结束");
}
@Override
public void onStreamDisconnected() {
log.info("视频流断开连接");
}
}).build();
detector.startDetection();
//阻塞主线程
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await(); // 一直阻塞,直到被 countDown
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 本地摄像头目标检测
*/
@Test
public void testLocalCamera(){
StreamDetector detector = new StreamDetector.Builder()
//视频源类型:支持视频流、本地摄像头、视频文件
.sourceType(VideoSourceType.CAMERA)
//摄像头序号
.cameraIndex(0)
//每隔多少帧检测一次(需要根据模型检测速度决定)
.frameDetectionInterval(5)
//目标检测模型
.detectorModel(getModel())
//回调函数检测到指定目标时触发getModel中可指定模型检测的物体
.listener(new StreamDetectionListener() {
@Override
public void onObjectDetected(List<DetectionInfo> detectionInfoList, Image image) {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
if (image != null){
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public void onStreamEnded() {
log.info("视频流检测结束");
}
@Override
public void onStreamDisconnected() {
log.info("视频流断开连接");
}
}).build();
detector.startDetection();
//阻塞主线程
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await(); // 一直阻塞,直到被 countDown
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 视频文件目标检测
*/
@Test
public void testVideoFile(){
StreamDetector detector = new StreamDetector.Builder()
//视频源类型:支持视频流、本地摄像头、视频文件
.sourceType(VideoSourceType.FILE)
//摄像头序号
.streamUrl("girl.mp4")
//每隔多少帧检测一次(需要根据模型检测速度决定)
.frameDetectionInterval(5)
//目标检测模型
.detectorModel(getModel())
//同物体重复检测时间间隔单位s
.repeatGap(5)
//回调函数检测到指定目标时触发getModel中可指定模型检测的物体
.listener(new StreamDetectionListener() {
@Override
public void onObjectDetected(List<DetectionInfo> detectionInfoList, Image image) {
log.info("时间:" + LocalDateTimeUtil.now().toString());
log.info("检测结果:{}", JsonUtils.toJson(detectionInfoList));
//绘制检测结果
ImageUtils.drawRectAndText(image, detectionInfoList);
//保存图片
ImageUtils.save(image, "test"+ UUID.fastUUID().toString() +".png","/Users/wenjie/Downloads");
if (image != null){
ImageUtils.releaseOpenCVMat(image);
}
}
@Override
public void onStreamEnded() {
log.info("视频流检测结束");
}
@Override
public void onStreamDisconnected() {
log.info("视频流断开连接");
}
}).build();
detector.startDetection();
//阻塞主线程
CountDownLatch latch = new CountDownLatch(1);
try {
latch.await(); // 一直阻塞,直到被 countDown
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* 摄像头目标检测并实时预览
* 注意事项:如果视频比较卡,可以使用更轻量的检测模型
*/
@Test
public void testDetectCamera(){
try {
DetectorModel detectorModel = getModel();
OpenCV.loadShared();
VideoCapture capture = new VideoCapture(0);
if (!capture.isOpened()) {
System.out.println("No camera detected");
return;
}
double ratio =
capture.get(Videoio.CAP_PROP_FRAME_WIDTH)
/ capture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int height = (int) (screenSize.height * 0.65f);
int width = (int) (height * ratio);
if (width > screenSize.width) {
width = screenSize.width;
}
Mat image = new Mat();
boolean captured = false;
for (int i = 0; i < 10; ++i) {
captured = capture.read(image);
if (captured) {
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException ignore) {
// ignore
}
}
if (!captured) {
JOptionPane.showConfirmDialog(null, "Failed to capture image from WebCam.");
}
ViewerFrame frame = new ViewerFrame(width, height);
SmartImageFactory factory = SmartImageFactory.getInstance();
Size size = new Size(width, height);
while (capture.isOpened()) {
if (!capture.read(image)) {
break;
}
Mat resizeImage = new Mat();
Imgproc.resize(image, resizeImage, size);
Image img = factory.fromMat(resizeImage);
DetectionResponse detectedResult = detectorModel.detect(img);
if (Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getDetectionInfoList()) || detectedResult.getDetectionInfoList().size() == 0){
log.debug("未检测到物体");
continue;
}
for(DetectionInfo detectionInfo : detectedResult.getDetectionInfoList()){
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
String text = detectionInfo.getObjectDetInfo().getClassName();
ImageUtils.drawRectAndText(img, detectionRectangle, text);
}
frame.showImage(ImageUtils.toBufferedImage(img));
}
capture.release();
System.exit(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

Some files were not shown because too many files have changed in this diff Show More