diff --git a/README.md b/README.md index 6ffa60d..7ec3480 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - + star @@ -230,7 +230,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 ### 1、环境要求 -- Java 版本:**JDK 11或更高版本** +- Java 版本:**JDK 8或更高版本** - 操作系统:不同模型支持的系统不一样,具体请查看文档 ### 2、Maven @@ -240,7 +240,7 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 cn.smartjavaai smartjavaai-all - 1.0.12 + 1.0.13 ``` ### 3、完整示例代码 @@ -273,7 +273,15 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 🚀 **如果这个项目对你有帮助,别忘了点个 Star ⭐!你的支持是我持续优化升级的动力!** ❤️ -## 更新日志 +## 近期更新日志 + +## [v1.0.13] - 2025-05-17 +- 支持 JDK8 环境运行 +- 引入离线依赖,支持完全离线使用 +- 优化 FaceNet 人脸比对性能,提升比对速度 +- 支持带 Alpha 通道的 4 通道图片检测 +- 目标检测:新增 YOLOv12 官方模型支持 +- 目标检测:支持加载自训练模型进行推理 ## [v1.0.12] - 2025-05-09 - 新增图片与视频活体检测 @@ -297,9 +305,6 @@ SmartJavaAI是专为JAVA 开发者打造的一个功能丰富、开箱即用的 - 修复若干已知问题 - 支持自定义选择使用 GPU 或 CPU 运算 - 人脸识别模块新增多种接口,功能更加完善 -## [v1.0.6] - 2025-04-01 -- 修复人脸识别算法facenet-pytorch实现方式 -- 优化Seetaface6算法,兼容jdk高版本 diff --git a/examples/pom.xml b/examples/pom.xml index e4926d2..f9c4ab6 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -12,7 +12,7 @@ 11 11 UTF-8 - 1.0.12 + 1.0.13 smartai.examples.face.facerec.RetinaFaceDemo @@ -104,6 +104,14 @@ + + ai.djl.pytorch + pytorch-jni + 2.5.1-0.32.0 + runtime + + + org.bytedeco @@ -132,6 +140,15 @@ ${javacv.platform.windows-x86_64} + + ai.djl.pytorch + pytorch-native-cpu + ${djl.platform.windows-x86_64} + 2.5.1 + runtime + + + @@ -161,6 +178,14 @@ ${javacv.platform.linux-x86_64} + + ai.djl.pytorch + pytorch-native-cpu + ${djl.platform.linux-x86_64} + 2.5.1 + runtime + + @@ -190,6 +215,14 @@ ${javacv.platform.macosx-arm64} + + ai.djl.pytorch + pytorch-native-cpu + ${djl.platform.osx-aarch64} + 2.5.1 + runtime + + @@ -220,8 +253,13 @@ ${javacv.platform.linux-arm64} - - + + ai.djl.pytorch + pytorch-native-cpu-precxx11 + ${djl.platform.linux-aarch64} + 2.5.1 + runtime + diff --git a/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java b/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java index a1dcd7c..67bf62a 100644 --- a/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java +++ b/examples/src/main/java/smartai/examples/face/facerec/FaceNetDemo.java @@ -54,14 +54,17 @@ public class FaceNetDemo { @Test public void testExtractFeaturesWithCustomConfig(){ try { - //人脸特征提取模型 - FaceModel faceModel = FaceModelFactory.getInstance().getModel( - new FaceModelConfig(FaceModelEnum.FACENET_FEATURE_EXTRACTION)); + //人脸模型参数 + FaceModelConfig config = new FaceModelConfig(); + config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION); //人脸特征提取参数 FaceExtractConfig extractConfig = new FaceExtractConfig(); //人脸检测模型配置 - extractConfig.setDetectModelConfig(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE)); - List faceResult = faceModel.extractFeatures("src/main/resources/kana1.jpg",extractConfig); + extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE))); + config.setExtractConfig(extractConfig); + //人脸特征提取模型 + FaceModel faceModel = FaceModelFactory.getInstance().getModel(config); + List faceResult = faceModel.extractFeatures("src/main/resources/kana1.jpg"); log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult)); }catch (Exception e){ e.printStackTrace(); @@ -94,13 +97,16 @@ public class FaceNetDemo { @Test public void testExtractTopFaceFeatureWithCustomConfig(){ try { - //人脸特征提取模型 - FaceModel faceModel = FaceModelFactory.getInstance().getModel( - new FaceModelConfig(FaceModelEnum.FACENET_FEATURE_EXTRACTION)); + //人脸模型参数 + FaceModelConfig config = new FaceModelConfig(); + config.setModelEnum(FaceModelEnum.FACENET_FEATURE_EXTRACTION); //人脸特征提取参数 FaceExtractConfig extractConfig = new FaceExtractConfig(); //人脸检测模型配置 - extractConfig.setDetectModelConfig(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE)); + extractConfig.setDetectModel(FaceModelFactory.getInstance().getModel(new FaceModelConfig(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE))); + config.setExtractConfig(extractConfig); + //人脸特征提取模型 + FaceModel faceModel = FaceModelFactory.getInstance().getModel(config); float[] faceResult = faceModel.extractTopFaceFeature("src/main/resources/kana1.jpg"); log.info("人脸特征提取结果:{}", JSONObject.toJSONString(faceResult)); }catch (Exception e){ diff --git a/examples/src/main/java/smartai/examples/objectdetection/ObjectDetection.java b/examples/src/main/java/smartai/examples/objectdetection/ObjectDetection.java index f8cd784..4fc321a 100644 --- a/examples/src/main/java/smartai/examples/objectdetection/ObjectDetection.java +++ b/examples/src/main/java/smartai/examples/objectdetection/ObjectDetection.java @@ -13,8 +13,8 @@ import ai.djl.repository.zoo.ZooModel; import ai.djl.training.util.ProgressBar; import cn.smartjavaai.common.entity.DetectionResponse; import cn.smartjavaai.common.enums.DeviceEnum; -import cn.smartjavaai.objectdetection.DetectorModelConfig; -import cn.smartjavaai.objectdetection.DetectorModelEnum; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; +import cn.smartjavaai.objectdetection.enums.DetectorModelEnum; import cn.smartjavaai.objectdetection.exception.DetectionException; import cn.smartjavaai.objectdetection.model.DetectorModel; import cn.smartjavaai.objectdetection.model.ObjectDetectionModelFactory; @@ -62,7 +62,7 @@ public class ObjectDetection { @Test public void objectDetection2(){ DetectorModelConfig config = new DetectorModelConfig(); - config.setModelEnum(DetectorModelEnum.SSD_300_RESNET50);//检测模型,目前支持19种模型 + config.setModelEnum(DetectorModelEnum.SSD_300_RESNET50);//检测模型,目前支持19种预置模型 DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config); DetectionResponse detectionResponse = detectorModel.detect("src/main/resources/dog_bike_car.jpg"); log.info("目标检测结果:{}", JSONObject.toJSONString(detectionResponse)); @@ -110,5 +110,35 @@ public class ObjectDetection { } + /** + * 使用yolo官方模型检测 + */ + @Test + public void objectDetectionWithOfficialModel(){ + DetectorModelConfig config = new DetectorModelConfig(); + //也支持YoloV8:YOLOV8_OFFICIAL 模型可以从文档中提供的地址下载 + config.setModelEnum(DetectorModelEnum.YOLOV12_OFFICIAL);//检测模型,目前支持19种模型 + // 指定模型路径,需要更改为自己的模型路径 + config.setModelPath("/Users/xxx/Documents/develop/face_model/yolov12n.onnx"); + DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config); + //一定要将yolo官方的类别文件:synset.txt(文档中下载)放在模型同目录下,否则报错 + detectorModel.detectAndDraw("src/main/resources/object_detection.jpg","output/object_detection_detected.png"); + } + + /** + * 使用自己训练的模型检测 + */ + @Test + public void objectDetectionWithCustomModel(){ + DetectorModelConfig config = new DetectorModelConfig(); + //也支持YoloV8:YOLOV8_CUSTOM 模型需要自己训练,训练教程可以查看文档 + config.setModelEnum(DetectorModelEnum.YOLOV12_CUSTOM);//自定义YOLOV12模型 + // 指定模型路径,需要更改为自己的模型路径 + config.setModelPath("/Users/xxx/Documents/develop/fire_model/best.onnx"); + DetectorModel detectorModel = ObjectDetectionModelFactory.getInstance().getModel(config); + //一定要将类别文件:synset.txt 放在模型同目录下,否则报错(具体请参看文档) + detectorModel.detectAndDraw("/Users/xxx/Downloads/test.jpg","output/test_detected.jpg"); + } + } diff --git a/pom.xml b/pom.xml index 88955f5..16d9dd9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 pom SmartJavaAI @@ -19,8 +19,8 @@ - 11 - 11 + 8 + 8 UTF-8 0.32.0 @@ -39,13 +39,13 @@ cn.smartjavaai smartjavaai-common - 1.0.12 + 1.0.13 cn.smartjavaai smartjavaai-face - 1.0.12 + 1.0.13 @@ -95,7 +95,7 @@ org.testng testng - 7.10.2 + 7.4.0 test @@ -260,7 +260,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-all/pom.xml b/smartjavaai-all/pom.xml index 53e144a..4f53fb8 100644 --- a/smartjavaai-all/pom.xml +++ b/smartjavaai-all/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 smartjavaai-all - 1.0.12 + 1.0.13 ${project.artifactId} SmartJavaAI https://github.com/geekwenjie/SmartJavaAI @@ -23,8 +23,8 @@ - 11 - 11 + + UTF-8 true @@ -111,7 +111,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-bom/pom.xml b/smartjavaai-bom/pom.xml index ad810da..d83389d 100644 --- a/smartjavaai-bom/pom.xml +++ b/smartjavaai-bom/pom.xml @@ -6,17 +6,17 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 - 1.0.12 + 1.0.13 smartjavaai-bom smartjavaai-bom 统一版本管理的 BOM 包,同时支持 import 和全量依赖 - 11 - 11 + + UTF-8 @@ -77,7 +77,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-common/pom.xml b/smartjavaai-common/pom.xml index 572d218..89f8293 100644 --- a/smartjavaai-common/pom.xml +++ b/smartjavaai-common/pom.xml @@ -6,7 +6,7 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 smartjavaai-common @@ -58,7 +58,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-face/pom.xml b/smartjavaai-face/pom.xml index f3f3008..31189d9 100644 --- a/smartjavaai-face/pom.xml +++ b/smartjavaai-face/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 smartjavaai-face - 1.0.12 + 1.0.13 smartjavaai-face SmartJavaAI https://github.com/geekwenjie/SmartJavaAI @@ -22,8 +22,8 @@ - 11 - 11 + + UTF-8 true 1.5.8 @@ -87,7 +87,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceExtractConfig.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceExtractConfig.java index 150a99a..53aa3a4 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceExtractConfig.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceExtractConfig.java @@ -1,5 +1,6 @@ package cn.smartjavaai.face.config; +import cn.smartjavaai.face.model.facerec.FaceModel; import lombok.Data; /** @@ -21,17 +22,17 @@ public class FaceExtractConfig { private boolean align = true; /** - * 人脸检测模型配置 + * 人脸检测模型 */ - private FaceModelConfig detectModelConfig; + private FaceModel detectModel; public FaceExtractConfig() { } - public FaceExtractConfig(boolean cropFace, boolean align, FaceModelConfig detectModelConfig) { + public FaceExtractConfig(boolean cropFace, boolean align, FaceModel detectModel) { this.cropFace = cropFace; this.align = align; - this.detectModelConfig = detectModelConfig; + this.detectModel = detectModel; } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceModelConfig.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceModelConfig.java index 9e16364..74fd113 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceModelConfig.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/config/FaceModelConfig.java @@ -52,6 +52,11 @@ public class FaceModelConfig { */ private int gpuId = 0; + /** + * 人脸特征提取配置 + */ + private FaceExtractConfig extractConfig; + public FaceModelConfig() { } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/AbstractFaceModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/AbstractFaceModel.java index 36f87bc..07ea934 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/AbstractFaceModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/AbstractFaceModel.java @@ -140,21 +140,6 @@ public abstract class AbstractFaceModel implements FaceModel { throw new UnsupportedOperationException("默认不支持该功能"); } - @Override - public List extractFeatures(BufferedImage image, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } - - @Override - public List extractFeatures(String imagePath, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } - - @Override - public List extractFeatures(byte[] imageData, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } - @Override public float[] extractTopFaceFeature(BufferedImage image) { throw new UnsupportedOperationException("默认不支持该功能"); @@ -170,18 +155,5 @@ public abstract class AbstractFaceModel implements FaceModel { throw new UnsupportedOperationException("默认不支持该功能"); } - @Override - public float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } - @Override - public float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } - - @Override - public float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config) { - throw new UnsupportedOperationException("默认不支持该功能"); - } } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceModel.java index 92e56f0..bf07399 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FaceModel.java @@ -199,30 +199,6 @@ public interface FaceModel { */ List extractFeatures(BufferedImage image); - /** - * 特征提取(使用自定义配置) - * 强制裁剪操作 - * @param image BufferedImage - * @param config - * @return - */ - List extractFeatures(BufferedImage image, FaceExtractConfig config); - - /** - * 特征提取(使用自定义配置) - * @param imagePath 图片路径 - * @param config - * @return - */ - List extractFeatures(String imagePath, FaceExtractConfig config); - - /** - * 特征提取(使用自定义配置) - * @param imageData 图片字节流 - * @param config - * @return - */ - List extractFeatures(byte[] imageData, FaceExtractConfig config); /** * 提取分数最高人脸特征(使用默认配置) @@ -245,27 +221,6 @@ public interface FaceModel { */ float[] extractTopFaceFeature(byte[] imageData); - /** - * 提取分数最高人脸特征(使用自定义配置) - * @param image BufferedImage - * @return - */ - float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config); - /** - * 提取分数最高人脸特征(使用自定义配置) - * @param imagePath 图片路径 - * @param config - * @return - */ - float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config); - - /** - * 提取分数最高人脸特征(使用自定义配置) - * @param imageData 图片字节流 - * @param config - * @return - */ - float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config); } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FeatureExtractionModel.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FeatureExtractionModel.java index c90dc72..9081b18 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FeatureExtractionModel.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/FeatureExtractionModel.java @@ -59,6 +59,8 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo private ZooModel model; + private FaceModelConfig config; + public static final List mean = Arrays.asList( 127.5f / 255.0f, @@ -75,10 +77,21 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo */ @Override public void loadModel(FaceModelConfig config) { + if(Objects.isNull(config)){ + throw new FaceException("config为null"); + } + if(Objects.isNull(config.getExtractConfig())){ + config.setExtractConfig(getDefaultConfig()); + }else{ + if(Objects.isNull(config.getExtractConfig().getDetectModel())){ + throw new FaceException("请设置人脸检测模型"); + } + } Device device = null; if(!Objects.isNull(config.getDevice())){ device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); } + this.config = config; String normalize = mean.stream().map(Object::toString).collect(Collectors.joining(",")); Criteria faceFeatureCriteria = Criteria.builder() @@ -162,7 +175,8 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo } float[] feature1 = extractTopFaceFeature(imagePath1); float[] feature2 = extractTopFaceFeature(imagePath2); - return calculSimilar(feature1, feature2); + float ret = calculSimilar(feature1, feature2); + return ret; } @@ -194,37 +208,18 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo FaceExtractConfig config = new FaceExtractConfig(); FaceModelConfig detectModelConfig = new FaceModelConfig(); detectModelConfig.setModelEnum(FaceModelEnum.ULTRA_LIGHT_FAST_GENERIC_FACE); - config.setDetectModelConfig(detectModelConfig); + log.debug("创建默认检测模型:ULTRA_LIGHT_FAST_GENERIC_FACE"); + FaceModel detectModel = FaceModelFactory.getInstance().getModel(detectModelConfig); + log.debug("创建检测模型完毕"); + config.setDetectModel(detectModel); return config; } - @Override - public List extractFeatures(String imagePath) { - return extractFeatures(imagePath, getDefaultConfig()); - } - - @Override - public List extractFeatures(byte[] imageData) { - return extractFeatures(imageData, getDefaultConfig()); - } - @Override public List extractFeatures(BufferedImage image) { - return extractFeatures(image, getDefaultConfig()); - } - - @Override - public List extractFeatures(BufferedImage image, FaceExtractConfig config) { - if(Objects.isNull(config)){ - throw new FaceException("config为null"); - } List featureList = new ArrayList(); - if(Objects.isNull(config.getDetectModelConfig())){ - throw new FaceException("config.detectModelConfig为null"); - } - FaceModel faceModel = FaceModelFactory.getInstance().getModel(config.getDetectModelConfig()); - DetectionResponse detectedResult = faceModel.detect(image); + DetectionResponse detectedResult = config.getExtractConfig().getDetectModel().detect(image); if(Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getDetectionInfoList()) || detectedResult.getDetectionInfoList().isEmpty()){ throw new FaceException("未检测到人脸"); } @@ -237,7 +232,7 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo //裁剪人脸 Image subImage = djlImage.getSubImage(rectangle.getX(), rectangle.getY() , rectangle.getWidth() , rectangle.getHeight()); //人脸对齐 - if(config.isAlign()){ + if(config.getExtractConfig().isAlign()){ //获取子图中人脸关键点坐标 double[][] pointsArray = FaceUtils.facePoints(detectionInfo.getFaceInfo().getKeyPoints()); NDArray srcPoints = manager.create(pointsArray); @@ -263,8 +258,21 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo return featureList; } + @Override - public List extractFeatures(String imagePath, FaceExtractConfig config) { + public List extractFeatures(byte[] imageData) { + if(Objects.isNull(imageData)){ + throw new FaceException("图像无效"); + } + try { + return extractFeatures(ImageIO.read(new ByteArrayInputStream(imageData))); + } catch (IOException e) { + throw new FaceException("错误的图像", e); + } + } + + @Override + public List extractFeatures(String imagePath) { if(!FileUtils.isFileExists(imagePath)){ throw new FaceException("图像文件不存在"); } @@ -275,49 +283,15 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo } catch (IOException e) { throw new FaceException("无效图片路径", e); } - return extractFeatures(image, config); - } - - @Override - public List extractFeatures(byte[] imageData, FaceExtractConfig config) { - if(Objects.isNull(imageData)){ - throw new FaceException("图像无效"); - } - try { - return extractFeatures(ImageIO.read(new ByteArrayInputStream(imageData)), config); - } catch (IOException e) { - throw new FaceException("错误的图像", e); - } + return extractFeatures(image); } @Override public float[] extractTopFaceFeature(BufferedImage image) { - return extractTopFaceFeature(image, getDefaultConfig()); - } - - @Override - public float[] extractTopFaceFeature(String imagePath) { - return extractTopFaceFeature(imagePath, getDefaultConfig()); - } - - @Override - public float[] extractTopFaceFeature(byte[] imageData) { - return extractTopFaceFeature(imageData, getDefaultConfig()); - } - - @Override - public float[] extractTopFaceFeature(BufferedImage image, FaceExtractConfig config) { - if(Objects.isNull(config)){ - throw new FaceException("config为null"); - } - if(Objects.isNull(config.getDetectModelConfig())){ - throw new FaceException("config.detectModelConfig为null"); - } Image djlImage = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image)); float[] features = null; - if(config.isCropFace()){ - FaceModel faceModel = FaceModelFactory.getInstance().getModel(config.getDetectModelConfig()); - DetectionResponse detectedResult = faceModel.detect(image); + if(config.getExtractConfig().isCropFace()){ + DetectionResponse detectedResult = config.getExtractConfig().getDetectModel().detect(image); if(Objects.isNull(detectedResult) || Objects.isNull(detectedResult.getDetectionInfoList()) || detectedResult.getDetectionInfoList().isEmpty()){ throw new FaceException("未检测到人脸"); } @@ -327,7 +301,7 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo //裁剪人脸 Image subImage = djlImage.getSubImage(rectangle.getX(), rectangle.getY() , rectangle.getWidth() , rectangle.getHeight()); //人脸对齐 - if(config.isAlign()){ + if(config.getExtractConfig().isAlign()){ NDManager manager = NDManager.newBaseManager(); //获取子图中人脸关键点坐标 double[][] pointsArray = FaceUtils.facePoints(detectionInfo.getFaceInfo().getKeyPoints()); @@ -355,7 +329,7 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo } @Override - public float[] extractTopFaceFeature(String imagePath, FaceExtractConfig config) { + public float[] extractTopFaceFeature(String imagePath) { if(!FileUtils.isFileExists(imagePath)){ throw new FaceException("图像文件不存在"); } @@ -366,16 +340,16 @@ public class FeatureExtractionModel extends AbstractFaceModel implements AutoClo } catch (IOException e) { throw new FaceException("无效图片路径", e); } - return extractTopFaceFeature(image, config); + return extractTopFaceFeature(image); } @Override - public float[] extractTopFaceFeature(byte[] imageData, FaceExtractConfig config) { + public float[] extractTopFaceFeature(byte[] imageData) { if(Objects.isNull(imageData)){ throw new FaceException("图像无效"); } try { - return extractTopFaceFeature(ImageIO.read(new ByteArrayInputStream(imageData)), config); + return extractTopFaceFeature(ImageIO.read(new ByteArrayInputStream(imageData))); } catch (IOException e) { throw new FaceException("错误的图像", e); } diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6Model.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6Model.java index fabb2f7..0ae0013 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6Model.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/model/facerec/SeetaFace6Model.java @@ -629,6 +629,7 @@ public class SeetaFace6Model extends AbstractFaceModel { @Override public FaceResult search(BufferedImage image) { + long time1 = System.currentTimeMillis(); if(!ImageUtils.isImageValid(image)){ throw new FaceException("图像无效"); } @@ -648,7 +649,12 @@ public class SeetaFace6Model extends AbstractFaceModel { if(similarity[0] < config.getSimilarityThreshold()){ return null; } - return searchFaceDb(index[0], similarity[0]); + long time2 = System.currentTimeMillis(); + System.out.println("总耗时1:" + (time2 - time1) + " ms"); + FaceResult faceResult = searchFaceDb(index[0], similarity[0]); + long time3 = System.currentTimeMillis(); + System.out.println("总耗时2:" + (time3 - time2) + " ms"); + return faceResult; } catch (FaceException e) { throw e; } catch (Exception e) { diff --git a/smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/OpenCVUtils.java b/smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/OpenCVUtils.java index 1061ffe..878c5a3 100644 --- a/smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/OpenCVUtils.java +++ b/smartjavaai-face/src/main/java/cn/smartjavaai/face/utils/OpenCVUtils.java @@ -111,8 +111,26 @@ public class OpenCVUtils { public static Mat image2Mat(BufferedImage img) { int width = img.getWidth(); int height = img.getHeight(); + int channels; + + // 获取图像类型 + int imageType = img.getType(); + + // 判断是3通道还是4通道 + if (imageType == BufferedImage.TYPE_3BYTE_BGR) { + channels = 3; + } else if (imageType == BufferedImage.TYPE_4BYTE_ABGR || imageType == BufferedImage.TYPE_4BYTE_ABGR_PRE) { + channels = 4; + } else { + // 如果不是已知格式,强制转换为 3 通道 BGR + BufferedImage convertedImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + convertedImg.getGraphics().drawImage(img, 0, 0, null); + img = convertedImg; + channels = 3; + } + byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData(); - Mat mat = new Mat(height, width, CvType.CV_8UC3); + Mat mat = new Mat(height, width, CvType.CV_8UC(channels)); mat.put(0, 0, data); return mat; } diff --git a/smartjavaai-objectdetection/pom.xml b/smartjavaai-objectdetection/pom.xml index 6d6e156..2c60614 100644 --- a/smartjavaai-objectdetection/pom.xml +++ b/smartjavaai-objectdetection/pom.xml @@ -6,11 +6,11 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 smartjavaai-objectdetection - 1.0.12 + 1.0.13 smartjavaai-objectdetection SmartJavaAI https://github.com/geekwenjie/SmartJavaAI @@ -23,8 +23,8 @@ - 11 - 11 + + UTF-8 true @@ -72,7 +72,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelConfig.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/config/DetectorModelConfig.java similarity index 53% rename from smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelConfig.java rename to smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/config/DetectorModelConfig.java index 3d3a466..f1440d0 100644 --- a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelConfig.java +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/config/DetectorModelConfig.java @@ -1,6 +1,8 @@ -package cn.smartjavaai.objectdetection; +package cn.smartjavaai.objectdetection.config; import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.objectdetection.constant.DetectorConstant; +import cn.smartjavaai.objectdetection.enums.DetectorModelEnum; import lombok.Data; /** @@ -20,13 +22,25 @@ public class DetectorModelConfig { /** * 置信度阈值 */ - private float threshold = DetectorConfig.DEFAULT_THRESHOLD; + private float threshold = DetectorConstant.DEFAULT_THRESHOLD; /** * 设备类型 */ private DeviceEnum device; + /** + * 模型路径 + */ + private String modelPath; + + /** + * 候选框数量:默认为8400. 应设置0到8400之间的整数 + * 用于性能优化的关键参数,它通过限制模型后处理阶段需要处理的候选框(bounding boxes)数量来提高推理速度 + * 建议不低于1000 + */ + private int maxBox; + public DetectorModelConfig() { } diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorConfig.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/constant/DetectorConstant.java similarity index 62% rename from smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorConfig.java rename to smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/constant/DetectorConstant.java index a7502ac..fb83c6d 100644 --- a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorConfig.java +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/constant/DetectorConstant.java @@ -1,14 +1,17 @@ -package cn.smartjavaai.objectdetection; +package cn.smartjavaai.objectdetection.constant; /** * @author dwj * @date 2025/4/7 */ -public class DetectorConfig { +public class DetectorConstant { /** * 置信度阈值 */ public static final float DEFAULT_THRESHOLD = 0.5F; + + + } diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderFactory.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderFactory.java new file mode 100644 index 0000000..44cc0d2 --- /dev/null +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderFactory.java @@ -0,0 +1,43 @@ +package cn.smartjavaai.objectdetection.criteria; + +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.repository.zoo.Criteria; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; +import cn.smartjavaai.objectdetection.enums.DetectorModelEnum; +import cn.smartjavaai.objectdetection.exception.DetectionException; +import org.apache.commons.lang3.StringUtils; + +/** + * Criteria构建工厂 + * @author dwj + * @date 2025/5/14 + */ +public class CriteriaBuilderFactory { + + public static Criteria createCriteria(DetectorModelConfig config) { + //以下模型modelPath不允许为空 + if(config.getModelEnum() == DetectorModelEnum.YOLOV8_OFFICIAL || + config.getModelEnum() == DetectorModelEnum.YOLOV12_OFFICIAL || + config.getModelEnum() == DetectorModelEnum.YOLOV8_CUSTOM || + config.getModelEnum() == DetectorModelEnum.YOLOV12_CUSTOM){ + if(StringUtils.isBlank(config.getModelPath())){ + throw new DetectionException("modelPath is null"); + } + } + switch (config.getModelEnum()) { + case YOLOV8_OFFICIAL: + return new YoloCriteriaBuilder().buildCriteria(config); + case YOLOV12_OFFICIAL: + return new YoloCriteriaBuilder().buildCriteria(config); + case YOLOV8_CUSTOM: + return new YoloCriteriaBuilder().buildCriteria(config); + case YOLOV12_CUSTOM: + return new YoloCriteriaBuilder().buildCriteria(config); + // 其他类型 + default: + return new DJLModelCriteriaBuilder().buildCriteria(config); + } + } + +} diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderStrategy.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderStrategy.java new file mode 100644 index 0000000..f54c0b7 --- /dev/null +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/CriteriaBuilderStrategy.java @@ -0,0 +1,22 @@ +package cn.smartjavaai.objectdetection.criteria; + +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.repository.zoo.Criteria; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; + +/** + * 模型加载策略接口,用于根据不同模型类型构建对应的 DJL Criteria 实例 + * @author dwj + * @date 2025/5/14 + */ +public interface CriteriaBuilderStrategy { + + /** + * 根据模型类型构建对应的 DJL Criteria 实例 + * @param config + * @return + */ + Criteria buildCriteria(DetectorModelConfig config); + +} diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/DJLModelCriteriaBuilder.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/DJLModelCriteriaBuilder.java new file mode 100644 index 0000000..4554866 --- /dev/null +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/DJLModelCriteriaBuilder.java @@ -0,0 +1,40 @@ +package cn.smartjavaai.objectdetection.criteria; + +import ai.djl.Application; +import ai.djl.Device; +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.common.enums.DeviceEnum; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; +import cn.smartjavaai.objectdetection.constant.DetectorConstant; + +import java.util.Objects; + +/** + * DJL提供的Criteria 构建器 + * @author dwj + * @date 2025/5/14 + */ +public class DJLModelCriteriaBuilder implements CriteriaBuilderStrategy { + + private static final String DJL_MODEL_PREFIX = "djl://"; + + @Override + public Criteria buildCriteria(DetectorModelConfig config) { + Device device = null; + if(!Objects.isNull(config.getDevice())){ + device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); + } + Criteria criteria = Criteria.builder() + .optApplication(Application.CV.OBJECT_DETECTION) + .setTypes(Image.class, DetectedObjects.class) + .optArgument("threshold", config.getThreshold() > 0 ? config.getThreshold() : DetectorConstant.DEFAULT_THRESHOLD) + .optModelUrls(DJL_MODEL_PREFIX + config.getModelEnum().getModelUri()) + .optDevice(device) + .optProgress(new ProgressBar()) + .build(); + return criteria; + } +} diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/YoloCriteriaBuilder.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/YoloCriteriaBuilder.java new file mode 100644 index 0000000..11605ee --- /dev/null +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/criteria/YoloCriteriaBuilder.java @@ -0,0 +1,40 @@ +package cn.smartjavaai.objectdetection.criteria; + +import ai.djl.modality.cv.Image; +import ai.djl.modality.cv.output.DetectedObjects; +import ai.djl.modality.cv.translator.YoloV8TranslatorFactory; +import ai.djl.repository.zoo.Criteria; +import ai.djl.training.util.ProgressBar; +import cn.smartjavaai.objectdetection.constant.DetectorConstant; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; + +import java.nio.file.Paths; + +/** + * YOLO模型Criteria 构建器 + * @author dwj + * @date 2025/5/14 + */ +public class YoloCriteriaBuilder implements CriteriaBuilderStrategy { + @Override + public Criteria buildCriteria(DetectorModelConfig config) { + Criteria.Builder criteriaBuilder = Criteria.builder() + .setTypes(Image.class, DetectedObjects.class) + //.optModelUrls("/Users/wenjie/Documents/develop/face_model/yolo") + .optModelPath(Paths.get(config.getModelPath())) + .optEngine("OnnxRuntime") + .optArgument("width", 640) //将输入图像的宽度缩放为 640 像素 + .optArgument("height", 640) + .optArgument("resize", true) + .optArgument("toTensor", true) + .optArgument("applyRatio", true) + .optTranslatorFactory(new YoloV8TranslatorFactory()) + .optProgress(new ProgressBar()) + .optArgument("threshold", config.getThreshold() > 0 ? config.getThreshold() : DetectorConstant.DEFAULT_THRESHOLD); + if(config.getMaxBox() > 0){ + criteriaBuilder.optArgument("maxBox", config.getMaxBox()); + } + Criteria criteria = criteriaBuilder.build(); + return criteria; + } +} diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelEnum.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/enums/DetectorModelEnum.java similarity index 93% rename from smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelEnum.java rename to smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/enums/DetectorModelEnum.java index 8a8d15a..ad69fb4 100644 --- a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/DetectorModelEnum.java +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/enums/DetectorModelEnum.java @@ -1,4 +1,4 @@ -package cn.smartjavaai.objectdetection; +package cn.smartjavaai.objectdetection.enums; /** * 目标检测模型枚举 @@ -34,7 +34,14 @@ public enum DetectorModelEnum { YOLO3_DARKNET_COCO_608("ai.djl.mxnet/yolo/0.0.1/yolo3_darknet_coco_608"), YOLO3_MOBILENET_COCO_320("ai.djl.mxnet/yolo/0.0.1/yolo3_mobilenet_coco_320"), YOLO3_MOBILENET_COCO_416("ai.djl.mxnet/yolo/0.0.1/yolo3_mobilenet_coco_416"), - YOLO3_MOBILENET_COCO_608("ai.djl.mxnet/yolo/0.0.1/yolo3_mobilenet_coco_608"); + YOLO3_MOBILENET_COCO_608("ai.djl.mxnet/yolo/0.0.1/yolo3_mobilenet_coco_608"), + + YOLOV12_OFFICIAL(""), + YOLOV8_OFFICIAL(""), + + YOLOV8_CUSTOM(""), + + YOLOV12_CUSTOM(""); /** * 根据名称获取枚举 (忽略大小写和下划线变体) diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/DetectorModel.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/DetectorModel.java index 133a57c..0f23684 100644 --- a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/DetectorModel.java +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/DetectorModel.java @@ -1,41 +1,34 @@ package cn.smartjavaai.objectdetection.model; -import ai.djl.Application; -import ai.djl.Device; import ai.djl.MalformedModelException; import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.output.DetectedObjects; -import ai.djl.opencv.OpenCVImageFactory; +import ai.djl.modality.cv.translator.YoloV8TranslatorFactory; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ZooModel; import ai.djl.training.util.ProgressBar; -import ai.djl.translate.TranslateException; import cn.smartjavaai.common.entity.DetectionResponse; -import cn.smartjavaai.common.enums.DeviceEnum; -import cn.smartjavaai.common.pool.ModelPredictorPoolManager; import cn.smartjavaai.common.pool.PredictorFactory; import cn.smartjavaai.common.utils.FileUtils; import cn.smartjavaai.common.utils.ImageUtils; import cn.smartjavaai.common.utils.OpenCVUtils; -import cn.smartjavaai.objectdetection.DetectorConfig; -import cn.smartjavaai.objectdetection.DetectorModelConfig; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; +import cn.smartjavaai.objectdetection.constant.DetectorConstant; +import cn.smartjavaai.objectdetection.criteria.CriteriaBuilderFactory; import cn.smartjavaai.objectdetection.exception.DetectionException; import cn.smartjavaai.objectdetection.utils.DetectorUtils; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; -import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Duration; import java.util.Objects; /** @@ -50,23 +43,16 @@ public class DetectorModel implements AutoCloseable{ //private Predictor predictor; - private static final String DJL_MODEL_PREFIX = "djl://"; + private ObjectPool> predictorPool; public void loadModel(DetectorModelConfig config){ - Device device = null; - if(!Objects.isNull(config.getDevice())){ - device = config.getDevice() == DeviceEnum.CPU ? Device.cpu() : Device.gpu(); + if(Objects.isNull(config.getModelEnum())){ + throw new DetectionException("未配置模型枚举"); } - Criteria criteria = Criteria.builder() - .optApplication(Application.CV.OBJECT_DETECTION) - .setTypes(Image.class, DetectedObjects.class) - .optArgument("threshold", config.getThreshold() > 0 ? config.getThreshold() : DetectorConfig.DEFAULT_THRESHOLD) - .optModelUrls(DJL_MODEL_PREFIX + config.getModelEnum().getModelUri()) - .optDevice(device) - .optProgress(new ProgressBar()) - .build(); + Criteria criteria = CriteriaBuilderFactory.createCriteria(config); + try { model = criteria.loadModel(); // 创建池子:每个线程独享 Predictor diff --git a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/ObjectDetectionModelFactory.java b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/ObjectDetectionModelFactory.java index f41a3b9..cc99cb1 100644 --- a/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/ObjectDetectionModelFactory.java +++ b/smartjavaai-objectdetection/src/main/java/cn/smartjavaai/objectdetection/model/ObjectDetectionModelFactory.java @@ -1,12 +1,11 @@ package cn.smartjavaai.objectdetection.model; import cn.smartjavaai.common.config.Config; -import cn.smartjavaai.objectdetection.DetectorModelConfig; -import cn.smartjavaai.objectdetection.DetectorModelEnum; +import cn.smartjavaai.objectdetection.config.DetectorModelConfig; +import cn.smartjavaai.objectdetection.enums.DetectorModelEnum; import cn.smartjavaai.objectdetection.exception.DetectionException; import lombok.extern.slf4j.Slf4j; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; diff --git a/smartjavaai-ocr/pom.xml b/smartjavaai-ocr/pom.xml index 3087d92..0d11804 100644 --- a/smartjavaai-ocr/pom.xml +++ b/smartjavaai-ocr/pom.xml @@ -6,14 +6,14 @@ cn.smartjavaai smartjavaai-parent - 1.0.12 + 1.0.13 smartjavaai-ocr - 11 - 11 + + UTF-8 1.5.8 5.1.2-1.5.8 @@ -41,7 +41,7 @@ - 1.0.12 + 1.0.13 smartjavaai-ocr SmartJavaAI https://github.com/geekwenjie/SmartJavaAI @@ -84,7 +84,7 @@ maven-javadoc-plugin 3.1.0 - ${java.home}/bin/javadoc + none -Xdoclint:none