mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-09 19:18:52 +00:00
集成算法seetaface6
This commit is contained in:
189
README.md
189
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 @@
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
@@ -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) <sub>*(基于JDK11构建的完整可执行示例)*</sub>
|
||||
|
||||
|
||||
|
||||
|
||||
## 🙏 鸣谢
|
||||
|
||||
本项目在开发过程中借鉴或使用了以下优秀开源项目,特此致谢:
|
||||
|
||||
- **[Seetaface6JNI](https://gitee.com/cnsugar/seetaface6JNI)**
|
||||
---
|
||||
|
||||
|
||||
## 联系方式
|
||||
|
||||
如果您有任何问题或建议,欢迎联系:
|
||||
|
||||
BIN
db/faces-data.db
Normal file
BIN
db/faces-data.db
Normal file
Binary file not shown.
@@ -1,99 +0,0 @@
|
||||
# SmartJavaAI离线下载模型案例
|
||||
|
||||
**SmartJavaAI**如果未指定模型地址,系统将自动下载模型至本地。因此,无论模型是否通过离线方式下载,SmartJavaAI 最终都会在离线环境下运行模型。
|
||||
|
||||
### 1. 安装人脸算法依赖
|
||||
|
||||
在 Maven 项目的 `pom.xml` 中添加 SmartJavaAI的人脸算法依赖:
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
### 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) <sub>*(基于JDK11构建的完整可执行示例)*</sub>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 357 KiB |
@@ -43,7 +43,7 @@
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -62,6 +62,54 @@
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl.onnxruntime</groupId>
|
||||
<artifactId>onnxruntime-engine</artifactId>
|
||||
<version>0.20.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>example</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<configuration>
|
||||
<!--如果不想在打包的后缀加上assembly.xml中设置的id,可以加上下面的配置-->
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!-- 是否绑定依赖,将外部jar包依赖加入到classPath中 -->
|
||||
<addClasspath>true</addClasspath>
|
||||
<!-- 依赖前缀,与之前设置的文件夹路径要匹配 -->
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
<!-- 执行的主程序入口 -->
|
||||
<mainClass>smartai.examples.face.FaceDemo</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<!--绑定的maven操作-->
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>assembly</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
BIN
examples/src/main/resources/jsy.jpg
Normal file
BIN
examples/src/main/resources/jsy.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
54
pom.xml
54
pom.xml
@@ -6,12 +6,13 @@
|
||||
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
<packaging>pom</packaging>
|
||||
<description>SmartJavaAI</description>
|
||||
<modules>
|
||||
<module>smartjavaai-face</module>
|
||||
<module>smartjavaai-common</module>
|
||||
<module>smartjavaai-seetaface6-lib</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@@ -34,33 +35,19 @@
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||
<version>2.24.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ai.djl</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
@@ -176,6 +163,37 @@
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.4</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.17.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-face</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
<name>smartjavaai-face</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
@@ -25,6 +25,7 @@
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
</properties>
|
||||
|
||||
|
||||
@@ -34,11 +35,24 @@
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-seetaface6-lib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.36.0.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
|
||||
@@ -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("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Rectangle> RectangleList = new ArrayList<Rectangle>();
|
||||
List<Double> probabilities = new ArrayList<Double>();
|
||||
if(seetaResult != null && seetaResult.length > 0){
|
||||
for(SeetaRect rect : seetaResult){
|
||||
Rectangle rectangle = new Rectangle();
|
||||
List<Point> 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<Long> 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<FaceData> 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Long> 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<Long>() {
|
||||
@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<FaceData> 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<FaceData>() {
|
||||
@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()});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* ResultSetExtractor
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface ResultSetExtractor<T> {
|
||||
|
||||
public abstract T extractData(ResultSet rs);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.smartjavaai.face.sqllite;
|
||||
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* RowMapper
|
||||
* @author dwj
|
||||
* @param <T>
|
||||
*/
|
||||
public interface RowMapper<T> {
|
||||
public abstract T mapRow(ResultSet rs, int index) throws SQLException;
|
||||
}
|
||||
@@ -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> T executeQuery(String sql, ResultSetExtractor<T> 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 <T> List<T> executeQuery(String sql, RowMapper<T> rm) throws SQLException, ClassNotFoundException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
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<String> 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 <T> List<T> executeQueryList(String sql, Class<T> clazz) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||
List<T> rsList = new ArrayList<T>();
|
||||
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> T executeQuery(String sql, Class<T> 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<String,Object> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
Normal file
144
smartjavaai-face/src/main/java/com/seetaface/NativeLoader.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
127
smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
Normal file
127
smartjavaai-face/src/main/java/com/seetaface/SeetaFace6JNI.java
Normal file
@@ -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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.seetaface.model;
|
||||
|
||||
|
||||
/**
|
||||
* 人脸识别结果
|
||||
*/
|
||||
public class RecognizeResult {
|
||||
public int index;
|
||||
public float similar;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
124
smartjavaai-seetaface6-lib/pom.xml
Normal file
124
smartjavaai-seetaface6-lib/pom.xml
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ink.numberone</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-seetaface6-lib</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/licenses/MIT</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>dengwenjie</publishingServerId>
|
||||
<tokenAuth>true</tokenAuth>
|
||||
<deploymentName>${project.groupId}:${project.artifactId}:${project.version}</deploymentName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
|
||||
<doclint>none</doclint>
|
||||
<additionalJOptions>
|
||||
<additionalJOption>-Xdoclint:none</additionalJOption>
|
||||
</additionalJOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- 必须添加:SCM信息 -->
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/geekwenjie/SmartJavaAI.git</connection>
|
||||
<developerConnection>scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git</developerConnection>
|
||||
<url>http://github.com/geekwenjie/SmartJavaAI/tree/master</url>
|
||||
</scm>
|
||||
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>dengwenjie</name>
|
||||
<email>775747758@qq.com</email>
|
||||
<roles>
|
||||
<role>Project Manager</role>
|
||||
<role>Architect</role>
|
||||
</roles>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user