diff --git a/README.md b/README.md
index b66cc85..4af503c 100644
--- a/README.md
+++ b/README.md
@@ -37,10 +37,10 @@
- **人脸特征提取**
基于深度学习算法生成512维特征向量
-- **人脸特征比对**
+- **人脸特征比对** (1:1)
-- **人证核验**
- 人脸照片与实时人脸画面特征比对
+- **人脸查询** (1:N)
+ 人脸库注册/人脸库查询/人脸库删除
### ⌛ 规划中功能
@@ -56,21 +56,34 @@
基于Transformer的语音转文本引擎,支持中文/英文多语种识别
-## 人脸算法模型
+## 目前已集成的人脸算法模型
-- server模型-**RetinaFace 模型**[[GitHub]](https://github.com/deepinsight/insightface/tree/master/detection/retinaface):一个高效的深度学习人脸检测模型,支持高精度的人脸检测。
-- 轻量模型-**Ultra-Light-Fast-Generic-Face-Detector-1MB ** [[GitHub\]](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB):一个轻量级的人脸检测模型,适用于需要较低延迟和较小模型尺寸的应用场景。
+- **RetinaFace 模型**[[GitHub]](https://github.com/deepinsight/insightface/tree/master/detection/retinaface):一个高效的深度学习人脸检测模型,支持高精度的人脸检测,但目前不支持人脸比对
+- **Ultra-Light-Fast-Generic-Face-Detector-1MB ** [[GitHub\]](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB):一个轻量级的人脸检测模型,适用于需要较低延迟和较小模型尺寸的应用场景。
+- **Seetaface6** [[GitHub\]](https://github.com/seetafaceengine/SeetaFace6):是中科视拓最新开放的商业正式级版本,支持人脸检测、关键点定位、人脸识别。同时增加了活体检测、质量评估、年龄性别估计。并且响应时事,开放了口罩检测以及戴口罩的人脸识别模型
+
+### 模型对比及下载地址
+
+| 模型名称 | 下载地址 | 文件大小 | 适用场景 | 兼容系统 |
+| :-----------------------: | :----------------------------------------------------------: | :------: | :---------------: | ------------------- |
+| retinaface | [下载](https://resources.djl.ai/test-models/pytorch/retinaface.zip) | 110MB | 高精度人脸检测 | Windows/Linux/MacOS |
+| ultralightfastgenericface | [下载](https://resources.djl.ai/test-models/pytorch/ultranet.zip) | 1.7MB | 高速人脸检测 | Windows/Linux |
+| seetaface6 | [下载](https://pan.baidu.com/s/1hfNacA8ISV2qHrycjOkgqA?pwd=1234) | 288MB | 人脸检测/人脸识别 | Windows/Linux |
## 环境要求
- Java 版本:**JDK 11或更高版本**
-- 操作系统:支持的操作系统(如 Windows、Linux 或 macOS)
+- 操作系统:不同模型支持的系统不一样,具体请查看文档
## 使用步骤
-📌 **运行提示**:首次启动时将自动完成模型下载及依赖项配置,建议保持网络畅通。初始化完成后,后续启动将恢复毫秒级响应速度。
-
-无网络环境下可指定本地模型路径(需提前预下载模型包)
+> [!CAUTION]
+>
+> 📌 **运行提示**:
+>
+> (1)默认算法(RetinaFace)或轻量算法(Ultra-Light-Fast-Generic-Face-Detector )都为python算法,兼容 Windows、Linux、MacOS,Android 等系统,SmartJavaAI首次启动将自动下载模型到及依赖库到本地(.djl.ai隐藏文件夹),建议保持网络畅通。初始化完成后,后续启动可实现毫秒级响应。在无网络环境下,可指定本地模型路径(需提前下载模型包)。目前,这两种算法不支持人脸识别或人脸比对功能。
+>
+> (2)Seetaface6 采用 C++ 编写,兼容 Windows、CentOS、Ubuntu 等系统。创建算法时,将自动加载对应系统的依赖库。Seetaface6 支持全功能人脸处理(人脸检测、人脸比对 1:1 或 1:N)。SmartJavaAI 通过 JNI 调用 C++ 接口,不支持在线下载模型,需手动下载并存储至本地。使用人脸比对等功能时,需要将项目中db/faces-data.db存放到您本地路径下并在config中指定人脸库路径。
### 1. 安装人脸算法依赖
@@ -81,7 +94,7 @@
ink.numberone
smartjavaai-face
- 1.0.4
+ 1.0.5
```
@@ -112,61 +125,109 @@ File input = new File("src/main/resources/largest_selfie.jpg");
FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(input));
```
-### 5. 人证核验
+### 5. 人脸比对(1:1)
-人证核验步骤:
-
-(1)提取身份证人脸特征,
-
-(2)提取实时人脸特征
-
-(3)特征比对
+> [!CAUTION]
+>
+> (1)将项目中db/faces-data.db存放到您本地路径下
+>
+> (2)下载模型到本地路径,下载地址:https://pan.baidu.com/s/1hfNacA8ISV2qHrycjOkgqA?pwd=1234 提取码:1234
+>
+> (3)如果网盘地址请联系本人,文档最后有联系方式
```java
-//创建脸算法
-FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm();
-//提取身份证人脸特征(图片仅供测试)
-float[] featureIdCard = currentAlgorithm.featureExtraction("src/main/resources/kana1.jpg");
-//提取身份证人脸特征(从图片流获取)
-//File input = new File("src/main/resources/kana1.jpg");
-//float[] featureIdCard = currentAlgorithm.featureExtraction(new FileInputStream(input));
-logger.info("身份证人脸特征:{}", JSONObject.toJSONString(featureIdCard));
-//提取实时人脸特征(图片仅供测试)
-float[] realTimeFeature = currentAlgorithm.featureExtraction("src/main/resources/kana2.jpg");
-logger.info("实时人脸特征:{}", JSONObject.toJSONString(realTimeFeature));
-if(realTimeFeature != null){
- if(currentAlgorithm.calculSimilar(featureIdCard, realTimeFeature) > 0.8){
- logger.info("人脸核验通过");
- }else{
- logger.info("人脸核验不通过");
- }
+// 初始化配置
+ModelConfig config = new ModelConfig();
+config.setAlgorithmName("seetaface6");//目前支持人脸比对的算法只有:seetaface6
+//人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+//改为模型存放路径
+config.setModelPath("/opt/sf3.0_models");
+//创建人脸算法
+FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+//自动裁剪人脸并比对人脸特征
+float similar = currentAlgorithm.featureComparison("src/main/resources/kana1.jpg","src/main/resources/kana2.jpg");
+log.info("相似度:{}", similar);
+```
+
+
+
+### 6. 人脸特征提取及比对
+
+```java
+// 初始化配置
+ModelConfig config = new ModelConfig();
+config.setAlgorithmName("seetaface6");
+//人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+//改为模型存放路径
+config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+//创建人脸算法
+FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+//提取图像中最大人脸的特征
+float[] feature1 = currentAlgorithm.featureExtraction("src/main/resources/kana1.jpg");
+float[] feature2 = currentAlgorithm.featureExtraction("src/main/resources/kana2.jpg");
+float similar = currentAlgorithm.calculSimilar(feature1, feature2);
+log.info("相似度:{}", similar);
+```
+
+### 7. 注册及搜索人脸(1:N)
+
+```java
+// 初始化配置
+ModelConfig config = new ModelConfig();
+config.setAlgorithmName("seetaface6");
+//人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+//改为模型存放路径
+config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+//创建人脸算法 自动将人脸库加载到内存中
+FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+//等待人脸库加载完毕
+Thread.sleep(1000);
+//注册kana1人脸,参数key建议设置为人名
+boolean isSuccss = currentAlgorithm.register("kana1","src/main/resources/kana1.jpg");
+//注册jsy人脸,参数key建议设置为人名
+isSuccss = currentAlgorithm.register("jsy","src/main/resources/jsy.jpg");
+FaceResult faceResult = currentAlgorithm.search("src/main/resources/kana2.jpg");
+if(faceResult != null){
+ log.info("查询到人脸:{}", faceResult.toString());
+}else{
+ log.info("未查询到人脸");
}
```
-### 6. 离线下载模型
+### 8. 人脸检测(离线下载模型)
- **SmartJavaAI**如果未指定模型地址,系统将自动下载模型至本地。因此,无论模型是否通过离线方式下载,SmartJavaAI 最终都会在离线环境下运行模型。
-
-- [离线下载模型代码示例](examples/face_offline.md)
-
-### 7. 测试结果
-
-不同电脑环境下例图的识别时间
-
-| 电脑环境 | **RetinaFace 模型** | 轻量模型 | 人证核验 |
-| ------------------------- | ------------------- | --------- | --------- |
-| windows intel i5 8400 8核 | 2s左右 | 700ms左右 | 600ms左右 |
-| macOS M1 Pro芯片 | 800ms左右 | 400ms左右 | 300ms左右 |
-
-测试说明:
-
-(1)使用默认方法创建人脸算法,会先下载人脸模型及相关文件,所以首次运行会比较慢
-
-(2)目前SmartJavaAI默认使用CPU识别,所以CPU性能越高,识别速度越快
-
-(3)由于例图中人脸数量多,所以识别的速度会稍慢
-
-(4)测试结果中的时间不包含创建算法时间(加载模型),实际生产环境使用,只需系统系统时创建一次算法即可
+```java
+// 初始化配置
+ModelConfig config = new ModelConfig();
+config.setAlgorithmName("retinaface");//人脸算法模型,目前支持:retinaface/ultralightfastgenericface/seetaface6
+//config.setAlgorithmName("ultralightfastgenericface");//轻量模型
+config.setConfidenceThreshold(FaceConfig.DEFAULT_CONFIDENCE_THRESHOLD);//置信度阈值
+config.setMaxFaceCount(FaceConfig.MAX_FACE_LIMIT);//每张特征图保留的最大候选框数量
+//nms阈值:控制重叠框的合并程度,取值越低,合并越多重叠框(减少误检但可能漏检);取值越高,保留更多框(增加检出但可能引入冗余)
+config.setNmsThresh(FaceConfig.NMS_THRESHOLD);
+//模型下载地址:
+//retinaface: https://resources.djl.ai/test-models/pytorch/retinaface.zip
+//ultralightfastgenericface: https://resources.djl.ai/test-models/pytorch/ultranet.zip
+//改为模型存放路径
+config.setModelPath("/Users/xxx/Documents/develop/face_model/retinaface.pt");
+//创建人脸算法
+FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+//使用图片路径检测
+FaceDetectedResult result = currentAlgorithm.detect("src/main/resources/largest_selfie.jpg");
+logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
+//使用图片流检测
+File input = new File("src/main/resources/largest_selfie.jpg");
+//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(input));
+//logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
+BufferedImage image = ImageIO.read(input);
+//创建保存路径
+Path imagePath = Paths.get("output").resolve("retinaface_detected.jpg");
+//绘制人脸框
+ImageUtils.drawBoundingBoxes(image, result, imagePath.toAbsolutePath().toString());
+```
## 完整代码
@@ -174,6 +235,16 @@ if(realTimeFeature != null){
└── 📄[FaceDemo.java](https://github.com/geekwenjie/SmartJavaAI/blob/master/examples/src/main/java/smartai/examples/face/FaceDemo.java) *(基于JDK11构建的完整可执行示例)*
+
+
+## 🙏 鸣谢
+
+本项目在开发过程中借鉴或使用了以下优秀开源项目,特此致谢:
+
+- **[Seetaface6JNI](https://gitee.com/cnsugar/seetaface6JNI)**
+---
+
+
## 联系方式
如果您有任何问题或建议,欢迎联系:
diff --git a/db/faces-data.db b/db/faces-data.db
new file mode 100644
index 0000000..cbbc660
Binary files /dev/null and b/db/faces-data.db differ
diff --git a/examples/face_offline.md b/examples/face_offline.md
deleted file mode 100644
index 73991bf..0000000
--- a/examples/face_offline.md
+++ /dev/null
@@ -1,99 +0,0 @@
-# SmartJavaAI离线下载模型案例
-
-**SmartJavaAI**如果未指定模型地址,系统将自动下载模型至本地。因此,无论模型是否通过离线方式下载,SmartJavaAI 最终都会在离线环境下运行模型。
-
-### 1. 安装人脸算法依赖
-
-在 Maven 项目的 `pom.xml` 中添加 SmartJavaAI的人脸算法依赖:
-
-```xml
-
-
- ink.numberone
- smartjavaai-face
- 1.0.4
-
-
-```
-
-### 2. 下载模型
-
-| 模型名称 | 下载地址 | 文件大小 | 适用场景 |
-| :-----------------------: | :----------------------------------------------------------: | :------: | :------------: |
-| retinaface | [下载](https://resources.djl.ai/test-models/pytorch/retinaface.zip) | 110MB | 高精度人脸检测 |
-| ultralightfastgenericface | [下载](https://resources.djl.ai/test-models/pytorch/ultranet.zip) | 1.7MB | 高速人脸检测 |
-| featureExtraction | [下载](https://resources.djl.ai/test-models/pytorch/face_feature.zip) | 104MB | 人脸特征提取 |
-
-### 3. 人脸检测代码示例(离线下载模型)
-
-```java
-// 初始化配置
-ModelConfig config = new ModelConfig();
-config.setAlgorithmName("retinaface");//人脸算法模型,目前支持:retinaface及ultralightfastgenericface
-//config.setAlgorithmName("ultralightfastgenericface");//轻量模型
-config.setConfidenceThreshold(FaceConfig.DEFAULT_CONFIDENCE_THRESHOLD);//置信度阈值
-config.setMaxFaceCount(FaceConfig.MAX_FACE_LIMIT);//每张特征图保留的最大候选框数量
-//nms阈值:控制重叠框的合并程度,取值越低,合并越多重叠框(减少误检但可能漏检);取值越高,保留更多框(增加检出但可能引入冗余)
-config.setNmsThresh(FaceConfig.NMS_THRESHOLD);
-//模型下载地址:
-//retinaface: https://resources.djl.ai/test-models/pytorch/retinaface.zip
-//ultralightfastgenericface: https://resources.djl.ai/test-models/pytorch/ultranet.zip
-//改为模型存放路径
-config.setModelPath("/Users/xxx/Documents/develop/face_model/retinaface.pt");
-//创建人脸算法
-FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
-//使用图片路径检测
-FaceDetectedResult result = currentAlgorithm.detect("src/main/resources/largest_selfie.jpg");
-logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
-//使用图片流检测
-File input = new File("src/main/resources/largest_selfie.jpg");
-//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(input));
-//logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
-BufferedImage image = ImageIO.read(input);
-//创建保存路径
-Path imagePath = Paths.get("output").resolve("retinaface_detected.jpg");
-//绘制人脸框
-ImageUtils.drawBoundingBoxes(image, result, imagePath.toAbsolutePath().toString());
-```
-
-### 4. 人证核验示例(离线下载模型)
-
-人证核验步骤:
-
-(1)提取身份证人脸特征,
-
-(2)提取实时人脸特征
-
-(3)特征比对
-
-```java
-// 初始化配置
-ModelConfig config = new ModelConfig();
-config.setAlgorithmName("featureExtraction");
-//模型下载地址:https://resources.djl.ai/test-models/pytorch/face_feature.zip
-//改为模型存放路径
-config.setModelPath("/Users/xxx/Documents/develop/face_model/face_feature.pt");
-//创建脸算法
-FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceFeatureAlgorithm(config);
-//提取身份证人脸特征(图片仅供测试)
-float[] featureIdCard = currentAlgorithm.featureExtraction("src/main/resources/kana1.jpg");
-//提取身份证人脸特征(从图片流获取)
-//File input = new File("src/main/resources/kana1.jpg");
-//float[] featureIdCard = currentAlgorithm.featureExtraction(new FileInputStream(input));
-logger.info("身份证人脸特征:{}", JSONObject.toJSONString(featureIdCard));
-//提取实时人脸特征(图片仅供测试)
-float[] realTimeFeature = currentAlgorithm.featureExtraction("src/main/resources/kana2.jpg");
-logger.info("实时人脸特征:{}", JSONObject.toJSONString(realTimeFeature));
-if(realTimeFeature != null){
- if(currentAlgorithm.calculSimilar(featureIdCard, realTimeFeature) > 0.8){
- logger.info("人脸核验通过");
- }else{
- logger.info("人脸核验不通过");
- }
-}
-```
-
-## 完整代码
-
-`📁 examples/src/main/java/smartai/examples/face`
- └── 📄[FaceDemo.java](https://github.com/geekwenjie/SmartJavaAI/blob/master/examples/src/main/java/smartai/examples/face/FaceDemo.java) *(基于JDK11构建的完整可执行示例)*
diff --git a/examples/output/retinaface_detected.jpg b/examples/output/retinaface_detected.jpg
index fc60d53..50ec51d 100644
Binary files a/examples/output/retinaface_detected.jpg and b/examples/output/retinaface_detected.jpg differ
diff --git a/examples/pom.xml b/examples/pom.xml
index 338ca0e..1fab46c 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -43,7 +43,7 @@
ink.numberone
smartjavaai-face
- 1.0.4
+ 1.0.5
@@ -62,6 +62,54 @@
fastjson
1.2.83
+
+
+ ai.djl.onnxruntime
+ onnxruntime-engine
+ 0.20.0
+
+
+ example
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.3
+
+
+ false
+
+ jar-with-dependencies
+
+
+
+
+ true
+
+ lib/
+
+ smartai.examples.face.FaceDemo
+
+
+
+
+
+ make-assembly
+
+ package
+
+ assembly
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/src/main/java/smartai/examples/face/FaceDemo.java b/examples/src/main/java/smartai/examples/face/FaceDemo.java
index bd8da30..c0897d2 100644
--- a/examples/src/main/java/smartai/examples/face/FaceDemo.java
+++ b/examples/src/main/java/smartai/examples/face/FaceDemo.java
@@ -2,7 +2,9 @@ package smartai.examples.face;
import cn.smartjavaai.common.entity.Rectangle;
import cn.smartjavaai.face.*;
+import cn.smartjavaai.face.entity.FaceResult;
import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,14 +24,13 @@ import java.nio.file.Paths;
/**
* @author dwj
*/
+@Slf4j
public class FaceDemo {
- private static final Logger logger = LoggerFactory.getLogger(FaceDemo.class);
-
public static void main(String[] args) {
try {
- verifyIDCard();
+ featureComparison();
//detectFace2();
//verifyIDCard();
} catch (Exception e) {
@@ -49,18 +50,18 @@ public class FaceDemo {
//创建人脸算法
FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm();
sw.stop();
- logger.info("创建人脸算法耗时:" + sw.getTime() + "ms");
+ log.info("创建人脸算法耗时:" + sw.getTime() + "ms");
sw.reset();
sw.start();
//使用图片路径检测
FaceDetectedResult result = currentAlgorithm.detect("src/main/resources/largest_selfie.jpg");
sw.stop();
- logger.info("人脸检测耗时:" + sw.getTime() + "ms");
- logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
+ log.info("人脸检测耗时:" + sw.getTime() + "ms");
+ log.info("人脸检测结果:{}", JSONObject.toJSONString(result));
//使用图片流检测
File input = new File("src/main/resources/largest_selfie.jpg");
//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(input));
- //logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
+ //log.info("人脸检测结果:{}", JSONObject.toJSONString(result));
BufferedImage image = ImageIO.read(input);
//创建保存路径
Path imagePath = Paths.get("output").resolve("retinaface_detected.jpg");
@@ -81,14 +82,14 @@ public class FaceDemo {
//创建轻量人脸算法
FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createLightFaceAlgorithm();
sw.stop();
- logger.info("创建人脸算法耗时:" + sw.getTime() + "ms");
+ log.info("创建人脸算法耗时:" + sw.getTime() + "ms");
sw.reset();
sw.start();
//使用图片路径检测
FaceDetectedResult result = currentAlgorithm.detect("src/main/resources/largest_selfie.jpg");
sw.stop();
- logger.info("人脸检测耗时:" + sw.getTime() + "ms");
- logger.info("轻量人脸检测结果:{}", JSONObject.toJSONString(result));
+ log.info("人脸检测耗时:" + sw.getTime() + "ms");
+ log.info("轻量人脸检测结果:{}", JSONObject.toJSONString(result));
//使用图片流检测
//File imageFile = new File("/Users/wenjie/Downloads/djl-master/examples/src/test/resources/largest_selfie.jpg");
//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(imageFile));
@@ -100,39 +101,7 @@ public class FaceDemo {
ImageUtils.drawBoundingBoxes(image, result, imagePath.toAbsolutePath().toString());
}
- /**
- * 人证核验
- * @throws Exception
- */
- public static void verifyIDCard() throws Exception {
- // 创建并启动计时器
- StopWatch sw = StopWatch.createStarted();
- //创建脸算法
- FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceFeatureAlgorithm();
- sw.stop();
- logger.info("创建人脸算法耗时:" + sw.getTime() + "ms");
- sw.reset();
- sw.start();
- //提取身份证人脸特征(图片仅供测试)
- float[] featureIdCard = currentAlgorithm.featureExtraction("src/main/resources/MJ_20250213_155245.png");
- sw.stop();
- logger.info("人脸特征提取耗时:" + sw.getTime() + "ms");
- //提取身份证人脸特征(从图片流获取)
- //File input = new File("src/main/resources/kana1.jpg");
- //float[] featureIdCard = currentAlgorithm.featureExtraction(new FileInputStream(input));
- logger.info("身份证人脸特征:{}", JSONObject.toJSONString(featureIdCard));
- //提取实时人脸特征(图片仅供测试)
- float[] realTimeFeature = currentAlgorithm.featureExtraction("src/main/resources/MJ_20250213_155228.png");
- logger.info("实时人脸特征:{}", JSONObject.toJSONString(realTimeFeature));
- if(realTimeFeature != null){
- System.out.println("相似度:" + currentAlgorithm.calculSimilar(featureIdCard, realTimeFeature));
- if(currentAlgorithm.calculSimilar(featureIdCard, realTimeFeature) > 0.8){
- logger.info("人脸核验通过");
- }else{
- logger.info("人脸核验不通过");
- }
- }
- }
+
/**
* 人脸检测(离线模型)
@@ -143,7 +112,7 @@ public class FaceDemo {
public static void detectFaceOffine() throws Exception {
// 初始化配置
ModelConfig config = new ModelConfig();
- config.setAlgorithmName("retinaface");//人脸算法模型,目前支持:retinaface及ultralightfastgenericface
+ config.setAlgorithmName("retinaface");//人脸算法模型,目前支持:retinaface/ultralightfastgenericface/seetaface6
//config.setAlgorithmName("ultralightfastgenericface");//轻量模型
config.setConfidenceThreshold(FaceConfig.DEFAULT_CONFIDENCE_THRESHOLD);//置信度阈值
config.setMaxFaceCount(FaceConfig.MAX_FACE_LIMIT);//每张特征图保留的最大候选框数量
@@ -158,7 +127,7 @@ public class FaceDemo {
FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
//使用图片路径检测
FaceDetectedResult result = currentAlgorithm.detect("src/main/resources/largest_selfie.jpg");
- logger.info("人脸检测结果:{}", JSONObject.toJSONString(result));
+ log.info("人脸检测结果:{}", JSONObject.toJSONString(result));
//使用图片流检测
File input = new File("src/main/resources/largest_selfie.jpg");
//FaceDetectedResult result = currentAlgorithm.detect(new FileInputStream(input));
@@ -171,34 +140,113 @@ public class FaceDemo {
}
/**
- * 人证核验(离线模型)
+ * 人脸比对(1:1)
* @throws Exception
*/
- public static void verifyIDCardOffine() throws Exception {
- // 初始化配置
- ModelConfig config = new ModelConfig();
- config.setAlgorithmName("featureExtraction");
- //模型下载地址:https://resources.djl.ai/test-models/pytorch/face_feature.zip
- //改为模型存放路径
- config.setModelPath("/Users/xxx/Documents/develop/face_model/face_feature.pt");
- //创建脸算法
- FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceFeatureAlgorithm(config);
- //提取身份证人脸特征(图片仅供测试)
- float[] featureIdCard = currentAlgorithm.featureExtraction("src/main/resources/kana1.jpg");
- //提取身份证人脸特征(从图片流获取)
- //File input = new File("src/main/resources/kana1.jpg");
- //float[] featureIdCard = currentAlgorithm.featureExtraction(new FileInputStream(input));
- logger.info("身份证人脸特征:{}", JSONObject.toJSONString(featureIdCard));
- //提取实时人脸特征(图片仅供测试)
- float[] realTimeFeature = currentAlgorithm.featureExtraction("src/main/resources/kana2.jpg");
- logger.info("实时人脸特征:{}", JSONObject.toJSONString(realTimeFeature));
- if(realTimeFeature != null){
- if(currentAlgorithm.calculSimilar(featureIdCard, realTimeFeature) > 0.8){
- logger.info("人脸核验通过");
- }else{
- logger.info("人脸核验不通过");
- }
+ public static void featureComparison(){
+ try {
+ // 初始化配置
+ ModelConfig config = new ModelConfig();
+ config.setAlgorithmName("seetaface6");//目前支持人脸比对的算法只有:seetaface6
+ //人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+ config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //改为模型存放路径
+ config.setModelPath("/opt/sf3.0_models");
+ //创建人脸算法
+ FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+ //自动裁剪人脸并比对人脸特征
+ float similar = currentAlgorithm.featureComparison("src/main/resources/kana1.jpg","src/main/resources/kana2.jpg");
+ log.info("相似度:{}", similar);
+ }
+ catch (Exception e){
+ e.printStackTrace();
}
}
+ /**
+ * 人脸特征提取及比对
+ */
+ public static void featureExtractionAndCompare(){
+ try {
+ // 初始化配置
+ ModelConfig config = new ModelConfig();
+ config.setAlgorithmName("seetaface6");
+ //人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+ config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //改为模型存放路径
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ //创建人脸算法
+ FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+ //提取图像中最大人脸的特征
+ float[] feature1 = currentAlgorithm.featureExtraction("src/main/resources/kana1.jpg");
+ float[] feature2 = currentAlgorithm.featureExtraction("src/main/resources/kana2.jpg");
+ float similar = currentAlgorithm.calculSimilar(feature1, feature2);
+ log.info("相似度:{}", similar);
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 注册人脸及搜索人脸(1:N)
+ */
+ public static void registerAndSearchFace(){
+ try {
+ // 初始化配置
+ ModelConfig config = new ModelConfig();
+ config.setAlgorithmName("seetaface6");
+ //人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+ config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //改为模型存放路径
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ //创建人脸算法 自动将人脸库加载到内存中
+ FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+ //等待人脸库加载完毕
+ Thread.sleep(1000);
+ //注册kana1人脸,参数key建议设置为人名
+ boolean isSuccss = currentAlgorithm.register("kana1","src/main/resources/kana1.jpg");
+ //注册jsy人脸,参数key建议设置为人名
+ isSuccss = currentAlgorithm.register("jsy","src/main/resources/jsy.jpg");
+ FaceResult faceResult = currentAlgorithm.search("src/main/resources/kana2.jpg");
+ if(faceResult != null){
+ log.info("查询到人脸:{}", faceResult.toString());
+ }else{
+ log.info("未查询到人脸");
+ }
+
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 删除已注册人脸
+ */
+ public static void removeRegisterFace(){
+ try {
+ // 初始化配置
+ ModelConfig config = new ModelConfig();
+ config.setAlgorithmName("seetaface6");
+ //人脸库路径 如果不指定人脸库,无法使用 1:N人脸搜索
+ config.setFaceDbPath("C:/Users/Administrator/Downloads/faces-data.db");
+ //改为模型存放路径
+ config.setModelPath("C:/Users/Administrator/Downloads/sf3.0_models/sf3.0_models");
+ //创建人脸算法 自动将人脸库加载到内存中
+ FaceAlgorithm currentAlgorithm = FaceAlgorithmFactory.createFaceAlgorithm(config);
+ //等待人脸库加载完毕
+ Thread.sleep(1000);
+ //使用注册人脸时的key值删除,可一次性删除单个
+ long num = currentAlgorithm.removeRegister("kana1");
+ //删除全部人脸
+ //long num = currentAlgorithm.clearFace();
+ log.info("删除成功数量:" + num);
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+
}
diff --git a/examples/src/main/java/smartai/examples/utils/ImageUtils.java b/examples/src/main/java/smartai/examples/utils/ImageUtils.java
index 038c233..1914760 100644
--- a/examples/src/main/java/smartai/examples/utils/ImageUtils.java
+++ b/examples/src/main/java/smartai/examples/utils/ImageUtils.java
@@ -48,7 +48,7 @@ public class ImageUtils {
int width = metrics.stringWidth(text) + padding * 2 - stroke / 2;
int height = metrics.getHeight() + metrics.getDescent();
int ascent = metrics.getAscent();
- java.awt.Rectangle background = new java.awt.Rectangle(x, y, width, height);
+ Rectangle background = new Rectangle(x, y, width, height);
g.fill(background);
g.setPaint(Color.WHITE);
g.drawString(text, x + padding, y + ascent);
diff --git a/examples/src/main/resources/jsy.jpg b/examples/src/main/resources/jsy.jpg
new file mode 100644
index 0000000..c4640e7
Binary files /dev/null and b/examples/src/main/resources/jsy.jpg differ
diff --git a/pom.xml b/pom.xml
index 2d0b4c5..6b8c658 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,12 +6,13 @@
ink.numberone
smartjavaai-parent
- 1.0.4
+ 1.0.5
pom
SmartJavaAI
smartjavaai-face
smartjavaai-common
+ smartjavaai-seetaface6-lib
@@ -34,33 +35,19 @@
ink.numberone
smartjavaai-common
- 1.0.4
+ 1.0.5
ink.numberone
smartjavaai-face
- 1.0.4
+ 1.0.5
-
- commons-cli
- commons-cli
- 1.9.0
-
-
- commons-io
- commons-io
- 2.17.0
-
-
- org.apache.logging.log4j
- log4j-slf4j2-impl
- 2.24.1
-
+
ai.djl
api
@@ -176,6 +163,37 @@
runtime
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.30
+
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.30
+
+
+
+ commons-cli
+ commons-cli
+ 1.9.0
+
+
+ commons-io
+ commons-io
+ 2.17.0
+
+
https://github.com/geekwenjie/SmartJavaAI
diff --git a/smartjavaai-common/pom.xml b/smartjavaai-common/pom.xml
index dc00073..8bcfa07 100644
--- a/smartjavaai-common/pom.xml
+++ b/smartjavaai-common/pom.xml
@@ -6,7 +6,7 @@
ink.numberone
smartjavaai-parent
- 1.0.4
+ 1.0.5
smartjavaai-common
diff --git a/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java
new file mode 100644
index 0000000..6cfd4a1
--- /dev/null
+++ b/smartjavaai-common/src/main/java/cn/smartjavaai/common/utils/ImageUtils.java
@@ -0,0 +1,77 @@
+package cn.smartjavaai.common.utils;
+
+import java.awt.image.BufferedImage;
+//import java.awt.image.ColorConvertOp;
+import java.awt.image.ComponentSampleModel;
+import java.util.Arrays;
+
+/**
+ * 图片处理工具类
+ */
+public class ImageUtils {
+ /**
+ * @param image
+ * @param bandOffset 用于推断通道顺序
+ * @return
+ */
+ private static boolean equalBandOffsetWith3Byte(BufferedImage image, int[] bandOffset) {
+ if (image.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+ if (image.getData().getSampleModel() instanceof ComponentSampleModel) {
+ ComponentSampleModel sampleModel = (ComponentSampleModel) image.getData().getSampleModel();
+ if (Arrays.equals(sampleModel.getBandOffsets(), bandOffset)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 推断图像是否为BGR格式
+ *
+ * @return
+ */
+ public static boolean isBGR3Byte(BufferedImage image) {
+ return equalBandOffsetWith3Byte(image, new int[]{0, 1, 2});
+ }
+
+ /**
+ * 对图像解码返回BGR格式矩阵数据
+ *
+ * @param image
+ * @return
+ */
+ public static byte[] getMatrixBGR(BufferedImage image) {
+ byte[] matrixBGR;
+ if (isBGR3Byte(image)) {
+ matrixBGR = (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
+ } else {
+ // ARGB格式图像数据
+ int intrgb[] = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
+ matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
+ // ARGB转BGR格式
+ for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
+ matrixBGR[j] = (byte) (intrgb[i] & 0xff);
+ matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
+ matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
+ }
+ }
+ return matrixBGR;
+ }
+
+
+ public static BufferedImage bgrToBufferedImage(byte[] data, int width, int height) {
+ int type = BufferedImage.TYPE_3BYTE_BGR;
+ // bgr to rgb
+ byte b;
+ for (int i = 0; i < data.length; i = i + 3) {
+ b = data[i];
+ data[i] = data[i + 2];
+ data[i + 2] = b;
+ }
+ BufferedImage image = new BufferedImage(width, height, type);
+ image.getRaster().setDataElements(0, 0, width, height, data);
+ return image;
+ }
+
+}
diff --git a/smartjavaai-face/pom.xml b/smartjavaai-face/pom.xml
index acfe021..cb1e0d2 100644
--- a/smartjavaai-face/pom.xml
+++ b/smartjavaai-face/pom.xml
@@ -6,11 +6,11 @@
ink.numberone
smartjavaai-parent
- 1.0.4
+ 1.0.5
smartjavaai-face
- 1.0.4
+ 1.0.5
smartjavaai-face
SmartJavaAI
https://github.com/geekwenjie/SmartJavaAI
@@ -25,6 +25,7 @@
11
11
UTF-8
+ true
@@ -34,11 +35,24 @@
smartjavaai-common
${project.version}
+
+
+ ink.numberone
+ smartjavaai-seetaface6-lib
+ ${project.version}
+
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.36.0.3
+
+
org.sonatype.central
central-publishing-maven-plugin
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/AbstractFaceAlgorithm.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/AbstractFaceAlgorithm.java
index 0589276..b58f914 100644
--- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/AbstractFaceAlgorithm.java
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/AbstractFaceAlgorithm.java
@@ -1,5 +1,8 @@
package cn.smartjavaai.face;
+import cn.smartjavaai.face.entity.FaceResult;
+
+import java.io.IOException;
import java.io.InputStream;
/**
@@ -51,4 +54,34 @@ public abstract class AbstractFaceAlgorithm implements FaceAlgorithm{
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
throw new UnsupportedOperationException("默认不支持该功能");
}
+
+ @Override
+ public boolean register(String key, String imagePath) throws Exception {
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ @Override
+ public boolean register(String key, InputStream inputStream) throws Exception {
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ @Override
+ public FaceResult search(String imagePath) throws Exception{
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ @Override
+ public FaceResult search(InputStream inputStream) throws Exception{
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ @Override
+ public long removeRegister(String... keys) throws Exception{
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
+
+ @Override
+ public long clearFace() throws Exception{
+ throw new UnsupportedOperationException("默认不支持该功能");
+ }
}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithm.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithm.java
index c72c4f1..b42dcd8 100644
--- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithm.java
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithm.java
@@ -3,7 +3,9 @@ package cn.smartjavaai.face;
import ai.djl.MalformedModelException;
import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.translate.TranslateException;
+import cn.smartjavaai.face.entity.FaceResult;
+import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
@@ -86,4 +88,46 @@ public interface FaceAlgorithm {
*/
float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception;
+ /**
+ * 注册人脸
+ * @param key
+ * @param imagePath
+ * @return
+ */
+ boolean register(String key, String imagePath) throws Exception;
+
+ /**
+ * 注册人脸
+ * @param key
+ * @param inputStream
+ * @return
+ */
+ boolean register(String key, InputStream inputStream) throws Exception;
+
+ /**
+ * 查询人脸
+ * @param imagePath
+ * @return
+ */
+ FaceResult search(String imagePath) throws Exception;
+
+ /**
+ * 查询人脸
+ * @param inputStream
+ * @return
+ */
+ FaceResult search(InputStream inputStream) throws Exception;
+
+ /**
+ * 删除已标记人脸
+ * @param keys
+ * @return
+ */
+ long removeRegister(String... keys) throws Exception;
+
+ /**
+ * 清空人脸库数据
+ */
+ long clearFace() throws Exception;
+
}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithmFactory.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithmFactory.java
index 5cb06fc..437e316 100644
--- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithmFactory.java
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/FaceAlgorithmFactory.java
@@ -2,6 +2,7 @@ package cn.smartjavaai.face;
import cn.smartjavaai.face.algo.FeatureExtractionAlgo;
import cn.smartjavaai.face.algo.RetinaFace;
+import cn.smartjavaai.face.algo.SeetaFace6Algo;
import cn.smartjavaai.face.algo.UltraLightFastGenericFace;
import java.util.Map;
@@ -37,6 +38,7 @@ public class FaceAlgorithmFactory {
public static FaceAlgorithm createFaceAlgorithm(ModelConfig config) throws Exception {
Class> clazz = registry.get(config.getAlgorithmName().toLowerCase());
if(clazz == null){
+ System.out.println("No such algorithm: " + config.getAlgorithmName().toLowerCase());
throw new IllegalArgumentException("Unsupported algorithm");
}
FaceAlgorithm algorithm = (FaceAlgorithm) clazz.newInstance();
@@ -114,6 +116,7 @@ public class FaceAlgorithmFactory {
registerAlgorithm("ultralightfastgenericface", UltraLightFastGenericFace.class);
//人脸特征提取
registerAlgorithm("featureExtraction", FeatureExtractionAlgo.class);
+ registerAlgorithm("seetaface6", SeetaFace6Algo.class);
}
}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/ModelConfig.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/ModelConfig.java
index c3a67e5..d8685e9 100644
--- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/ModelConfig.java
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/ModelConfig.java
@@ -32,6 +32,11 @@ public class ModelConfig {
*/
private String modelPath;
+ /**
+ * 人脸库路径
+ */
+ private String faceDbPath;
+
public String getAlgorithmName() {
return algorithmName;
@@ -72,4 +77,12 @@ public class ModelConfig {
public void setModelPath(String modelPath) {
this.modelPath = modelPath;
}
+
+ public String getFaceDbPath() {
+ return faceDbPath;
+ }
+
+ public void setFaceDbPath(String faceDbPath) {
+ this.faceDbPath = faceDbPath;
+ }
}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/FeatureExtractionAlgo.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/FeatureExtractionAlgo.java
index 9d8d997..9b10373 100644
--- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/FeatureExtractionAlgo.java
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/FeatureExtractionAlgo.java
@@ -25,7 +25,6 @@ import java.util.List;
import java.util.stream.Collectors;
/**
- * RetinaFace实现
* @author dwj
*/
public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
@@ -51,7 +50,7 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
* 加载人脸特征提取模型
* @param config
* @throws Exception
- */
+ *//*
@Override
public void loadFaceFeatureModel(ModelConfig config) throws Exception {
String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(","));
@@ -71,12 +70,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
}
- /**
+ *//**
* 特征提取
* @param imagePath 图片路径
* @return
* @throws Exception
- */
+ *//*
@Override
public float[] featureExtraction(String imagePath) throws Exception {
Path imageFile = Paths.get(imagePath);
@@ -85,12 +84,12 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
return predictor.predict(img);
}
- /**
+ *//**
* 特征提取
* @param inputStream 输入流
* @return
* @throws Exception
- */
+ *//*
@Override
public float[] featureExtraction(InputStream inputStream) throws Exception {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
@@ -98,13 +97,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
return predictor.predict(img);
}
- /**
+ *//**
* 计算相似度
* @param feature1 图1特征
* @param feature2 图2特征
* @return
* @throws Exception
- */
+ *//*
@Override
public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
float ret = 0.0f;
@@ -119,13 +118,13 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
return (float) ((ret / Math.sqrt(mod1) / Math.sqrt(mod2) + 1) / 2.0f);
}
- /**
+ *//**
* 特征比较
* @param imagePath1 图1路径
* @param imagePath2 图2路径
* @return
* @throws Exception
- */
+ *//*
@Override
public float featureComparison(String imagePath1, String imagePath2) throws Exception {
float[] feature1 = featureExtraction(imagePath1);
@@ -133,19 +132,19 @@ public class FeatureExtractionAlgo extends AbstractFaceAlgorithm {
return calculSimilar(feature1, feature2);
}
- /**
+ *//**
* 特征比较
* @param inputStream1 图1输入流
* @param inputStream2 图2输入流
* @return
* @throws Exception
- */
+ *//*
@Override
public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
float[] feature1 = featureExtraction(inputStream1);
float[] feature2 = featureExtraction(inputStream2);
return calculSimilar(feature1, feature2);
- }
+ }*/
/*@Override
public float[] recognize(FaceRegion region) {
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/SeetaFace6Algo.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/SeetaFace6Algo.java
new file mode 100644
index 0000000..e06ba05
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/algo/SeetaFace6Algo.java
@@ -0,0 +1,319 @@
+package cn.smartjavaai.face.algo;
+
+import cn.smartjavaai.common.entity.Point;
+import cn.smartjavaai.common.entity.Rectangle;
+import cn.smartjavaai.common.utils.ImageUtils;
+import cn.smartjavaai.face.AbstractFaceAlgorithm;
+import cn.smartjavaai.face.FaceDetectedResult;
+import cn.smartjavaai.face.ModelConfig;
+import cn.smartjavaai.face.dao.FaceDao;
+import cn.smartjavaai.face.entity.FaceData;
+import cn.smartjavaai.face.entity.FaceResult;
+import com.seetaface.NativeLoader;
+import com.seetaface.SeetaFace6JNI;
+import com.seetaface.model.RecognizeResult;
+import com.seetaface.model.SeetaImageData;
+import com.seetaface.model.SeetaRect;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * SeetaFace6 人脸算法
+ * @author dwj
+ */
+@Slf4j
+public class SeetaFace6Algo extends AbstractFaceAlgorithm {
+
+
+ private ModelConfig config;
+
+
+ @Override
+ public void loadModel(ModelConfig config) throws Exception {
+ this.config = config;
+ if (NativeLoader.seetaFace6SDK == null) {
+ synchronized (SeetaFace6JNI.class) {
+ if(StringUtils.isBlank(config.getModelPath())){
+ throw new Exception("modelPath is null");
+ }
+ //加载依赖库
+ NativeLoader.loadNativeLibraries(config.getModelPath());
+ log.info("Loading seetaFace6 library successfully.");
+ NativeLoader.seetaFace6SDK = new SeetaFace6JNI();
+ //加载模型
+ boolean isSuccess = NativeLoader.seetaFace6SDK.initModel(config.getModelPath());
+ if(!isSuccess){
+ throw new Exception("seetaFace6模型初始化失败," + config.getModelPath());
+ }
+ log.info("Load seetaFace6 model success!");
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ log.info("start load faceDb...");
+ loadFaceDb();
+ log.info("Load faceDb success!");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+ }
+ }
+
+ @Override
+ public FaceDetectedResult detect(String imagePath) throws Exception {
+ // 将图片路径转换为 BufferedImage
+ BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
+ return convertToFaceDetectedResult(seetaResult);
+ }
+
+ @Override
+ public FaceDetectedResult detect(InputStream imageInputStream) throws Exception {
+ BufferedImage image = ImageIO.read(imageInputStream);
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ SeetaRect[] seetaResult = NativeLoader.seetaFace6SDK.detect(imageData);
+ return convertToFaceDetectedResult(seetaResult);
+ }
+
+ @Override
+ public float[] featureExtraction(String imagePath) throws Exception {
+ BufferedImage image = ImageIO.read(new File(Paths.get(imagePath).toAbsolutePath().toString()));
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
+ }
+
+ @Override
+ public float[] featureExtraction(InputStream inputStream) throws Exception {
+ BufferedImage image = ImageIO.read(inputStream);
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ return NativeLoader.seetaFace6SDK.extractMaxFace(imageData);
+ }
+
+ @Override
+ public float calculSimilar(float[] feature1, float[] feature2) throws Exception {
+ return NativeLoader.seetaFace6SDK.calculateSimilarity(feature1, feature2);
+ }
+
+ @Override
+ public float featureComparison(String imagePath1, String imagePath2) throws Exception {
+ return featureComparison(new FileInputStream(Paths.get(imagePath1).toAbsolutePath().toString()),
+ new FileInputStream(Paths.get(imagePath2).toAbsolutePath().toString()));
+ }
+
+ @Override
+ public float featureComparison(InputStream inputStream1, InputStream inputStream2) throws Exception {
+ BufferedImage image1 = ImageIO.read(inputStream1);
+ BufferedImage image2 = ImageIO.read(inputStream2);
+ SeetaImageData imageData1 = new SeetaImageData(image1.getWidth(), image1.getHeight(), 3);
+ imageData1.data = ImageUtils.getMatrixBGR(image1);
+
+ SeetaImageData imageData2 = new SeetaImageData(image2.getWidth(), image2.getHeight(), 3);
+ imageData2.data = ImageUtils.getMatrixBGR(image2);
+ //裁剪
+ byte[][] cropImg1 = NativeLoader.seetaFace6SDK.crop(imageData1);
+ byte[][] cropImg2 = NativeLoader.seetaFace6SDK.crop(imageData2);
+ if(cropImg1 == null || cropImg1.length == 0){
+ throw new Exception("未发现人脸");
+ }
+ if(cropImg2 == null || cropImg2.length == 0){
+ throw new Exception("未发现人脸");
+ }
+
+ BufferedImage cropImage1 = ImageUtils.bgrToBufferedImage(cropImg1[0], 256, 256);
+ BufferedImage cropImage2 = ImageUtils.bgrToBufferedImage(cropImg2[0], 256, 256);
+
+ SeetaImageData cropImageData1 = new SeetaImageData(cropImage1.getWidth(), cropImage1.getHeight(), 3);
+ cropImageData1.data = ImageUtils.getMatrixBGR(cropImage1);
+ SeetaImageData cropImageData2 = new SeetaImageData(cropImage2.getWidth(), cropImage2.getHeight(), 3);
+ cropImageData2.data = ImageUtils.getMatrixBGR(cropImage2);
+ return NativeLoader.seetaFace6SDK.compare(cropImageData1, cropImageData2);
+ }
+
+
+ /**
+ * 转换为FaceDetectedResult
+ * @param seetaResult
+ * @return
+ */
+ private FaceDetectedResult convertToFaceDetectedResult(SeetaRect[] seetaResult){
+ FaceDetectedResult faceDetectedResult = new FaceDetectedResult();
+ List RectangleList = new ArrayList();
+ List probabilities = new ArrayList();
+ if(seetaResult != null && seetaResult.length > 0){
+ for(SeetaRect rect : seetaResult){
+ Rectangle rectangle = new Rectangle();
+ List pointList = new ArrayList<>();
+ pointList.add(new Point(rect.x,rect.y));
+ pointList.add(new Point(rect.x + rect.width,rect.y));
+ pointList.add(new Point(rect.x,rect.y + rect.height));
+ pointList.add(new Point(rect.x + rect.width,rect.y + rect.height));
+ rectangle.setPointList(pointList);
+ rectangle.setHeight(rect.height);
+ rectangle.setWidth(rect.width);
+ RectangleList.add(rectangle);
+ probabilities.add(new Double(rect.score));
+ }
+ }
+ faceDetectedResult.setProbabilities(probabilities);
+ faceDetectedResult.setRectangles(RectangleList);
+ return faceDetectedResult;
+ }
+
+ @Override
+ public boolean register(String key, String imagePath) throws Exception {
+ return register(key, new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
+ }
+
+
+ @Override
+ public boolean register(String key, InputStream inputStream) throws Exception {
+ if(!checkFaceDb()){
+ throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
+ }
+ //裁剪人脸
+ BufferedImage image = ImageIO.read(inputStream);
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ byte[][] bytes = NativeLoader.seetaFace6SDK.crop(imageData);
+ if (bytes == null || bytes.length == 0) {
+ log.info("register face fail: key={}, error=no valid face", key);
+ return false;
+ }
+ long index = NativeLoader.seetaFace6SDK.registerCroppedFace(bytes[0]);
+ if (index < 0) {
+ log.info("register face fail: key={}, index={}", key, index);
+ return false;
+ }
+ //持久化到sqlite数据库
+ FaceData face = new FaceData();
+ face.setKey(key);
+ face.setIndex(index);
+ face.setImgData(bytes[0]);
+ new FaceDao(config.getFaceDbPath()).save(face);
+ return true;
+ }
+
+ public boolean register(String key, FaceData faceData) throws Exception {
+ long index = NativeLoader.seetaFace6SDK.registerCroppedFace(faceData.getImgData());
+ if (index < 0) {
+ log.info("register face fail: key={}, index={}", key, index);
+ return false;
+ }
+ int rows = new FaceDao(config.getFaceDbPath()).updateIndex(index, faceData);
+ return rows > 0;
+ }
+
+
+
+ @Override
+ public FaceResult search(String imagePath) throws Exception {
+ return search(new FileInputStream(Paths.get(imagePath).toAbsolutePath().toString()));
+ }
+
+ @Override
+ public FaceResult search(InputStream inputStream) throws Exception{
+ if(!checkFaceDb()){
+ throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
+ }
+ BufferedImage image = ImageIO.read(inputStream);
+ SeetaImageData imageData = new SeetaImageData(image.getWidth(), image.getHeight(), 3);
+ imageData.data = ImageUtils.getMatrixBGR(image);
+ RecognizeResult recognizeResult = NativeLoader.seetaFace6SDK.query(imageData);
+ return searchFaceDb(recognizeResult);
+
+ }
+
+ @Override
+ public long removeRegister(String... keys) throws Exception {
+ if(!checkFaceDb()){
+ throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
+ }
+ List list = new FaceDao(config.getFaceDbPath()).findIndexList(keys);
+ if (list == null) {
+ return 0;
+ }
+ long[] array = list.stream().mapToLong(Long::longValue).toArray();
+ long rows = NativeLoader.seetaFace6SDK.delete(array);
+ new FaceDao(config.getFaceDbPath()).deleteFace(keys);
+ return rows;
+ }
+
+ @Override
+ public long clearFace() throws Exception{
+ if(!checkFaceDb()){
+ throw new Exception("未找到人脸库,无法使用此功能(请检查是否配置人脸库路径)");
+ }
+ long rows = NativeLoader.seetaFace6SDK.delete(new long[]{-1});
+ new FaceDao(config.getFaceDbPath()).deleteAll();
+ return rows;
+ }
+
+ /**
+ * 检查是否存在人脸库
+ * @return
+ */
+ private boolean checkFaceDb(){
+ if(Objects.nonNull(config) && StringUtils.isNotBlank(config.getFaceDbPath())){
+ File file = new File(config.getFaceDbPath());
+ return file.exists() && file.isFile();
+ }
+ return false;
+ }
+
+ private FaceResult searchFaceDb(RecognizeResult recognizeResult) throws SQLException, ClassNotFoundException {
+ if(recognizeResult != null && recognizeResult.index >= 0){
+ String key = new FaceDao(config.getFaceDbPath()).findKeyByIndex(recognizeResult.index);
+ return new FaceResult(key, recognizeResult.similar);
+ }
+ return null;
+ }
+
+
+ /**
+ * 加载人脸库
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ private void loadFaceDb() throws SQLException, ClassNotFoundException {
+ if(!checkFaceDb()){
+ log.info("未配置人脸库");
+ return;
+ }
+ //分页查询人脸库
+ int pageNo = 0, pageSize = 100;
+ while (true) {
+ List list = new FaceDao(config.getFaceDbPath()).findFace(pageNo, pageSize);
+ if (list == null) {
+ break;
+ }
+ list.forEach(face -> {
+ try {
+ register(face.getKey(), face);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ if (list.size() < pageSize) {
+ break;
+ }
+ pageNo++;
+ }
+ }
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java
new file mode 100644
index 0000000..deaa1b6
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/dao/FaceDao.java
@@ -0,0 +1,151 @@
+package cn.smartjavaai.face.dao;
+
+import cn.smartjavaai.face.entity.FaceData;
+import cn.smartjavaai.face.sqllite.RowMapper;
+import cn.smartjavaai.face.sqllite.SqliteHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 人脸库持久层
+ * @author dwj
+ */
+@Slf4j
+public class FaceDao {
+
+ private static final String TABLE_NAME_IMG = "face";
+
+
+ private String dbFilePath;
+
+ public FaceDao(String dbFilePath) {
+ this.dbFilePath = dbFilePath;
+ }
+
+
+ public void save(FaceData faceData) throws SQLException, ClassNotFoundException {
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{faceData.getIndex(),faceData
+ .getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
+ }
+
+ /**
+ * 使用index查询key
+ *
+ * @param index
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public String findKeyByIndex(int index) throws SQLException, ClassNotFoundException {
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ return sqliteHelper.executeQuery("select \"key\" from " + TABLE_NAME_IMG + " where \"index\"=" + index);
+ }
+
+ /**
+ * 删除全部
+ *
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public long deleteAll() throws SQLException, ClassNotFoundException {
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ long rows = sqliteHelper.executeUpdate("delete from " + TABLE_NAME_IMG);
+ return rows;
+ }
+
+ /**
+ * 查询index
+ *
+ * @param keys
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public List findIndexList(String... keys) throws SQLException, ClassNotFoundException {
+ // 使用 Stream API
+ String inKeys = Arrays.stream(keys)
+ .map(s -> "'" + s + "'")
+ .reduce((s1, s2) -> s1 + "," + s2)
+ .orElse("");
+ String sql = "select \"index\" from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
+ log.info("sql:{}", sql.toString());
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ return sqliteHelper.executeQuery(sql, new RowMapper() {
+ @Override
+ public Long mapRow(ResultSet rs, int index) throws SQLException {
+ return rs.getLong(1);
+ }
+ });
+ }
+
+ /**
+ * 删除人脸
+ * @param keys
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public boolean deleteFace(String... keys) throws SQLException, ClassNotFoundException {
+ String inKeys = Arrays.stream(keys)
+ .map(s -> "'" + s + "'")
+ .reduce((s1, s2) -> s1 + "," + s2)
+ .orElse("");
+
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ String sql = "delete from " + TABLE_NAME_IMG + " where \"key\" in (" + inKeys + ")";
+ log.info("sql:{}", sql.toString());
+ sqliteHelper.executeUpdate(sql);
+ return true;
+ }
+
+ /**
+ * 分页查询人脸
+ * @param pageNo
+ * @param pageSize
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public List findFace(int pageNo, int pageSize) throws SQLException, ClassNotFoundException {
+ String sql = "select \"key\",\"img_data\",\"width\",\"height\",\"channel\" from " + TABLE_NAME_IMG +
+ " limit " + pageNo * pageSize + "," + pageSize;
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ return sqliteHelper.executeQuery(sql, new RowMapper() {
+ @Override
+ public FaceData mapRow(ResultSet rs, int index) throws SQLException {
+ FaceData face = new FaceData();
+ face.setKey(rs.getString("key"));
+ face.setImgData(rs.getBytes("img_data"));
+ face.setWidth(rs.getInt("width"));
+ face.setHeight(rs.getInt("height"));
+ face.setChannel(rs.getInt("channel"));
+ return face;
+ }
+ });
+ }
+
+ /**
+ * 更新index
+ * @param index
+ * @param faceData
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public int updateIndex(long index,FaceData faceData) throws SQLException, ClassNotFoundException {
+ SqliteHelper sqliteHelper = new SqliteHelper(dbFilePath);
+ return sqliteHelper.executeUpdate("INSERT OR REPLACE INTO " + TABLE_NAME_IMG + " (\"index\",\"key\",\"img_data\",\"width\",\"height\",\"channel\") VALUES (?,?,?,?,?,?)", new Object[]{index,faceData
+ .getKey(), faceData.getImgData(), faceData.getWidth(), faceData.getHeight(), faceData.getChannel()});
+ }
+
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java
new file mode 100644
index 0000000..2f33f1f
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceData.java
@@ -0,0 +1,19 @@
+package cn.smartjavaai.face.entity;
+
+import lombok.Data;
+
+/**
+ * 人脸数据
+ * @author dwj
+ */
+@Data
+public class FaceData {
+
+ private String key;
+ private long index;
+ private byte[] imgData;
+ private int width = 256;
+ private int height = 256;
+ private int channel = 3;
+
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceResult.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceResult.java
new file mode 100644
index 0000000..6fd9a33
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/entity/FaceResult.java
@@ -0,0 +1,22 @@
+package cn.smartjavaai.face.entity;
+
+import lombok.Data;
+
+/**
+ * 人脸查询结果
+ * @author dwj
+ */
+@Data
+public class FaceResult {
+
+ private String key;
+ private float similar;
+
+ public FaceResult() {
+ }
+
+ public FaceResult(String key, float similar) {
+ this.key = key;
+ this.similar = similar;
+ }
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/ResultSetExtractor.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/ResultSetExtractor.java
new file mode 100644
index 0000000..f693ff9
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/ResultSetExtractor.java
@@ -0,0 +1,15 @@
+package cn.smartjavaai.face.sqllite;
+
+
+import java.sql.ResultSet;
+
+/**
+ * ResultSetExtractor
+ * @author dwj
+ * @param
+ */
+public interface ResultSetExtractor {
+
+ public abstract T extractData(ResultSet rs);
+
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/RowMapper.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/RowMapper.java
new file mode 100644
index 0000000..77589e6
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/RowMapper.java
@@ -0,0 +1,14 @@
+package cn.smartjavaai.face.sqllite;
+
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * RowMapper
+ * @author dwj
+ * @param
+ */
+public interface RowMapper {
+ public abstract T mapRow(ResultSet rs, int index) throws SQLException;
+}
diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/SqliteHelper.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/SqliteHelper.java
new file mode 100644
index 0000000..cee9c9b
--- /dev/null
+++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/sqllite/SqliteHelper.java
@@ -0,0 +1,296 @@
+package cn.smartjavaai.face.sqllite;
+
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+
+/**
+ * sqlite帮助类
+ * @author dwj
+ */
+@Slf4j
+public class SqliteHelper {
+
+ private Connection connection;
+ private Statement statement;
+ private ResultSet resultSet;
+ private String dbFilePath;
+
+ /**
+ * 构造函数
+ * @param dbFilePath sqlite db 文件路径
+ * @throws ClassNotFoundException
+ * @throws SQLException
+ */
+ public SqliteHelper(String dbFilePath) throws ClassNotFoundException, SQLException {
+ this.dbFilePath = dbFilePath;
+ connection = getConnection(dbFilePath);
+ }
+
+ /**
+ * 获取数据库连接
+ * @param dbFilePath db文件路径
+ * @return 数据库连接
+ * @throws ClassNotFoundException
+ * @throws SQLException
+ */
+ public Connection getConnection(String dbFilePath) throws ClassNotFoundException, SQLException {
+ Connection conn = null;
+ Class.forName("org.sqlite.JDBC");
+ conn = DriverManager.getConnection("jdbc:sqlite:" + dbFilePath);
+ return conn;
+ }
+
+ /**
+ * 执行sql查询
+ * @param sql sql select 语句
+ * @param rse 结果集处理类对象
+ * @return 查询结果
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public T executeQuery(String sql, ResultSetExtractor rse) throws SQLException, ClassNotFoundException {
+ try {
+ resultSet = getStatement().executeQuery(sql);
+ T rs = rse.extractData(resultSet);
+ return rs;
+ } finally {
+ destroyed();
+ }
+ }
+
+ /**
+ * 执行select查询,返回结果列表
+ *
+ * @param sql sql select 语句
+ * @param rm 结果集的行数据处理类对象
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public List executeQuery(String sql, RowMapper rm) throws SQLException, ClassNotFoundException {
+ List rsList = new ArrayList();
+ try {
+ resultSet = getStatement().executeQuery(sql);
+ while (resultSet.next()) {
+ rsList.add(rm.mapRow(resultSet, resultSet.getRow()));
+ }
+ } finally {
+ destroyed();
+ }
+ return rsList;
+ }
+
+ /**
+ * 简单查询某个字段
+ * @param sql
+ * @return
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public String executeQuery(String sql) throws SQLException, ClassNotFoundException {
+ try {
+ resultSet = getStatement().executeQuery(sql);
+ if(resultSet.next()){
+ return resultSet.getString(1);
+ }
+ return null;
+ } finally {
+ destroyed();
+ }
+ }
+
+ /**
+ * 执行数据库更新sql语句
+ * @param sql
+ * @return 更新行数
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public int executeUpdate(String sql) throws SQLException, ClassNotFoundException {
+ try {
+ int c = getStatement().executeUpdate(sql);
+ return c;
+ } finally {
+ destroyed();
+ }
+
+ }
+
+ /**
+ * 执行多个sql更新语句
+ * @param sqls
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public void executeUpdate(String...sqls) throws SQLException, ClassNotFoundException {
+ try {
+ for (String sql : sqls) {
+ getStatement().executeUpdate(sql);
+ }
+ } finally {
+ destroyed();
+ }
+ }
+
+ /**
+ * 执行数据库更新 sql List
+ * @param sqls sql列表
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public void executeUpdate(List sqls) throws SQLException, ClassNotFoundException {
+ try {
+ for (String sql : sqls) {
+ getStatement().executeUpdate(sql);
+ }
+ } finally {
+ destroyed();
+ }
+ }
+
+ private Connection getConnection() throws ClassNotFoundException, SQLException {
+ if (null == connection) connection = getConnection(dbFilePath);
+ return connection;
+ }
+
+ private Statement getStatement() throws SQLException, ClassNotFoundException {
+ if (null == statement) statement = getConnection().createStatement();
+ return statement;
+ }
+
+ /**
+ * 数据库资源关闭和释放
+ */
+ public void destroyed() {
+ try {
+ if (null != statement) {
+ statement.close();
+ statement = null;
+ }
+
+ if (null != connection) {
+ connection.close();
+ connection = null;
+ }
+
+ if (null != resultSet) {
+ resultSet.close();
+ resultSet = null;
+ }
+ } catch (SQLException e) {
+ log.error("Sqlite数据库关闭时异常", e);
+ }
+ }
+
+ /**
+ * 执行select查询,返回结果列表
+ *
+ * @param sql sql select 语句
+ * @param clazz 实体泛型
+ * @return 实体集合
+ * @throws SQLException 异常信息
+ * @throws ClassNotFoundException 异常信息
+ */
+ public List executeQueryList(String sql, Class clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
+ List rsList = new ArrayList();
+ try {
+ resultSet = getStatement().executeQuery(sql);
+ while (resultSet.next()) {
+ T t = clazz.newInstance();
+ for (Field field : t.getClass().getDeclaredFields()) {
+ field.setAccessible(true);
+ field.set(t,resultSet.getObject(field.getName()));
+ }
+ rsList.add(t);
+ }
+ } finally {
+ destroyed();
+ }
+ return rsList;
+ }
+
+ /**
+ * 执行sql查询,适用单条结果集
+ * @param sql sql select 语句
+ * @param clazz 结果集处理类对象
+ * @return 查询结果
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public T executeQuery(String sql, Class clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
+ try {
+ resultSet = getStatement().executeQuery(sql);
+ T t = clazz.newInstance();
+ for (Field field : t.getClass().getDeclaredFields()) {
+ field.setAccessible(true);
+ field.set(t,resultSet.getObject(field.getName()));
+ }
+ return t;
+ } finally {
+ destroyed();
+ }
+ }
+
+ /**
+ * 执行数据库更新sql语句
+ * @param tableName 表名
+ * @param param key-value键值对,key:表中字段名,value:值
+ * @return 更新行数
+ * @throws SQLException
+ * @throws ClassNotFoundException
+ */
+ public int executeInsertOrUpdate(String tableName, Map param) throws SQLException, ClassNotFoundException {
+ try {
+ StringBuffer sql = new StringBuffer();
+ sql.append("INSERT OR REPLACE INTO ");
+ sql.append(tableName);
+ sql.append(" ( ");
+ for (String key : param.keySet()) {
+ sql.append("'" + key + "'");
+ sql.append(",");
+ }
+ sql.delete(sql.length()-1,sql.length());
+ sql.append(") VALUES ( ");
+ for (String key : param.keySet()) {
+ sql.append("'");
+ sql.append(param.get(key));
+ sql.append("',");
+ }
+ sql.delete(sql.length()-1,sql.length());
+ sql.append(");");
+ log.info("sql:{}", sql.toString());
+ int c = getStatement().executeUpdate(sql.toString());
+ return c;
+ } finally {
+ destroyed();
+ }
+ }
+
+ public int executeUpdate(String sql, Object[] args) throws SQLException, ClassNotFoundException {
+ Connection conn = getConnection();
+ if (args == null || args.length == 0) {
+ try (Statement stmt = conn.createStatement()) {
+ return stmt.executeUpdate(sql);
+ } finally {
+ destroyed();
+ }
+ } else {
+ try (PreparedStatement stmt = conn.prepareStatement(sql)) {
+ for (int i = 0; i < args.length; i++) {
+ stmt.setObject(i + 1, args[i]);
+ }
+ return stmt.executeUpdate();
+ } finally {
+ destroyed();
+ }
+ }
+ }
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java b/smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
new file mode 100644
index 0000000..ad2d7dc
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
@@ -0,0 +1,144 @@
+package com.seetaface;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+
+/**
+ * 依赖库加载器
+ * @author dwj
+ */
+@Slf4j
+public class NativeLoader {
+
+
+ private static Path tempNativeDir;
+ private static final String[] WIN_LIBS = {"tennis","tennis_haswell","tennis_pentium","tennis_sandy_bridge","SeetaAuthorize","SeetaFaceAntiSpoofingX600","SeetaFaceDetector600","SeetaFaceLandmarker600","SeetaFaceRecognizer610","SeetaFace6JNI"};
+ private static final String[] LINUX_CENTOS_LIBS = {"libmain.so"};
+ private static final String[] LINUX_UBUNTU_LIBS = {"libdependency1.so", "libdependency2.so", "libmain.so"};
+
+ private static final String TEMP_DIR = "smartjavaai-native-libs";
+
+ public static SeetaFace6JNI seetaFace6SDK;
+
+
+
+ public static void loadNativeLibraries(String modelPath) {
+ try {
+ // 创建临时目录
+ tempNativeDir = Files.createTempDirectory(TEMP_DIR);
+ log.info("create temp native directory: " + tempNativeDir.toAbsolutePath().toString());
+
+ // 获取当前平台库列表
+ String libDir = getLibDir();
+ String[] libNames = getPlatformLibs(libDir);
+
+ // 批量提取库文件
+ for (String libName : libNames) {
+ extractLibrary(libName,libDir);
+ }
+
+ String separator = System.getProperty("path.separator");
+ String sysLib = System.getProperty("java.library.path");
+ if (sysLib.endsWith(separator)) {
+ System.setProperty("java.library.path", sysLib + tempNativeDir);
+ } else {
+ System.setProperty("java.library.path", sysLib + separator + tempNativeDir);
+ }
+ try {
+ //使java.library.path生效
+ Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
+ sysPathsField.setAccessible(true);
+ sysPathsField.set(null, null);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ // 按顺序加载库(确保依赖关系)
+ for (String libName : libNames) {
+ System.loadLibrary(libName);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Native library loading failed", e);
+ }
+ }
+
+
+ private static String[] getPlatformLibs(String libDir) {
+ if (libDir.contains("windows")) return WIN_LIBS;
+ if (libDir.contains("centos")) return LINUX_CENTOS_LIBS;
+ if (libDir.contains("ubuntu")) return LINUX_UBUNTU_LIBS;
+ throw new UnsupportedOperationException("Unsupported OS");
+ }
+
+ /**
+ * 拷贝依赖库到临时目录
+ * @param libName
+ * @param libDir
+ * @throws IOException
+ */
+ private static void extractLibrary(String libName,String libDir) throws IOException {
+ String resourcePath = "/native" + libDir + "/" + libName + ".dll";
+ try (InputStream in = NativeLoader.class.getResourceAsStream(resourcePath)) {
+ if (in == null) throw new FileNotFoundException(resourcePath);
+
+ Path targetPath = tempNativeDir.resolve(libName);
+ Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
+ log.info("copy target path success : " + targetPath.toAbsolutePath().toString());
+
+ // 设置可执行权限
+ if (!System.getProperty("os.name").toLowerCase().contains("win")) {
+ targetPath.toFile().setExecutable(true);
+ }
+ }
+ }
+
+ /**
+ * 获取依赖库目录
+ * @return
+ */
+ private static String getLibDir() {
+ String osName = System.getProperty("os.name").toLowerCase();
+ if (osName.contains("win")) {
+ return "/windows";
+ } else if (osName.contains("linux")) {
+ String linuxOsName = getLinuxOsName();
+ if(StringUtils.isBlank(linuxOsName)){
+ throw new UnsupportedOperationException("Unsupported platform");
+ };
+ if(linuxOsName.contains("ubuntu")){
+ return "/linux/ubuntu";
+ }else if(linuxOsName.contains("centos")){
+ return "/linux/centos";
+ }
+ }
+ throw new UnsupportedOperationException("Unsupported platform");
+ }
+
+
+
+ /**
+ * 获取linux系统名称
+ * @return
+ */
+ private static String getLinuxOsName(){
+ try (BufferedReader reader = new BufferedReader(new FileReader("/etc/os-release"))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("ID=")) {
+ String distro = line.substring(3).replace("\"", "").trim();
+ return distro;
+ }
+ }
+ } catch (IOException e) {
+ System.out.println("Failed to read /etc/os-release: " + e.getMessage());
+ }
+ return null;
+ }
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java b/smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
new file mode 100644
index 0000000..e3cfd6d
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
@@ -0,0 +1,127 @@
+package com.seetaface;
+
+
+import com.seetaface.model.RecognizeResult;
+import com.seetaface.model.SeetaImageData;
+import com.seetaface.model.SeetaPointF;
+import com.seetaface.model.SeetaRect;
+
+/**
+ * seetaface6 sdk
+ * @author dwj
+ */
+public class SeetaFace6JNI {
+
+ /**
+ * 初始化,指定人脸识别模型文件目录
+ *
+ * @param modelDir
+ * @return
+ */
+
+ public native boolean initModel(String modelDir);
+
+ /**
+ * 检测人脸
+ *
+ * @param img
+ * @return
+ */
+ public native SeetaRect[] detect(SeetaImageData img);
+
+ /**
+ * 根据人脸检测关键点
+ * 关键定定位输入的是原始图片和人脸检测结果,给出指定人脸上的关键点的依次坐标。
+ * 这里检测到的5点坐标循序依次为,左眼中心、右眼中心、鼻尖、左嘴角和右嘴角。
+ * 注意这里的左右是基于图片内容的左右,并不是图片中人的左右,即左眼中心就是图片中左边的眼睛的中心。
+ *
+ * @param img
+ * @param faces
+ * @return
+ */
+ public native SeetaPointF[] mark(SeetaImageData img, SeetaRect faces);
+
+ /**
+ * 1 v 1 人脸比对
+ *
+ * @param img1
+ * @param img2
+ * @return 相似度范围在0~1,返回负数表示出错
+ */
+ public native float compare(SeetaImageData img1, SeetaImageData img2);
+
+ /**
+ * 提取人脸区域特性
+ * @param face crop方法返回的人脸图像
+ * @return
+ */
+ public native float[] extractCroppedFace(byte[] face);
+
+ /**
+ * 提取一个图像中最大人脸的特征
+ * @param img
+ * @return
+ */
+ public native float[] extractMaxFace(SeetaImageData img);
+
+ /**
+ * 计算两个特性的相似度
+ * @param features1
+ * @param features2
+ * @return
+ */
+ public native float calculateSimilarity(float[] features1, float[] features2);
+
+ /**
+ * 注册人脸
+ *
+ * @param img
+ * @return The returned value is the index of face database. Reture -1 if failed
+ */
+ public native long register(SeetaImageData img);
+
+ /**
+ * 注册裁剪后的人脸,推荐使用该方法
+ * @param bytes
+ * @return
+ */
+ public native long registerCroppedFace(byte[] bytes);
+
+ /**
+ * 从人脸库中搜索,返回相似度最高的索引
+ *
+ * @param img
+ * @return index saves the index of face databese, which is same as the retured value by Register. similar saves the most similar.
+ */
+ public native RecognizeResult query(SeetaImageData img);
+
+ /**
+ * 用裁剪后的人脸进行搜索
+ * @param bytes
+ * @return
+ */
+ public native RecognizeResult queryByCroppedFace(byte[] bytes);
+
+ /**
+ * 将人脸从数据库中删除
+ * @param index -1: 删除所有
+ * @return 返回删除记录数
+ */
+ public native long delete(long[] index);
+
+ /**
+ * 人脸提取
+ *
+ * @param img
+ * @return The returned value is face data. Reture null if failed
+ */
+ public native byte[][] crop(SeetaImageData img);
+
+ /**
+ * 图片活体检测
+ * @param img
+ * @return
+ */
+ public native int predictImage(SeetaImageData img);
+
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/model/RecognizeResult.java b/smartjavaai-face/src/main/java/com/seetaface/model/RecognizeResult.java
new file mode 100644
index 0000000..09593d8
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/model/RecognizeResult.java
@@ -0,0 +1,10 @@
+package com.seetaface.model;
+
+
+/**
+ * 人脸识别结果
+ */
+public class RecognizeResult {
+ public int index;
+ public float similar;
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/model/SeetaImageData.java b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaImageData.java
new file mode 100644
index 0000000..8b59514
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaImageData.java
@@ -0,0 +1,27 @@
+package com.seetaface.model;
+
+/**
+ * 人脸识别参数
+ * @author dwj
+ */
+public class SeetaImageData {
+ public SeetaImageData() {
+
+ }
+
+ public SeetaImageData(int width, int height, int channels) {
+ this.data = new byte[width * height * channels];
+ this.width = width;
+ this.height = height;
+ this.channels = channels;
+ }
+
+ public SeetaImageData(int width, int height) {
+ this(width, height, 3);
+ }
+
+ public byte[] data;
+ public int width;
+ public int height;
+ public int channels;
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/model/SeetaPointF.java b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaPointF.java
new file mode 100644
index 0000000..bf16153
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaPointF.java
@@ -0,0 +1,18 @@
+package com.seetaface.model;
+
+/**
+ * SeetaPointF
+ * @author dwj
+ */
+public class SeetaPointF {
+ public double x;
+ public double y;
+
+ @Override
+ public String toString() {
+ return "{" +
+ "x=" + x +
+ ", y=" + y +
+ '}';
+ }
+}
diff --git a/smartjavaai-face/src/main/java/com/seetaface/model/SeetaRect.java b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaRect.java
new file mode 100644
index 0000000..7e994e3
--- /dev/null
+++ b/smartjavaai-face/src/main/java/com/seetaface/model/SeetaRect.java
@@ -0,0 +1,24 @@
+package com.seetaface.model;
+
+/**
+ * SeetaPointF
+ * @author dwj
+ */
+public class SeetaRect {
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ public float score;
+
+ @Override
+ public String toString() {
+ return "SeetaRect{" +
+ "x=" + x +
+ ", y=" + y +
+ ", width=" + width +
+ ", height=" + height +
+ ", score=" + score +
+ '}';
+ }
+}
diff --git a/smartjavaai-seetaface6-lib/pom.xml b/smartjavaai-seetaface6-lib/pom.xml
new file mode 100644
index 0000000..369977a
--- /dev/null
+++ b/smartjavaai-seetaface6-lib/pom.xml
@@ -0,0 +1,124 @@
+
+
+ 4.0.0
+
+ ink.numberone
+ smartjavaai-parent
+ 1.0.5
+
+
+ smartjavaai-seetaface6-lib
+
+
+ 11
+ 11
+ UTF-8
+
+
+ SmartJavaAI
+ https://github.com/geekwenjie/SmartJavaAI
+
+
+ MIT License
+ https://opensource.org/licenses/MIT
+
+
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.4.0
+ true
+
+ dengwenjie
+ true
+ ${project.groupId}:${project.artifactId}:${project.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.1.0
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.1.0
+
+ ${java.home}/bin/javadoc
+ none
+
+ -Xdoclint:none
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.1.0
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+ scm:git:git://github.com/geekwenjie/SmartJavaAI.git
+ scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git
+ http://github.com/geekwenjie/SmartJavaAI/tree/master
+
+
+
+
+
+ dengwenjie
+ https://s01.oss.sonatype.org/content/repositories/snapshots
+
+
+ dengwenjie
+ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+
+ dengwenjie
+ 775747758@qq.com
+
+ Project Manager
+ Architect
+
+
+
+
+
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAgePredictor600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAgePredictor600.so
new file mode 100644
index 0000000..cee2324
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAgePredictor600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAuthorize.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAuthorize.so
new file mode 100644
index 0000000..2560a78
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaAuthorize.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaEyeStateDetector200.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaEyeStateDetector200.so
new file mode 100644
index 0000000..862b21a
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaEyeStateDetector200.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceAntiSpoofingX600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceAntiSpoofingX600.so
new file mode 100644
index 0000000..da0d784
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceAntiSpoofingX600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceDetector600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceDetector600.so
new file mode 100644
index 0000000..501087e
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceDetector600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceLandmarker600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceLandmarker600.so
new file mode 100644
index 0000000..e3263c7
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceLandmarker600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceRecognizer610.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceRecognizer610.so
new file mode 100644
index 0000000..c023622
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceRecognizer610.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceTracking600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceTracking600.so
new file mode 100644
index 0000000..93265fa
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaFaceTracking600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaGenderPredictor600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaGenderPredictor600.so
new file mode 100644
index 0000000..e23969f
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaGenderPredictor600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaMaskDetector200.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaMaskDetector200.so
new file mode 100644
index 0000000..dce9f73
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaMaskDetector200.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaPoseEstimation600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaPoseEstimation600.so
new file mode 100644
index 0000000..bd17c6d
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaPoseEstimation600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaQualityAssessor300.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaQualityAssessor300.so
new file mode 100644
index 0000000..6a1f8a0
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libSeetaQualityAssessor300.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis.so
new file mode 100644
index 0000000..06fee2b
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_haswell.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_haswell.so
new file mode 100644
index 0000000..3e357d0
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_haswell.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_pentium.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_pentium.so
new file mode 100644
index 0000000..f60e405
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_pentium.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_sandy_bridge.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_sandy_bridge.so
new file mode 100644
index 0000000..3a0474d
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/centos/libtennis_sandy_bridge.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAgePredictor600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAgePredictor600.so
new file mode 100644
index 0000000..0e489bd
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAgePredictor600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAuthorize.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAuthorize.so
new file mode 100644
index 0000000..446a8ad
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaAuthorize.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaEyeStateDetector200.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaEyeStateDetector200.so
new file mode 100644
index 0000000..7d74b9e
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaEyeStateDetector200.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceAntiSpoofingX600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceAntiSpoofingX600.so
new file mode 100644
index 0000000..128751e
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceAntiSpoofingX600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceDetector600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceDetector600.so
new file mode 100644
index 0000000..52307f0
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceDetector600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceLandmarker600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceLandmarker600.so
new file mode 100644
index 0000000..92f230e
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceLandmarker600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceRecognizer610.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceRecognizer610.so
new file mode 100644
index 0000000..d967d29
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceRecognizer610.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceTracking600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceTracking600.so
new file mode 100644
index 0000000..bc647bf
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaFaceTracking600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaGenderPredictor600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaGenderPredictor600.so
new file mode 100644
index 0000000..9a26839
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaGenderPredictor600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaMaskDetector200.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaMaskDetector200.so
new file mode 100644
index 0000000..a894911
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaMaskDetector200.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaPoseEstimation600.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaPoseEstimation600.so
new file mode 100644
index 0000000..0730ebf
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaPoseEstimation600.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaQualityAssessor300.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaQualityAssessor300.so
new file mode 100644
index 0000000..0e6b50d
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libSeetaQualityAssessor300.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis.so
new file mode 100644
index 0000000..37c2c3e
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_haswell.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_haswell.so
new file mode 100644
index 0000000..4a7c4d3
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_haswell.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_pentium.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_pentium.so
new file mode 100644
index 0000000..6bab905
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_pentium.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_sandy_bridge.so b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_sandy_bridge.so
new file mode 100644
index 0000000..3fcc830
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/linux/ubuntu/libtennis_sandy_bridge.so differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaAuthorize.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaAuthorize.dll
new file mode 100644
index 0000000..69b5554
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaAuthorize.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.dll
new file mode 100644
index 0000000..0a12041
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.lib b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.lib
new file mode 100644
index 0000000..567cfac
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFace6JNI.lib differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.dll
new file mode 100644
index 0000000..da1492f
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.lib b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.lib
new file mode 100644
index 0000000..e988c24
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceAntiSpoofingX600.lib differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.dll
new file mode 100644
index 0000000..0ef7d2b
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.lib b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.lib
new file mode 100644
index 0000000..3eae7d3
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceDetector600.lib differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.dll
new file mode 100644
index 0000000..1fed8c1
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.lib b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.lib
new file mode 100644
index 0000000..052ea01
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceLandmarker600.lib differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.dll
new file mode 100644
index 0000000..5a2c20c
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.lib b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.lib
new file mode 100644
index 0000000..2a54e36
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/SeetaFaceRecognizer610.lib differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis.dll
new file mode 100644
index 0000000..2ddaabe
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_haswell.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_haswell.dll
new file mode 100644
index 0000000..87107b7
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_haswell.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_pentium.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_pentium.dll
new file mode 100644
index 0000000..4d34908
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_pentium.dll differ
diff --git a/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_sandy_bridge.dll b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_sandy_bridge.dll
new file mode 100644
index 0000000..3e2fe78
Binary files /dev/null and b/smartjavaai-seetaface6-lib/src/main/resources/native/windows/tennis_sandy_bridge.dll differ