From 8cba5fe502b2c9879cba65fe75b2747fd3e43d58 Mon Sep 17 00:00:00 2001 From: cuizhibin Date: Thu, 3 Jul 2025 14:55:07 +0800 Subject: [PATCH] =?UTF-8?q?1.FilePathEnum=E5=A2=9E=E5=8A=A0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=B7=AF=E5=BE=84=E8=BD=AC=E5=AE=9E=E9=99=85=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=202.=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E5=8F=8A?= =?UTF-8?q?=E6=A3=80=E6=B5=8Binit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/pom.xml | 13 ++ .../com/dite/znpt/config/WebMvcConfig.java | 5 +- .../com/dite/znpt/config/YoloModelConfig.java | 45 ++++++ .../dite/znpt/config/YoloModelRegistry.java | 112 +++++++++++++ .../com/dite/znpt/domain/bo/Detection.java | 21 +++ .../com/dite/znpt/domain/bo/Letterbox.java | 73 +++++++++ .../com/dite/znpt/domain/bo/ODConfig.java | 55 +++++++ .../znpt/domain/entity/ModelConfigEntity.java | 56 +++++++ .../dite/znpt/domain/entity/PartEntity.java | 15 +- .../znpt/domain/vo/ModelConfigListReq.java | 41 +++++ .../dite/znpt/domain/vo/ModelConfigReq.java | 50 ++++++ .../dite/znpt/domain/vo/ModelConfigResp.java | 18 +++ .../znpt/domain/vo/ProjectTaskImportReq.java | 24 +-- .../znpt/enums/AttachBusinessTypeEnum.java | 1 + .../com/dite/znpt/enums/FilePathEnum.java | 21 ++- .../dite/znpt/mapper/ModelConfigMapper.java | 18 +++ .../java/com/dite/znpt/mapper/PartMapper.java | 4 +- .../dite/znpt/service/AttachInfoService.java | 1 - .../dite/znpt/service/ModelConfigService.java | 65 ++++++++ .../com/dite/znpt/service/PartService.java | 2 +- .../service/impl/AttachInfoServiceImpl.java | 12 +- .../impl/AudioFileInfoServiceImpl.java | 4 +- .../znpt/service/impl/DictServiceImpl.java | 2 +- .../service/impl/ImageCollectServiceImpl.java | 6 +- .../znpt/service/impl/ImageServiceImpl.java | 34 ++-- .../service/impl/ModelConfigServiceImpl.java | 123 +++++++++++++++ .../service/impl/MultiModelYoloService.java | 147 ++++++++++++++++++ .../znpt/service/impl/PartServiceImpl.java | 3 +- .../service/impl/ProjectTaskServiceImpl.java | 13 +- .../impl/VideoFileInfoServiceImpl.java | 16 +- .../java/com/dite/znpt/util/ModelUtil.java | 74 +++++++++ .../resources/mapper/ModelConfigMapper.xml | 34 ++++ .../web/controller/ModelConfigController.java | 83 ++++++++++ 33 files changed, 1105 insertions(+), 86 deletions(-) create mode 100644 core/src/main/java/com/dite/znpt/config/YoloModelConfig.java create mode 100644 core/src/main/java/com/dite/znpt/config/YoloModelRegistry.java create mode 100644 core/src/main/java/com/dite/znpt/domain/bo/Detection.java create mode 100644 core/src/main/java/com/dite/znpt/domain/bo/Letterbox.java create mode 100644 core/src/main/java/com/dite/znpt/domain/bo/ODConfig.java create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/ModelConfigEntity.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ModelConfigListReq.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ModelConfigReq.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ModelConfigResp.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/ModelConfigMapper.java create mode 100644 core/src/main/java/com/dite/znpt/service/ModelConfigService.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/ModelConfigServiceImpl.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/MultiModelYoloService.java create mode 100644 core/src/main/java/com/dite/znpt/util/ModelUtil.java create mode 100644 core/src/main/resources/mapper/ModelConfigMapper.xml create mode 100644 web/src/main/java/com/dite/znpt/web/controller/ModelConfigController.java diff --git a/core/pom.xml b/core/pom.xml index b90f9ba..94afd03 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -149,7 +149,20 @@ 1.3.0-91 compile + + + com.microsoft.onnxruntime + onnxruntime + 1.16.1 + + + + org.openpnp + opencv + 4.7.0-0 + + diff --git a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java index 7f8d651..c09ff82 100644 --- a/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java +++ b/core/src/main/java/com/dite/znpt/config/WebMvcConfig.java @@ -1,15 +1,12 @@ package com.dite.znpt.config; import cn.hutool.core.collection.ListUtil; -import com.dite.znpt.constant.Constants; import com.dite.znpt.enums.FilePathEnum; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -27,7 +24,7 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); for (FilePathEnum pathEnum : FilePathEnum.values()) { - registry.addResourceHandler(pathEnum.getUrlPath() + "**").addResourceLocations("file:" + pathEnum.getFileAbsolutePath()); + registry.addResourceHandler(pathEnum.getUrlPath() + "**").addResourceLocations("file:" + pathEnum.getFileAbsolutePathPrefix()); } } diff --git a/core/src/main/java/com/dite/znpt/config/YoloModelConfig.java b/core/src/main/java/com/dite/znpt/config/YoloModelConfig.java new file mode 100644 index 0000000..7e92b49 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/config/YoloModelConfig.java @@ -0,0 +1,45 @@ +package com.dite.znpt.config; + +import ai.onnxruntime.OrtEnvironment; +import ai.onnxruntime.OrtException; +import ai.onnxruntime.OrtSession; +import lombok.Getter; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Getter +@Configuration +public class YoloModelConfig { + + private OrtEnvironment env; + private OrtSession session; + private String[] labels; + private List colors; + + @PostConstruct + public void init() throws OrtException { + env = OrtEnvironment.getEnvironment(); + OrtSession.SessionOptions options = new OrtSession.SessionOptions(); + session = env.createSession("d:\\tmp\\best.onnx", options); + + // 解析 labels + String names = session.getMetadata().getCustomMetadata().get("names"); + Pattern pattern = Pattern.compile("'([^']*)'"); + Matcher matcher = pattern.matcher(names); + List labelList = new ArrayList<>(); + colors = new ArrayList<>(); + + Random random = new Random(); + while (matcher.find()) { + labelList.add(matcher.group(1)); + colors.add(new double[]{random.nextDouble()*256, random.nextDouble()*256, random.nextDouble()*256}); + } + labels = labelList.toArray(new String[0]); + } +} diff --git a/core/src/main/java/com/dite/znpt/config/YoloModelRegistry.java b/core/src/main/java/com/dite/znpt/config/YoloModelRegistry.java new file mode 100644 index 0000000..8d3584c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/config/YoloModelRegistry.java @@ -0,0 +1,112 @@ +package com.dite.znpt.config; + +import ai.onnxruntime.OrtEnvironment; +import ai.onnxruntime.OrtException; +import ai.onnxruntime.OrtSession; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.dite.znpt.domain.entity.ModelConfigEntity; +import com.dite.znpt.enums.FilePathEnum; +import com.dite.znpt.mapper.ModelConfigMapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +@Component +@RequiredArgsConstructor +public class YoloModelRegistry { + + private final ModelConfigMapper modelConfigMapper; + + private final Map sessionMap = new ConcurrentHashMap<>(); + private final Map metaMap = new ConcurrentHashMap<>(); + private final Map modelParamMap = new ConcurrentHashMap<>(); + @Getter + private final OrtEnvironment environment = OrtEnvironment.getEnvironment(); + + static { + // 加载opencv动态库 + nu.pattern.OpenCV.loadLocally(); + } + + @PostConstruct + public void loadModelsOnStartup() throws OrtException { + List configs = modelConfigMapper.selectList(Wrappers.emptyWrapper()); + for (ModelConfigEntity config : configs) { + loadModel(config); + } + } + + public void loadModel(ModelConfigEntity config) throws OrtException { + OrtSession.SessionOptions opts = new OrtSession.SessionOptions(); + // 使用gpu,需要本机安装过cuda,并修改pom.xml,不安装也能运行本程序 + // sessionOptions.addCUDA(0); + OrtSession session = environment.createSession(FilePathEnum.ATTACH.getFileAbsolutePath(config.getModelPath()), opts); + String labelStr = session.getMetadata().getCustomMetadata().get("names"); + + // label解析 + List labels = new ArrayList<>(); + List colors = new ArrayList<>(); + Pattern pattern = Pattern.compile("'([^']*)'"); + Matcher matcher = pattern.matcher(labelStr); + Random random = new Random(); + while (matcher.find()) { + labels.add(matcher.group(1)); + colors.add(new double[]{random.nextDouble() * 256, random.nextDouble() * 256, random.nextDouble() * 256}); + } + + sessionMap.put(config.getModelId(), session); + metaMap.put(config.getModelId(), new ModelMetadata(labels.toArray(new String[0]), colors, config)); + modelParamMap.put(config.getModelId(), config); + } + + public OrtSession getSession(String modelId) { + return sessionMap.get(modelId); + } + + public ModelMetadata getMetadata(String modelId) { + return metaMap.get(modelId); + } + + public ModelConfigEntity getModelConfig(String modelId) { + return modelParamMap.get(modelId); + } + + public void unloadModel(String modelId) { + OrtSession session = sessionMap.remove(modelId); + if (session != null) { + try { + session.close(); // 释放 ONNX runtime 资源 + } catch (OrtException e) { + log.warn("模型 {} 卸载时发生错误: {}", modelId, e.getMessage()); + } + } + metaMap.remove(modelId); + } + + public void reloadModel(ModelConfigEntity modelConfig) throws OrtException { + unloadModel(modelConfig.getModelId()); + loadModel(modelConfig); + } + + @Data + @AllArgsConstructor + public static class ModelMetadata { + private String[] labels; + private List colors; + private ModelConfigEntity config; + } +} + diff --git a/core/src/main/java/com/dite/znpt/domain/bo/Detection.java b/core/src/main/java/com/dite/znpt/domain/bo/Detection.java new file mode 100644 index 0000000..68e942c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/bo/Detection.java @@ -0,0 +1,21 @@ +package com.dite.znpt.domain.bo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Detection{ + + @ApiModelProperty("标签") + public String label; + @ApiModelProperty("分类id") + private Integer clsId; + @ApiModelProperty("位置") + private float[] bbox; + @ApiModelProperty("置信度") + private float confidence; +} diff --git a/core/src/main/java/com/dite/znpt/domain/bo/Letterbox.java b/core/src/main/java/com/dite/znpt/domain/bo/Letterbox.java new file mode 100644 index 0000000..b3fa640 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/bo/Letterbox.java @@ -0,0 +1,73 @@ +package com.dite.znpt.domain.bo; + +import lombok.Getter; +import lombok.Setter; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.Size; +import org.opencv.imgproc.Imgproc; + +public class Letterbox { + + @Setter + private Size newShape ; + private final double[] color = new double[]{114,114,114}; + private final Boolean auto = false; + private final Boolean scaleUp = true; + @Setter + private Integer stride = 32; + + @Getter + private double ratio; + @Getter + private double dw; + @Getter + private double dh; + + public Letterbox(int w,int h) { + this.newShape = new Size(w, h); + } + + public Letterbox() { + this.newShape = new Size(640, 640); + } + + + public Integer getWidth() { + return (int) this.newShape.width; + } + + public Integer getHeight() { + return (int) this.newShape.height; + } + + public Mat letterbox(Mat im) { // 调整图像大小和填充图像,使满足步长约束,并记录参数 + + int[] shape = {im.rows(), im.cols()}; // 当前形状 [height, width] + // Scale ratio (new / old) + double r = Math.min(this.newShape.height / shape[0], this.newShape.width / shape[1]); + if (!this.scaleUp) { // 仅缩小,不扩大(一且为了mAP) + r = Math.min(r, 1.0); + } + // Compute padding + Size newUnpad = new Size(Math.round(shape[1] * r), Math.round(shape[0] * r)); + double dw = this.newShape.width - newUnpad.width, dh = this.newShape.height - newUnpad.height; // wh 填充 + if (this.auto) { // 最小矩形 + dw = dw % this.stride; + dh = dh % this.stride; + } + dw /= 2; // 填充的时候两边都填充一半,使图像居于中心 + dh /= 2; + if (shape[1] != newUnpad.width || shape[0] != newUnpad.height) { // resize + Imgproc.resize(im, im, newUnpad, 0, 0, Imgproc.INTER_LINEAR); + } + int top = (int) Math.round(dh - 0.1), bottom = (int) Math.round(dh + 0.1); + int left = (int) Math.round(dw - 0.1), right = (int) Math.round(dw + 0.1); + // 将图像填充为正方形 + Core.copyMakeBorder(im, im, top, bottom, left, right, Core.BORDER_CONSTANT, new org.opencv.core.Scalar(this.color)); + this.ratio = r; + this.dh = dh; + this.dw = dw; + return im; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/bo/ODConfig.java b/core/src/main/java/com/dite/znpt/domain/bo/ODConfig.java new file mode 100644 index 0000000..689a7bd --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/bo/ODConfig.java @@ -0,0 +1,55 @@ +package com.dite.znpt.domain.bo; + +import java.util.*; + +public final class ODConfig { + + + public static final Integer lineThicknessRatio = 333; + public static final Double fontSizeRatio = 1080.0; + + private static final List default_names = new ArrayList<>(Arrays.asList( + "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", + "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", + "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", + "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", + "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", + "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", + "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", + "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", + "cake", "chair", "couch", "potted plant", "bed", "dining table", "toilet", + "tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", + "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", + "teddy bear", "hair drier", "toothbrush")); + + + private static final List names = new ArrayList<>(Arrays.asList( + "no_helmet", "helmet")); + + private final Map colors; + + public ODConfig() { + this.colors = new HashMap<>(); + default_names.forEach(name->{ + Random random = new Random(); + double[] color = {random.nextDouble()*256, random.nextDouble()*256, random.nextDouble()*256}; + colors.put(name, color); + }); + } + + public String getName(int clsId) { + return names.get(clsId); + } + + public double[] getColor(int clsId) { + return colors.get(getName(clsId)); + } + + public double[] getNameColor(String Name){ + return colors.get(Name); + } + + public double[] getOtherColor(int clsId) { + return colors.get(default_names.get(clsId)); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ModelConfigEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ModelConfigEntity.java new file mode 100644 index 0000000..549b9c8 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/ModelConfigEntity.java @@ -0,0 +1,56 @@ +package com.dite.znpt.domain.entity; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.dite.znpt.domain.AuditableEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 模型配置表实体类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("model_config") +@ApiModel(value="ModelConfigEntity对象", description="模型配置表") +public class ModelConfigEntity extends AuditableEntity implements Serializable { + + @Serial + private static final long serialVersionUID = -73052440757340126L; + + @ExcelProperty("模型id") + @ApiModelProperty("模型id") + @TableId(value = "model_id", type = IdType.ASSIGN_ID) + private String modelId; + + @ExcelProperty("模型名称") + @ApiModelProperty("模型名称") + @TableField("model_name") + private String modelName; + + @ExcelProperty("模型路径") + @ApiModelProperty("模型路径") + @TableField("model_path") + private String modelPath; + + @ExcelProperty("模型置信度") + @ApiModelProperty("模型置信度") + @TableField("conf_threshold") + private Float confThreshold; + + @ExcelProperty("非极大抑制") + @ApiModelProperty("非极大抑制") + @TableField("nms_threshold") + private Float nmsThreshold; +} + diff --git a/core/src/main/java/com/dite/znpt/domain/entity/PartEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/PartEntity.java index f602cbf..d29345d 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/PartEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/PartEntity.java @@ -1,24 +1,27 @@ package com.dite.znpt.domain.entity; -import java.io.Serializable; - -import com.baomidou.mybatisplus.annotation.*; +import com.alibaba.excel.annotation.ExcelProperty; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; import com.dite.znpt.domain.AuditableEntity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import com.alibaba.excel.annotation.ExcelProperty; + +import java.io.Serializable; /** * @author huise23 * @date 2025/04/11 23:17 - * @Description: 表实体类 + * @Description: 部件表实体类 */ @Data @EqualsAndHashCode(callSuper = false) @TableName("part") -@ApiModel(value="PartEntity对象", description="表") +@ApiModel(value="PartEntity对象", description="部件表") public class PartEntity extends AuditableEntity implements Serializable { private static final long serialVersionUID = -53853862365306266L; diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigListReq.java new file mode 100644 index 0000000..8944150 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigListReq.java @@ -0,0 +1,41 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 请求实体 + */ +@Data +@ApiModel("列表请求实体") +public class ModelConfigListReq implements Serializable { + + @Serial + private static final long serialVersionUID = -41204426418525667L; + + @ApiModelProperty("查询关键字") + private String keyword; + + @ApiModelProperty("Id") + private String modelId; + + @ApiModelProperty("模型名称") + private String modelName; + + @ApiModelProperty("模型路径") + private String modelPath; + + @ApiModelProperty("模型置信度") + private Float confThreshold; + + @ApiModelProperty("非极大抑制") + private Float nmsThreshold; + +} + diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigReq.java new file mode 100644 index 0000000..73a3742 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigReq.java @@ -0,0 +1,50 @@ +package com.dite.znpt.domain.vo; + +import com.dite.znpt.util.ValidationGroup; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.*; +import java.io.Serial; +import java.io.Serializable; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 模型配置表请求类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value="ModelConfig请求对象", description="模型配置表") +public class ModelConfigReq implements Serializable { + + @Serial + private static final long serialVersionUID = 930798215980875267L; + + @ApiModelProperty("模型id") + private String modelId; + + @NotBlank(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型名称不能为空") + @Size(max = 50, groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型名称长度不能超过50") + @ApiModelProperty("模型名称") + private String modelName; + + @NotEmpty(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型附件不能为空") + @ApiModelProperty("模型附件id") + private String attachId; + + @NotNull(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型置信度不能为空") + @Min(value = 0, groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型置信度只能在0-100之间") + @Max(value = 100, groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "模型置信度只能在0-100之间") + @ApiModelProperty("模型置信度") + private Float confThreshold; + + @NotNull(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "nms不能为空") + @Min(value = 0, groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "nms只能在0-1之间") + @Max(value = 1, groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "nms只能在0-1之间") + @ApiModelProperty("nms") + private Float nmsThreshold; +} + diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigResp.java new file mode 100644 index 0000000..7b7d588 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ModelConfigResp.java @@ -0,0 +1,18 @@ +package com.dite.znpt.domain.vo; + +import com.dite.znpt.domain.entity.ModelConfigEntity; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 响应实体 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("响应实体") +public class ModelConfigResp extends ModelConfigEntity { +} + diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectTaskImportReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectTaskImportReq.java index c2b6103..bc56499 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectTaskImportReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectTaskImportReq.java @@ -1,21 +1,13 @@ package com.dite.znpt.domain.vo; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.io.Serial; -import java.io.Serializable; - -import com.baomidou.mybatisplus.annotation.*; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import com.alibaba.excel.annotation.ExcelProperty; -import com.dite.znpt.util.ValidationGroup; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDate; /** * @author huise23 * @date 2025/06/27 14:21 @@ -60,11 +52,11 @@ public class ProjectTaskImportReq implements Serializable { @ExcelProperty(value = "实际结束时间") private LocalDate actualEndDate; - @ExcelProperty(value = "任务状态,0未开始,1进行中,2已结束") - private Integer status; + @ExcelProperty(value = "任务状态(未开始,进行中,已结束)") + private String status; - @ExcelProperty(value = "是否逾期,默认未逾期") - private Integer overdueStatus; + @ExcelProperty(value = "是否逾期(已逾期,未逾期)") + private String overdueStatus; @ExcelProperty(value = "备注") private String remark; diff --git a/core/src/main/java/com/dite/znpt/enums/AttachBusinessTypeEnum.java b/core/src/main/java/com/dite/znpt/enums/AttachBusinessTypeEnum.java index c5c0909..502a022 100644 --- a/core/src/main/java/com/dite/znpt/enums/AttachBusinessTypeEnum.java +++ b/core/src/main/java/com/dite/znpt/enums/AttachBusinessTypeEnum.java @@ -15,6 +15,7 @@ import java.util.List; public enum AttachBusinessTypeEnum { PROJECT_TASK("PROJECT_TASK", "项目任务"), INSURANCE_FILE("insurance", "保险文件"), + MODEL_FILE("model", "模型文件"), ; private final String code; private final String desc; diff --git a/core/src/main/java/com/dite/znpt/enums/FilePathEnum.java b/core/src/main/java/com/dite/znpt/enums/FilePathEnum.java index 596613a..702b30f 100644 --- a/core/src/main/java/com/dite/znpt/enums/FilePathEnum.java +++ b/core/src/main/java/com/dite/znpt/enums/FilePathEnum.java @@ -22,26 +22,37 @@ public enum FilePathEnum { private final String fileRelativePath; /** - * 功能描述:获取文件绝对路径 + * 功能描述:获取文件绝对路径前缀 * * @return {@link String } * @author cuizhibin * @date 2025/06/23 14:46 **/ - public String getFileAbsolutePath() { + public String getFileAbsolutePathPrefix() { return SpringUtil.getBean(Environment.class).getProperty("upload.save-path") + fileRelativePath + FileUtil.FILE_SEPARATOR; } /** - * 功能描述:获取图像下载路径 + * 功能描述:获取文件绝对路径全路径 + * + * @return {@link String } + * @author cuizhibin + * @date 2025/06/23 14:46 + **/ + public String getFileAbsolutePath(String fileDownPath) { + return FileUtil.normalize(getFileAbsolutePathPrefix() + StrUtil.removePrefix(fileDownPath, urlPath)); + } + + /** + * 功能描述:获取下载路径 * * @param absolutePath * @return {@link String } * @author cuizhibin * @date 2025/06/06 09:07 **/ - public String getImageDownPath(String absolutePath) { - String relativePath = StrUtil.removePrefix(absolutePath, getFileAbsolutePath()); + public String getFileDownPath(String absolutePath) { + String relativePath = StrUtil.removePrefix(absolutePath, getFileAbsolutePathPrefix()); return StrUtil.replace(urlPath.concat(relativePath), FileUtil.FILE_SEPARATOR, StrUtil.SLASH); } diff --git a/core/src/main/java/com/dite/znpt/mapper/ModelConfigMapper.java b/core/src/main/java/com/dite/znpt/mapper/ModelConfigMapper.java new file mode 100644 index 0000000..37906a4 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/ModelConfigMapper.java @@ -0,0 +1,18 @@ +package com.dite.znpt.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.domain.entity.ModelConfigEntity; +import com.dite.znpt.domain.vo.ModelConfigListReq; +import com.dite.znpt.domain.vo.ModelConfigResp; + +import java.util.List; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 模型配置表数据库访问层 + */ +public interface ModelConfigMapper extends BaseMapper { + List queryBySelective(ModelConfigListReq modelConfigReq); +} + diff --git a/core/src/main/java/com/dite/znpt/mapper/PartMapper.java b/core/src/main/java/com/dite/znpt/mapper/PartMapper.java index efcb3fa..2ec4e0d 100644 --- a/core/src/main/java/com/dite/znpt/mapper/PartMapper.java +++ b/core/src/main/java/com/dite/znpt/mapper/PartMapper.java @@ -4,15 +4,13 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.dite.znpt.domain.entity.PartEntity; import com.dite.znpt.domain.vo.PartListReq; import com.dite.znpt.domain.vo.PartListResp; -import com.dite.znpt.domain.vo.PartResp; -import org.apache.ibatis.annotations.Param; import java.util.List; /** * @author huise23 * @date 2025/04/11 23:17 - * @Description: 表数据库访问层 + * @Description: 部件表数据库访问层 */ public interface PartMapper extends BaseMapper { List queryBySelective(PartListReq partReq); diff --git a/core/src/main/java/com/dite/znpt/service/AttachInfoService.java b/core/src/main/java/com/dite/znpt/service/AttachInfoService.java index f11a8df..3c4987f 100644 --- a/core/src/main/java/com/dite/znpt/service/AttachInfoService.java +++ b/core/src/main/java/com/dite/znpt/service/AttachInfoService.java @@ -7,7 +7,6 @@ import com.dite.znpt.enums.AttachBusinessTypeEnum; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; -import java.rmi.ServerException; import java.util.List; /** diff --git a/core/src/main/java/com/dite/znpt/service/ModelConfigService.java b/core/src/main/java/com/dite/znpt/service/ModelConfigService.java new file mode 100644 index 0000000..052ef99 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/ModelConfigService.java @@ -0,0 +1,65 @@ +package com.dite.znpt.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.entity.ModelConfigEntity; +import com.dite.znpt.domain.vo.ModelConfigListReq; +import com.dite.znpt.domain.vo.ModelConfigReq; +import com.dite.znpt.domain.vo.ModelConfigResp; + +import java.util.List; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 模型配置表服务接口 + */ +public interface ModelConfigService extends IService { + + /** + * 功能描述:查询列表 + * + * @param modelConfigReq + * @return {@link List }<{@link ModelConfigResp }> + * @author huise23 + * @date 2025/07/02 20:57 + **/ + List selectList(ModelConfigListReq modelConfigReq); + + /** + * 功能描述:查询单条 + * + * @param modelId Id + * @return {@link ModelConfigResp } + * @author huise23 + * @date 2025/07/02 20:57 + **/ + ModelConfigResp selectById(String modelId); + + /** + * 功能描述:新增 + * + * @param modelConfigReq + * @author huise23 + * @date 2025/07/02 20:57 + **/ + void saveData(ModelConfigReq modelConfigReq); + + /** + * 功能描述:更新 + * + * @param modelConfigReq + * @author huise23 + * @date 2025/07/02 20:57 + **/ + void updateData(ModelConfigReq modelConfigReq); + + /** + * 功能描述:删除 + * + * @param modelId Id + * @author huise23 + * @date 2025/07/02 20:57 + **/ + void deleteById(String modelId); +} + diff --git a/core/src/main/java/com/dite/znpt/service/PartService.java b/core/src/main/java/com/dite/znpt/service/PartService.java index 1eba964..1901493 100644 --- a/core/src/main/java/com/dite/znpt/service/PartService.java +++ b/core/src/main/java/com/dite/znpt/service/PartService.java @@ -13,7 +13,7 @@ import java.util.List; /** * @author huise23 * @date 2025/04/11 23:17 - * @Description: 表服务接口 + * @Description: 部件表服务接口 */ public interface PartService extends IService { diff --git a/core/src/main/java/com/dite/znpt/service/impl/AttachInfoServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/AttachInfoServiceImpl.java index 2762f34..93c77dd 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/AttachInfoServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/AttachInfoServiceImpl.java @@ -10,17 +10,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.dite.znpt.constant.Constants; import com.dite.znpt.constant.Message; import com.dite.znpt.domain.entity.AttachInfoEntity; -import com.dite.znpt.domain.entity.InsuranceInfoEntity; import com.dite.znpt.domain.vo.AttachInfoReq; import com.dite.znpt.enums.AttachBusinessTypeEnum; import com.dite.znpt.enums.FilePathEnum; -import com.dite.znpt.service.AttachInfoService; import com.dite.znpt.mapper.FileInfoMapper; +import com.dite.znpt.service.AttachInfoService; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import lombok.RequiredArgsConstructor; -import com.dite.znpt.util.PageUtil; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -69,7 +67,7 @@ public class AttachInfoServiceImpl extends ServiceImpl saveData(String businessType, AttachInfoReq infoReq, MultipartFile[] files) { - String temPathPrefix = FilePathEnum.ATTACH.getFileAbsolutePath().concat(businessType).concat(FileUtil.FILE_SEPARATOR); + String temPathPrefix = FilePathEnum.ATTACH.getFileAbsolutePathPrefix().concat(businessType).concat(FileUtil.FILE_SEPARATOR); if(StrUtil.isNotBlank(infoReq.getUserDefinedPath())){ temPathPrefix = temPathPrefix.concat(infoReq.getUserDefinedPath()).concat(FileUtil.FILE_SEPARATOR); } @@ -84,7 +82,7 @@ public class AttachInfoServiceImpl extends ServiceImpl list = new ArrayList<>(); for (MultipartFile file : files) { AudioFileInfoEntity audio = new AudioFileInfoEntity(); @@ -113,7 +113,7 @@ public class AudioFileInfoServiceImpl extends ServiceImpl impleme dictReq.setDictId(dictId); List list = selectList(dictReq); - return list.isEmpty() ? CollUtil.getFirst(list) : new DictResp(); + return CollUtil.isNotEmpty(list) ? CollUtil.getFirst(list) : new DictResp(); } /** diff --git a/core/src/main/java/com/dite/znpt/service/impl/ImageCollectServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ImageCollectServiceImpl.java index dd87b06..d786566 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/ImageCollectServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/ImageCollectServiceImpl.java @@ -52,8 +52,8 @@ public class ImageCollectServiceImpl extends ServiceImpl imageList = Converts.INSTANCE.toImageEntity(req.getImageList()); - String permPathPrefix = FilePathEnum.IMAGE.getFileAbsolutePath().concat(ImageSourceEnum.COLLECT.getCode()).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR).concat(dateStr).concat(FileUtil.FILE_SEPARATOR); - String temPathPrefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePath().concat(ImageSourceEnum.COLLECT.getCode()).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR); + String permPathPrefix = FilePathEnum.IMAGE.getFileAbsolutePathPrefix().concat(ImageSourceEnum.COLLECT.getCode()).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR).concat(dateStr).concat(FileUtil.FILE_SEPARATOR); + String temPathPrefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePathPrefix().concat(ImageSourceEnum.COLLECT.getCode()).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR); imageList.forEach(image -> { image.setPartId(partId); image.setCollectId(imageCollect.getCollectId()); @@ -62,7 +62,7 @@ public class ImageCollectServiceImpl extends ServiceImpl impl Map partIdMap= partService.listByIds(partIds).stream().collect(Collectors.toMap(PartEntity::getPartId, Function.identity())); list.forEach(req -> { if(partIdMap.containsKey(req.getPartId())){ - String path_prefix = FilePathEnum.IMAGE.getFileAbsolutePath().concat(StrUtil.BACKSLASH).concat(req.getImageSource()).concat(StrUtil.BACKSLASH).concat(req.getPartId()).concat(StrUtil.BACKSLASH); - String temp_path_prefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePath().concat(StrUtil.BACKSLASH).concat(req.getImageSource()).concat(StrUtil.BACKSLASH).concat(req.getPartId()).concat(StrUtil.BACKSLASH); + String path_prefix = FilePathEnum.IMAGE.getFileAbsolutePathPrefix().concat(StrUtil.BACKSLASH).concat(req.getImageSource()).concat(StrUtil.BACKSLASH).concat(req.getPartId()).concat(StrUtil.BACKSLASH); + String temp_path_prefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePathPrefix().concat(StrUtil.BACKSLASH).concat(req.getImageSource()).concat(StrUtil.BACKSLASH).concat(req.getPartId()).concat(StrUtil.BACKSLASH); File file = new File(req.getImagePath()); if(file.exists() && req.getImagePath().contains(temp_path_prefix)){ ImageEntity entity = new ImageEntity(); @@ -146,12 +142,12 @@ public class ImageServiceImpl extends ServiceImpl impl throw new ServiceException(Message.IMAGE_IS_EMPTY); } List list = new ArrayList<>(files.length); - File temCategory = new File(FilePathEnum.IMAGE_TEMP.getFileAbsolutePath()); + File temCategory = new File(FilePathEnum.IMAGE_TEMP.getFileAbsolutePathPrefix()); if (!temCategory.exists()) { // 创建完整的目录 temCategory.mkdirs(); } - String temPathPrefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePath().concat(imageSource).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR); + String temPathPrefix = FilePathEnum.IMAGE_TEMP.getFileAbsolutePathPrefix().concat(imageSource).concat(FileUtil.FILE_SEPARATOR).concat(partId).concat(FileUtil.FILE_SEPARATOR); for (MultipartFile file : files) { if (!file.isEmpty()) { try { @@ -173,7 +169,7 @@ public class ImageServiceImpl extends ServiceImpl impl throw new ServiceException(Message.IMAGE_IS_EMPTY); } String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); - String path_prefix = FilePathEnum.IMAGE.getFileAbsolutePath().concat(imageSource).concat(FileUtil.FILE_SEPARATOR).concat(dateStr).concat(FileUtil.FILE_SEPARATOR); + String path_prefix = FilePathEnum.IMAGE.getFileAbsolutePathPrefix().concat(imageSource).concat(FileUtil.FILE_SEPARATOR).concat(dateStr).concat(FileUtil.FILE_SEPARATOR); if (Objects.nonNull(imageWorkReq)) { path_prefix = path_prefix.concat(StrUtil.emptyToDefault(imageWorkReq.getUploadUser(), "默认用户")).concat(FileUtil.FILE_SEPARATOR) .concat(StrUtil.emptyToDefault(imageWorkReq.getLongitude(), "0")) @@ -188,7 +184,7 @@ public class ImageServiceImpl extends ServiceImpl impl for (MultipartFile multipartFile : files) { String path = path_prefix + multipartFile.getOriginalFilename(); FileUtil.writeBytes(multipartFile.getBytes(),path); - result.add(FilePathEnum.IMAGE.getImageDownPath(path)); + result.add(FilePathEnum.IMAGE.getFileDownPath(path)); } String partId = imageWorkReq.getPartId(); if (StrUtil.isNotEmpty(partId)) { @@ -202,7 +198,7 @@ public class ImageServiceImpl extends ServiceImpl impl List imageList = new ArrayList<>(); result.forEach(path -> { ImageEntity imageEntity = new ImageEntity(); - String absolutePath = FilePathEnum.IMAGE.getFileAbsolutePath() + StrUtil.removePrefix(path, FilePathEnum.IMAGE.getUrlPath()); + String absolutePath = FilePathEnum.IMAGE.getFileAbsolutePathPrefix() + StrUtil.removePrefix(path, FilePathEnum.IMAGE.getUrlPath()); try { ImageReq imageReq = imageRespBuilder(absolutePath); BeanUtil.copyProperties(imageReq, imageEntity); @@ -251,7 +247,7 @@ public class ImageServiceImpl extends ServiceImpl impl req.setCameraManufacturer(obj.getStr("Make")); req.setCameraModel(obj.getStr("Model")); req.setImageName(obj.getStr("File Name")); - req.setImagePath(FilePathEnum.IMAGE_TEMP.getImageDownPath(path)); + req.setImagePath(FilePathEnum.IMAGE_TEMP.getFileDownPath(path)); BigDecimal imageSize = new BigDecimal(extractDigit(obj.getStr("File Size"))).divide(new BigDecimal(1024*1024), 4, RoundingMode.HALF_UP); req.setImageSize(imageSize.toString().concat("M")); req.setImageWidth(extractDigit(obj.getStr("Image Width"))); @@ -308,11 +304,11 @@ public class ImageServiceImpl extends ServiceImpl impl @Override public List listAppUploadImages() { List filePaths = new ArrayList<>(); - PathUtil.walkFiles(Path.of(FilePathEnum.IMAGE.getFileAbsolutePath()), new SimpleFileVisitor<>() { + PathUtil.walkFiles(Path.of(FilePathEnum.IMAGE.getFileAbsolutePathPrefix()), new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { if (path.toFile().isFile()) { - String imageDownPath = FilePathEnum.IMAGE.getImageDownPath(path.toFile().getAbsolutePath()); + String imageDownPath = FilePathEnum.IMAGE.getFileDownPath(path.toFile().getAbsolutePath()); List split = StrUtil.split(imageDownPath, StrUtil.SLASH, true, true); // /static/image/source/date try { @@ -405,7 +401,7 @@ public class ImageServiceImpl extends ServiceImpl impl List newImageList = new ArrayList<>(); partReq.getImagePaths().forEach(path -> { ImageEntity imageEntity = new ImageEntity(); - String absolutePath = FilePathEnum.IMAGE.getFileAbsolutePath() + StrUtil.removePrefix(path, FilePathEnum.IMAGE.getUrlPath()); + String absolutePath = FilePathEnum.IMAGE.getFileAbsolutePathPrefix() + StrUtil.removePrefix(path, FilePathEnum.IMAGE.getUrlPath()); try { ImageReq imageReq = imageRespBuilder(absolutePath); BeanUtil.copyProperties(imageReq, imageEntity); diff --git a/core/src/main/java/com/dite/znpt/service/impl/ModelConfigServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ModelConfigServiceImpl.java new file mode 100644 index 0000000..b940600 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/ModelConfigServiceImpl.java @@ -0,0 +1,123 @@ +package com.dite.znpt.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.config.YoloModelRegistry; +import com.dite.znpt.domain.entity.AttachInfoEntity; +import com.dite.znpt.domain.entity.ModelConfigEntity; +import com.dite.znpt.domain.vo.ModelConfigListReq; +import com.dite.znpt.domain.vo.ModelConfigReq; +import com.dite.znpt.domain.vo.ModelConfigResp; +import com.dite.znpt.enums.AttachBusinessTypeEnum; +import com.dite.znpt.mapper.ModelConfigMapper; +import com.dite.znpt.service.AttachInfoService; +import com.dite.znpt.service.ModelConfigService; +import com.dite.znpt.util.PageUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * @author huise23 + * @date 2025/07/02 20:57 + * @Description: 模型配置表服务实现类 + */ +@Service +@RequiredArgsConstructor +public class ModelConfigServiceImpl extends ServiceImpl implements ModelConfigService { + + private final YoloModelRegistry modelRegistry; + private final AttachInfoService attachInfoService; + + /** + * 功能描述:查询列表 + * + * @param modelConfigReq 信息 + * @return {@link List }<{@link ModelConfigResp }> + * @author huise23 + * @date 2025/07/02 20:57 + **/ + @Override + public List selectList(ModelConfigListReq modelConfigReq) { + PageUtil.startPage(); + List modelConfigList= this.baseMapper.queryBySelective(modelConfigReq); + modelConfigList.forEach(resp -> { + + }); + return modelConfigList; + } + + /** + * 功能描述:查询单条 + * + * @param modelId Id + * @return {@link ModelConfigResp } + * @author huise23 + * @date 2025/07/02 20:57 + **/ + @Override + public ModelConfigResp selectById(String modelId) { + ModelConfigListReq modelConfigReq = new ModelConfigListReq(); + modelConfigReq.setModelId(modelId); + + List list = selectList(modelConfigReq); + return CollUtil.isNotEmpty(list) ? CollUtil.getFirst(list) : new ModelConfigResp(); + } + + /** + * 功能描述:新增 + * + * @param modelConfigReq + * @author huise23 + * @date 2025/07/02 20:57 + **/ + @SneakyThrows + @Override + @Transactional(rollbackFor = Exception.class) + public void saveData(ModelConfigReq modelConfigReq) { + ModelConfigEntity entity = BeanUtil.copyProperties(modelConfigReq, ModelConfigEntity.class); + AttachInfoEntity attachInfo = attachInfoService.getById(modelConfigReq.getAttachId()); + entity.setModelPath(attachInfo.getAttachPath()); + save(entity); + attachInfoService.updateBusinessIdByAttachIds(entity.getModelId(), ListUtil.of(modelConfigReq.getAttachId()), AttachBusinessTypeEnum.MODEL_FILE); + modelRegistry.loadModel(entity); + } + + /** + * 功能描述:更新 + * + * @param modelConfigReq + * @author huise23 + * @date 2025/07/02 20:57 + **/ + @SneakyThrows + @Override + @Transactional(rollbackFor = Exception.class) + public void updateData(ModelConfigReq modelConfigReq) { + ModelConfigEntity entity = BeanUtil.copyProperties(modelConfigReq, ModelConfigEntity.class); + AttachInfoEntity attachInfo = attachInfoService.getById(modelConfigReq.getAttachId()); + entity.setModelPath(attachInfo.getAttachPath()); + attachInfoService.updateBusinessIdByAttachIds(entity.getModelId(), ListUtil.of(modelConfigReq.getAttachId()), AttachBusinessTypeEnum.MODEL_FILE); + updateById(entity); + modelRegistry.reloadModel(entity); + } + + /** + * 功能描述:删除 + * + * @param modelId Id + * @author huise23 + * @date 2025/07/02 20:57 + **/ + @Override + public void deleteById(String modelId) { + modelRegistry.unloadModel(modelId); + removeById(modelId); + } + +} diff --git a/core/src/main/java/com/dite/znpt/service/impl/MultiModelYoloService.java b/core/src/main/java/com/dite/znpt/service/impl/MultiModelYoloService.java new file mode 100644 index 0000000..b1946b9 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/MultiModelYoloService.java @@ -0,0 +1,147 @@ +package com.dite.znpt.service.impl; + +import ai.onnxruntime.OnnxTensor; +import ai.onnxruntime.OrtEnvironment; +import ai.onnxruntime.OrtException; +import ai.onnxruntime.OrtSession; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.config.YoloModelRegistry; +import com.dite.znpt.domain.bo.Detection; +import com.dite.znpt.domain.bo.Letterbox; +import com.dite.znpt.domain.bo.ODConfig; +import com.dite.znpt.domain.entity.ModelConfigEntity; +import com.dite.znpt.util.ModelUtil; +import lombok.extern.slf4j.Slf4j; +import org.opencv.core.Mat; +import org.opencv.core.Point; +import org.opencv.core.Scalar; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.nio.FloatBuffer; +import java.util.*; + +@Slf4j +@Service +public class MultiModelYoloService { + + @Autowired + private YoloModelRegistry registry; + + public List detect(String modelId, String inputFile, String outputFile, Float confThreshold) throws OrtException { + OrtSession session = registry.getSession(modelId); + YoloModelRegistry.ModelMetadata meta = registry.getMetadata(modelId); + OrtEnvironment environment = registry.getEnvironment(); + ModelConfigEntity modelConfig = registry.getModelConfig(modelId); + confThreshold = (Objects.isNull(confThreshold) ? modelConfig.getConfThreshold() : confThreshold) / 100; + + Mat img = Imgcodecs.imread(inputFile); + Mat image = img.clone(); + Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2RGB); + + // 在这里先定义下框的粗细、字的大小、字的类型、字的颜色(按比例设置大小粗细比较好一些) + int minDwDh = Math.min(img.width(), img.height()); + int thickness = minDwDh/ ODConfig.lineThicknessRatio; + long start_time = System.currentTimeMillis(); + // 更改 image 尺寸 + Letterbox letterbox = new Letterbox(); + image = letterbox.letterbox(image); + + double ratio = letterbox.getRatio(); + double dw = letterbox.getDw(); + double dh = letterbox.getDh(); + int rows = letterbox.getHeight(); + int cols = letterbox.getWidth(); + int channels = image.channels(); + + // 将Mat对象的像素值赋值给Float[]对象 + float[] pixels = new float[channels * rows * cols]; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + double[] pixel = image.get(j,i); + for (int k = 0; k < channels; k++) { + // 这样设置相当于同时做了image.transpose((2, 0, 1))操作 + pixels[rows*cols*k+j*cols+i] = (float) pixel[k]/255.0f; + } + } + } + + // 创建OnnxTensor对象 + long[] shape = { 1L, (long)channels, (long)rows, (long)cols }; + OnnxTensor tensor = OnnxTensor.createTensor(environment, FloatBuffer.wrap(pixels), shape); + HashMap stringOnnxTensorHashMap = new HashMap<>(); + stringOnnxTensorHashMap.put(session.getInputInfo().keySet().iterator().next(), tensor); + + // 运行推理 + OrtSession.Result output = session.run(stringOnnxTensorHashMap); + float[][] outputData = ((float[][][])output.get(0).getValue())[0]; + + outputData = ModelUtil.transposeMatrix(outputData); + Map> class2Bbox = new HashMap<>(); + + for (float[] bbox : outputData) { + float[] conditionalProbabilities = Arrays.copyOfRange(bbox, 4, bbox.length); + int label = ModelUtil.argmax(conditionalProbabilities); + float conf = conditionalProbabilities[label]; + if (conf < confThreshold) continue; + + bbox[4] = conf; + + // xywh to (x1, y1, x2, y2) + ModelUtil.xywh2xyxy(bbox); + + // skip invalid predictions + if (bbox[0] >= bbox[2] || bbox[1] >= bbox[3]) continue; + + class2Bbox.putIfAbsent(label, new ArrayList<>()); + class2Bbox.get(label).add(bbox); + } + + List detections = new ArrayList<>(); + for (Map.Entry> entry : class2Bbox.entrySet()) { + int label = entry.getKey(); + List bboxes = entry.getValue(); + bboxes = ModelUtil.nonMaxSuppression(bboxes, modelConfig.getNmsThreshold()); + for (float[] bbox : bboxes) { + String labelString = meta.getLabels()[label]; + detections.add(new Detection(labelString,entry.getKey(), Arrays.copyOfRange(bbox, 0, 4), bbox[4])); + } + } + +// 打印检测结果并将图片写入指定目录 + for (Detection detection : detections) { + float[] bbox = detection.getBbox(); + log.info(detection.toString()); + // 画框 + Point topLeft = new Point((bbox[0]-dw)/ratio, (bbox[1]-dh)/ratio); + Point bottomRight = new Point((bbox[2]-dw)/ratio, (bbox[3]-dh)/ratio); + Scalar color = new Scalar(meta.getColors().get(detection.getClsId())); + Imgproc.rectangle(img, topLeft, bottomRight, color, thickness); + // 框上写文字 + Point boxNameLoc = new Point((bbox[0]-dw)/ratio, (bbox[1]-dh-2)/ratio-3); + + Imgproc.putText(img, detection.getLabel(), boxNameLoc, Imgproc.FONT_HERSHEY_SIMPLEX, 2.5, color, thickness); + } + log.info("检测{},发现{}处问题,耗时:{} ms.", inputFile, detections.size(), (System.currentTimeMillis() - start_time)); + + // 保存图像到输出目录 + Imgcodecs.imwrite(outputFile, img); + return detections; + } + + public void runFolderDetection(String modelId, String inputFolder, String outputFolder, Float confThreshold) throws Exception { + List fileList = FileUtil.loopFiles(inputFolder, file -> { + String extName = FileUtil.extName(file); + return StrUtil.equalsAnyIgnoreCase(extName, "jpg", "png"); + }); + for (File file : fileList) { + List detections = detect(modelId, file.getAbsolutePath(), outputFolder+ FileUtil.FILE_SEPARATOR+FileUtil.getName(file), confThreshold); + } + } + +} + diff --git a/core/src/main/java/com/dite/znpt/service/impl/PartServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/PartServiceImpl.java index f4df3a7..1e5ba8c 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/PartServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/PartServiceImpl.java @@ -17,7 +17,6 @@ import com.dite.znpt.domain.vo.PartResp; import com.dite.znpt.enums.PartTypeEnum; import com.dite.znpt.exception.ServiceException; import com.dite.znpt.mapper.PartMapper; -import com.dite.znpt.mapper.TurbineMapper; import com.dite.znpt.service.PartService; import com.dite.znpt.service.ProjectService; import com.dite.znpt.service.TurbineService; @@ -35,7 +34,7 @@ import java.util.stream.Collectors; /** * @author huise23 * @date 2025/04/11 23:17 - * @Description: 表服务实现类 + * @Description: 部件表服务实现类 */ @Service @RequiredArgsConstructor diff --git a/core/src/main/java/com/dite/znpt/service/impl/ProjectTaskServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ProjectTaskServiceImpl.java index d734aee..8c26691 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/ProjectTaskServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/ProjectTaskServiceImpl.java @@ -1,8 +1,8 @@ package com.dite.znpt.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -10,20 +10,19 @@ import com.dite.znpt.constant.Message; import com.dite.znpt.domain.entity.AttachInfoEntity; import com.dite.znpt.domain.entity.ProjectTaskEntity; import com.dite.znpt.domain.vo.ProjectTaskListReq; -import com.dite.znpt.domain.vo.ProjectTaskResp; import com.dite.znpt.domain.vo.ProjectTaskReq; +import com.dite.znpt.domain.vo.ProjectTaskResp; import com.dite.znpt.domain.vo.ProjectTaskStartReq; import com.dite.znpt.enums.AttachBusinessTypeEnum; import com.dite.znpt.enums.ProjectTaskStateEnum; import com.dite.znpt.exception.ServiceException; import com.dite.znpt.mapper.ProjectTaskGroupMapper; +import com.dite.znpt.mapper.ProjectTaskMapper; import com.dite.znpt.service.AttachInfoService; import com.dite.znpt.service.ProjectTaskService; -import com.dite.znpt.mapper.ProjectTaskMapper; -import org.springframework.stereotype.Service; -import cn.hutool.core.collection.CollUtil; -import lombok.RequiredArgsConstructor; import com.dite.znpt.util.PageUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -82,7 +81,7 @@ public class ProjectTaskServiceImpl extends ServiceImpl list = selectList(projectTaskReq); - return list.isEmpty() ? CollUtil.getFirst(list) : new ProjectTaskResp(); + return CollUtil.isNotEmpty(list) ? CollUtil.getFirst(list) : new ProjectTaskResp(); } /** diff --git a/core/src/main/java/com/dite/znpt/service/impl/VideoFileInfoServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/VideoFileInfoServiceImpl.java index a93ee3c..fbf09f4 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/VideoFileInfoServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/VideoFileInfoServiceImpl.java @@ -1,10 +1,10 @@ package com.dite.znpt.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.dite.znpt.constant.Constants; import com.dite.znpt.constant.Message; import com.dite.znpt.domain.entity.VideoFileInfoEntity; import com.dite.znpt.domain.vo.VideoFileInfoListReq; @@ -12,15 +12,13 @@ import com.dite.znpt.domain.vo.VideoFileInfoReq; import com.dite.znpt.domain.vo.VideoFileInfoResp; import com.dite.znpt.enums.FilePathEnum; import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.mapper.VideoFileInfoMapper; import com.dite.znpt.service.PartService; import com.dite.znpt.service.VideoFileInfoService; -import com.dite.znpt.mapper.VideoFileInfoMapper; -import lombok.SneakyThrows; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import cn.hutool.core.collection.CollUtil; -import lombok.RequiredArgsConstructor; import com.dite.znpt.util.PageUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -120,7 +118,7 @@ public class VideoFileInfoServiceImpl extends ServiceImpl= re) { + re = a[i]; + arg = i; + } + } + return arg; + } + + public static List nonMaxSuppression(List bboxes, float iouThreshold) { + + List bestBboxes = new ArrayList<>(); + + bboxes.sort(Comparator.comparing(a -> a[4])); + + while (!bboxes.isEmpty()) { + float[] bestBbox = bboxes.remove(bboxes.size() - 1); + bestBboxes.add(bestBbox); + bboxes = bboxes.stream().filter(a -> computeIOU(a, bestBbox) < iouThreshold).collect(Collectors.toList()); + } + + return bestBboxes; + } + + public static float computeIOU(float[] box1, float[] box2) { + + float area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]); + float area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]); + + float left = Math.max(box1[0], box2[0]); + float top = Math.max(box1[1], box2[1]); + float right = Math.min(box1[2], box2[2]); + float bottom = Math.min(box1[3], box2[3]); + + float interArea = Math.max(right - left, 0) * Math.max(bottom - top, 0); + float unionArea = area1 + area2 - interArea; + return Math.max(interArea / unionArea, 1e-8f); + + } + +} diff --git a/core/src/main/resources/mapper/ModelConfigMapper.xml b/core/src/main/resources/mapper/ModelConfigMapper.xml new file mode 100644 index 0000000..385cbab --- /dev/null +++ b/core/src/main/resources/mapper/ModelConfigMapper.xml @@ -0,0 +1,34 @@ + + + + + + a.model_id, a.model_name, a.model_path, a.conf_threshold, + a.nms_threshold, a.update_by, a.create_time, a.create_by, + a.update_time + + + + + diff --git a/web/src/main/java/com/dite/znpt/web/controller/ModelConfigController.java b/web/src/main/java/com/dite/znpt/web/controller/ModelConfigController.java new file mode 100644 index 0000000..51ceb8a --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/ModelConfigController.java @@ -0,0 +1,83 @@ +package com.dite.znpt.web.controller; + + +import com.dite.znpt.config.YoloModelRegistry; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.vo.ModelConfigListReq; +import com.dite.znpt.domain.vo.ModelConfigReq; +import com.dite.znpt.domain.vo.ModelConfigResp; +import com.dite.znpt.service.ModelConfigService; +import com.dite.znpt.service.impl.MultiModelYoloService; +import com.dite.znpt.util.ValidationGroup; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/** + * @author huise23 + * @date 2025/07/02 21:21 + */ +@Api(tags = "模型配置") +@RestController +@RequestMapping("/model-config") +public class ModelConfigController { + @Resource + private ModelConfigService modelConfigService; + @Resource + private YoloModelRegistry registry; + @Autowired + private MultiModelYoloService multiModelYoloService; + + @ApiOperation(value = "获取列表", httpMethod = "GET") + @GetMapping("/list") + public PageResult list(ModelConfigListReq modelConfigReq) { + return PageResult.ok(modelConfigService.selectList(modelConfigReq)); + } + + @ApiOperation(value = "根据Id获取详细信息", httpMethod = "GET") + @GetMapping("/{modelId}") + public Result getInfo(@PathVariable String modelId) { + return Result.ok(modelConfigService.selectById(modelId)); + } + + @ApiOperation(value = "新增", httpMethod = "POST") + @PostMapping + public Result add(@Validated(ValidationGroup.Insert.class) @RequestBody ModelConfigReq modelConfigReq) { + modelConfigService.saveData(modelConfigReq); + return Result.ok(); + } + + @ApiOperation(value = "修改", httpMethod = "PUT") + @PutMapping + public Result edit(@Validated(ValidationGroup.Update.class) @RequestBody ModelConfigReq modelConfigReq) { + modelConfigService.updateData(modelConfigReq); + return Result.ok(); + } + + @ApiOperation(value = "删除", httpMethod = "DELETE") + @DeleteMapping("/{modelId}") + public Result remove(@PathVariable String modelId) { + modelConfigService.deleteById(modelId); + return Result.ok(); + } + + @PostMapping("/run") + public ResponseEntity runBatch(@RequestParam("modelId") String modelId, + @RequestParam("inputDir") String inputDir, + @RequestParam("outputDir") String outputDir, + @RequestParam("confThreshold") Float confThreshold) { + try { + multiModelYoloService.runFolderDetection(modelId, inputDir, outputDir, confThreshold); + return ResponseEntity.ok("批量推理完成"); + } catch (Exception e) { + return ResponseEntity.status(500).body("处理失败:" + e.getMessage()); + } + } +} +