From 166f0f9d202e3da067bdb41d6cee184b4bcc727b Mon Sep 17 00:00:00 2001
From: dengwenjie <775747758@qq.com>
Date: Mon, 11 Aug 2025 10:38:50 +0800
Subject: [PATCH] =?UTF-8?q?-=20=E6=96=B0=E5=A2=9E=20=E8=AF=AD=E9=9F=B3?=
=?UTF-8?q?=E8=AF=86=E5=88=AB=E6=A8=A1=E5=9D=97=EF=BC=8C=E9=9B=86=E6=88=90?=
=?UTF-8?q?=20OpenAI=20=E5=BC=80=E6=BA=90=E7=9A=84=20Whisper=20=E5=92=8C?=
=?UTF-8?q?=20Vosk=20-=20=E4=BF=AE=E5=A4=8D=20=E8=B4=A8=E9=87=8F=E8=AF=84?=
=?UTF-8?q?=E4=BC=B0=E6=A8=A1=E5=9E=8B=E7=9A=84=20Bug=20-=20=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=20OCR=20=E6=A8=A1=E5=9D=97=20recognizeAndDraw=20?=
=?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=20Bug=20-=20=E4=BF=AE=E5=A4=8D=20?=
=?UTF-8?q?=E8=BD=A6=E7=89=8C=E8=AF=86=E5=88=AB=E5=9C=A8=E6=9C=AA=E6=A3=80?=
=?UTF-8?q?=E6=B5=8B=E5=88=B0=E8=BD=A6=E7=89=8C=E6=97=B6=E7=9A=84=E6=8A=A5?=
=?UTF-8?q?=E9=94=99=E9=97=AE=E9=A2=98=20-=20=E4=BC=98=E5=8C=96=20OCR=20?=
=?UTF-8?q?=E8=A1=A8=E6=A0=BC=E8=AF=86=E5=88=AB=E5=8A=9F=E8=83=BD=EF=BC=8C?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=BC=E5=87=BA=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 80 +++--
examples/face-example/pom.xml | 10 +-
.../face/attribute/FaceAttributeDetDemo.java | 8 +
.../face/expression/ExpressionRecDemo.java | 8 +
.../examples/face/facedet/FaceDetDemo.java | 43 +--
.../examples/face/facerec/FaceRecDemo.java | 98 +++++-
.../face/liveness/LivenessDetDemo.java | 8 +
.../face/quality/FaceQualityDetDemo.java | 9 +
examples/objectdetection-example/pom.xml | 2 +-
.../objectdetection/ObjectDetection.java | 30 +-
examples/ocr-examples/pom.xml | 2 +-
.../examples/ocr/common/OcrDetectionDemo.java | 11 +
.../ocr/common/OcrDirectionDetDemo.java | 9 +
.../examples/ocr/common/OcrRecognizeDemo.java | 74 ++++-
.../examples/ocr/plate/PlateRecDemo.java | 9 +
.../examples/ocr/table/TableRecDemo.java | 65 +++-
examples/speech-examples/.gitignore | 7 +
examples/speech-examples/README.md | 43 +++
examples/speech-examples/pom.xml | 214 ++++++++++++
.../speech/asr/SpeechRecognizeDemo.java | 311 ++++++++++++++++++
.../src/main/resources/META-INF/MANIFEST.MF | 3 +
.../src/main/resources/jfk_en.wav | Bin 0 -> 352078 bytes
.../src/main/resources/lff_zh.mp3 | Bin 0 -> 116547 bytes
.../src/main/resources/lff_zh.wav | Bin 0 -> 617784 bytes
.../src/main/resources/logback.xml | 14 +
.../src/main/resources/speech_zh.mp3 | Bin 0 -> 342195 bytes
.../src/main/resources/test_en.wav | Bin 0 -> 265914 bytes
examples/translation-example/pom.xml | 2 +-
.../nlp/translation/TranslationDemo.java | 10 +
pom.xml | 33 +-
smartjavaai-all/pom.xml | 10 +-
smartjavaai-bom/pom.xml | 9 +-
smartjavaai-common/pom.xml | 2 +-
.../cn/smartjavaai/common/config/Config.java | 1 +
.../java/cn/smartjavaai/common/entity/R.java | 2 +-
smartjavaai-face/pom.xml | 4 +-
smartjavaai-objectdetection/pom.xml | 4 +-
smartjavaai-ocr/pom.xml | 4 +-
.../ocr/model/table/TableRecognizer.java | 47 ++-
smartjavaai-speech/pom.xml | 38 ++-
.../speech/asr/config/AsrModelConfig.java | 8 +
.../speech/asr/entity/VoskParams.java | 12 +-
.../asr/factory/SpeechRecognizerFactory.java | 103 ++++++
.../speech/asr/model/VoskRecognizer.java | 113 ++++++-
.../speech/asr/model/WhisperRecognizer.java | 9 +-
smartjavaai-translate/pom.xml | 2 +-
46 files changed, 1325 insertions(+), 146 deletions(-)
create mode 100644 examples/speech-examples/.gitignore
create mode 100644 examples/speech-examples/README.md
create mode 100644 examples/speech-examples/pom.xml
create mode 100644 examples/speech-examples/src/main/java/smartai/examples/speech/asr/SpeechRecognizeDemo.java
create mode 100644 examples/speech-examples/src/main/resources/META-INF/MANIFEST.MF
create mode 100644 examples/speech-examples/src/main/resources/jfk_en.wav
create mode 100644 examples/speech-examples/src/main/resources/lff_zh.mp3
create mode 100644 examples/speech-examples/src/main/resources/lff_zh.wav
create mode 100644 examples/speech-examples/src/main/resources/logback.xml
create mode 100644 examples/speech-examples/src/main/resources/speech_zh.mp3
create mode 100644 examples/speech-examples/src/main/resources/test_en.wav
create mode 100644 smartjavaai-speech/src/main/java/cn/smartjavaai/speech/asr/factory/SpeechRecognizerFactory.java
diff --git a/README.md b/README.md
index 12a273a..5c10bd7 100644
--- a/README.md
+++ b/README.md
@@ -205,7 +205,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
-
+ |
@@ -224,6 +224,19 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
|
+
+ |
+
+ |
+
+
+ 
+
+ |
+
@@ -273,6 +286,9 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 支持中文车牌识别:单层/双层检测,颜色识别,支持12种中文车牌
- **机器翻译**
- 集成NLLB-200模型:支持200+语言互相翻译
+- **语音识别**
+ - 集成openai的whisper模型:支持100种语言
+ - 集成vosk语音识别
## 🌟 AI集成方式对比
@@ -291,13 +307,15 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
## 🛠️包含组件
-| 模块 | 介绍 |
-|-----------------------------|---------------------------|
-| smartjavaai-common | 基础通用模块,封装了公共功能,供各算法模块共享使用 |
-| smartjavaai-face | 人脸功能模块 |
-| smartjavaai-objectdetection | 目标检测模块 |
-| smartjavaai-ocr | OCR文字识别模块 |
-| smartjavaai-translate | 机器翻译模块 |
+| 模块 | 介绍 |
+|-----------------------------|------------------------------|
+| smartjavaai-common | 基础通用模块,封装了公共功能,供各算法模块共享使用 |
+| smartjavaai-bom | 依赖管理模块 |
+| smartjavaai-face | 人脸功能模块 |
+| smartjavaai-objectdetection | 目标检测模块 |
+| smartjavaai-ocr | OCR文字识别模块 |
+| smartjavaai-translate | 机器翻译模块 |
+| smartjavaai-speech | 语音功能模块,包含 ASR 和 TTS |
可以根据需求对每个模块单独引入,也可以通过引入`smartjavaai-all`方式引入所有模块。
@@ -325,7 +343,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
cn.smartjavaai
smartjavaai-all
- 1.0.22
+ 1.0.23
```
### 3、完整示例代码
@@ -540,6 +558,18 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
| ------------------------------ | ------------------------ |-----------------------------------------------|
| NLLB-200 | Meta AI 开发的一个先进的单一多语言机器翻译模型 | [Github](https://github.com/facebookresearch/fairseq/tree/nllb) |
+---
+
+#### 语音识别模型
+
+这里仅介绍模型的开源项目,每个开源项目通常包含多个具体模型,本文不逐一列出。
+
+| 模型名称 | 模型简介 | 模型官网 |
+|---------| ------------------------ |-----------------------------------------------|
+| Whisper | OpenAI 开源的通用语音识别(ASR)模型,支持多语言转写和翻译,具有较高的识别精度,尤其在嘈杂环境中表现良好,适合离线和批量音频处理。 | [Github](https://github.com/ggml-org/whisper.cpp) |
+| Vosk | 一个轻量级离线语音识别工具包,支持多种语言和平台(包括移动端与嵌入式设备),可在低资源环境中运行,适合实时语音识别场景。 | [Github](https://github.com/alphacep/vosk-api) |
+
+
---
## 🙏 鸣谢
@@ -559,9 +589,24 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
🚀 **如果这个项目对你有帮助,别忘了点个 Star ⭐!你的支持是我持续优化升级的动力!** ❤️
+## 献代码的步骤
+
+1、在Gitee或者Github/Gitcode上fork项目到自己的repo
+2、把fork过去的项目也就是你的项目clone到你的本地
+3、修改代码(记得一定要修改dev分支)
+4、commit后push到自己的库(dev分支)
+5、登录Gitee或Github/Gitcode在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
+6、等待维护者合并
## 近期更新日志
+## [v1.0.23] - 2025-08-09
+- 新增 语音识别模块,集成 OpenAI 开源的 Whisper 和 Vosk
+- 修复 质量评估模型的 Bug
+- 修复 OCR 模块 recognizeAndDraw 方法的 Bug
+- 修复 车牌识别在未检测到车牌时的报错问题
+- 优化 OCR 表格识别功能,新增导出方式
+
## [v1.0.22] - 2025-07-28
- 新增 Milvus 身份验证支持
- 集成车牌识别模型,支持车牌检测与识别
@@ -590,21 +635,4 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 人脸模块:修复人脸更新后的缓存异常问题
- 其他:优化部分功能与细节体验
-## [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文本识别:支持文字方向检测与自动校正
diff --git a/examples/face-example/pom.xml b/examples/face-example/pom.xml
index 40ed531..cd2e3c9 100644
--- a/examples/face-example/pom.xml
+++ b/examples/face-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.22
+ 1.0.23
smartai.examples.face.facedet.FaceDetDemo
@@ -95,16 +95,8 @@
cn.smartjavaai
smartjavaai-face
- 1.0.22
-
- cn.smartjavaai
- smartjavaai-all
- 1.0.22
-
-
-
ai.djl.pytorch
diff --git a/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
index 48f0c8e..cfc2c18 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/attribute/FaceAttributeDetDemo.java
@@ -1,5 +1,6 @@
package smartai.examples.face.attribute;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
@@ -16,6 +17,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;
@@ -33,6 +35,12 @@ import java.util.List;
@Slf4j
public class FaceAttributeDetDemo {
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
public FaceAttributeModel getFaceAttributeModel() {
FaceAttributeConfig config = new FaceAttributeConfig();
diff --git a/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java b/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
index a187ea9..8e351db 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/expression/ExpressionRecDemo.java
@@ -2,6 +2,7 @@ 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.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -26,6 +27,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;
@@ -54,6 +56,12 @@ public class ExpressionRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取人脸检测模型
* @return
diff --git a/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
index 2e084a0..e084c20 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/facedet/FaceDetDemo.java
@@ -2,6 +2,7 @@ package smartai.examples.face.facedet;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -19,6 +20,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;
@@ -32,6 +34,7 @@ import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
+import java.io.IOException;
import java.nio.file.Paths;
/**
@@ -47,6 +50,12 @@ public class FaceDetDemo {
public static String imgPath = "src/main/resources/iu_1.jpg";
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取人脸检测模型
@@ -55,7 +64,10 @@ public class FaceDetDemo {
*/
public FaceDetModel getFaceDetModel(){
FaceDetConfig config = new FaceDetConfig();
+ //高精度模型,速度慢
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
+ //下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/retinaface.pt");
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
return FaceDetModelFactory.getInstance().getModel(config);
@@ -70,7 +82,7 @@ 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");
return FaceDetModelFactory.getInstance().getModel(config);
}
@@ -153,28 +165,6 @@ 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 detectedResult = faceModel.detect(imgPath);
- if(detectedResult.isSuccess()){
- log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult.getData()));
- }else{
- log.info("人脸检测失败:{}", detectedResult.getMessage());
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
/**
* 人脸检测(GPU模式)
*/
@@ -182,7 +172,10 @@ public class FaceDetDemo {
public void testDetectFaceGPU(){
try {
FaceDetConfig config = new FaceDetConfig();
- config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸模型
+ //高精度模型,速度慢
+ config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
+ //下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/retinaface.pt");
config.setDevice(DeviceEnum.GPU);
FaceDetModel faceModel = FaceDetModelFactory.getInstance().getModel(config);
R detectedResult = faceModel.detect(imgPath);
@@ -223,7 +216,7 @@ public class FaceDetDemo {
@Test
public void testDetectCamera(){
try {
- FaceDetModel faceModel = getFaceDetModel();
+ FaceDetModel faceModel = getSeetaface6DetModel();
OpenCV.loadShared();
VideoCapture capture = new VideoCapture(0);
if (!capture.isOpened()) {
diff --git a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
index 3cee699..1ed3740 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/facerec/FaceRecDemo.java
@@ -1,5 +1,6 @@
package smartai.examples.face.facerec;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.entity.face.FaceSearchResult;
@@ -24,8 +25,10 @@ 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.io.IOException;
import java.util.List;
/**
@@ -42,14 +45,25 @@ public class FaceRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
- * 获取人脸检测模型
+ * 获取人脸检测模型(高精度,速度慢)
+ * 追求准确度可以使用
+ * 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
* @return
*/
- public FaceDetModel getFaceDetModel(){
+ public FaceDetModel getHighAccuracyDetModel(){
FaceDetConfig config = new FaceDetConfig();
+ //高精度模型,速度慢
config.setModelEnum(FaceDetModelEnum.RETINA_FACE);//人脸检测模型
+ //下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/retinaface.pt");
config.setConfidenceThreshold(FaceDetectConstant.DEFAULT_CONFIDENCE_THRESHOLD);//只返回相似度大于该值的人脸
config.setNmsThresh(FaceDetectConstant.NMS_THRESHOLD);//用于去除重复的人脸框,当两个框的重叠度超过该值时,只保留一个
config.setDevice(device);
@@ -57,21 +71,62 @@ public class FaceRecDemo {
}
/**
- * 获取人脸识别模型
+ * 获取人脸检测模型(高速模型,精度一般)
+ * 追求速度可以使用
+ * 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
* @return
*/
- public FaceRecModel getFaceRecModel(){
+ public FaceDetModel getHighSpeedDetModel(){
+ FaceDetConfig config = new FaceDetConfig();
+ //高速模型,速度快,精度一般
+ config.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
+ //下载模型并替换本地路径,下载地址:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/sf3.0_models");
+ config.setDevice(device);
+ return FaceDetModelFactory.getInstance().getModel(config);
+ }
+
+ /**
+ * 获取人脸识别模型(高精度,速度慢)
+ * 追求准确度可以使用
+ * 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
+ * @return
+ */
+ public FaceRecModel getHighAccuracyFaceRecModel(){
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.ELASTIC_FACE_MODEL);
+ //模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
config.setDevice(device);
//指定人脸检测模型
- config.setDetectModel(getFaceDetModel());
+ config.setDetectModel(getHighAccuracyDetModel());
+ return FaceRecModelFactory.getInstance().getModel(config);
+ }
+
+ /**
+ * 获取人脸识别模型(高速模型,精度一般)
+ * 追求速度可以使用
+ * 也可以使用其他模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
+ * @return
+ */
+ public FaceRecModel getHighSpeedFaceRecModel(){
+ FaceRecConfig config = new FaceRecConfig();
+ //高精度模型,速度慢
+ config.setModelEnum(FaceRecModelEnum.SEETA_FACE6_MODEL);
+ //模型路径,请下载模型并替换为本地路径:https://pan.baidu.com/s/10l22x5fRz_gwLr8EAHa1Jg?pwd=1234 提取码: 1234
+ config.setModelPath("/Users/xxx/Documents/develop/model/sf3.0_models");
+ //裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
+ config.setCropFace(true);
+ //开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
+ config.setAlign(false);
+ config.setDevice(device);
+ //指定人脸检测模型
+ config.setDetectModel(getHighSpeedDetModel());
return FaceRecModelFactory.getInstance().getModel(config);
}
@@ -81,14 +136,15 @@ public class FaceRecDemo {
*/
public FaceRecModel getFaceRecModelWithDbConfig(){
FaceRecConfig config = new FaceRecConfig();
+ //高精度模型,速度慢,追求速度请更换高速模型,具体其他模型参数可以查看文档:http://doc.smartjavaai.cn/face.html
config.setModelEnum(FaceRecModelEnum.ELASTIC_FACE_MODEL);//人脸检测模型
config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
- //指定人脸检测模型
- config.setDetectModel(getFaceDetModel());
+ //指定人脸检测模型,高精度模型,速度慢,追求速度请更换高速模型getHighSpeedDetModel
+ config.setDetectModel(getHighAccuracyDetModel());
config.setDevice(device);
//初始化向量数据库:Milvus数据库配置
@@ -112,13 +168,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.ELASTIC_FACE_MODEL);//人脸检测模型
+ config.setModelPath("/Users/xxx/Documents/develop/model/elasticface.pt");
//裁剪人脸:如果图片已经是裁剪过的,则请将此参数设置为false
config.setCropFace(true);
//开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
config.setAlign(true);
- //指定人脸检测模型
- config.setDetectModel(getFaceDetModel());
+ //指定人脸检测模型,高精度模型,速度慢,追求速度请更换高速模型getHighSpeedDetModel
+ config.setDetectModel(getHighAccuracyDetModel());
config.setDevice(device);
//初始化SQLite数据库
@@ -138,7 +196,8 @@ public class FaceRecDemo {
@Test
public void testExtractFeatures(){
try {
- FaceRecModel faceRecModel = getFaceRecModel();
+ //高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
+ FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
//提取图片中所有人脸特征
R faceResult = faceRecModel.extractFeatures("src/main/resources/iu_1.jpg");
if(faceResult.isSuccess()){
@@ -162,7 +221,8 @@ public class FaceRecDemo {
@Test
public void featureComparison(){
try {
- FaceRecModel faceRecModel = getFaceRecModel();
+ //高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
+ FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
//基于图像直接比对人脸特征
R similarResult = faceRecModel.featureComparison("src/main/resources/iu_1.jpg","src/main/resources/iu_2.jpg");
if(similarResult.isSuccess()){
@@ -188,7 +248,8 @@ public class FaceRecDemo {
@Test
public void featureComparison2(){
try {
- FaceRecModel faceRecModel = getFaceRecModel();
+ //高精度模型,速度慢, 追求速度请更换高速模型: getHighSpeedFaceRecModel
+ FaceRecModel faceRecModel = getHighAccuracyFaceRecModel();
//特征提取(提取分数最高人脸特征),适用于单人脸场景
R featureResult1 = faceRecModel.extractTopFaceFeature("src/main/resources/iu_1.jpg");
if(featureResult1.isSuccess()){
@@ -226,6 +287,7 @@ public class FaceRecDemo {
@Test
public void searchFace(){
try {
+ //高精度模型,速度慢, 追求速度请更换高速模型
FaceRecModel faceRecModel = getFaceRecModelWithDbConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
@@ -303,6 +365,7 @@ public class FaceRecDemo {
@Test
public void searchFace2(){
try {
+ //高精度模型,速度慢, 追求速度请更换高速模型
FaceRecModel faceRecModel = getFaceRecModelWithSQLiteConfig();
//等待加载人脸库结束
while (!faceRecModel.isLoadFaceCompleted()){
@@ -419,4 +482,7 @@ public class FaceRecDemo {
+
+
+
}
diff --git a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
index b55ea7e..6f3dfb8 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/liveness/LivenessDetDemo.java
@@ -3,6 +3,7 @@ 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.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -32,6 +33,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;
@@ -61,6 +63,12 @@ public class LivenessDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
diff --git a/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java b/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
index 714b59e..77b53a6 100644
--- a/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
+++ b/examples/face-example/src/main/java/smartai/examples/face/quality/FaceQualityDetDemo.java
@@ -1,5 +1,6 @@
package smartai.examples.face.quality;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
@@ -23,11 +24,13 @@ 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;
/**
@@ -41,6 +44,12 @@ public class FaceQualityDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
diff --git a/examples/objectdetection-example/pom.xml b/examples/objectdetection-example/pom.xml
index b879a77..79d8365 100644
--- a/examples/objectdetection-example/pom.xml
+++ b/examples/objectdetection-example/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.22
+ 1.0.23
smartai.examples.objectdetection.ObjectDetection
diff --git a/examples/objectdetection-example/src/main/java/smartai/examples/objectdetection/ObjectDetection.java b/examples/objectdetection-example/src/main/java/smartai/examples/objectdetection/ObjectDetection.java
index 14be538..20663f8 100644
--- a/examples/objectdetection-example/src/main/java/smartai/examples/objectdetection/ObjectDetection.java
+++ b/examples/objectdetection-example/src/main/java/smartai/examples/objectdetection/ObjectDetection.java
@@ -11,6 +11,7 @@ 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.config.Config;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
@@ -27,6 +28,7 @@ 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;
@@ -47,6 +49,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.stream.Collectors;
/**
* 目标检测模型demo
@@ -61,6 +64,12 @@ public class ObjectDetection {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
@@ -139,15 +148,30 @@ public class ObjectDetection {
public void objectDetectionWithOfficialModel(){
try {
DetectorModelConfig config = new DetectorModelConfig();
- config.setThreshold(0.3f);
+// config.setThreshold(0.3f);
//也支持YoloV8:YOLOV8_OFFICIAL 模型可以从文档中提供的地址下载
config.setModelEnum(DetectorModelEnum.YOLOV12_OFFICIAL);//检测模型,目前支持19种模型
// 指定模型路径,需要更改为自己的模型路径
- config.setModelPath("/Users/xxx/Documents/yolov12n.onnx");
+ config.setModelPath("/Users/wenjie/Documents/develop/face_model/yolo11n.torchscript");
config.setDevice(device);
+ config.putCustomParam("width", 640);//resize 宽
+ config.putCustomParam("height", 640);// resize 高
+ config.putCustomParam("resize", true);
+ config.putCustomParam("toTensor", true);
+ config.putCustomParam("applyRatio", true);
+ config.putCustomParam("threshold", 0.6f);
+ // for performance optimization maxBox parameter can reduce number of
+ // considered boxes from 8400
+ config.putCustomParam("maxBox", 8400);
+// config.putCustomParam("pad", 114d);
+// List mean = Arrays.asList(0.5f,0.5f,0.5f,0.5f,0.5f,0.5f);
+// String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
+// config.putCustomParam("normalize", normalize);
+// config.putCustomParam("flag", Image.Flag.COLOR);
+// config.putCustomParam("pad", 114);
//一定要将yolo官方的类别文件:synset.txt(文档中下载)放在模型同目录下,否则报错
DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config);
- DetectionResponse detect = detectorModel.detect("src/main/resources/dog_bike_car.jpg");
+ DetectionResponse detect = detectorModel.detect("src/main/resources/object_detection.jpg");
log.info("目标检测结果:{}", JSONObject.toJSONString(detect));
} catch (Exception e) {
e.printStackTrace();
diff --git a/examples/ocr-examples/pom.xml b/examples/ocr-examples/pom.xml
index f89411e..e9b4155 100644
--- a/examples/ocr-examples/pom.xml
+++ b/examples/ocr-examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.22
+ 1.0.23
smartai.examples.ocr.common.OcrRecognizeDemo
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
index e64cc30..e089bba 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDetectionDemo.java
@@ -1,6 +1,7 @@
package smartai.examples.ocr.common;
import ai.djl.modality.cv.Image;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -14,12 +15,14 @@ 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;
@@ -32,9 +35,17 @@ import java.util.List;
public class OcrDetectionDemo {
+
+
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取文本检测模型
* @return
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
index 5e93842..d3f74b0 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrDirectionDetDemo.java
@@ -1,5 +1,6 @@
package smartai.examples.ocr.common;
+import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
@@ -12,8 +13,10 @@ 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;
/**
@@ -28,6 +31,12 @@ public class OcrDirectionDetDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取方向检测模型
* @return
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
index 263d5bd..583d08e 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/common/OcrRecognizeDemo.java
@@ -1,6 +1,10 @@
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.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
@@ -18,9 +22,15 @@ 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;
/**
@@ -35,6 +45,13 @@ public class OcrRecognizeDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+ //Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取通用识别模型(不带方向矫正)
* @return
@@ -44,7 +61,7 @@ public class OcrRecognizeDemo {
//指定文本识别模型
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);
@@ -59,7 +76,7 @@ public class OcrRecognizeDemo {
//指定检测模型
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);
}
@@ -180,6 +197,59 @@ public class OcrRecognizeDemo {
}
}
+ @Test
+ public void recognizeAndDraw2(){
+ try {
+ OcrCommonRecModel recModel = getRecModel();
+ int fontSize = 18;
+ //创建保存路径
+ Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
+ Path imageOutputPath = Paths.get("output/ocr_4_recognized.jpg");
+ BufferedImage image = null;
+ image = ImageIO.read(new File(inputImagePath.toAbsolutePath().toString()));
+ BufferedImage resultImage = recModel.recognizeAndDraw(image, fontSize, new OcrRecOptions());
+ ImageUtils.saveImage(resultImage, imageOutputPath.toAbsolutePath().toString());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 文本识别并绘制结果(返回base64)
+ */
+ @Test
+ public void recognizeAndDrawToBase64(){
+ try {
+ OcrCommonRecModel recModel = getRecModel();
+ int fontSize = 18;
+ //创建保存路径
+ Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
+ byte[] imageBytes = FileUtil.readBytes(inputImagePath);
+ String base64 = recModel.recognizeAndDrawToBase64(imageBytes, fontSize, new OcrRecOptions());
+ log.info("base64:{}", base64);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 文本识别并绘制结果(返回OcrInfo,OcrInfo中包含base64)
+ */
+ @Test
+ public void recognizeAndDraw3(){
+ try {
+ OcrCommonRecModel recModel = getRecModel();
+ int fontSize = 18;
+ //创建保存路径
+ Path inputImagePath = Paths.get("src/main/resources/general_ocr_002.png");
+ byte[] imageBytes = FileUtil.readBytes(inputImagePath);
+ OcrInfo ocrInfo = recModel.recognizeAndDraw(imageBytes, fontSize, new OcrRecOptions());
+ log.info("ocrInfo:{}", JsonUtils.toJson(ocrInfo));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* 批量识别
* 注意事项:
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
index 7cb59ae..9ee74b8 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/plate/PlateRecDemo.java
@@ -1,6 +1,7 @@
package smartai.examples.ocr.plate;
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.common.utils.ImageUtils;
@@ -13,9 +14,11 @@ 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 java.io.File;
+import java.io.IOException;
import java.util.List;
/**
@@ -27,6 +30,12 @@ public class PlateRecDemo {
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
/**
* 获取车牌检测模型
* @return
diff --git a/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
index 0aba097..bdbbb2b 100644
--- a/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
+++ b/examples/ocr-examples/src/main/java/smartai/examples/ocr/table/TableRecDemo.java
@@ -2,6 +2,7 @@ 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.entity.R;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
@@ -21,17 +22,21 @@ 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
* @author dwj
* @date 2025/5/25
@@ -39,6 +44,13 @@ import java.util.List;
@Slf4j
public class TableRecDemo {
+
+ @BeforeClass
+ public static void beforeAll() throws IOException {
+ //修改缓存路径
+// Config.setCachePath("/Users/xxx/smartjavaai_cache");
+ }
+
//设备类型
public static DeviceEnum device = DeviceEnum.CPU;
@@ -51,7 +63,7 @@ public class TableRecDemo {
//指定文本识别模型
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);
@@ -66,7 +78,7 @@ public class TableRecDemo {
//指定检测模型
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.setDetModelPath("/Users/xxx/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
@@ -81,7 +93,7 @@ public class TableRecDemo {
//指定行文本方向检测模型
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);
}
@@ -95,7 +107,7 @@ public class TableRecDemo {
//指定行文本方向检测模型
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);
@@ -142,4 +154,47 @@ public class TableRecDemo {
}
}
+
+ /**
+ * 表格识别
+ * 仅支持简单表格
+ * 流程:表格结构识别 -> 文本检测 -> 文本识别 -> 合成html table
+ * 注意事项:
+ * 1、批量检测时,模型应统一放在外层 try 中使用,避免重复加载,自动释放资源更安全。
+ * 2、模型文件需要放在单独文件夹
+ */
+ @Test
+ public void recognize2(){
+ try {
+ TableStructureModel tableStructureModel = getTableStructureModel();
+ OcrCommonDetModel detModel = getDetectionModel();
+ OcrCommonRecModel recModel = getRecModel();
+ OcrDirectionModel directionModel = getDirectionModel();
+ //创建表格识别器
+ TableRecognizer tableRecognizer = TableRecognizer.builder()
+ .withStructureModel(tableStructureModel)
+ .withTextDetModel(detModel)
+// .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()));
+ R result = tableRecognizer.recognize(image);
+ if(result.isSuccess()){
+ log.info("result: {}", result.getData().getHtml());
+ //导出html内容到文件
+ Path outputPath = Paths.get("output/table_ch2_result.html");
+ FileUtil.writeUtf8String(result.getData().getHtml(), outputPath.toAbsolutePath().toString());
+ //绘制表格结构
+ BufferedImage resultImage = tableRecognizer.drawTable(result.getData(), image);
+ ImageUtils.saveImage(resultImage, "output/table_ch2_result.jpg");
+ //导出excel,如果导出失败,可能是因为表格结果识别的结果是错乱的
+ try (OutputStream out = Files.newOutputStream(Paths.get("output/table_ch2_result2.xls"))) {
+ tableRecognizer.exportExcel(result.getData().getHtml(), out);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
}
diff --git a/examples/speech-examples/.gitignore b/examples/speech-examples/.gitignore
new file mode 100644
index 0000000..93dbf83
--- /dev/null
+++ b/examples/speech-examples/.gitignore
@@ -0,0 +1,7 @@
+.idea
+.idea/
+target
+log
+*.iml
+/.settings/
+/logging.file_IS_UNDEFINED/
diff --git a/examples/speech-examples/README.md b/examples/speech-examples/README.md
new file mode 100644
index 0000000..a747d4b
--- /dev/null
+++ b/examples/speech-examples/README.md
@@ -0,0 +1,43 @@
+# OCR文字识别示例
+
+
+我来为你写一个 `speech-examples` 项目的 README.md 文件。
+
+## 🎤 语音识别示例
+
+本项目展示了如何使用 SmartJavaAI SDK 进行语音识别,支持 Whisper 和 Vosk 两种语音识别引擎。
+
+## 📁 项目结构
+
+```
+src
+├── main
+│ ├── java
+│ │ └── smartai/examples/speech/asr
+│ │ └── SpeechRecognizeDemo.java # 语音识别示例
+│ └── resources
+│ ├── logback.xml # 日志配置文件
+│ ├── speech_zh.mp3 # 中文测试音频
+│ ├── lff_zh.mp3 # 中文测试音频
+│ └── jfk_en.wav # 英文测试音频
+└── test
+```
+
+## 🚀 快速开始
+
+1. 克隆项目到本地:
+
+2. 导入项目至 IntelliJ IDEA。
+
+3. 根据需要修改模型路径(见各 demo 中注释)。
+
+4. 运行对应的 JUnit 测试类方法即可体验各项功能。
+
+---
+
+## 📄 文档
+
+有关完整使用说明,请查阅 SmartJavaAI 官方文档:
+[http://doc.smartjavaai.cn](http://doc.smartjavaai.cn)
+
+---
diff --git a/examples/speech-examples/pom.xml b/examples/speech-examples/pom.xml
new file mode 100644
index 0000000..3cf90ba
--- /dev/null
+++ b/examples/speech-examples/pom.xml
@@ -0,0 +1,214 @@
+
+
+ 4.0.0
+
+ cn.smartjavaai
+ examples-speech
+ 1.0.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+ 1.0.23
+
+ smartai.examples.speech.asr.common.OcrRecognizeDemo
+
+
+
+
+
+
+ cn.smartjavaai
+ smartjavaai-bom
+ ${smartjavaai.version}
+ pom
+
+ import
+
+
+
+
+
+
+
+ commons-cli
+ commons-cli
+ 1.9.0
+
+
+ commons-io
+ commons-io
+ 2.17.0
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.24.1
+
+
+ org.testng
+ testng
+ 7.10.2
+ test
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.30
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.83
+
+
+
+ junit
+ junit
+ 4.13.2
+
+
+
+
+
+
+ cn.smartjavaai
+ smartjavaai-speech
+
+
+ com.microsoft.onnxruntime
+ onnxruntime
+
+
+ org.openpnp
+ opencv
+
+
+ ai.djl.huggingface
+ tokenizers
+
+
+ ai.djl.ml.xgboost
+ xgboost
+
+
+
+
+
+
+ ws.schild
+ jave-all-deps
+ 3.5.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+ shade
+
+ false
+
+
+
+ ${exec.mainClass}
+
+
+
+
+
+
+
+
+
+
+
+ aliyunmaven
+ 阿里云公共仓库
+ https://maven.aliyun.com/repository/public
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/speech-examples/src/main/java/smartai/examples/speech/asr/SpeechRecognizeDemo.java b/examples/speech-examples/src/main/java/smartai/examples/speech/asr/SpeechRecognizeDemo.java
new file mode 100644
index 0000000..b2dbfb2
--- /dev/null
+++ b/examples/speech-examples/src/main/java/smartai/examples/speech/asr/SpeechRecognizeDemo.java
@@ -0,0 +1,311 @@
+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.AudioSystem;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.file.Paths;
+
+/**
+ *
+ * 语音识别demo
+ * @author dwj
+ * @date 2025/8/6
+ */
+@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 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 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 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/xxx/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/xxx/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 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 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();
+ }
+ }
+
+
+}
diff --git a/examples/speech-examples/src/main/resources/META-INF/MANIFEST.MF b/examples/speech-examples/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..91b424f
--- /dev/null
+++ b/examples/speech-examples/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: smartai.examples.face.SeetaFace6LinuxDemo
+
diff --git a/examples/speech-examples/src/main/resources/jfk_en.wav b/examples/speech-examples/src/main/resources/jfk_en.wav
new file mode 100644
index 0000000000000000000000000000000000000000..3184d372cd2f8b804d3a540c70ec50d927b335d2
GIT binary patch
literal 352078
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