From bca9462331cc4ad1a97902a893d9554cc66fa35e Mon Sep 17 00:00:00 2001
From: dengwenjie <775747758@qq.com>
Date: Mon, 9 Jun 2025 12:12:10 +0800
Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=EF=BC=9A=E4=BA=BA=E8=84=B8=E6=9F=A5=E8=AF=A2=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=20=E5=90=91=E9=87=8F=E6=95=B0=E6=8D=AE=E5=BA=93Milvus=20?=
=?UTF-8?q?=E5=92=8C=20SQLite=202=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=EF=BC=9AFaceNet=E4=BA=BA=E8=84=B8=E6=A8=A1=E5=9E=8B?=
=?UTF-8?q?=E4=B9=9F=E6=94=AF=E6=8C=81=E4=BA=BA=E8=84=B8=E6=B3=A8=E5=86=8C?=
=?UTF-8?q?=EF=BC=8C=E6=9F=A5=E8=AF=A2=E7=AD=89=E5=8A=9F=E8=83=BD=203?=
=?UTF-8?q?=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1=E5=9D=97=EF=BC=9ASeetaface?=
=?UTF-8?q?6=20=E8=87=AA=E5=8A=A8=E4=B8=8B=E8=BD=BD=E4=BA=BA=E8=84=B8?=
=?UTF-8?q?=E5=BA=93=204=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=EF=BC=9ASeetaface6=E8=A7=A3=E5=86=B3=E4=BE=9D=E8=B5=96?=
=?UTF-8?q?=E5=BA=93=E9=87=8D=E5=A4=8D=E4=B8=8B=E8=BD=BD=E9=97=AE=E9=A2=98?=
=?UTF-8?q?=205=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1=E5=9D=97=EF=BC=9A?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E6=89=8B=E5=8A=A8=E5=8A=A0=E8=BD=BD=E4=BA=BA?=
=?UTF-8?q?=E8=84=B8=E5=BA=93=206=E3=80=81=E4=BA=BA=E8=84=B8=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97=EF=BC=9A=E4=BA=BA=E8=84=B8=E8=AF=86=E5=88=AB=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A?=
=?UTF-8?q?=E5=8F=82=E6=95=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 31 +-
examples/pom.xml | 2 +-
.../examples/face/facerec/FaceNetDemo.java | 348 +++++++--
.../examples/face/facerec/SeetaFace6Demo.java | 397 ++++++----
.../examples/ocr/OcrDetectionDemo.java | 2 +-
.../examples/ocr/OcrDirectionDetDemo.java | 4 +-
.../examples/ocr/OcrRecognizeDemo.java | 32 +-
examples/src/main/resources/face/iu_1.jpg | Bin 0 -> 65536 bytes
examples/src/main/resources/face/iu_2.jpg | Bin 0 -> 58465 bytes
examples/src/main/resources/face/iu_3.jpg | Bin 0 -> 126348 bytes
pom.xml | 3 +-
smartjavaai-all/pom.xml | 4 +-
smartjavaai-bom/pom.xml | 4 +-
smartjavaai-common/pom.xml | 3 +-
.../common/entity/DetectionResponse.java | 1 +
.../smartjavaai/common/entity/FaceInfo.java | 10 +
.../common/entity/FaceSearchResult.java | 37 +
.../java/cn/smartjavaai/common/entity/R.java | 84 ++
smartjavaai-face/pom.xml | 10 +-
.../face/config/FaceExtractConfig.java | 2 +-
.../face/config/FaceModelConfig.java | 15 +-
.../face/constant/FaceDetectConstant.java | 5 +
.../java/cn/smartjavaai/face/dao/FaceDao.java | 194 +++--
.../face/entity/FaceRegisterInfo.java | 30 +
.../face/entity/FaceSearchParams.java | 43 ++
.../smartjavaai/face/enums/FaceModelEnum.java | 2 +-
.../cn/smartjavaai/face/enums/IdStrategy.java | 13 +
.../face/enums/SimilarityType.java | 13 +
.../smartjavaai/face/enums/VectorDBType.java | 27 +
.../factory/FaceAttributeModelFactory.java | 2 +-
.../face/factory/FaceModelFactory.java | 4 +-
.../face/factory/LivenessModelFactory.java | 2 +-
.../Seetaface6FaceAttributeModel.java | 2 +-
.../face/model/facerec/AbstractFaceModel.java | 159 ----
.../face/model/facerec/FaceModel.java | 293 +++++--
.../face/model/facerec/FaceNetModel.java | 722 ++++++++++++++++++
.../model/facerec/FeatureExtractionModel.java | 367 ---------
.../face/model/facerec/RetinaFaceModel.java | 4 +-
.../face/model/facerec/SeetaFace6Model.java | 688 ++++++++++-------
.../UltraLightFastGenericFaceModel.java | 2 +-
.../liveness/Seetaface6LivenessModel.java | 4 +-
.../face/seetaface/NativeLoader.java | 23 +-
.../face/sqllite/SqliteHelper.java | 422 +++++-----
.../translator/FaceFeatureTranslator.java | 4 +-
.../cn/smartjavaai/face/utils/FaceUtils.java | 75 +-
.../face/utils/SimilarityUtil.java | 129 ++++
.../smartjavaai/face/utils/VectorUtils.java | 42 +
.../face/vector/config/MilvusConfig.java | 82 ++
.../face/vector/config/SQLiteConfig.java | 30 +
.../face/vector/config/VectorDBConfig.java | 20 +
.../vector/constant/VectorDBConstants.java | 48 ++
.../face/vector/core/MilvusClient.java | 594 ++++++++++++++
.../face/vector/core/SQLiteClient.java | 266 +++++++
.../face/vector/core/VectorDBClient.java | 114 +++
.../face/vector/core/VectorDBFactory.java | 52 ++
.../face/vector/entity/FaceVector.java | 68 ++
.../vector/exception/VectorDBException.java | 26 +
.../src/main/resources/db/schema.sql | 14 +
smartjavaai-objectdetection/pom.xml | 4 +-
.../objectdetection/model/DetectorModel.java | 4 +-
.../model/ObjectDetectionModelFactory.java | 2 +-
smartjavaai-ocr/pom.xml | 4 +-
.../ocr/factory/OcrModelFactory.java | 2 +-
.../model/common/detect/PpOCRV5DetModel.java | 6 +-
.../common/direction/PPOCRMobileV2Model.java | 6 +-
.../common/recognize/PpOCRV5RecModel.java | 4 +-
.../cn/smartjavaai/ocr/utils/OcrUtils.java | 2 +-
67 files changed, 4170 insertions(+), 1438 deletions(-)
create mode 100644 examples/src/main/resources/face/iu_1.jpg
create mode 100644 examples/src/main/resources/face/iu_2.jpg
create mode 100644 examples/src/main/resources/face/iu_3.jpg
create mode 100644 smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/FaceSearchResult.java
create mode 100644 smartjavaai-common/src/main/java/cn/smartjavaai/common/entity/R.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceRegisterInfo.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceSearchParams.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/enums/IdStrategy.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/enums/SimilarityType.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/enums/VectorDBType.java
delete mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/AbstractFaceModel.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceNetModel.java
delete mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FeatureExtractionModel.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/SimilarityUtil.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/VectorUtils.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/config/MilvusConfig.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/config/SQLiteConfig.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/config/VectorDBConfig.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/constant/VectorDBConstants.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/MilvusClient.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/SQLiteClient.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBClient.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/core/VectorDBFactory.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/entity/FaceVector.java
create mode 100644 smartjavaai-face/src/main/java/cn/smartjavaai/face/vector/exception/VectorDBException.java
create mode 100644 smartjavaai-face/src/main/resources/db/schema.sql
diff --git a/README.md b/README.md
index 081e3d0..16299ec 100644
--- a/README.md
+++ b/README.md
@@ -185,7 +185,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
### ✅ 已实现功能
- **人脸识别**
- - 人脸检测、人脸识别、人脸比对1:1、人脸比对1:N、人脸库注册、人脸库、人脸库删除
+ - 人脸检测、人脸识别、人脸比对1:1、人脸比对1:N(支持向量数据库milvus/sqlite)、人脸库注册、人脸库删除
- 5点人脸关键点定位
- 人脸属性检测(性别、年龄、口罩、眼睛状态、脸部姿态)
- 人脸活体检测:图片、视频活体检测
@@ -263,7 +263,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
cn.smartjavaai
smartjavaai-all
- 1.0.15
+ 1.0.16
```
### 3、完整示例代码
@@ -298,6 +298,15 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
## 近期更新日志
+## [v1.0.15] - 2025-06-09
+- 人脸模块:人脸查询支持 Milvus 和 SQLite
+- 人脸模块:FaceNet人脸模型也支持人脸注册,查询等功能
+- 人脸模块:Seetaface6 自动下载人脸库
+- 人脸模块:Seetaface6解决依赖库重复下载问题
+- 人脸模块:支持手动加载人脸库
+- 人脸模块:人脸识别相关功能支持更多参数
+
+
## [v1.0.15] - 2025-05-17
- 新增OCR文字识别模块:支持最新 PP-OCRv5
- OCR文本识别:支持文字方向检测与自动校正
@@ -316,22 +325,4 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的
- 优化检测返回与包结构
- 新增 dependencyManagement 统一依赖版本管理
-## [v1.0.11] - 2025-04-28
-- FaceNet 特征提取新增人脸对齐
-- 人脸检测新5点人脸关键点定位
-- 特征提取接口支持多人脸和最佳人脸提取
-- 修复人脸框边界精度问题
-- 更新 Maven 发布的 groupId
-## [v1.0.10] - 2025-04-19
-- 兼容 SeetaFace6 在 Linux 系统下的运行
-- 新增全局缓存路径设置功能
-- 优化若干功能细节,提升稳定性与性能
-## [v1.0.8] - 2025-04-13
-- 新增目标检测功能
-- 模型调用接口统一封装
-- 修复若干已知问题
-- 支持自定义选择使用 GPU 或 CPU 运算
-- 人脸识别模块新增多种接口,功能更加完善
-
-
diff --git a/examples/pom.xml b/examples/pom.xml
index 38216a9..4865b50 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -12,7 +12,7 @@
11
11
UTF-8
- 1.0.15
+ 1.0.16
smartai.examples.face.facerec.RetinaFaceDemo
diff --git a/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java b/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java
index eaaec70..d56ce63 100644
--- a/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java
+++ b/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java
@@ -1,12 +1,23 @@
package smartai.examples.face.facerec;
+import cn.smartjavaai.common.entity.DetectionResponse;
+import cn.smartjavaai.common.entity.FaceSearchResult;
+import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.face.config.FaceExtractConfig;
import cn.smartjavaai.face.config.FaceModelConfig;
+import cn.smartjavaai.face.entity.FaceRegisterInfo;
import cn.smartjavaai.face.entity.FaceResult;
+import cn.smartjavaai.face.entity.FaceSearchParams;
import cn.smartjavaai.face.enums.FaceModelEnum;
+import cn.smartjavaai.face.enums.IdStrategy;
+import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.face.factory.FaceModelFactory;
import cn.smartjavaai.face.model.facerec.FaceModel;
+import cn.smartjavaai.face.vector.config.MilvusConfig;
+import cn.smartjavaai.face.vector.config.SQLiteConfig;
+import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
+import io.milvus.param.MetricType;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
@@ -21,7 +32,8 @@ import java.util.List;
/**
* FaceNet人脸算法模型demo
- * 支持功能:人脸特征提取、人脸比对(1:1)
+ * 支持系统:windows 64位,linux 64位,macOS M系列芯片
+ * 支持功能:人脸特征提取、人脸比对(1:1)、人脸比对(1:N)、人脸注册
* @author dwj
* @date 2025/4/11
*/
@@ -29,106 +41,90 @@ import java.util.List;
public class FaceNetDemo {
/**
- * 提取人脸特征(支持多人脸)
- * 默认使用检测模型:FACENET_FEATURE_EXTRACTION
- * 自动裁剪人脸 + 人脸对齐
+ * 提取人脸特征(多人脸场景)
+ * 默认使用检测模型:ULTRA_LIGHT_FAST_GENERIC_FACE
+ * 自动裁剪人脸(处理耗时略有增加)
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
*/
@Test
public void testExtractFeatures(){
try {
//人脸特征提取模型
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- List faceResult = faceModel.extractFeatures("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
+ log.info("人脸特征提取模型加载成功");
+ //提取图片中所有人脸特征
+ R faceResult = faceModel.extractFeatures("src/main/resources/face/iu_1.jpg");
+ if(faceResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(faceResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", faceResult.getMessage());
+ }
}catch (Exception e){
e.printStackTrace();
}
}
/**
- * 提取人脸特征(支持多人脸,自定义配置)
- * 自动裁剪人脸 + 人脸对齐
+ * 提取人脸特征(自定义配置)
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
*/
@Test
public void testExtractFeaturesWithCustomConfig(){
try {
//人脸模型参数
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
//人脸特征提取参数
FaceExtractConfig extractConfig = new FaceExtractConfig();
- //人脸检测模型配置
- extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE)));
+ //当关闭人脸裁剪时,程序将跳过人脸检测与裁剪流程,直接进行特征提取,适用于输入已为标准人脸区域的图像,有助于提升处理效率。
+ extractConfig.setCropFace(true);
+ //开启人脸对齐:适用于人脸不正的场景,开启将提升人脸特征准确度,关闭可以提升性能
+ extractConfig.setAlign(true);
+ //人脸检测模型配置,指定人脸检测模型:ULTRA_LIGHT_FAST_GENERIC_FACE
+ FaceModelConfig detectModelConfig = new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE);
+ //设置人脸检测置信度阈值
+ detectModelConfig.setConfidenceThreshold(0.98);
+ extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(detectModelConfig));
config.setExtractConfig(extractConfig);
- //人脸特征提取模型
+ //获取人脸模型
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- List faceResult = faceModel.extractFeatures("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
- }catch (Exception e){
- e.printStackTrace();
- }
- }
-
- /**
- * 提取人脸特征(分数最高人脸)
- * 默认使用检测模型:FACENET_FEATURE_EXTRACTION
- * 自动裁剪人脸 + 人脸对齐
- */
- @Test
- public void testExtractTopFaceFeature(){
- try {
- //人脸特征提取模型
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- float[] faceResult = faceModel.extractTopFaceFeature("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
- }catch (Exception e){
- e.printStackTrace();
- }
- }
-
- /**
- * 提取人脸特征(分数最高人脸,自定义配置)
- * 自动裁剪人脸 + 人脸对齐
- */
- @Test
- public void testExtractTopFaceFeatureWithCustomConfig(){
- try {
- //人脸模型参数
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);
- //人脸特征提取参数
- FaceExtractConfig extractConfig = new FaceExtractConfig();
- //人脸检测模型配置
- extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE)));
- config.setExtractConfig(extractConfig);
- //人脸特征提取模型
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- float[] faceResult = faceModel.extractTopFaceFeature("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult.getMessage());
+ }
}catch (Exception e){
e.printStackTrace();
}
}
+
/**
- * 人脸比对(1:1)-在线模型
- * 图片参数:图片路径
+ * 人脸比对1:1(基于图像直接比对)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。(接口内自动完成)
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
* @throws Exception
*/
@Test
public void featureComparison(){
try {
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);//人脸模型
- config.setModelPath("/Users/wenjie/Documents/develop/face_model/face_feature.pt");
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //自动裁剪人脸并比对人脸特征
- float similar = faceModel.featureComparison("src/main/resources/kana1.jpg","src/main/resources/kana2.jpg");
+ //基于图像直接比对人脸特征
+ float similar = faceModel.featureComparison("src/main/resources/face/iu_1.jpg","src/main/resources/face/iu_2.jpg");
log.info("相似度:{}", similar);
}
catch (Exception e){
@@ -137,27 +133,239 @@ public class FaceNetDemo {
}
/**
- * 人脸比对(1:1)- 使用离线模型
- * 图片参数:图片路径
+ * 人脸比对1:1(基于特征值比对)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
+ */
+ @Test
+ public void featureComparison2(){
+ try {
+ FaceModelConfig config = new FaceModelConfig();
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult1 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult1.isSuccess()){
+ log.info("图片1人脸特征提取成功:{}", JSONObject.toJSONString(featureResult1.getData()));
+ }else{
+ log.info("图片1人脸特征提取失败:{}", featureResult1.getMessage());
+ return;
+ }
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_2.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("图片2人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("图片2人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ //计算相似度
+ float similar = faceModel.calculSimilar(featureResult1.getData(), featureResult2.getData());
+ log.info("相似度:{}", similar);
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * 人脸注册 + 人脸更新 + 人脸查询 + 人脸删除(使用向量数据库Milvus)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 注册人脸
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
+ */
+ @Test
+ public void searchFace(){
+ try {
+ FaceModelConfig config = new FaceModelConfig();
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
+ //初始化向量数据库:Milvus数据库配置
+ MilvusConfig vectorDBConfig = new MilvusConfig();
+ vectorDBConfig.setHost("127.0.0.1");
+ vectorDBConfig.setPort(19530);
+ //vectorDBConfig.setCollectionName("face5");
+ //ID策略:自动生成
+ vectorDBConfig.setIdStrategy(IdStrategy.AUTO);
+ //索引类型:内积 (Inner Product) 不建议修改
+ //vectorDBConfig.setMetricType(MetricType.IP);
+ config.setVectorDBConfig(vectorDBConfig);
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //等待加载人脸库结束
+ while (!faceModel.isLoadFaceCompleted()){
+ Thread.sleep(100);
+ }
+ log.info("====================人脸注册==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult.getMessage());
+ return;
+ }
+ //人脸注册信息
+ FaceRegisterInfo faceRegisterInfo = new FaceRegisterInfo();
+ //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
+ JSONObject metadataJson = new JSONObject();
+ metadataJson.put("name", "iu");
+ metadataJson.put("age", "25");
+ faceRegisterInfo.setMetadata(metadataJson.toJSONString());
+ //人脸注册,返回人脸库ID
+ R registerResult = faceModel.register(faceRegisterInfo, featureResult.getData());
+ if(registerResult.isSuccess()){
+ log.info("注册成功:ID-{}", registerResult.getData());
+ }else{
+ log.info("注册失败:{}", registerResult.getMessage());
+ }
+ /*log.info("====================人脸更新==========================");
+ //更新人脸 只支持自定义ID:vectorDBConfig.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());
+ faceModel.upsertFace(updateInfo, "src/main/resources/face/iu_2.jpg");
+ log.info("更新人脸成功");*/
+ log.info("====================人脸查询==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_3.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ FaceSearchParams faceSearchParams = new FaceSearchParams();
+ faceSearchParams.setTopK(1);
+ faceSearchParams.setThreshold(0.8f);
+
+ List faceSearchResults = faceModel.search(featureResult2.getData(), faceSearchParams);
+// R faceSearchResults = faceModel.search("src/main/resources/face/iu_3.jpg", faceSearchParams);
+ log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
+ log.info("====================人脸删除==========================");
+ faceModel.removeRegister(registerResult.getData());
+ log.info("人脸删除成功");
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 人脸注册 + 人脸更新 + 人脸查询 + 人脸删除(使用轻量数据库SQLite)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 注册人脸
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向不正,可开启人脸对齐以提升特征提取准确度。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
+ */
+ @Test
+ public void searchFace2(){
+ try {
+ FaceModelConfig config = new FaceModelConfig();
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);
+ //初始化向量数据库:Milvus数据库配置
+ SQLiteConfig vectorDBConfig = new SQLiteConfig();
+ vectorDBConfig.setDbPath("/Users/wenjie/Downloads/face.db");
+ vectorDBConfig.setSimilarityType(SimilarityType.IP);
+ config.setVectorDBConfig(vectorDBConfig);
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //等待加载人脸库结束
+ while (!faceModel.isLoadFaceCompleted()){
+ Thread.sleep(100);
+ }
+ log.info("====================人脸注册==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult.getMessage());
+ return;
+ }
+ //人脸注册信息
+ 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 registerResult = faceModel.register(faceRegisterInfo, featureResult.getData());
+ if(registerResult.isSuccess()){
+ log.info("注册成功:ID-{}", registerResult.getData());
+ }else{
+ log.info("注册失败:{}", registerResult.getMessage());
+ }
+ 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());
+ faceModel.upsertFace(updateInfo, "src/main/resources/face/iu_2.jpg");
+ log.info("更新人脸成功");
+ log.info("====================人脸查询==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_3.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ FaceSearchParams faceSearchParams = new FaceSearchParams();
+ faceSearchParams.setTopK(1);
+ faceSearchParams.setThreshold(0.8f);
+ List faceSearchResults = faceModel.search(featureResult2.getData(), faceSearchParams);
+ log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
+ log.info("====================人脸删除==========================");
+ faceModel.removeRegister(registerResult.getData());
+ log.info("人脸删除成功");
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * 使用离线模型
* @throws Exception
*/
@Test
public void featureComparisonOffline(){
try {
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION);//人脸模型
+ config.setModelEnum(FaceModelEnum.FACENET_MODEL);//人脸模型
+ //设置人脸识别模型文件路径,请根据实际情况替换为本地模型文件的绝对路径
config.setModelPath("/Users/xxx/Documents/develop/face_model/face_feature.pt");
//人脸特征提取参数
FaceExtractConfig extractConfig = new FaceExtractConfig();
FaceModelConfig detectModelConfig = new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE);
+ //设置人脸检测模型文件路径,请根据实际情况替换为本地模型文件的绝对路径
detectModelConfig.setModelPath("/Users/xxx/Documents/develop/face_model/ultranet.pt");
//人脸检测模型配置
extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(detectModelConfig));
config.setExtractConfig(extractConfig);
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //自动裁剪人脸并比对人脸特征
- float similar = faceModel.featureComparison("src/main/resources/kana1.jpg","src/main/resources/kana2.jpg");
- log.info("相似度:{}", similar);
}
catch (Exception e){
e.printStackTrace();
diff --git a/examples/src/main/java/smartai/examples/face/facerec/SeetaFace6Demo.java b/examples/src/main/java/smartai/examples/face/facerec/SeetaFace6Demo.java
index 34441e2..3cb3d21 100644
--- a/examples/src/main/java/smartai/examples/face/facerec/SeetaFace6Demo.java
+++ b/examples/src/main/java/smartai/examples/face/facerec/SeetaFace6Demo.java
@@ -1,12 +1,24 @@
package smartai.examples.face.facerec;
import cn.smartjavaai.common.entity.DetectionResponse;
+import cn.smartjavaai.common.entity.FaceSearchResult;
+import cn.smartjavaai.common.entity.R;
+import cn.smartjavaai.face.config.FaceExtractConfig;
import cn.smartjavaai.face.config.FaceModelConfig;
+import cn.smartjavaai.face.entity.FaceRegisterInfo;
import cn.smartjavaai.face.entity.FaceResult;
+import cn.smartjavaai.face.entity.FaceSearchParams;
import cn.smartjavaai.face.enums.FaceModelEnum;
+import cn.smartjavaai.face.enums.IdStrategy;
+import cn.smartjavaai.face.enums.SimilarityType;
import cn.smartjavaai.face.factory.FaceModelFactory;
import cn.smartjavaai.face.model.facerec.FaceModel;
+import cn.smartjavaai.face.utils.SimilarityUtil;
+import cn.smartjavaai.face.vector.config.MilvusConfig;
+import cn.smartjavaai.face.vector.config.SQLiteConfig;
+import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
+import io.milvus.param.MetricType;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
@@ -21,7 +33,7 @@ import java.util.List;
/**
* SeetaFace6人脸算法模型demo
- * 支持系统:windows 64位
+ * 支持系统:windows 64位,linux 64位
* 支持功能:人脸检测、人脸特征提取、人脸比对(1:1)、人脸比对(1:N)、人脸注册
* @author dwj
* @date 2025/4/11
@@ -31,84 +43,56 @@ public class SeetaFace6Demo {
/**
- * 人脸检测(自定义模型参数)
- * 图片参数:图片路径
- */
- @Test
- public void testFaceDetectCustomConfig(){
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- DetectionResponse detectedResult = faceModel.detect("src/main/resources/largest_selfie.jpg");
- log.info("人脸检测结果:{}", JSONObject.toJSONString(detectedResult));
- }
-
-
- /**
- * 人脸检测并绘制人脸框
- */
- @Test
- public void testFaceDetectAndDraw(){
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- faceModel.detectAndDraw("src/main/resources/largest_selfie.jpg","output/largest_selfie_detected.png");
- }
-
- /**
- * 人脸检测并绘制人脸框,返回BufferedImage
- *
- */
- @Test
- public void testFaceDetectAndDraw2(){
- try {
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- BufferedImage image = null;
- String imagePath = "src/main/resources/largest_selfie.jpg";
- image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
- //可以根据后续业务场景使用detectedImage
- BufferedImage detectedImage = faceModel.detectAndDraw(image);
- Assert.assertNotNull("detectedImage null", detectedImage);
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- }
-
-
- /**
- * 提取人脸特征(支持多人脸)
- * 自动裁剪人脸 + 人脸对齐
+ * 提取人脸特征(多人脸场景)
+ * 默认使用SEETA_FACE6_MODEL自己的检测模型
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
*/
@Test
public void testExtractFeatures(){
try {
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.SEETA_FACE6_MODEL,
- "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models"));
- List faceResult = faceModel.extractFeatures("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
+ //人脸特征提取模型
+ FaceModelConfig config = new FaceModelConfig();
+ //指定模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
+ //指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(模型下载地址请查看文档)
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //提取图片中所有人脸特征
+ R faceResult = faceModel.extractFeatures("src/main/resources/face/iu_1.jpg");
+ if(faceResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(faceResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", faceResult.getMessage());
+ }
}catch (Exception e){
e.printStackTrace();
}
}
-
/**
- * 提取人脸特征(分数最高人脸)
- * 自动裁剪人脸 + 人脸对齐
+ * 提取人脸特征(只提取图片中分数最高人脸特征)
+ * 默认使用SEETA_FACE6_MODEL自己的检测模型
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
*/
@Test
- public void testExtractTopFaceFeature(){
+ public void testExtractFeatures2(){
try {
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.SEETA_FACE6_MODEL,
- "C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models"));
- float[] faceResult = faceModel.extractTopFaceFeature("src/main/resources/kana1.jpg");
- log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult));
+ //人脸特征提取模型
+ FaceModelConfig config = new FaceModelConfig();
+ //指定模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
+ //指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(模型下载地址请查看文档)
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //提取图片中检测分数最高人脸特征
+ R faceResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(faceResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", faceResult.getData());
+ }else{
+ log.info("人脸特征提取失败:{}", faceResult.getMessage());
+ }
}catch (Exception e){
e.printStackTrace();
}
@@ -117,20 +101,66 @@ public class SeetaFace6Demo {
+
/**
- * 人脸比对(1:1)
- * 图片参数:图片路径
+ * 人脸比对1:1(基于图像直接比对)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。(接口内自动完成)
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
* @throws Exception
*/
@Test
public void featureComparison(){
try {
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
+ //指定模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
+ //指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(模型下载地址请查看文档)
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //自动裁剪人脸并比对人脸特征
- float similar = faceModel.featureComparison("src/main/resources/kana1.jpg","src/main/resources/kana2.jpg");
+ //基于图像直接比对人脸特征
+ float similar = faceModel.featureComparison("src/main/resources/face/iu_1.jpg","src/main/resources/face/iu_2.jpg");
+ log.info("相似度:{}", similar);
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 人脸比对1:1(基于特征值比对)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 比对两张图片中提取的人脸特征。
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * @throws Exception
+ */
+ @Test
+ public void featureComparison2(){
+ try {
+ FaceModelConfig config = new FaceModelConfig();
+ //指定模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
+ //指定模型路径:请根据实际情况替换为本地模型文件的绝对路径(模型下载地址请查看文档)
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult1 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult1.isSuccess()){
+ log.info("图片1人脸特征提取成功:{}", JSONObject.toJSONString(featureResult1.getData()));
+ }else{
+ log.info("图片1人脸特征提取失败:{}", featureResult1.getMessage());
+ return;
+ }
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_2.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("图片2人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("图片2人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ //计算相似度
+ float similar = faceModel.calculSimilar(featureResult1.getData(), featureResult2.getData());
log.info("相似度:{}", similar);
}
catch (Exception e){
@@ -140,81 +170,90 @@ public class SeetaFace6Demo {
/**
- * 人脸比对(1:1)
- * 先特征提取,后比对人脸特征
- * 提取人脸特征图片参数:图片路径
- */
- @Test
- public void featureExtractionAndCompare(){
- try {
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //提取图像中最大人脸的特征
- float[] feature1 = faceModel.extractTopFaceFeature("src/main/resources/kana1.jpg");
- float[] feature2 = faceModel.extractTopFaceFeature("src/main/resources/kana2.jpg");
- if(feature1 != null && feature2 != null){
- float similar = faceModel.calculSimilar(feature1, feature2);
- log.info("相似度:{}", similar);
- }else{
- log.warn("人脸特征提取失败");
- }
- }
- catch (Exception e){
- e.printStackTrace();
- }
- }
-
-
-
- /**
- * 注册人脸
- * 图片参数:图片路径
- */
- @Test
- public void registerFace(){
- try {
- FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- //人脸库路径,从项目中 db/faces-data.db下载到本地
- config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
- config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
- FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //等待人脸库加载完毕
- Thread.sleep(1000);
- //注册kana1人脸,参数key建议设置为人名
- boolean isSuccss = faceModel.register("kana1","src/main/resources/kana1.jpg");
- log.info("注册结果:{}", isSuccss);
- }
- catch (Exception e){
- e.printStackTrace();
- }
- }
-
-
- /**
- * 搜索人脸(1:N)
- * 图片参数:图片路径
- * 注意事项:请先注册人脸
+ * 人脸注册 + 人脸更新 + 人脸查询 + 人脸删除(使用向量数据库Milvus)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 注册人脸
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向较正,可关闭人脸对齐以提升性能。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
*/
@Test
public void searchFace(){
try {
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- //人脸库路径,从项目中 db/faces-data.db下载到本地
- config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ //初始化向量数据库:Milvus数据库配置
+ MilvusConfig vectorDBConfig = new MilvusConfig();
+ vectorDBConfig.setHost("127.0.0.1");
+ vectorDBConfig.setPort(19530);
+ //vectorDBConfig.setCollectionName("face10");
+ //ID策略:自动生成
+ vectorDBConfig.setIdStrategy(IdStrategy.AUTO);
+ //索引类型:内积 (Inner Product) 不建议修改
+ vectorDBConfig.setMetricType(MetricType.COSINE);
+ config.setVectorDBConfig(vectorDBConfig);
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //等待人脸库加载完毕
- Thread.sleep(1000);
- FaceResult faceResult = faceModel.search("src/main/resources/kana1.jpg");
- if(faceResult != null){
- log.info("查询到人脸:{}", faceResult.toString());
- }else{
- log.info("未查询到人脸");
+
+ //等待加载人脸库结束
+ while (!faceModel.isLoadFaceCompleted()) {
+ Thread.sleep(50); // 避免 CPU 占用过高
}
+
+ log.info("====================人脸注册==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult.getMessage());
+ return;
+ }
+
+ //人脸注册信息
+ FaceRegisterInfo faceRegisterInfo = new FaceRegisterInfo();
+ //设置人脸注册的自定义元数据,本例中使用 JSON 格式存储用户信息
+ JSONObject metadataJson = new JSONObject();
+ metadataJson.put("name", "iu");
+ metadataJson.put("age", "25");
+ faceRegisterInfo.setMetadata(metadataJson.toJSONString());
+ //人脸注册,返回人脸库ID
+ R registerResult = faceModel.register(faceRegisterInfo, featureResult.getData());
+ if(registerResult.isSuccess()){
+ log.info("注册成功:ID-{}", registerResult.getData());
+ }else{
+ log.info("注册失败:{}", registerResult.getMessage());
+ }
+ /*log.info("====================人脸更新==========================");
+ //更新人脸 只支持自定义ID:vectorDBConfig.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());
+ faceModel.upsertFace(updateInfo, "src/main/resources/face/iu_2.jpg");
+ log.info("更新人脸成功");*/
+ log.info("====================人脸查询==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_2.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ FaceSearchParams faceSearchParams = new FaceSearchParams();
+ faceSearchParams.setTopK(1);
+ faceSearchParams.setThreshold(0.8f);
+ List faceSearchResults = faceModel.search(featureResult2.getData(), faceSearchParams);
+ log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
+ log.info("====================人脸删除==========================");
+ faceModel.removeRegister(registerResult.getData());
+ log.info("人脸删除成功");
}
catch (Exception e){
e.printStackTrace();
@@ -222,25 +261,80 @@ public class SeetaFace6Demo {
}
/**
- * 删除已注册人脸
- * 注意事项:请先注册人脸
+ * 人脸注册 + 人脸更新 + 人脸查询 + 人脸删除(使用轻量数据库SQLite)
+ * 流程:从输入图像中裁剪分数最高的人脸 → 提取其人脸特征 → 注册人脸
+ * 注意事项:
+ * 1、首次调用接口,可能会较慢。只要不关闭程序,后续调用会明显加快。若每次重启程序,则每次首次调用都将重新加载,仍会较慢。
+ * 2、若人脸朝向较正,可关闭人脸对齐以提升性能。(方法参考自定义配置人脸特征提取)
+ * @throws Exception
*/
@Test
- public void removeRegisterFace(){
+ public void searchFace2(){
try {
FaceModelConfig config = new FaceModelConfig();
- config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);//人脸模型
- //人脸库路径,从项目中 db/faces-data.db下载到本地
- config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //人脸模型
+ config.setModelEnum(FaceModelEnum.SEETA_FACE6_MODEL);
config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ //使用轻量数据库SQLite
+ config.setVectorDBConfig(new SQLiteConfig());
FaceModel faceModel = FaceModelFactory.getInstance().getModel(config);
- //等待人脸库加载完毕
- Thread.sleep(1000);
- //使用注册人脸时的key值删除,可一次性删除单个
- long num = faceModel.removeRegister("kana1");
- //删除全部人脸
- //long num = currentAlgorithm.clearFace();
- log.info("删除成功数量:" + num);
+ log.info("====================人脸注册==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult = faceModel.extractTopFaceFeature("src/main/resources/face/iu_1.jpg");
+ if(featureResult.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult.getMessage());
+ return;
+ }
+ //人脸注册信息
+ 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 registerResult = faceModel.register(faceRegisterInfo, featureResult.getData());
+ if(registerResult.isSuccess()){
+ log.info("注册成功:ID-{}", registerResult.getData());
+ }else{
+ log.info("注册失败:{}", registerResult.getMessage());
+ }
+ 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());
+ faceModel.upsertFace(updateInfo, "src/main/resources/face/iu_2.jpg");
+ log.info("更新人脸成功");
+ log.info("====================人脸查询==========================");
+ //特征提取(提取分数最高人脸特征),适用于单人脸场景
+ R featureResult2 = faceModel.extractTopFaceFeature("src/main/resources/face/iu_3.jpg");
+ if(featureResult2.isSuccess()){
+ log.info("人脸特征提取成功:{}", JSONObject.toJSONString(featureResult2.getData()));
+ }else{
+ log.info("人脸特征提取失败:{}", featureResult2.getMessage());
+ return;
+ }
+ FaceSearchParams faceSearchParams = new FaceSearchParams();
+ faceSearchParams.setTopK(1);
+ faceSearchParams.setThreshold(0.62f);
+ //等待加载人脸库结束
+ while (!faceModel.isLoadFaceCompleted()) {
+ Thread.sleep(50); // 避免 CPU 占用过高
+ }
+ List faceSearchResults = faceModel.search(featureResult2.getData(), faceSearchParams);
+ log.info("人脸查询结果:{}", JSONArray.toJSONString(faceSearchResults));
+ log.info("====================人脸删除==========================");
+ faceModel.removeRegister(registerResult.getData());
+ log.info("人脸删除成功");
}
catch (Exception e){
e.printStackTrace();
@@ -248,4 +342,5 @@ public class SeetaFace6Demo {
}
+
}
diff --git a/examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java b/examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java
index ed2d7ed..151fc23 100644
--- a/examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java
+++ b/examples/src/main/java/smartai/examples/ocr/OcrDetectionDemo.java
@@ -38,7 +38,7 @@ public class OcrDetectionDemo {
//指定检测模型
config.setModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL);
//指定模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- config.setDetModelPath("/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
+ config.setDetModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
OcrCommonDetModel model = OcrModelFactory.getInstance().getDetModel(config);
List boxes = model.detect("src/main/resources/ocr_1.jpg");
log.info("OCR检测结果:{}", JSONObject.toJSONString(boxes));
diff --git a/examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java b/examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java
index 7cd68bc..56c3de8 100644
--- a/examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java
+++ b/examples/src/main/java/smartai/examples/ocr/OcrDirectionDetDemo.java
@@ -37,11 +37,11 @@ public class OcrDirectionDetDemo {
//指定检测模型
directionModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL);
//指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- directionModelConfig.setDetModelPath("/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
+ directionModelConfig.setDetModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
//指定文本方向检测模型
directionModelConfig.setModelEnum(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS);
//指定文本方向检测模型路径,需要更改为自己的模型路径(下载地址请查看文档)
- directionModelConfig.setModelPath("/cls/ch_ppocr_mobile_v2.0_cls.onnx");
+ directionModelConfig.setModelPath("/Users/wenjie/Documents/develop/ocr模型/ch_ppocr_mobile_v2.0_cls.onnx");
OcrDirectionModel directionModel = OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
List itemList = directionModel.detect("src/main/resources/ocr_3.jpg");
log.info("OCR方向检测结果:{}", JSONObject.toJSONString(itemList));
diff --git a/examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java b/examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java
index 3901083..4c176cf 100644
--- a/examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java
+++ b/examples/src/main/java/smartai/examples/ocr/OcrRecognizeDemo.java
@@ -27,8 +27,7 @@ public class OcrRecognizeDemo {
/**
* 文本识别
- * 本方法支持旋转角度范围为 -90 到 90 度的文字
- * 同时兼容印刷体和手写体文字。
+ * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字
* 流程:文本检测 -> 文本识别
* 模型需要放在单独文件夹
*/
@@ -38,21 +37,20 @@ public class OcrRecognizeDemo {
//指定检测模型
recModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL);
//指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- recModelConfig.setDetModelPath("/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
+ recModelConfig.setDetModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
//指定识别模型
recModelConfig.setRecModelEnum(CommonRecModelEnum.PADDLEOCR_V5_REC_MODEL);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- recModelConfig.setRecModelPath("/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
+ recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
OcrCommonRecModel recModel = OcrModelFactory.getInstance().getRecModel(recModelConfig);
- OcrInfo ocrInfo = recModel.recognize("src/main/resources/general_ocr_002.png");
+ OcrInfo ocrInfo = recModel.recognize("src/main/resources/ocr_1.jpg");
log.info("OCR识别结果:{}", JSONObject.toJSONString(ocrInfo));
}
/**
* 文本识别(手写字)
- * 本方法支持旋转角度范围为 -90 到 90 度的文字
- * 同时兼容印刷体和手写体文字。
+ * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字
* 流程:文本检测 -> 文本识别
* 模型需要放在单独文件夹
*/
@@ -74,8 +72,8 @@ public class OcrRecognizeDemo {
/**
* 文本识别(带方向矫正)
- * 本方法支持任意角度文字识别
- * 同时兼容印刷体和手写体文字。
+ * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字
+ * 本方法支持多角度文字识别
* 流程:文本检测 -> 方向检测 -> 方向矫正 -> 文本识别
* 模型需要放在单独文件夹
*/
@@ -103,8 +101,7 @@ public class OcrRecognizeDemo {
/**
* 文本识别并绘制结果
- * 本方法支持旋转角度范围为 -90 到 90 度的文字
- * 同时兼容印刷体和手写体文字。
+ * 支持简体中文、繁体中文、英文、日文四种主要语言,以及手写、竖版、拼音、生僻字
* 流程:文本检测 -> 文本识别
* 模型需要放在单独文件夹
*/
@@ -114,16 +111,19 @@ public class OcrRecognizeDemo {
//指定检测模型
recModelConfig.setDetModelEnum(CommonDetModelEnum.PADDLEOCR_V5_DET_MODEL);
//指定检测模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- recModelConfig.setDetModelPath("/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
+ recModelConfig.setDetModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx");
//指定识别模型
recModelConfig.setRecModelEnum(CommonRecModelEnum.PADDLEOCR_V5_REC_MODEL);
//directionModelConfig.setDirectionModelEnum(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS);
//指定识别模型位置,需要更改为自己的模型路径(下载地址请查看文档)
- recModelConfig.setRecModelPath("/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
- //directionModelConfig.setDirectionModelPath("/Users/wenjie/Documents/develop/ocr模型/ch_ppocr_mobile_v2.0_cls.onnx");
+ recModelConfig.setRecModelPath("/Users/wenjie/Documents/develop/ocr模型/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx");
+ //指定方向检测模型
+ recModelConfig.setDirectionModelEnum(DirectionModelEnum.CH_PPOCR_MOBILE_V2_CLS);
+ //指定方向模型位置,需要更改为自己的模型路径(下载地址请查看文档)
+ recModelConfig.setDirectionModelPath("/Users/wenjie/Documents/develop/ocr模型/ch_ppocr_mobile_v2.0_cls.onnx");
OcrCommonRecModel recModel = OcrModelFactory.getInstance().getRecModel(recModelConfig);
- int fontSize = 20;
- recModel.recognizeAndDraw("src/main/resources/general_ocr_002.png", "output/general_ocr_002_recognized.png", fontSize);
+ int fontSize = 25;
+ recModel.recognizeAndDraw("src/main/resources/ocr_4.jpg", "output/ocr_4_recognized.jpg", fontSize);
}
diff --git a/examples/src/main/resources/face/iu_1.jpg b/examples/src/main/resources/face/iu_1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..06bb29f199ebc7248e313edb350b4ed81e03ae9b
GIT binary patch
literal 65536
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