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