现存一波

master
LAPTOP-S9HJSOEB\昊天 11 months ago
parent acdec70da5
commit 5f124b01b9

@ -0,0 +1,106 @@
task: detect
mode: train
model: yolov8n.pt
data: 'null'
epochs: 50
time: null
patience: 100
batch: 4
imgsz: 640
save: true
save_period: -1
cache: false
device: cpu
workers: 8
project: 'null'
name: train
exist_ok: false
pretrained: true
optimizer: auto
verbose: true
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: false
close_mosaic: 10
resume: false
amp: true
fraction: 1.0
profile: false
freeze: null
multi_scale: false
compile: false
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: true
opset: null
workspace: null
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
bgr: 0.0
mosaic: 1.0
mixup: 0.0
cutmix: 0.0
copy_paste: 0.0
copy_paste_mode: flip
auto_augment: randaugment
erasing: 0.4
cfg: null
tracker: botsort.yaml
save_dir: D:\git\lpNewgit\null\train

@ -0,0 +1,106 @@
task: detect
mode: train
model: yolov8n.pt
data: 'null'
epochs: 50
time: null
patience: 100
batch: 4
imgsz: 640
save: true
save_period: -1
cache: false
device: cpu
workers: 8
project: 'null'
name: train2
exist_ok: false
pretrained: true
optimizer: auto
verbose: true
seed: 0
deterministic: true
single_cls: false
rect: false
cos_lr: false
close_mosaic: 10
resume: false
amp: true
fraction: 1.0
profile: false
freeze: null
multi_scale: false
compile: false
overlap_mask: true
mask_ratio: 4
dropout: 0.0
val: true
split: val
save_json: false
conf: null
iou: 0.7
max_det: 300
half: false
dnn: false
plots: true
source: null
vid_stride: 1
stream_buffer: false
visualize: false
augment: false
agnostic_nms: false
classes: null
retina_masks: false
embed: null
show: false
save_frames: false
save_txt: false
save_conf: false
save_crop: false
show_labels: true
show_conf: true
show_boxes: true
line_width: null
format: torchscript
keras: false
optimize: false
int8: false
dynamic: false
simplify: true
opset: null
workspace: null
nms: false
lr0: 0.01
lrf: 0.01
momentum: 0.937
weight_decay: 0.0005
warmup_epochs: 3.0
warmup_momentum: 0.8
warmup_bias_lr: 0.1
box: 7.5
cls: 0.5
dfl: 1.5
pose: 12.0
kobj: 1.0
nbs: 64
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 0.0
translate: 0.1
scale: 0.5
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
bgr: 0.0
mosaic: 1.0
mixup: 0.0
cutmix: 0.0
copy_paste: 0.0
copy_paste_mode: flip
auto_augment: randaugment
erasing: 0.4
cfg: null
tracker: botsort.yaml
save_dir: D:\git\lpNewgit\null\train2

Binary file not shown.

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.annotation.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* annotation 错误码枚举类
*
* annotation 系统,使用 1-008-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 训练信息模块 1-008-001-000 ==========
ErrorCode TRAIN_INFO_NOT_EXISTS = new ErrorCode(1_008_001_000, "训练信息不存在");
// ========== 训练结果模块 1-008-002-000 ==========
ErrorCode TRAIN_RESULT_NOT_EXISTS = new ErrorCode(1_008_002_000, "训练结果不存在");
}

@ -39,7 +39,10 @@
<artifactId>jna-platform</artifactId> <artifactId>jna-platform</artifactId>
<version>5.13.0</version> <version>5.13.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>

@ -6,11 +6,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasEnhance;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasRespVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO;
import cn.iocoder.yudao.module.annotation.service.datas.DatasService; import cn.iocoder.yudao.module.annotation.service.datas.DatasService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -70,6 +72,25 @@ public class DatasController {
return success(BeanUtils.toBean(datas, DatasRespVO.class)); return success(BeanUtils.toBean(datas, DatasRespVO.class));
} }
// 刷新数据集
/**
* 刷新数据集
* 步骤1.根据path,将
* @param id
* @return
*/
@GetMapping("/refreshDatas")
@Operation(summary = "获得数据集管理")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> refreshDatas(@RequestParam("id") Integer id) {
List<DatasDO> list = datasService.list(new LambdaQueryWrapper<DatasDO>().eq(DatasDO::getId, id));
for (DatasDO datasDO : list){
datasService.refreshDatas(datasDO);
}
return success(true);
}
@PostMapping("/list") @PostMapping("/list")
@Operation(summary = "获得数据集管理列表") @Operation(summary = "获得数据集管理列表")
@PreAuthorize("@ss.hasPermission('annotation:datas:query')") @PreAuthorize("@ss.hasPermission('annotation:datas:query')")
@ -78,6 +99,21 @@ public class DatasController {
return success(result); return success(result);
} }
@PostMapping("/listStatus")
@Operation(summary = "获得数据集管理列表")
@PreAuthorize("@ss.hasPermission('annotation:datas:query')")
public CommonResult<List<DatasDO>> listStatus(@RequestBody DatasPageReqVO pageReqVO) {
List<DatasDO> result = datasService.list(new LambdaQueryWrapper<DatasDO>().eq(DatasDO::getStatus, pageReqVO.getStatus()));
return success(result);
}
///默认每张图片都生成一个新图片,循环遍历选择增强的类型
@PostMapping("/enhance")
@Operation(summary = "图片增强")
// @PreAuthorize("@ss.hasPermission('annotation:datas:query')")
public CommonResult<Boolean> enhance( @RequestBody DatasEnhance datasEnhance) {
datasService.enhance(datasEnhance);
return success(true);
}
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得数据集管理分页") @Operation(summary = "获得数据集管理分页")
@PreAuthorize("@ss.hasPermission('annotation:datas:query')") @PreAuthorize("@ss.hasPermission('annotation:datas:query')")

@ -0,0 +1,9 @@
package cn.iocoder.yudao.module.annotation.controller.admin.datas.vo;
import lombok.Data;
@Data
public class DatasEnhance {
private Long id;
private String[] enhancements;
}

@ -1,10 +1,12 @@
package cn.iocoder.yudao.module.annotation.controller.admin.datas.vo; package cn.iocoder.yudao.module.annotation.controller.admin.datas.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -21,7 +23,7 @@ public class DatasPageReqVO extends PageParam {
@Schema(description = "描述", example = "你猜") @Schema(description = "描述", example = "你猜")
private String description; private String description;
@Schema(description = "类型,还未标注,正在标注,正在训练,训练完成", example = "1") @Schema(description = "类型,还未标注,标注完成,正在训练,训练完成", example = "1")
private String status; private String status;
@Schema(description = "路径") @Schema(description = "路径")

@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.MarkRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.MarkSaveReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.MarkSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO;
import cn.iocoder.yudao.module.annotation.service.mark.MarkService; import cn.iocoder.yudao.module.annotation.service.mark.MarkService;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -51,12 +53,20 @@ public class MarkController {
markService.updateMark(updateReqVO); markService.updateMark(updateReqVO);
return success(true); return success(true);
} }
@Resource
private DictDataService dictDataService;
@PostMapping("/list") @PostMapping("/list")
@Operation(summary = "获得图片列表") @Operation(summary = "获得图片列表")
@PreAuthorize("@ss.hasPermission('annotation:datas:query')") @PreAuthorize("@ss.hasPermission('annotation:datas:query')")
public CommonResult<List<MarkDO>> list(@Valid @RequestBody MarkSaveReqVO createReqVO) { public CommonResult<List<MarkDO>> list(@Valid @RequestBody MarkSaveReqVO createReqVO) {
DictDataDO basePath = dictDataService.parseDictData("visual_annotation_conf","base_url");
List<MarkDO> result = markService.list(new QueryWrapper<MarkDO>().eq("data_id",createReqVO.getDataId())); List<MarkDO> result = markService.list(new QueryWrapper<MarkDO>().eq("data_id",createReqVO.getDataId()));
for (MarkDO markDO : result){
if (!markDO.getPath().startsWith("http")){
markDO.setPath(basePath.getValue() + markDO.getPath());
}
}
return success(result); return success(result);
} }
@PutMapping("/update") @PutMapping("/update")

@ -1,8 +1,12 @@
package cn.iocoder.yudao.module.annotation.controller.admin.mark; package cn.iocoder.yudao.module.annotation.controller.admin.mark;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.MarkSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkInfoDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkInfoDO;
import cn.iocoder.yudao.module.annotation.service.MarkInfo.AnnotationData;
import cn.iocoder.yudao.module.annotation.service.MarkInfo.MarkInfoService; import cn.iocoder.yudao.module.annotation.service.MarkInfo.MarkInfoService;
import cn.iocoder.yudao.module.annotation.service.mark.MarkService;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -24,16 +28,27 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
public class MarkInfoController { public class MarkInfoController {
@Resource @Resource
private MarkInfoService markService; private MarkInfoService markInfoService;
@Resource
private MarkService markService;
@Resource
private DictDataService dictDataService;
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建标注详情") @Operation(summary = "创建标注详情")
@PreAuthorize("@ss.hasPermission('annotation:mark:create')") @PreAuthorize("@ss.hasPermission('annotation:mark:create')")
public CommonResult<Integer> createMark(@Valid @RequestBody List<MarkInfoDO> createReqVO) { public CommonResult<Integer> createMark(@Valid @RequestBody List<MarkInfoDO> createReqVO) {
for (MarkInfoDO markInfoDO : createReqVO){ for (MarkInfoDO markInfoDO : createReqVO){
markInfoDO.setCreator(null);
markInfoDO.setUpdater(null);
markInfoDO.setAnnotationDataString(markInfoDO.getAnnotationData().toString()); markInfoDO.setAnnotationDataString(markInfoDO.getAnnotationData().toString());
markService.saveOrUpdate(markInfoDO); markInfoService.saveOrUpdate(markInfoDO);
} }
markInfoService.remove(new QueryWrapper<MarkInfoDO>().eq("mark_id",createReqVO.get(0).getMarkId()).notIn("id",createReqVO.stream().map(MarkInfoDO::getId).toList()));
markService.updateMark(new MarkSaveReqVO()
.setId(createReqVO.get(0).getMarkId())
.setStatus(1));
return success(1); return success(1);
} }
@ -41,8 +56,14 @@ public class MarkInfoController {
@Operation(summary = "获得标注详情列表") @Operation(summary = "获得标注详情列表")
@PreAuthorize("@ss.hasPermission('annotation:datas:query')") @PreAuthorize("@ss.hasPermission('annotation:datas:query')")
public CommonResult<List<MarkInfoDO>> list(@RequestParam("markId") Integer markId) { public CommonResult<List<MarkInfoDO>> list(@RequestParam("markId") Integer markId) {
List<MarkInfoDO> result = markService.list(new QueryWrapper<MarkInfoDO>()
List<MarkInfoDO> result = markInfoService.list(new QueryWrapper<MarkInfoDO>()
.eq("mark_id",markId)); .eq("mark_id",markId));
for (MarkInfoDO markInfoDO : result){
markInfoDO.setAnnotationData(AnnotationData.fromString(markInfoDO.getAnnotationDataString()));
}
return success(result); return success(result);
} }
@ -52,7 +73,7 @@ public class MarkInfoController {
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('annotation:mark:delete')") @PreAuthorize("@ss.hasPermission('annotation:mark:delete')")
public CommonResult<Boolean> deleteMark(@RequestParam("id") Integer id) { public CommonResult<Boolean> deleteMark(@RequestParam("id") Integer id) {
markService.deleteMarkInfo(id); markInfoService.deleteMarkInfo(id);
return success(true); return success(true);
} }

@ -1,33 +1,32 @@
package cn.iocoder.yudao.module.annotation.controller.admin.train; package cn.iocoder.yudao.module.annotation.controller.admin.train;
import org.springframework.web.bind.annotation.*; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import jakarta.annotation.Resource; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.SystemInfoRespVO;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainPageReqVO;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainSaveReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import cn.iocoder.yudao.module.annotation.service.train.TrainService; import cn.iocoder.yudao.module.annotation.service.train.TrainService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 训练") @Tag(name = "管理后台 - 训练")
@RestController @RestController
@ -53,6 +52,14 @@ public class TrainController {
return success(true); return success(true);
} }
@PutMapping("/init")
@Operation(summary = "更新训练")
@PreAuthorize("@ss.hasPermission('annotation:train:update')")
public CommonResult<Boolean> init(@Valid @RequestBody TrainSaveReqVO updateReqVO) {
trainService.init(updateReqVO);
return success(true);
}
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除训练") @Operation(summary = "删除训练")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "编号", required = true)
@ -92,4 +99,11 @@ public class TrainController {
BeanUtils.toBean(list, TrainRespVO.class)); BeanUtils.toBean(list, TrainRespVO.class));
} }
@GetMapping("/system-info")
@Operation(summary = "获取系统信息(CPU、GPU、Python环境)")
@PreAuthorize("@ss.hasPermission('annotation:train:query')")
public CommonResult<SystemInfoRespVO> getSystemInfo() {
return success(trainService.getSystemInfo());
}
} }

@ -0,0 +1,108 @@
package cn.iocoder.yudao.module.annotation.controller.admin.train.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "系统信息 Response VO")
@Data
public class SystemInfoRespVO {
@Schema(description = "CPU信息")
private CpuInfo cpu;
@Schema(description = "GPU信息列表")
private List<GpuInfo> gpus;
@Schema(description = "Python信息")
private PythonInfo python;
@Data
@Schema(description = "CPU信息")
public static class CpuInfo {
@Schema(description = "CPU型号")
private String model;
@Schema(description = "CPU核心数")
private Integer cores;
@Schema(description = "CPU使用率")
private Double usage;
}
@Data
@Schema(description = "GPU信息")
public static class GpuInfo {
@Schema(description = "GPU ID")
private Integer id;
@Schema(description = "GPU名称")
private String name;
@Schema(description = "GPU内存总量(MB)")
private Long memoryTotal;
@Schema(description = "GPU已用内存(MB)")
private Long memoryUsed;
@Schema(description = "GPU可用内存(MB)")
private Long memoryFree;
@Schema(description = "GPU使用率")
private Double usage;
@Schema(description = "GPU温度")
private Double temperature;
}
@Data
@Schema(description = "Python信息")
public static class PythonInfo {
@Schema(description = "是否安装Python")
private Boolean installed;
@Schema(description = "Python版本")
private String version;
@Schema(description = "Python路径")
private String path;
@Schema(description = "是否支持venv虚拟环境")
private Boolean venvSupported;
@Schema(description = "是否安装virtualenv")
private Boolean virtualenvInstalled;
@Schema(description = "是否安装conda")
private Boolean condaInstalled;
@Schema(description = "yolo虚拟环境是否存在")
private Boolean yoloEnvExists;
@Schema(description = "yolo虚拟环境中是否安装YOLO")
private Boolean yoloInstalled;
@Schema(description = "YOLO版本")
private String yoloVersion;
@Schema(description = "Python虚拟环境列表")
private List<VirtualEnvInfo> virtualEnvs;
}
@Data
@Schema(description = "虚拟环境信息")
public static class VirtualEnvInfo {
@Schema(description = "虚拟环境名称")
private String name;
@Schema(description = "虚拟环境路径")
private String path;
@Schema(description = "虚拟环境类型 (venv/virtualenv/conda)")
private String type;
@Schema(description = "Python版本")
private String pythonVersion;
}
}

@ -1,10 +1,12 @@
package cn.iocoder.yudao.module.annotation.controller.admin.train.vo; package cn.iocoder.yudao.module.annotation.controller.admin.train.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -15,7 +17,10 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true) @ToString(callSuper = true)
public class TrainPageReqVO extends PageParam { public class TrainPageReqVO extends PageParam {
@Schema(description = "项目id", example = "14776") @Schema(description = "项目名", example = "14776")
private String name;
@Schema(description = "数据集id", example = "14776")
private Integer dataId; private Integer dataId;
@Schema(description = "训练集比例") @Schema(description = "训练集比例")

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.annotation.controller.admin.train.vo; package cn.iocoder.yudao.module.annotation.controller.admin.train.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.Data;
import java.util.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - 训练 Response VO") @Schema(description = "管理后台 - 训练 Response VO")
@Data @Data
@ -17,7 +16,11 @@ public class TrainRespVO {
@ExcelProperty("id") @ExcelProperty("id")
private Integer id; private Integer id;
@Schema(description = "项目id", example = "14776") @Schema(description = "name", requiredMode = Schema.RequiredMode.REQUIRED, example = "32206")
@ExcelProperty("name")
private String name;
@Schema(description = "数据集id", example = "14776")
@ExcelProperty("项目id") @ExcelProperty("项目id")
private Integer dataId; private Integer dataId;

@ -1,9 +1,7 @@
package cn.iocoder.yudao.module.annotation.controller.admin.train.vo; package cn.iocoder.yudao.module.annotation.controller.admin.train.vo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.Data;
import java.util.*;
import jakarta.validation.constraints.*;
@Schema(description = "管理后台 - 训练新增/修改 Request VO") @Schema(description = "管理后台 - 训练新增/修改 Request VO")
@Data @Data
@ -12,7 +10,9 @@ public class TrainSaveReqVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "32206") @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "32206")
private Integer id; private Integer id;
@Schema(description = "项目id", example = "14776") @Schema(description = "name", requiredMode = Schema.RequiredMode.REQUIRED, example = "32206")
private String name;
@Schema(description = "数据集id", example = "14776")
private Integer dataId; private Integer dataId;
@Schema(description = "训练集比例") @Schema(description = "训练集比例")

@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.annotation.controller.admin.traininfo;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.traininfo.TrainInfoDO;
import cn.iocoder.yudao.module.annotation.service.traininfo.TrainInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 识别结果")
@RestController
@RequestMapping("/annotation/train-Info")
@Validated
public class TrainInfoController {
@Resource
private TrainInfoService trainInfoService;
@PostMapping("/create")
@Operation(summary = "创建识别结果")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:create')")
public CommonResult<Integer> createTrainInfo(@Valid @RequestBody TrainInfoSaveReqVO createReqVO) {
return success(trainInfoService.createTrainInfo(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新识别结果")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:update')")
public CommonResult<Boolean> updateTrainInfo(@Valid @RequestBody TrainInfoSaveReqVO updateReqVO) {
trainInfoService.updateTrainInfo(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除识别结果")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('annotation:train-Info:delete')")
public CommonResult<Boolean> deleteTrainInfo(@RequestParam("id") Integer id) {
trainInfoService.deleteTrainInfo(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得识别结果")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:query')")
public CommonResult<TrainInfoRespVO> getTrainInfo(@RequestParam("id") Integer id) {
TrainInfoDO trainInfo = trainInfoService.getTrainInfo(id);
return success(BeanUtils.toBean(trainInfo, TrainInfoRespVO.class));
}
@GetMapping("/get-list")
@Operation(summary = "获得识别结果")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:query')")
public CommonResult<List<TrainInfoRespVO>> getTrainInfoList(@RequestParam("trainId") Integer id) {
List<TrainInfoRespVO> trainInfo = trainInfoService.getTrainInfoList(id);
return success(BeanUtils.toBean(trainInfo, TrainInfoRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得识别结果分页")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:query')")
public CommonResult<PageResult<TrainInfoRespVO>> getTrainInfoPage(@Valid TrainInfoPageReqVO pageReqVO) {
PageResult<TrainInfoDO> pageInfo = trainInfoService.getTrainInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageInfo, TrainInfoRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出识别结果 Excel")
@PreAuthorize("@ss.hasPermission('annotation:train-Info:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportTrainInfoExcel(@Valid TrainInfoPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<TrainInfoDO> list = trainInfoService.getTrainInfoPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "识别结果.xls", "数据", TrainInfoRespVO.class,
BeanUtils.toBean(list, TrainInfoRespVO.class));
}
}

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 训练信息分页 Request VO")
@Data
public class TrainInfoPageReqVO extends PageParam {
@Schema(description = "训练id", example = "27530")
private Integer trainId;
@Schema(description = "识别批次", example = "1")
private Integer round;
@Schema(description = "识别的总批次", example = "100")
private Integer roundTotal;
@Schema(description = "训练信息")
private String info;
@Schema(description = "识别结果rate")
private String rate;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - 训练信息 Response VO")
@Data
@ExcelIgnoreUnannotated
public class TrainInfoRespVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "15312")
@ExcelProperty("id")
private Integer id;
@Schema(description = "训练id", example = "27530")
@ExcelProperty("训练id")
private Integer trainId;
@Schema(description = "识别批次", example = "1")
@ExcelProperty("识别批次")
private Integer round;
@Schema(description = "识别的总批次", example = "100")
@ExcelProperty("识别的总批次")
private Integer roundTotal;
@Schema(description = "训练信息")
@ExcelProperty("训练信息")
private String info;
@Schema(description = "识别结果rate")
@ExcelProperty("识别结果rate")
private String rate;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "修改时间")
@ExcelProperty("修改时间")
private LocalDateTime updateTime;
}

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Schema(description = "管理后台 - 训练信息新增/修改 Request VO")
@Data
public class TrainInfoSaveReqVO {
@Schema(description = "训练id", requiredMode = Schema.RequiredMode.REQUIRED, example = "27530")
private Integer trainId;
@Schema(description = "识别批次", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer round;
@Schema(description = "识别的总批次", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer roundTotal;
@Schema(description = "训练信息")
private String info;
@Schema(description = "识别结果rate")
private String rate;
}

@ -1,33 +1,31 @@
package cn.iocoder.yudao.module.annotation.controller.admin.trainresult; package cn.iocoder.yudao.module.annotation.controller.admin.trainresult;
import org.springframework.web.bind.annotation.*; import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import jakarta.annotation.Resource; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.constraints.*;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultPageReqVO;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultRespVO;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultSaveReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import cn.iocoder.yudao.module.annotation.service.trainresult.TrainResultService; import cn.iocoder.yudao.module.annotation.service.trainresult.TrainResultService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 识别结果") @Tag(name = "管理后台 - 识别结果")
@RestController @RestController
@ -71,6 +69,16 @@ public class TrainResultController {
return success(BeanUtils.toBean(trainResult, TrainResultRespVO.class)); return success(BeanUtils.toBean(trainResult, TrainResultRespVO.class));
} }
@GetMapping("/status")
@Operation(summary = "获得识别结果")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('annotation:train-result:query')")
public CommonResult<TrainResultRespVO> status(@RequestParam("trainId") Integer trainId) {
TrainResultDO trainResult = trainResultService.getStatus(trainId);
return success(BeanUtils.toBean(trainResult, TrainResultRespVO.class));
}
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得识别结果分页") @Operation(summary = "获得识别结果分页")
@PreAuthorize("@ss.hasPermission('annotation:train-result:query')") @PreAuthorize("@ss.hasPermission('annotation:train-result:query')")

@ -2,11 +2,22 @@ package cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
@Schema(description = "管理后台 - 识别结果新增/修改 Request VO") @Schema(description = "管理后台 - 识别结果新增/修改 Request VO")
@Data @Data
public class TrainResultSaveReqVO { public class TrainResultSaveReqVO {
@Schema(description = "训练id", requiredMode = Schema.RequiredMode.REQUIRED, example = "27530")
@NotNull(message = "训练id不能为空")
private Integer trainId;
@Schema(description = "输出路径")
private String path;
@Schema(description = "识别结果rate")
private String rate;
@Schema(description = "数据集", example = "196")
private Integer dataId;
} }

@ -0,0 +1,177 @@
package cn.iocoder.yudao.module.annotation.controller.admin.yolo;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import cn.iocoder.yudao.module.annotation.dal.yolo.YoloConfig;
import cn.iocoder.yudao.module.annotation.service.python.PythonVirtualEnvService;
import cn.iocoder.yudao.module.annotation.service.python.vo.PythonVirtualEnvInfo;
import cn.iocoder.yudao.module.annotation.service.train.TrainService;
import cn.iocoder.yudao.module.annotation.service.yolo.YoloOperationService;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - YOLO操作")
@RestController
@RequestMapping("/annotation/yolo")
@Validated
@Slf4j
public class YoloOperationController {
@Resource
private PythonVirtualEnvService pythonVirtualEnvService;
@Resource
private YoloOperationService yoloOperationService;
@Resource
private DictDataService dictDataService;
@PostMapping("/detect-env")
@Operation(summary = "检测Python和虚拟环境")
@PermitAll
public CommonResult<PythonVirtualEnvInfo> detectPythonAndVirtualEnv() {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
PythonVirtualEnvInfo info = pythonVirtualEnvService.detectPythonAndVirtualEnv(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue());
// 如果虚拟环境不存在,尝试创建虚拟环境
if (!info.getVirtualEnvExists() && info.getPythonExists()) {
log.info("虚拟环境不存在,尝试创建虚拟环境: {}", dictDataMap.get("python_venv").getValue());
info = pythonVirtualEnvService.createVirtualEnvironment(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue());
}
// 如果YOLO不存在,尝试安装YOLO
if (info.getVirtualEnvExists() && !info.getYoloInstalled()) {
log.info("YOLO未安装,尝试安装YOLO");
info = pythonVirtualEnvService.installYolo(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue());
}
log.info("最终环境信息: {}", info);
return success(info);
}
@PostMapping("/activate-env")
@Operation(summary = "进入虚拟环境")
@PermitAll
public CommonResult<PythonVirtualEnvInfo> activateVirtualEnv() {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
PythonVirtualEnvInfo info = pythonVirtualEnvService.activateVirtualEnv(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue());
return success(info);
}
@Resource
private TrainService trainService;
@PostMapping("/train")
@Operation(summary = "YOLO训练(异步)")
@PermitAll
public CommonResult<CompletableFuture<YoloConfig>> trainAsync(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
CompletableFuture<YoloConfig> result = yoloOperationService.trainAsync(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@PostMapping("/validate")
@Operation(summary = "YOLO验证")
@PermitAll
public CommonResult<YoloConfig> validate(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
YoloConfig result = yoloOperationService.validate(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@PostMapping("/predict")
@Operation(summary = "YOLO预测")
@PermitAll
public CommonResult<YoloConfig> predict(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
YoloConfig result = yoloOperationService.predict(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@PostMapping("/classify")
@Operation(summary = "YOLO分类")
@PermitAll
public CommonResult<YoloConfig> classify(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
YoloConfig result = yoloOperationService.classify(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@PostMapping("/export")
@Operation(summary = "导出YOLO模型")
@PermitAll
public CommonResult<YoloConfig> export(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
YoloConfig result = yoloOperationService.export(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@PostMapping("/track")
@Operation(summary = "YOLO追踪")
@PermitAll
public CommonResult<YoloConfig> track(@RequestParam("trainId") Integer id) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
TrainDO trainDO = trainService.getTrain(id);
// trainDO.setPath(dictDataMap.get("training_address").getValue());
YoloConfig config = trainDO.getYolofig(dictDataMap);
YoloConfig result = yoloOperationService.track(
dictDataMap.get("python_path").getValue(), dictDataMap.get("python_venv").getValue(), config);
return success(result);
}
@GetMapping("/task-status")
@Operation(summary = "获取异步任务状态")
@PermitAll
public CommonResult<String> getTaskStatus(
@RequestParam @Parameter(description = "任务ID", required = true) String taskId) {
// 这里可以添加任务状态查询逻辑
// 实际项目中可能需要使用缓存或数据库来存储任务状态
return success("Task status query not implemented yet");
}
}

@ -2,13 +2,10 @@ package cn.iocoder.yudao.module.annotation.dal.dataobject.mark;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.*;
import java.util.List;
/** /**
* 标注 DO * 标注 DO
* *
@ -45,8 +42,8 @@ public class MarkDO extends BaseDO {
/** /**
* 标注,类型是[{对象}],class_id,center_x,center_y,width,height,polygon_points,angle * 标注,类型是[{对象}],class_id,center_x,center_y,width,height,polygon_points,angle
*/ */
@TableField(typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class) // @TableField(typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
private List<AnnotationItem> annotation; // private List<AnnotationItem> annotation;
/** /**
* 1.标注完成,0,未标注 * 1.标注完成,0,未标注
*/ */

@ -24,7 +24,7 @@ public class MarkInfoDO extends BaseDO {
private Long id; private Long id;
private Integer classId; private Integer classId;
@TableField(exist = false) @TableField(exist = false)
private Integer className; private String className;
private Integer markId; private Integer markId;
private Double centerX; private Double centerX;
private Double centerY; private Double centerY;

@ -1,11 +1,14 @@
package cn.iocoder.yudao.module.annotation.dal.dataobject.train; package cn.iocoder.yudao.module.annotation.dal.dataobject.train;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.annotation.dal.yolo.YoloConfig;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.util.Map;
/** /**
* 训练 DO * 训练 DO
@ -27,6 +30,8 @@ public class TrainDO extends BaseDO {
*/ */
@TableId @TableId
private Integer id; private Integer id;
private String name;
/** /**
* 项目id * 项目id
*/ */
@ -68,4 +73,54 @@ public class TrainDO extends BaseDO {
*/ */
private Integer trainType; private Integer trainType;
private String type;
public YoloConfig getYolofig(Map<String, DictDataDO> dictDataMap) {
YoloConfig config = new YoloConfig();
// 设置数据相关配置
config.setDatasetPath(this.path);
// 设置训练参数
if (this.round != null) {
config.setEpochs(this.round);
}
if (this.size != null) {
config.setBatchSize(this.size);
}
if (this.imageSize != null) {
config.setImageSize(this.imageSize);
}
// 设置数据集比例(YOLO中需要转换)
// 注意:YOLO通常通过数据集配置文件来设置train/val/test比例
// 这里我们可以将比例信息保存到配置中,供后续处理使用
// 设置预训练模型路径
if (this.type.equals("1")) {
config.setModelPath(dictDataMap.get("detect_path").getValue());
config.setPretrained(true);
}else {
config.setModelPath(dictDataMap.get("classify_path").getValue());
config.setPretrained(false);
}
// 设置GPU/CPU设备
// 设置输出路径(默认在数据集路径下的runs/train目录)
if (this.path != null) {
config.setOutputPath(this.path + "/runs/train");
}
// 设置其他默认参数
config.setTaskType(this.type.equals("1") ? "detect" : "classify");
config.setModelName(this.modelPath);
config.setModelPath(this.modelPath);
config.setLearningRate(0.01);
config.setConfThresh(0.25);
config.setIouThresh(0.45);
config.setSave(true);
config.setSaveTxt(true);
return config;
}
} }

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.annotation.dal.dataobject.traininfo;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 识别结果 DO
*
* @author 管理员
*/
@TableName("annotation_train_info")
@KeySequence("annotation_train_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TrainInfoDO extends BaseDO {
/**
* id
*/
@TableId
private Integer id;
/**
* 训练id
*/
private Integer trainId;
// 识别批次
private Integer round;
private String info;
/**
* 识别的总批次
*/
private Integer roundTotal;
/**
* 识别结果rate
*/
private String rate;
}

@ -33,11 +33,7 @@ public class TypesDO extends BaseDO {
* 项目id * 项目id
*/ */
private Integer dataId; private Integer dataId;
/**
* index
*/
@TableField(value = "`index`")
private Integer index;
/** /**
* 颜色 * 颜色
*/ */

@ -1,13 +1,11 @@
package cn.iocoder.yudao.module.annotation.dal.mysql.mark; package cn.iocoder.yudao.module.annotation.dal.mysql.mark;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.MarkPageReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.annotation.controller.admin.mark.vo.*;
/** /**
* 标注 Mapper * 标注 Mapper
@ -23,4 +21,5 @@ public interface MarkMapper extends BaseMapperX<MarkDO> {
.orderByDesc(MarkDO::getId)); .orderByDesc(MarkDO::getId));
} }
void updateByIdSetStatus(MarkDO updateObj);
} }

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.annotation.dal.mysql.traininfo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.annotation.dal.dataobject.traininfo.TrainInfoDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.*;
/**
* 训练信息 Mapper
*
* @author 管理员
*/
@Mapper
public interface TrainInfoMapper extends BaseMapperX<TrainInfoDO> {
default PageResult<TrainInfoDO> selectPage(TrainInfoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<TrainInfoDO>()
.eqIfPresent(TrainInfoDO::getTrainId, reqVO.getTrainId())
.eqIfPresent(TrainInfoDO::getRound, reqVO.getRound())
.eqIfPresent(TrainInfoDO::getRoundTotal, reqVO.getRoundTotal())
.likeIfPresent(TrainInfoDO::getInfo, reqVO.getInfo())
.eqIfPresent(TrainInfoDO::getRate, reqVO.getRate())
.betweenIfPresent(TrainInfoDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(TrainInfoDO::getId));
}
}

@ -1,13 +1,11 @@
package cn.iocoder.yudao.module.annotation.dal.mysql.types; package cn.iocoder.yudao.module.annotation.dal.mysql.types;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.annotation.controller.admin.types.vo.TypesPageReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.types.TypesDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.types.TypesDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.annotation.controller.admin.types.vo.*;
/** /**
* 类别 Mapper * 类别 Mapper
@ -21,7 +19,7 @@ public interface TypesMapper extends BaseMapperX<TypesDO> {
return selectPage(reqVO, new LambdaQueryWrapperX<TypesDO>() return selectPage(reqVO, new LambdaQueryWrapperX<TypesDO>()
.likeIfPresent(TypesDO::getName, reqVO.getName()) .likeIfPresent(TypesDO::getName, reqVO.getName())
.eqIfPresent(TypesDO::getDataId, reqVO.getDataId()) .eqIfPresent(TypesDO::getDataId, reqVO.getDataId())
.eqIfPresent(TypesDO::getIndex, reqVO.getIndex()) // .eqIfPresent(TypesDO::getIndex, reqVO.getIndex())
.betweenIfPresent(TypesDO::getCreateTime, reqVO.getCreateTime()) .betweenIfPresent(TypesDO::getCreateTime, reqVO.getCreateTime())
.eqIfPresent(TypesDO::getColor, reqVO.getColor()) .eqIfPresent(TypesDO::getColor, reqVO.getColor())
.orderByDesc(TypesDO::getId)); .orderByDesc(TypesDO::getId));

@ -2,16 +2,262 @@ package cn.iocoder.yudao.module.annotation.dal.yolo;
import lombok.Data; import lombok.Data;
// YoloConfig.java /**
* YOLO配置类
*
* @author 管理员
*/
@Data @Data
public class YoloConfig { public class YoloConfig {
Integer trainId;
// ========== Python环境配置 ==========
/**
* Python可执行文件路径
*/
private String pythonPath;
/**
* 虚拟环境名称
*/
private String virtualEnvName;
// ========== 基础配置 ==========
/**
* 模型路径(用于验证、预测、导出)
*/
private String modelPath; private String modelPath;
/**
* 数据集路径(用于训练、验证)
*/
private String datasetPath; private String datasetPath;
/**
* 输出路径(训练结果、验证结果、预测结果等)
*/
private String outputPath; private String outputPath;
private int epochs = 100;
private int batchSize = 16; /**
* 模型名称(如 yolov8n, yolov8s, yolov8m, yolov8l, yolov8x)
*/
private String modelName = "yolov8n";
/**
* 任务类型(detect, classify, segment)
*/
private String taskType = "detect";
// ========== 训练配置 ==========
/**
* 训练轮数
*/
private int epochs = 50;
/**
* 批次大小
*/
private int batchSize = 4;
/**
* 学习率
*/
private double learningRate = 0.01; private double learningRate = 0.01;
private String device = "cpu"; // or "cuda"
/**
* 设备("0"表示GPU 0,"1"表示GPU 1,"cpu"表示CPU)
*/
private String device = "0";
/**
* 是否使用预训练模型
*/
private boolean pretrained = true; private boolean pretrained = true;
/**
* 图像大小(像素)
*/
private int imageSize = 640;
/**
* 工作线程数
*/
private int workers = 8;
/**
* 保存间隔(多少轮保存一次模型)
*/
private int savePeriod = -1;
/**
* 优化器(SGD, Adam, AdamW等)
*/
private String optimizer = "SGD";
// ========== 验证配置 ==========
/**
* 验证数据集路径(可选,如果不提供则使用datasetPath中的验证集)
*/
private String valDatasetPath;
/**
* 置信度阈值
*/
private double confThresh = 0.25;
/**
* IOU阈值
*/
private double iouThresh = 0.45;
/**
* 最大检测数
*/
private int maxDet = 1000;
// ========== 预测配置 ==========
/**
* 输入图片/视频路径(用于预测)
*/
private String inputPath;
/**
* 保存预测结果
*/
private boolean save = true;
/**
* 是否显示预测结果
*/
private boolean show = false;
/**
* 保存文本结果
*/
private boolean saveTxt = false;
/**
* 保存裁剪结果
*/
private boolean saveCrops = false;
// ========== 导出配置 ==========
/**
* 导出格式(onnx, torchscript, coreml等)
*/
private String format = "onnx";
/**
* 是否优化导出模型
*/
private boolean optimize = true;
/**
* 导出图像大小
*/
private int imgsz = 640;
// ========== 追踪配置 ==========
/**
* 追踪器类型(bytetrack, botsort等)
*/
private String tracker = "bytetrack";
/**
* 追踪置信度阈值
*/
private double trackConf = 0.5;
/**
* 追踪IOU阈值
*/
private double trackIou = 0.5;
// ========== 运行结果 ==========
/**
* 任务状态(pending, running, completed, failed)
*/
private String status = "pending";
/**
* 进度(0-100)
*/
private int progress = 0;
/**
* 日志信息
*/
private String logMessage;
/**
* 错误信息
*/
private String errorMessage;
/**
* 训练结果指标(如mAP, precision, recall等)
*/
private String metrics;
/**
* 任务ID(用于异步任务追踪)
*/
private String taskId;
// ========== 详细训练进度信息 ==========
/**
* 当前训练轮次
*/
private int currentEpoch = 0;
/**
* 当前批次进度(0-100)
*/
private int batchProgress = 0;
/**
* 当前批次索引
*/
private int currentBatch = 0;
/**
* 总批次数
*/
private int totalBatches = 0;
/**
* 损失值
*/
private double loss = 0.0;
/**
* 训练精度
*/
private double precision = 0.0;
/**
* 训练召回率
*/
private double recall = 0.0;
/**
* mAP值
*/
private double map = 0.0;
/**
* 最终训练结果摘要
*/
private String trainingSummary;
} }

@ -0,0 +1,287 @@
package cn.iocoder.yudao.module.annotation.service;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Random;
@Service
public class ImageService {
/**
* 亮度调整
* @param image 原始图像
* @param factor 亮度因子 (-1.0到1.0之间,0为原图)
* @return 调整后的图像
*/
public BufferedImage adjustBrightness(BufferedImage image, float factor) {
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
// 限制factor范围
factor = Math.max(-1.0f, Math.min(1.0f, factor));
int offset = (int)(factor * 100); // 转换为亮度偏移值
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int rgb = image.getRGB(x, y);
int r = Math.min(255, Math.max(0, ((rgb >> 16) & 0xFF) + offset));
int g = Math.min(255, Math.max(0, ((rgb >> 8) & 0xFF) + offset));
int b = Math.min(255, Math.max(0, (rgb & 0xFF) + offset));
int newRgb = (r << 16) | (g << 8) | b;
result.setRGB(x, y, newRgb);
}
}
return result;
}
/**
* 对比度调整
* @param image 原始图像
* @param factor 对比度因子 (0.0到2.0之间,1.0为原图)
* @return 调整后的图像
*/
public BufferedImage adjustContrast(BufferedImage image, float factor) {
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
factor = Math.max(0.0f, Math.min(2.0f, factor));
float contrast = (100.0f * (factor - 1.0f)) / 100.0f + 1.0f; // 转换对比度值
float intercept = 128 * (1 - contrast);
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int rgb = image.getRGB(x, y);
int r = Math.min(255, Math.max(0, (int)((((rgb >> 16) & 0xFF) * contrast) + intercept)));
int g = Math.min(255, Math.max(0, (int)((((rgb >> 8) & 0xFF) * contrast) + intercept)));
int b = Math.min(255, Math.max(0, (int)((((rgb & 0xFF) * contrast) + intercept))));
int newRgb = (r << 16) | (g << 8) | b;
result.setRGB(x, y, newRgb);
}
}
return result;
}
/**
* 饱和度调整
* @param image 原始图像
* @param factor 饱和度因子 (0.0到2.0之间,1.0为原图)
* @return 调整后的图像
*/
public BufferedImage adjustSaturation(BufferedImage image, float factor) {
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
factor = Math.max(0.0f, Math.min(2.0f, factor));
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// 计算灰度值
int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b);
// 调整饱和度
int newR = (int)(gray + factor * (r - gray));
int newG = (int)(gray + factor * (g - gray));
int newB = (int)(gray + factor * (b - gray));
newR = Math.min(255, Math.max(0, newR));
newG = Math.min(255, Math.max(0, newG));
newB = Math.min(255, Math.max(0, newB));
int newRgb = (newR << 16) | (newG << 8) | newB;
result.setRGB(x, y, newRgb);
}
}
return result;
}
/**
* 色相调整
* @param image 原始图像
* @param degrees 色相调整角度 (-180到180度)
* @return 调整后的图像
*/
public BufferedImage adjustHue(BufferedImage image, float degrees) {
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
degrees = degrees % 360;
float hueShift = degrees / 360.0f;
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
float[] hsb = Color.RGBtoHSB(r, g, b, null);
hsb[0] = (hsb[0] + hueShift) % 1.0f; // 调整色相
if (hsb[0] < 0) hsb[0] += 1.0f;
int newRgb = Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
result.setRGB(x, y, newRgb);
}
}
return result;
}
/**
* 灰度化处理
* @param image 原始图像
* @return 灰度图像
*/
public BufferedImage convertToGrayscale(BufferedImage image) {
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics g = result.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return result;
}
/**
* 部分区域灰度化处理
* @param image 原始图像
* @param ratio 灰度化区域比例 (0.0到1.0之间)
* @return 部分灰度化图像
*/
public BufferedImage partialGrayscale(BufferedImage image, float ratio) {
// 限制ratio范围在0.3到1.0之间
ratio = Math.max(0.3f, Math.min(1.0f, ratio));
BufferedImage result = new BufferedImage(
image.getWidth(), image.getHeight(), image.getType());
// 复制原图到结果图像
Graphics2D g = result.createGraphics();
g.drawImage(image, 0, 0, null);
// 计算需要灰度化的区域宽度
int grayscaleWidth = (int)(image.getWidth() * ratio);
// 创建灰度化区域
BufferedImage grayscaleRegion = convertToGrayscale(
image.getSubimage(0, 0, grayscaleWidth, image.getHeight()));
// 将灰度化区域绘制到结果图像上
g.drawImage(grayscaleRegion, 0, 0, null);
g.dispose();
return result;
}
private Random random = new Random();
/**
* 随机亮度调整
* @param image 原始图像
* @return 调整后的图像
*/
public BufferedImage adjustBrightness(BufferedImage image) {
float factor = (random.nextFloat() - 0.5f) ; // -1.0 到 1.0之间的随机值
return adjustBrightness(image, factor);
}
/**
* 随机对比度调整
* @param image 原始图像
* @return 调整后的图像
*/
public BufferedImage adjustContrast(BufferedImage image) {
float factor = 0.5f + random.nextFloat() * 1.5f; // 0.5 到 2.0之间的随机值
return adjustContrast(image, factor);
}
/**
* 随机饱和度调整
* @param image 原始图像
* @return 调整后的图像
*/
public BufferedImage adjustSaturation(BufferedImage image) {
float factor = random.nextFloat() * 2.0f; // 0.0 到 2.0之间的随机值
return adjustSaturation(image, factor);
}
/**
* 随机色相调整
* @param image 原始图像
* @return 调整后的图像
*/
public BufferedImage adjustHue(BufferedImage image) {
float degrees = (random.nextFloat() - 0.5f) * 360.0f; // -180 到 180之间的随机值
return adjustHue(image, degrees);
}
/**
* 随机灰度化处理(有一定概率应用灰度化)
* @param image 原始图像
* @return 可能处理后的图像
*/
public BufferedImage randomGrayscale(BufferedImage image) {
float degrees = random.nextFloat(); // -180 到 180之间的随机值
if (random.nextBoolean()) { // 50%概率应用灰度化
return partialGrayscale(image,degrees);
}
return image;
}
/**
* 图像增强处理方法
* @param inputPath 输入图片路径
* @param enhancementType 增强处理类型
* @return 处理后的图片路径
*/
public String enhanceImage(String inputPath,String basePath, String enhancementType) {
try {
// 构建输出路径: 原路径-enhance-处理类型.jpg
String outputPath = inputPath.replaceAll("\\.(?=[^.]*$)", "-enhance-" + enhancementType + ".");
if (!outputPath.toLowerCase().endsWith(".jpg")) {
outputPath = outputPath.substring(0, outputPath.lastIndexOf(".")) + ".jpg";
}
// 读取原图
BufferedImage originalImage = ImageIO.read(new File(basePath +inputPath));
BufferedImage enhancedImage = originalImage;
// 根据处理类型应用不同的增强效果
switch (enhancementType) {
case "brightness":
enhancedImage = adjustBrightness(originalImage);
break;
case "contrast":
enhancedImage = adjustContrast(originalImage);
break;
case "saturation":
enhancedImage = adjustSaturation(originalImage);
break;
case "hue":
enhancedImage = adjustHue(originalImage);
break;
case "grayscale":
enhancedImage = randomGrayscale(originalImage);
break;
default:
// 未识别的处理类型,返回原图
break;
}
// 保存处理后的图像
ImageIO.write(enhancedImage, "jpg", new File(basePath + outputPath));
return outputPath;
} catch (Exception e) {
throw new RuntimeException("图像增强处理失败: " + e.getMessage(), e);
}
}
}

@ -1,6 +1,7 @@
// AnnotationData.java // AnnotationData.java
package cn.iocoder.yudao.module.annotation.service.MarkInfo; package cn.iocoder.yudao.module.annotation.service.MarkInfo;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -59,4 +60,15 @@ public class AnnotationData {
} }
} }
public String toString() {
return JSONUtil.toJsonStr(this);
}
static public AnnotationData fromString(String json) {
if (json == null || json.isEmpty()) {
return null;
}
return JSONUtil.toBean(json, AnnotationData.class);
}
} }

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.annotation.service.datas; package cn.iocoder.yudao.module.annotation.service.datas;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasEnhance;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO;
@ -52,4 +53,7 @@ public interface DatasService extends IService<DatasDO> {
*/ */
PageResult<DatasDO> getDatasPage(DatasPageReqVO pageReqVO); PageResult<DatasDO> getDatasPage(DatasPageReqVO pageReqVO);
void refreshDatas(DatasDO datasDO);
void enhance(DatasEnhance datasEnhance);
} }

@ -2,12 +2,19 @@ package cn.iocoder.yudao.module.annotation.service.datas;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasEnhance;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO; import cn.iocoder.yudao.module.annotation.controller.admin.datas.vo.DatasSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO; import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkInfoDO;
import cn.iocoder.yudao.module.annotation.dal.mysql.datas.DatasMapper; import cn.iocoder.yudao.module.annotation.dal.mysql.datas.DatasMapper;
import cn.iocoder.yudao.module.annotation.dal.mysql.mark.MarkMapper; import cn.iocoder.yudao.module.annotation.dal.mysql.mark.MarkInfoMapper;
import cn.iocoder.yudao.module.annotation.service.ImageService;
import cn.iocoder.yudao.module.annotation.service.mark.MarkService;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -16,6 +23,8 @@ import org.springframework.validation.annotation.Validated;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
//import static cn.iocoder.yudao.module.annotation.enums.ErrorCodeConstants.*; //import static cn.iocoder.yudao.module.annotation.enums.ErrorCodeConstants.*;
/** /**
@ -31,14 +40,19 @@ public class DatasServiceImpl extends ServiceImpl<DatasMapper, DatasDO> impleme
private DatasMapper datasMapper; private DatasMapper datasMapper;
@Resource @Resource
private MarkMapper markMapper; private MarkService markService;
@Resource
private DictDataService dictDataService;
/** /**
* 检查路径是否存在,并获取其中所有图片文件名称 * 递归检查路径是否存在,并获取其中所有图片文件名称(包括子目录中的图片)
* @param path 要检查的路径字符串 * @param path 要检查的路径字符串
* @return 图片文件名称列表 * @return 图片文件名称列表
*/ */
public List<String> getImageNamesFromPath(String path) { public List<String> getImageNamesFromPath(String superiorPath) {
DictDataDO basePath = dictDataService.parseDictData("visual_annotation_conf","base_path");
String path = basePath.getValue() + superiorPath;
List<String> imageNames = new ArrayList<>(); List<String> imageNames = new ArrayList<>();
// 检查路径是否为空 // 检查路径是否为空
@ -47,23 +61,67 @@ public class DatasServiceImpl extends ServiceImpl<DatasMapper, DatasDO> impleme
} }
File directory = new File(path); File directory = new File(path);
File rootDirectory = directory; // 保存根目录引用
// 检查路径是否存在且为目录 // 检查路径是否存在且为目录
if (!directory.exists() || !directory.isDirectory()) { if (!directory.exists() || !directory.isDirectory()) {
return imageNames; return imageNames;
} }
// 获取目录中的所有文件 // 递归遍历目录,传递根目录用于计算相对路径
traverseDirectory(directory, rootDirectory, imageNames);
imageNames = imageNames.stream()
.map(v->{return superiorPath+"/"+v;})
.collect(Collectors.toList());
return imageNames;
}
/**
* 递归遍历目录查找图片文件
* @param directory 要遍历的目录
* @param rootDirectory 根目录
* @param imageNames 图片文件名称收集列表
*/
private void traverseDirectory(File directory, File rootDirectory, List<String> imageNames) {
File[] files = directory.listFiles(); File[] files = directory.listFiles();
if (files != null) { if (files != null) {
for (File file : files) { for (File file : files) {
if (isImageFile(file)) { if (file.isDirectory()) {
imageNames.add(file.getName()); // 递归处理子目录,保持根目录不变
traverseDirectory(file, rootDirectory, imageNames);
} else if (isImageFile(file)) {
// 添加图片文件(包含相对于根目录的路径)
String relativePath = getRelativePath(file, rootDirectory);
imageNames.add(relativePath);
}
} }
} }
} }
return imageNames;
/**
* 获取文件相对于根目录的路径
* @param file 文件
* @param rootDirectory 根目录
* @return 相对路径
*/
private String getRelativePath(File file, File rootDirectory) {
// 使用 Path API 来正确计算相对路径
try {
return rootDirectory.toPath().relativize(file.toPath()).toString();
} catch (IllegalArgumentException e) {
// 如果无法计算相对路径,则返回文件名
return file.getName();
}
}
public static void main(String[] args) {
DatasServiceImpl datasService = new DatasServiceImpl();
List<String> imageNames = datasService.getImageNamesFromPath("D:\\PycharmProjects\\yolo\\runs\\detect\\11");
System.out.println(imageNames);
} }
/** /**
@ -95,12 +153,12 @@ public class DatasServiceImpl extends ServiceImpl<DatasMapper, DatasDO> impleme
datas.setCount((long) imageNames.size()); datas.setCount((long) imageNames.size());
datas.setProgress(0.0); datas.setProgress(0.0);
for (String imageName : imageNames){ for (String imageName : imageNames){
markMapper.insert(new MarkDO() markService.save(new MarkDO()
.setDataId(datas.getId()) .setDataId(datas.getId())
.setPath(imageName) .setPath(imageName));
.setAnnotation( new ArrayList<>()));
} }
datasMapper.insert(datas); datasMapper.insert(datas);
// 返回 // 返回
return datas.getId(); return datas.getId();
} }
@ -137,5 +195,120 @@ public class DatasServiceImpl extends ServiceImpl<DatasMapper, DatasDO> impleme
public PageResult<DatasDO> getDatasPage(DatasPageReqVO pageReqVO) { public PageResult<DatasDO> getDatasPage(DatasPageReqVO pageReqVO) {
return datasMapper.selectPage(pageReqVO); return datasMapper.selectPage(pageReqVO);
} }
@Override
public void refreshDatas(DatasDO datasDO) {
// 1. 获取路径中的所有图片文件
List<String> currentImageNames = getImageNamesFromPath(datasDO.getPath());
// 2. 获取数据库中已有的标记记录
List<MarkDO> existingMarks = markService.list(new QueryWrapper<MarkDO>().eq("data_id", datasDO.getId()));
// 3. 找出需要删除的图片(数据库中有但文件系统中没有)
List<String> existingImageNames = existingMarks.stream()
.map(MarkDO::getPath)
.collect(Collectors.toList());
List<String> imagesToDelete = existingImageNames.stream()
.filter(imageName -> !currentImageNames.contains(imageName))
.collect(Collectors.toList());
// 删除不存在的标记记录
if (!imagesToDelete.isEmpty()) {
markService.remove(new QueryWrapper<MarkDO>()
.eq("data_id", datasDO.getId())
.in("path", imagesToDelete));
}
// 4. 找出需要新增的图片(文件系统中有但数据库中没有)
List<String> imagesToAdd = currentImageNames.stream()
.filter(imageName -> !existingImageNames.contains(imageName))
.collect(Collectors.toList());
// 新增图片标记记录
for (String imageName : imagesToAdd) {
markService.save(new MarkDO()
.setDataId(datasDO.getId())
.setPath(imageName)
.setStatus(0)); // 默认未标注状态
}
// 5. 更新数据集统计信息
List<MarkDO> updatedMarks = markService.list(new QueryWrapper<MarkDO>().eq("data_id", datasDO.getId()));
// 计算进度
long totalImages = updatedMarks.size();
long completedImages = updatedMarks.stream()
.filter(mark -> mark.getStatus() != null && mark.getStatus() == 1)
.count();
double progress = totalImages > 0 ? (completedImages * 100.0 / totalImages) : 0.0;
// 判断是否全部完成
String status = (completedImages == totalImages && totalImages > 0) ? "2" : "1"; // 2表示完成,1表示进行中
// 更新数据集
datasDO.setCount(totalImages);
datasDO.setProgress(progress);
datasDO.setStatus(status);
datasDO.setCreator( null);
datasDO.setUpdater(null);
datasMapper.updateById(datasDO);
}
@Resource
ImageService imageService;
@Resource
MarkInfoMapper markInfoMapper;
@Override
public void enhance(DatasEnhance datasEnhance) {
DictDataDO basePath = dictDataService.parseDictData("visual_annotation_conf","base_path");
// 遍历图片,根据选择的类型,新增图片,并且新增他自身的标记记录
List<MarkDO> markDOList = markService.list(new QueryWrapper<MarkDO>()
.eq("data_id", datasEnhance.getId()));
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < markDOList.size(); i++) {
final int index = i;
final MarkDO markDO = markDOList.get(i);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
String path = imageService.enhanceImage(
markDO.getPath(),
basePath.getValue(),
datasEnhance.getEnhancements()[index % datasEnhance.getEnhancements().length]
);
List<MarkInfoDO> markInfoDOList = markInfoMapper.selectList(
new QueryWrapper<MarkInfoDO>().eq("mark_id", markDO.getId())
);
MarkDO newMarkDO = BeanUtils.toBean(markDO, MarkDO.class);
newMarkDO.setId(null);
newMarkDO.setPath(path);
markService.save(newMarkDO);
for (MarkInfoDO markInfoDO : markInfoDOList) {
MarkInfoDO newMarkInfoDO = BeanUtils.toBean(markInfoDO, MarkInfoDO.class);
newMarkInfoDO.setId(null);
newMarkInfoDO.setMarkId(newMarkDO.getId());
markInfoMapper.insert(newMarkInfoDO);
}
} catch (Exception e) {
log.error("图像增强处理失败,图片路径: "+markDO.getPath(),e);
}
});
futures.add(future);
}
// 等待所有异步任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
} }

@ -29,6 +29,7 @@ public class MarkServiceImpl extends ServiceImpl<MarkMapper, MarkDO> implements
// 插入 // 插入
MarkDO mark = BeanUtils.toBean(createReqVO, MarkDO.class); MarkDO mark = BeanUtils.toBean(createReqVO, MarkDO.class);
markMapper.insert(mark); markMapper.insert(mark);
// 返回 // 返回
return mark.getId(); return mark.getId();
} }
@ -39,7 +40,7 @@ public class MarkServiceImpl extends ServiceImpl<MarkMapper, MarkDO> implements
// validateMarkExists(updateReqVO.getId()); // validateMarkExists(updateReqVO.getId());
// 更新 // 更新
MarkDO updateObj = BeanUtils.toBean(updateReqVO, MarkDO.class); MarkDO updateObj = BeanUtils.toBean(updateReqVO, MarkDO.class);
markMapper.updateById(updateObj); markMapper.updateByIdSetStatus(updateObj);
} }
@Override @Override

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.annotation.service.python;
import cn.iocoder.yudao.module.annotation.service.python.vo.PythonVirtualEnvInfo;
/**
* Python虚拟环境服务接口
*
* @author 管理员
*/
public interface PythonVirtualEnvService {
/**
* 进入指定的Python虚拟环境
*
* @param pythonPath Python可执行文件路径
* @param envName 虚拟环境名称
* @return 虚拟环境信息
*/
PythonVirtualEnvInfo activateVirtualEnv(String pythonPath, String envName);
/**
* 检测Python和虚拟环境
*
* @param pythonPath Python可执行文件路径
* @param envName 虚拟环境名称(可为null,表示检测Python本身)
* @return 检测结果
*/
PythonVirtualEnvInfo detectPythonAndVirtualEnv(String pythonPath, String envName);
/**
* 创建Python虚拟环境
*
* @param pythonPath Python可执行文件路径
* @param envName 虚拟环境名称
* @return 创建结果
*/
PythonVirtualEnvInfo createVirtualEnvironment(String pythonPath, String envName);
/**
* 在虚拟环境中安装YOLO
*
* @param pythonPath Python可执行文件路径
* @param envName 虚拟环境名称
* @return 安装结果
*/
PythonVirtualEnvInfo installYolo(String pythonPath, String envName);
}

@ -0,0 +1,524 @@
package cn.iocoder.yudao.module.annotation.service.python;
import cn.iocoder.yudao.module.annotation.service.python.vo.PythonVirtualEnvInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
/**
* Python虚拟环境服务实现类
*
* @author 管理员
*/
@Slf4j
@Service
public class PythonVirtualEnvServiceImpl implements PythonVirtualEnvService {
@Override
public PythonVirtualEnvInfo activateVirtualEnv(String pythonPath, String envName) {
PythonVirtualEnvInfo info = new PythonVirtualEnvInfo();
try {
// 获取实际的Python可执行文件路径
String actualPythonPath = getActualPythonExecutable(pythonPath);
if (actualPythonPath == null) {
info.setPythonExists(false);
info.setErrorMessage("Python文件不存在或无法找到可执行文件: " + pythonPath);
return info;
}
// 检测Python版本
String version = getPythonVersion(pythonPath);
if (version == null) {
info.setPythonExists(false);
info.setErrorMessage("无法获取Python版本");
return info;
}
info.setPythonExists(true);
info.setPythonVersion(version);
info.setPythonPath(actualPythonPath);
// 检测虚拟环境
detectVirtualEnvironment(info, envName);
} catch (Exception e) {
log.error("激活虚拟环境失败", e);
info.setErrorMessage("激活虚拟环境异常: " + e.getMessage());
}
return info;
}
@Override
public PythonVirtualEnvInfo createVirtualEnvironment(String pythonPath, String envName) {
PythonVirtualEnvInfo info = new PythonVirtualEnvInfo();
try {
// 获取实际的Python可执行文件路径
String actualPythonPath = getActualPythonExecutable(pythonPath);
if (actualPythonPath == null) {
info.setPythonExists(false);
info.setErrorMessage("Python文件不存在或无法找到可执行文件: " + pythonPath);
return info;
}
// 获取Python目录
File pythonFile = new File(actualPythonPath);
String pythonDir = pythonFile.getParent();
// 构建虚拟环境路径
String venvPath = pythonDir + File.separator + envName;
File venvDir = new File(venvPath);
// 检查虚拟环境是否已存在
if (venvDir.exists()) {
log.info("虚拟环境已存在: {}", venvPath);
return detectPythonAndVirtualEnv(pythonPath, envName);
}
// 创建虚拟环境
String os = System.getProperty("os.name").toLowerCase();
String command;
if (os.contains("win")) {
command = actualPythonPath + " -m venv " + venvPath;
} else {
command = actualPythonPath + " -m venv " + venvPath;
}
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
} else {
processBuilder.command("/bin/bash", "-c", command);
}
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
log.info("虚拟环境创建成功: {}", venvPath);
// 重新检测环境
return detectPythonAndVirtualEnv(pythonPath, envName);
} else {
info.setErrorMessage("创建虚拟环境失败,退出码: " + exitCode);
}
} catch (Exception e) {
log.error("创建虚拟环境失败", e);
info.setErrorMessage("创建虚拟环境异常: " + e.getMessage());
}
return info;
}
@Override
public PythonVirtualEnvInfo installYolo(String pythonPath, String envName) {
PythonVirtualEnvInfo info = detectPythonAndVirtualEnv(pythonPath, envName);
if (!info.getVirtualEnvExists()) {
info.setErrorMessage("虚拟环境不存在,无法安装YOLO");
return info;
}
try {
// 在虚拟环境中安装YOLO,使用清华源
String os = System.getProperty("os.name").toLowerCase();
String command;
if (os.contains("win")) {
// Windows: 使用cmd激活虚拟环境后安装
command = info.getActivateCommand() + " && pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U ultralytics";
} else {
// Linux/Mac: 使用bash激活虚拟环境后安装
command = info.getActivateCommand() + " pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -U ultralytics";
}
log.info("在虚拟环境中安装YOLO命令: {}", command);
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
} else {
processBuilder.command("/bin/bash", "-c", command);
}
Process process = processBuilder.start();
// 读取输出
BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
String line;
while ((line = inputReader.readLine()) != null) {
output.append(line).append("\n");
}
while ((line = errorReader.readLine()) != null) {
error.append(line).append("\n");
}
int exitCode = process.waitFor();
inputReader.close();
errorReader.close();
if (exitCode == 0) {
log.info("YOLO安装成功: {}", output.toString());
// 重新检测环境,确认YOLO已安装
return detectPythonAndVirtualEnv(pythonPath, envName);
} else {
info.setErrorMessage("YOLO安装失败,退出码: " + exitCode + ", 错误信息: " + error);
}
} catch (Exception e) {
log.error("安装YOLO失败", e);
info.setErrorMessage("安装YOLO异常: " + e.getMessage());
}
return info;
}
@Override
public PythonVirtualEnvInfo detectPythonAndVirtualEnv(String pythonPath, String envName) {
PythonVirtualEnvInfo info = new PythonVirtualEnvInfo();
try {
// 获取实际的Python可执行文件路径
String actualPythonPath = getActualPythonExecutable(pythonPath);
if (actualPythonPath == null) {
info.setPythonExists(false);
info.setErrorMessage("Python文件不存在或无法找到可执行文件: " + pythonPath);
return info;
}
String version = getPythonVersion(pythonPath);
info.setPythonExists(true);
info.setPythonVersion(version);
info.setPythonPath(actualPythonPath);
// 如果指定了虚拟环境名称,则检测虚拟环境
if (envName != null && !envName.trim().isEmpty()) {
detectVirtualEnvironment(info, envName);
}
} catch (Exception e) {
log.error("检测Python和虚拟环境失败", e);
info.setErrorMessage("检测异常: " + e.getMessage());
}
return info;
}
private String getPythonVersion(String pythonPath) {
try {
// 检查并修正Python路径
String actualPythonPath = getActualPythonExecutable(pythonPath);
if (actualPythonPath == null) {
return null;
}
ProcessBuilder processBuilder = new ProcessBuilder();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", actualPythonPath + " --version");
} else {
processBuilder.command("/bin/bash", "-c", actualPythonPath + " --version");
}
Process process = processBuilder.start();
// Python版本信息可能在标准输出或错误输出中,都检查一下
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String stdoutLine = stdoutReader.readLine();
String stderrLine = stderrReader.readLine();
stdoutReader.close();
stderrReader.close();
// 检查两个输出流
String versionLine = null;
if (stdoutLine != null && (stdoutLine.toLowerCase().contains("python") || stdoutLine.matches("\\d+\\.\\d+\\.\\d+.*"))) {
versionLine = stdoutLine;
} else if (stderrLine != null && (stderrLine.toLowerCase().contains("python") || stderrLine.matches("\\d+\\.\\d+\\.\\d+.*"))) {
versionLine = stderrLine;
}
if (versionLine != null) {
return versionLine.trim();
}
} catch (Exception e) {
log.warn("获取Python版本失败: " + pythonPath, e);
}
return null;
}
/**
* 获取实际的Python可执行文件路径
*/
private String getActualPythonExecutable(String pythonPath) {
File file = new File(pythonPath);
// 如果是目录,尝试找到Python可执行文件
if (file.isDirectory()) {
String os = System.getProperty("os.name").toLowerCase();
String pythonExeName = os.contains("win") ? "python.exe" : "python3";
File pythonExe = new File(file, pythonExeName);
if (pythonExe.exists()) {
return pythonExe.getAbsolutePath();
}
// 在Windows上还尝试python3.exe
if (os.contains("win")) {
File python3Exe = new File(file, "python3.exe");
if (python3Exe.exists()) {
return python3Exe.getAbsolutePath();
}
}
return null;
}
// 如果是文件,直接返回
return file.exists() ? pythonPath : null;
}
private void detectVirtualEnvironment(PythonVirtualEnvInfo info, String envName) {
String os = System.getProperty("os.name").toLowerCase();
try {
// 1. 检测conda环境
// if (isCondaInstalled()) {
// String condaEnvPath = getCondaEnvPath(envName);
// if (condaEnvPath != null) {
// info.setVirtualEnvExists(true);
// info.setVirtualEnvType("conda");
// info.setVirtualEnvPath(condaEnvPath);
// info.setVirtualEnvPythonPath(getCondaEnvPythonPath(condaEnvPath));
// info.setActivateCommand("conda run -n " + envName);
//
// // 检测YOLO
// detectYoloInEnv(info);
// return;
// }
// }
// 2. 检测venv环境 - 专门检测Python目录下的虚拟环境
String pythonDir = info.getPythonPath();
if (pythonDir != null) {
// 如果是具体的python.exe路径,获取其所在目录
File pythonFile = new File(pythonDir);
if (pythonFile.isFile()) {
pythonDir = pythonFile.getParent();
}
// 构建虚拟环境路径:Python目录/envName
String venvPath = pythonDir + File.separator + envName;
File venvDir = new File(venvPath);
if (venvDir.exists() && venvDir.isDirectory()) {
// 检查是否是有效的虚拟环境
String venvPythonPath = getVenvEnvPythonPath(venvPath, os);
if (venvPythonPath != null && new File(venvPythonPath).exists()) {
info.setVirtualEnvExists(true);
info.setVirtualEnvType("venv");
info.setVirtualEnvPath(venvPath);
info.setVirtualEnvPythonPath(venvPythonPath);
info.setActivateCommand(os.contains("win") ?
venvPath + "\\Scripts\\activate && " :
"source " + venvPath + "/bin/activate && ");
// 检测YOLO
detectYoloInEnv(info);
return;
}
}
}
// 3. 检测virtualenv环境
// String virtualenvPath = getVirtualenvEnvPath(envName);
// if (virtualenvPath != null) {
// info.setVirtualEnvExists(true);
// info.setVirtualEnvType("virtualenv");
// info.setVirtualEnvPath(virtualenvPath);
// info.setVirtualEnvPythonPath(getVenvEnvPythonPath(virtualenvPath, os));
// info.setActivateCommand(os.contains("win") ?
// virtualenvPath + "\\Scripts\\activate" :
// "source " + virtualenvPath + "/bin/activate");
//
// // 检测YOLO
// detectYoloInEnv(info);
// return;
// }
info.setVirtualEnvExists(false);
info.setErrorMessage("未找到虚拟环境: " + envName);
} catch (Exception e) {
log.error("检测虚拟环境失败: " + envName, e);
info.setVirtualEnvExists(false);
info.setErrorMessage("检测虚拟环境异常: " + e.getMessage());
}
}
private boolean isCondaInstalled() {
try {
String os = System.getProperty("os.name").toLowerCase();
String command = "conda --version";
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
} else {
processBuilder.command("/bin/bash", "-c", command);
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = reader.readLine();
reader.close();
return line != null && line.toLowerCase().contains("conda");
} catch (Exception e) {
return false;
}
}
private String getCondaEnvPath(String envName) {
try {
String os = System.getProperty("os.name").toLowerCase();
String command = "conda env list";
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
} else {
processBuilder.command("/bin/bash", "-c", command);
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty() && !line.startsWith("#") && line.contains(envName)) {
String[] parts = line.split("\\s+");
if (parts.length >= 2) {
reader.close();
return parts[1].trim();
}
}
}
reader.close();
} catch (Exception e) {
log.warn("获取conda环境路径失败: " + envName, e);
}
return null;
}
private String getCondaEnvPythonPath(String condaEnvPath) {
String os = System.getProperty("os.name").toLowerCase();
return os.contains("win") ?
condaEnvPath + "\\python.exe" :
condaEnvPath + "/bin/python";
}
private String getVenvEnvPath(String pythonPath, String envName) {
// 检查 pythonPath\venv 目录下是否存在 envName 文件夹
File venvBaseDir = new File(pythonPath, "venv");
if (venvBaseDir.exists() && venvBaseDir.isDirectory()) {
File targetEnvDir = new File(venvBaseDir, envName);
if (targetEnvDir.exists() && targetEnvDir.isDirectory()) {
return targetEnvDir.getAbsolutePath();
}
}
// 检查当前目录下的常见虚拟环境目录
String[] commonDirs = {envName, "venv", ".venv", "env", ".env"};
for (String dir : commonDirs) {
File venvDir = new File(dir);
if (venvDir.exists() && venvDir.isDirectory()) {
return venvDir.getAbsolutePath();
}
}
// 检查当前目录下的常见虚拟环境目录
// 检查用户主目录下的虚拟环境
String userHome = System.getProperty("user.home");
File virtualenvsDir = new File(userHome, ".virtualenvs");
if (virtualenvsDir.exists()) {
File envDir = new File(virtualenvsDir, envName);
if (envDir.exists()) {
return envDir.getAbsolutePath();
}
}
return null;
}
private String getVirtualenvEnvPath(String envName) {
// 主要检查.virtualenvs目录
String userHome = System.getProperty("user.home");
File virtualenvsDir = new File(userHome, ".virtualenvs");
if (virtualenvsDir.exists()) {
File envDir = new File(virtualenvsDir, envName);
if (envDir.exists()) {
return envDir.getAbsolutePath();
}
}
return null;
}
private String getVenvEnvPythonPath(String venvPath, String os) {
return os.contains("win") ?
venvPath + "\\Scripts\\python.exe" :
venvPath + "/bin/python";
}
private void detectYoloInEnv(PythonVirtualEnvInfo info) {
if (info.getVirtualEnvPythonPath() == null) {
return;
}
try {
// 在虚拟环境中检测ultralytics包
String os = System.getProperty("os.name").toLowerCase();
String command;
if ("conda".equals(info.getVirtualEnvType())) {
command = "conda run -n " + new File(info.getVirtualEnvPath()).getName() + " python -c \"import ultralytics; print(ultralytics.__version__)\"";
} else if (os.contains("win")) {
command = info.getActivateCommand() + " python -c \"import ultralytics; print(ultralytics.__version__)\"";
} else {
command = info.getActivateCommand() + " python -c \"import ultralytics; print(ultralytics.__version__)\"";
}
ProcessBuilder processBuilder = new ProcessBuilder();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
} else {
processBuilder.command("/bin/bash", "-c", command);
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
reader.close();
if (line != null && !line.trim().isEmpty()) {
info.setYoloInstalled(true);
info.setYoloVersion(line.trim());
} else {
info.setYoloInstalled(false);
}
} catch (Exception e) {
log.warn("检测YOLO安装状态失败", e);
info.setYoloInstalled(false);
}
}
public static void main(String[] args) {
}
}

@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.annotation.service.python.vo;
import lombok.Data;
/**
* Python虚拟环境信息
*
* @author 管理员
*/
@Data
public class PythonVirtualEnvInfo {
/**
* Python是否存在
*/
private Boolean pythonExists;
/**
* Python版本
*/
private String pythonVersion;
/**
* Python路径
*/
private String pythonPath;
/**
* 虚拟环境是否存在
*/
private Boolean virtualEnvExists;
/**
* 虚拟环境类型 (venv/virtualenv/conda)
*/
private String virtualEnvType;
/**
* 虚拟环境路径
*/
private String virtualEnvPath;
/**
* 虚拟环境中Python路径
*/
private String virtualEnvPythonPath;
/**
* 是否安装了YOLO
*/
private Boolean yoloInstalled;
/**
* YOLO版本
*/
private String yoloVersion;
/**
* 激活命令(用于后续操作)
*/
private String activateCommand;
/**
* 错误信息
*/
private String errorMessage;
}

@ -1,18 +1,19 @@
package cn.iocoder.yudao.module.annotation.service.train; package cn.iocoder.yudao.module.annotation.service.train;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.SystemInfoRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.validation.Valid;
/** /**
* 训练 Service 接口 * 训练 Service 接口
* *
* @author 管理员 * @author 管理员
*/ */
public interface TrainService { public interface TrainService extends IService<TrainDO> {
/** /**
* 创建训练 * 创建训练
@ -52,4 +53,13 @@ public interface TrainService {
*/ */
PageResult<TrainDO> getTrainPage(TrainPageReqVO pageReqVO); PageResult<TrainDO> getTrainPage(TrainPageReqVO pageReqVO);
/**
* 获取系统信息(CPU、GPU、Python环境)
*
* @return 系统信息
*/
SystemInfoRespVO getSystemInfo();
public boolean init(TrainSaveReqVO createReqVO);
} }

@ -1,42 +1,447 @@
package cn.iocoder.yudao.module.annotation.service.train; package cn.iocoder.yudao.module.annotation.service.train;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.SystemInfoRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.train.vo.TrainSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.datas.DatasDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.mark.MarkInfoDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.train.TrainDO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.types.TypesDO;
import cn.iocoder.yudao.module.annotation.dal.mysql.train.TrainMapper; import cn.iocoder.yudao.module.annotation.dal.mysql.train.TrainMapper;
import cn.iocoder.yudao.module.annotation.service.MarkInfo.AnnotationData;
import cn.iocoder.yudao.module.annotation.service.MarkInfo.MarkInfoService;
import cn.iocoder.yudao.module.annotation.service.datas.DatasService;
import cn.iocoder.yudao.module.annotation.service.mark.MarkService;
import cn.iocoder.yudao.module.annotation.service.types.TypesService;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import javax.imageio.ImageIO;
//import static cn.iocoder.yudao.module.annotation.enums.ErrorCodeConstants.*; import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.stream.Collectors;
/** /**
* 训练 Service 实现类 * 训练 Service 实现类
* *
* @author 管理员 * @author 管理员
*/ */
@Slf4j
@Service @Service
@Validated @Validated
public class TrainServiceImpl implements TrainService { public class TrainServiceImpl extends ServiceImpl<TrainMapper, TrainDO> implements TrainService {
@Resource @Resource
private TrainMapper trainMapper; private TrainMapper trainMapper;
@Resource
private MarkService markService;
@Resource
private MarkInfoService markInfoService;
@Resource
private TypesService typesService;
@Resource
private DictDataService dictDataService;
@Resource
private DatasService datasService;
@Override @Override
public Integer createTrain(TrainSaveReqVO createReqVO) { public Integer createTrain(TrainSaveReqVO createReqVO) {
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
// 插入 // 插入
TrainDO train = BeanUtils.toBean(createReqVO, TrainDO.class); TrainDO train = BeanUtils.toBean(createReqVO, TrainDO.class);
DatasDO datas = datasService.getDatas(createReqVO.getDataId());
train.setType(datas.getType());
if(train.getType().equals("1")){
train.setModelPath(dictDataMap.get("detect_path").getValue());
}else if(train.getType().equals("2")){
train.setModelPath(dictDataMap.get("classify_path").getValue());
}
trainMapper.insert(train); trainMapper.insert(train);
String yoloDatasetPath = createYoloDatasetPath(train, dictDataMap.get("training_address").getValue());
train.setPath(yoloDatasetPath);
trainMapper.updateById(train);
// 返回 // 返回
return train.getId(); return train.getId();
} }
public boolean init(TrainSaveReqVO createReqVO) {
try {
TrainDO train = trainMapper.selectById(createReqVO.getId());
DatasDO datas = datasService.getDatas(createReqVO.getDataId());
// 获取配置和基础数据
Map<String, DictDataDO> dictDataMap = dictDataService.getDictDataList("visual_annotation_conf");
List<MarkDO> markList = markService.list(new QueryWrapper<MarkDO>()
.eq("data_id", createReqVO.getDataId()));
List<TypesDO> typesList = typesService.list(new QueryWrapper<TypesDO>()
.eq("data_id", createReqVO.getDataId()));
if (markList.isEmpty() || typesList.isEmpty()) {
log.warn("项目 {} 没有标注数据或类别数据", createReqVO.getDataId());
return false;
}
// 确定任务类型(检测/分类)
String taskType = getTaskType( datas.getType());
// 创建YOLO数据集目录结构
String yoloDatasetPath = train.getPath();
// 生成类别映射文件
generateClassesFile(typesList, yoloDatasetPath);
// 根据任务类型生成数据
if ("detect".equals(taskType)) {
generateDetectionDataset(dictDataMap,markList, typesList, createReqVO, yoloDatasetPath);
} else if ("classify".equals(taskType)) {
generateClassificationDataset(dictDataMap,markList, typesList, createReqVO, yoloDatasetPath);
}
// 生成YOLO配置文件
generateYamlConfig(typesList, taskType, yoloDatasetPath);
update( new UpdateWrapper<TrainDO>().eq("id", createReqVO.getId()).set("train_type", 6));
log.info("YOLO数据集生成完成: {}", yoloDatasetPath);
return true;
} catch (Exception e) {
log.error("生成YOLO数据集失败", e);
return false;
}
}
/**
* 获取任务类型
*/
private String getTaskType( String type) {
// 优先使用传入的type参数
if (type != null && "1".equals(type)) {
return "detect";
}else if(type != null && "2".equals(type)){
return "classify";
}
// 默认为检测任务
return "detect";
}
/**
* 创建YOLO数据集目录结构
*/
private String createYoloDatasetPath(TrainDO train, String customPath) {
String basePath = customPath != null ? customPath : System.getProperty("user.dir") + "/yolo_datasets";
String datasetPath = basePath + "/"+train.getName()+"_" + train.getId();
File datasetDir = new File(datasetPath);
if (!datasetDir.exists()) {
datasetDir.mkdirs();
}
// 创建YOLO标准目录结构
new File(datasetPath, "/images/train").mkdirs();
new File(datasetPath, "/images/val").mkdirs();
new File(datasetPath, "/images/test").mkdirs();
new File(datasetPath, "/labels/train").mkdirs();
new File(datasetPath, "/labels/val").mkdirs();
new File(datasetPath, "/labels/test").mkdirs();
// 分类任务需要额外的类别目录
new File(datasetPath, "/images/train/classify").mkdirs();
new File(datasetPath, "/images/val/classify").mkdirs();
new File(datasetPath, "/images/test/classify").mkdirs();
return datasetPath;
}
/**
* 生成classes.txt文件
*/
private void generateClassesFile(List<TypesDO> typesList, String datasetPath) {
try (PrintWriter writer = new PrintWriter(new FileWriter(datasetPath + "/classes.txt"))) {
// 按id排序(如果没有index字段,使用id排序)
typesList.sort(Comparator.comparing(TypesDO::getId));
for (TypesDO type : typesList) {
writer.println(type.getName());
}
} catch (IOException e) {
log.error("生成classes.txt失败", e);
}
}
/**
* 生成检测任务数据集
*/
private void generateDetectionDataset(Map<String, DictDataDO> dictDataMap,List<MarkDO> markList, List<TypesDO> typesList,
TrainSaveReqVO createReqVO, String datasetPath) {
// 按id排序建立类别ID映射(如果没有index字段,使用id排序)
typesList.sort(Comparator.comparing(TypesDO::getId));
Map<Integer, Integer> classIdMap = new HashMap<>();
for (int i = 0; i < typesList.size(); i++) {
classIdMap.put(typesList.get(i).getId(), i);
}
// 划分训练集、验证集、测试集
List<MarkDO> trainMarks = new ArrayList<>();
List<MarkDO> valMarks = new ArrayList<>();
List<MarkDO> testMarks = new ArrayList<>();
splitDataset(markList, createReqVO, trainMarks, valMarks, testMarks);
// 处理训练集
processDetectionDataset(dictDataMap,trainMarks, classIdMap, datasetPath + "/images/train",
datasetPath + "/labels/train");
// 处理验证集
processDetectionDataset(dictDataMap,valMarks, classIdMap, datasetPath + "/images/val",
datasetPath + "/labels/val");
// 处理测试集
processDetectionDataset(dictDataMap,testMarks, classIdMap, datasetPath + "/images/test",
datasetPath + "/labels/test");
}
/**
* 处理检测数据集
*/
private void processDetectionDataset(Map<String, DictDataDO> dictDataMap,List<MarkDO> marks, Map<Integer, Integer> classIdMap,
String imageDir, String labelDir) {
for (MarkDO mark : marks) {
try {
// 复制图片
String imageFileName = new File(dictDataMap.get("base_path").getValue()+mark.getPath()).getName();
File sourceImage = new File(dictDataMap.get("base_path").getValue()+mark.getPath());
File targetImage = new File(imageDir, imageFileName);
if (sourceImage.exists()) {
Files.copy(sourceImage.toPath(), targetImage.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
// 获取图片尺寸
BufferedImage image = ImageIO.read(sourceImage);
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// 生成YOLO格式的标注文件
String labelFileName = imageFileName.replaceAll("\\.[^.]+$", ".txt");
File labelFile = new File(labelDir, labelFileName);
List<MarkInfoDO> markInfoList = markInfoService.list(new QueryWrapper<MarkInfoDO>()
.eq("data_id", mark.getDataId()).eq("mark_id", mark.getId()));
try (PrintWriter writer = new PrintWriter(new FileWriter(labelFile))) {
for (MarkInfoDO markInfo : markInfoList) {
Integer classIndex = classIdMap.get(markInfo.getClassId());
if (classIndex != null) {
// 解析标注数据
AnnotationData annotationData = parseAnnotationData(markInfo);
if (annotationData != null) {
String yoloLine = convertToYOLOFormat(annotationData, classIndex,
imageWidth, imageHeight);
if (yoloLine != null) {
writer.println(yoloLine);
}
}
}
}
}
} catch (Exception e) {
log.error("处理检测数据失败: {}", mark.getPath(), e);
}
}
}
/**
* 生成分类任务数据集
*/
private void generateClassificationDataset(Map<String, DictDataDO> dictDataMap,List<MarkDO> markList, List<TypesDO> typesList,
TrainSaveReqVO createReqVO, String datasetPath) {
// 建立类别映射
Map<Integer, String> classNameMap = new HashMap<>();
for (TypesDO type : typesList) {
classNameMap.put(type.getId(), type.getName());
}
// 划分数据集
List<MarkDO> trainMarks = new ArrayList<>();
List<MarkDO> valMarks = new ArrayList<>();
List<MarkDO> testMarks = new ArrayList<>();
splitDataset(markList, createReqVO, trainMarks, valMarks, testMarks);
// 处理各数据集
processClassificationDataset(dictDataMap,trainMarks, classNameMap,
datasetPath + "/images/train/classify");
processClassificationDataset(dictDataMap,valMarks, classNameMap,
datasetPath + "/images/val/classify");
processClassificationDataset(dictDataMap,testMarks, classNameMap,
datasetPath + "/images/test/classify");
}
/**
* 处理分类数据集
*/
private void processClassificationDataset(Map<String, DictDataDO> dictDataMap,List<MarkDO> marks, Map<Integer, String> classNameMap,
String targetDir) {
for (MarkDO mark : marks) {
try {
// 获取图片的主要分类(假设一张图片只有一个主要类别)
List<MarkInfoDO> markInfoList = markInfoService.list(new QueryWrapper<MarkInfoDO>()
.eq("data_id", mark.getDataId()).eq("mark_id", mark.getId()));
if (!markInfoList.isEmpty()) {
String className = classNameMap.get(markInfoList.get(0).getClassId());
if (className != null) {
// 创建类别子目录
File classDir = new File(targetDir, className);
classDir.mkdirs();
// 复制图片到对应类别目录
String imageFileName = new File(mark.getPath()).getName();
File sourceImage = new File(mark.getPath());
File targetImage = new File(classDir, imageFileName);
if (sourceImage.exists()) {
Files.copy(sourceImage.toPath(), targetImage.toPath(),
StandardCopyOption.REPLACE_EXISTING);
}
}
}
} catch (Exception e) {
log.error("处理分类数据失败: {}", mark.getPath(), e);
}
}
}
/**
* 数据集划分
*/
private void splitDataset(List<MarkDO> markList, TrainSaveReqVO createReqVO,
List<MarkDO> trainMarks, List<MarkDO> valMarks, List<MarkDO> testMarks) {
int total = markList.size();
int trainCount = (int) (total * (createReqVO.getTrain() != null ? createReqVO.getTrain() / 100.0 : 0.8));
int valCount = (int) (total * (createReqVO.getVal() != null ? createReqVO.getVal() / 100.0 : 0.1));
// 随机打乱
Collections.shuffle(markList);
trainMarks.addAll(markList.subList(0, trainCount));
valMarks.addAll(markList.subList(trainCount, Math.min(trainCount + valCount, total)));
testMarks.addAll(markList.subList(trainCount + valCount, total));
}
/**
* 解析标注数据
*/
private AnnotationData parseAnnotationData(MarkInfoDO markInfo) {
if (markInfo.getAnnotationData() != null) {
return markInfo.getAnnotationData();
}
if (markInfo.getAnnotationDataString() != null && !markInfo.getAnnotationDataString().isEmpty()) {
return AnnotationData.fromString(markInfo.getAnnotationDataString());
}
// 如果没有结构化数据,使用基本字段
AnnotationData data = new AnnotationData();
AnnotationData.Target target = new AnnotationData.Target();
AnnotationData.Target.Selector selector = new AnnotationData.Target.Selector();
AnnotationData.Target.Selector.Geometry geometry = new AnnotationData.Target.Selector.Geometry();
selector.setGeometry(geometry);
target.setSelector(selector);
data.setTarget(target);
return data;
}
/**
* 转换为YOLO格式
*/
private String convertToYOLOFormat(AnnotationData annotationData, Integer classIndex,
int imageWidth, int imageHeight) {
try {
AnnotationData.Target.Selector.Geometry geometry = annotationData.getTarget().getSelector().getGeometry();
// 获取边界框信息
double x = geometry.getX() != null ? geometry.getX() : 0;
double y = geometry.getY() != null ? geometry.getY() : 0;
double w = geometry.getW() != null ? geometry.getW() : 0;
double h = geometry.getH() != null ? geometry.getH() : 0;
// 如果使用基本字段
if (x == 0 && y == 0 && w == 0 && h == 0) {
// 这里可以添加从MarkInfoDO基本字段获取坐标的逻辑
// 暂时返回null,表示无法转换
return null;
}
// 转换为YOLO格式(相对坐标和宽高)
double centerX = (x + w / 2) / imageWidth;
double centerY = (y + h / 2) / imageHeight;
double width = w / imageWidth;
double height = h / imageHeight;
// 确保坐标在0-1范围内
centerX = Math.max(0, Math.min(1, centerX));
centerY = Math.max(0, Math.min(1, centerY));
width = Math.max(0, Math.min(1, width));
height = Math.max(0, Math.min(1, height));
return String.format("%d %.6f %.6f %.6f %.6f", classIndex, centerX, centerY, width, height);
} catch (Exception e) {
log.error("转换YOLO格式失败", e);
return null;
}
}
/**
* 生成YAML配置文件
*/
private void generateYamlConfig(List<TypesDO> typesList, String taskType, String datasetPath) {
try (PrintWriter writer = new PrintWriter(new FileWriter(datasetPath + "/dataset.yaml"))) {
writer.println("# YOLO Dataset Configuration");
writer.println("path: " + datasetPath);
writer.println("train: images/train");
writer.println("val: images/val");
writer.println("test: images/test");
writer.println();
writer.println("# Classes");
writer.println("nc: " + typesList.size());
writer.println("names:");
// 按id排序输出类别名称(如果没有index字段,使用id)
typesList.sort(Comparator.comparing(TypesDO::getId));
for (int i = 0; i < typesList.size(); i++) {
TypesDO type = typesList.get(i);
writer.println(" " + i + ": " + type.getName());
}
} catch (IOException e) {
log.error("生成YAML配置文件失败", e);
}
}
@Override @Override
public void updateTrain(TrainSaveReqVO updateReqVO) { public void updateTrain(TrainSaveReqVO updateReqVO) {
@ -71,4 +476,252 @@ public class TrainServiceImpl implements TrainService {
return trainMapper.selectPage(pageReqVO); return trainMapper.selectPage(pageReqVO);
} }
@Override
public SystemInfoRespVO getSystemInfo() {
SystemInfoRespVO systemInfo = new SystemInfoRespVO();
// 获取CPU信息
systemInfo.setCpu(getCpuInfo());
// 获取GPU信息
systemInfo.setGpus(getGpuInfo());
// 获取Python信息
systemInfo.setPython(getPythonInfo());
return systemInfo;
}
private SystemInfoRespVO.CpuInfo getCpuInfo() {
SystemInfoRespVO.CpuInfo cpuInfo = new SystemInfoRespVO.CpuInfo();
try {
// 获取CPU型号
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows系统
Process process = Runtime.getRuntime().exec("wmic cpu get name");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty() && !line.contains("Name")) {
cpuInfo.setModel(line.trim());
break;
}
}
reader.close();
// 获取CPU核心数
process = Runtime.getRuntime().exec("wmic cpu get numberofcores");
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty() && !line.contains("NumberOfCores")) {
cpuInfo.setCores(Integer.parseInt(line.trim()));
break;
}
}
reader.close();
} else {
// Linux系统
Process process = Runtime.getRuntime().exec("cat /proc/cpuinfo | grep 'model name' | head -1");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
if (line != null) {
cpuInfo.setModel(line.split(":")[1].trim());
}
reader.close();
// 获取CPU核心数
process = Runtime.getRuntime().exec("nproc");
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
line = reader.readLine();
if (line != null) {
cpuInfo.setCores(Integer.parseInt(line.trim()));
}
reader.close();
}
// 获取CPU使用率(简化实现)
cpuInfo.setUsage(0.0);
} catch (Exception e) {
log.error("获取CPU信息失败", e);
cpuInfo.setModel("Unknown");
cpuInfo.setCores(0);
cpuInfo.setUsage(0.0);
}
return cpuInfo;
}
private List<SystemInfoRespVO.GpuInfo> getGpuInfo() {
List<SystemInfoRespVO.GpuInfo> gpuList = new ArrayList<>();
try {
// 使用nvidia-smi获取GPU信息
Process process = Runtime.getRuntime().exec("nvidia-smi --query-gpu=index,name,memory.total,memory.used,memory.free,utilization.gpu,temperature.gpu --format=csv,noheader,nounits");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty()) {
String[] parts = line.split(",\\s*");
if (parts.length >= 7) {
SystemInfoRespVO.GpuInfo gpuInfo = new SystemInfoRespVO.GpuInfo();
gpuInfo.setId(Integer.parseInt(parts[0].trim()));
gpuInfo.setName(parts[1].trim());
gpuInfo.setMemoryTotal(Long.parseLong(parts[2].trim()));
gpuInfo.setMemoryUsed(Long.parseLong(parts[3].trim()));
gpuInfo.setMemoryFree(Long.parseLong(parts[4].trim()));
gpuInfo.setUsage(Double.parseDouble(parts[5].trim()));
gpuInfo.setTemperature(Double.parseDouble(parts[6].trim()));
gpuList.add(gpuInfo);
}
}
}
reader.close();
} catch (Exception e) {
log.error("获取GPU信息失败", e);
// 如果nvidia-smi不可用,返回空列表
}
return gpuList;
}
public static void main(String[] args) {
TrainServiceImpl trainService = new TrainServiceImpl();
System.out.println(trainService.getPythonInfo());
}
private SystemInfoRespVO.PythonInfo getPythonInfo() {
SystemInfoRespVO.PythonInfo pythonInfo = new SystemInfoRespVO.PythonInfo();
try {
// 尝试多种Python命令
String[] pythonCommands = {"python", "python3", "py"};
String pythonCmd = null;
String version = null;
for (String cmd : pythonCommands) {
try {
Process process = Runtime.getRuntime().exec(cmd + " --version");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = reader.readLine();
if (line != null && line.toLowerCase().contains("python")) {
pythonCmd = cmd;
version = line.trim();
reader.close();
break;
}
reader.close();
} catch (Exception e) {
// 继续尝试下一个命令
}
}
if (pythonCmd != null) {
pythonInfo.setInstalled(true);
pythonInfo.setVersion(version);
// 获取Python路径
try {
Process process = Runtime.getRuntime().exec("where " + pythonCmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = reader.readLine();
if (line != null) {
pythonInfo.setPath(line.trim());
}
reader.close();
} catch (Exception e) {
log.warn("获取Python路径失败", e);
}
// 检查各种虚拟环境支持
checkVirtualEnvironmentSupport(pythonCmd, pythonInfo);
// 获取虚拟环境列表
// pythonInfo.setVirtualEnvs(getVirtualEnvironments(pythonCmd));
// 检查yolo虚拟环境
try {
Process process = Runtime.getRuntime().exec("conda env list");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
boolean yoloExists = false;
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("yolo")) {
yoloExists = true;
break;
}
}
pythonInfo.setYoloEnvExists(yoloExists);
reader.close();
if (yoloExists) {
// 使用conda直接在yolo环境中执行Python命令
process = Runtime.getRuntime().exec("conda run -n yolo python -c \"import ultralytics; print(ultralytics.__version__)\"");
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
line = reader.readLine();
if (line != null) {
pythonInfo.setYoloInstalled(true);
pythonInfo.setYoloVersion(line.trim());
} else {
pythonInfo.setYoloInstalled(false);
}
reader.close();
}
} catch (Exception e) {
pythonInfo.setYoloEnvExists(false);
pythonInfo.setYoloInstalled(false);
log.warn("检查YOLO环境失败", e);
}
} else {
pythonInfo.setInstalled(false);
}
} catch (Exception e) {
log.error("获取Python信息失败", e);
pythonInfo.setInstalled(false);
// pythonInfo.setVirtualEnvInstalled(false);
pythonInfo.setYoloEnvExists(false);
pythonInfo.setYoloInstalled(false);
}
return pythonInfo;
}
private void checkVirtualEnvironmentSupport(String pythonCmd, SystemInfoRespVO.PythonInfo pythonInfo) {
// 检查venv支持(Python 3.3+内置)
try {
Process process = Runtime.getRuntime().exec(pythonCmd + " -m venv --help");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String output = reader.lines().collect(Collectors.joining());
pythonInfo.setVenvSupported(!output.isEmpty());
reader.close();
} catch (Exception e) {
pythonInfo.setVenvSupported(false);
}
// 检查virtualenv
try {
Process process = Runtime.getRuntime().exec(pythonCmd + " -m virtualenv --version");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = reader.readLine();
pythonInfo.setVirtualenvInstalled(line != null && !line.isEmpty());
reader.close();
} catch (Exception e) {
pythonInfo.setVirtualenvInstalled(false);
}
// 检查conda
try {
Process process = Runtime.getRuntime().exec("conda --version");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = reader.readLine();
pythonInfo.setCondaInstalled(line != null && line.toLowerCase().contains("conda"));
reader.close();
} catch (Exception e) {
pythonInfo.setCondaInstalled(false);
}
}
} }

@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.annotation.service.traininfo;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.traininfo.TrainInfoDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* 训练信息 Service 接口
*
* @author 管理员
*/
public interface TrainInfoService {
/**
* 创建训练信息
*
* @param createReqVO 创建信息
* @return 编号
*/
Integer createTrainInfo(@Valid TrainInfoSaveReqVO createReqVO);
/**
* 更新训练信息
*
* @param updateReqVO 更新信息
*/
void updateTrainInfo(@Valid TrainInfoSaveReqVO updateReqVO);
/**
* 删除训练信息
*
* @param id 编号
*/
void deleteTrainInfo(Integer id);
/**
* 获得训练信息
*
* @param id 编号
* @return 训练信息
*/
TrainInfoDO getTrainInfo(Integer id);
/**
* 获得训练信息分页
*
* @param pageReqVO 分页查询
* @return 训练信息分页
*/
PageResult<TrainInfoDO> getTrainInfoPage(TrainInfoPageReqVO pageReqVO);
/**
* 保存训练轮次信息
*
* @param trainId 训练ID
* @param round 轮次
* @param roundTotal 总轮次
* @param info 轮次信息
* @param rate 识别率
* @return 编号
*/
Integer saveTrainRoundInfo(Integer trainId, Integer round, Integer roundTotal, String info, String rate);
List<TrainInfoRespVO> getTrainInfoList(Integer id);
}

@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.annotation.service.traininfo;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoRespVO;
import cn.iocoder.yudao.module.annotation.controller.admin.traininfo.vo.TrainInfoSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.traininfo.TrainInfoDO;
import cn.iocoder.yudao.module.annotation.dal.mysql.traininfo.TrainInfoMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* 训练信息 Service 实现类
*
* @author 管理员
*/
@Service
@Validated
public class TrainInfoServiceImpl implements TrainInfoService {
@Resource
private TrainInfoMapper trainInfoMapper;
@Override
public Integer createTrainInfo(TrainInfoSaveReqVO createReqVO) {
// 插入
TrainInfoDO data = BeanUtils.toBean(createReqVO, TrainInfoDO.class);
trainInfoMapper.insert(data);
// 返回
return data.getId();
}
@Override
public void updateTrainInfo(TrainInfoSaveReqVO updateReqVO) {
// 校验存在
// validateTrainInfoExists(updateReqVO.getId());
// 更新
TrainInfoDO updateObj = BeanUtils.toBean(updateReqVO, TrainInfoDO.class);
trainInfoMapper.updateById(updateObj);
}
@Override
public void deleteTrainInfo(Integer id) {
// 校验存在
validateTrainInfoExists(id);
// 删除
trainInfoMapper.deleteById(id);
}
private void validateTrainInfoExists(Integer id) {
if (trainInfoMapper.selectById(id) == null) {
// throw exception("训练信息不存在");
}
}
@Override
public TrainInfoDO getTrainInfo(Integer id) {
return trainInfoMapper.selectById(id);
}
@Override
public PageResult<TrainInfoDO> getTrainInfoPage(TrainInfoPageReqVO pageReqVO) {
return trainInfoMapper.selectPage(pageReqVO);
}
@Override
public Integer saveTrainRoundInfo(Integer trainId, Integer round, Integer roundTotal, String info, String rate) {
TrainInfoDO trainInfo = TrainInfoDO.builder()
.trainId(trainId)
.round(round)
.roundTotal(roundTotal)
.info(info)
.rate(rate)
.build();
trainInfoMapper.insert(trainInfo);
return trainInfo.getId();
}
@Override
public List<TrainInfoRespVO> getTrainInfoList(Integer trainId) {
List<TrainInfoDO> trainInfoList = trainInfoMapper.selectList(new LambdaQueryWrapperX<TrainInfoDO>()
.eq(TrainInfoDO::getTrainId, trainId)
.orderByAsc(TrainInfoDO::getRound)
.last("limit 15"));
return BeanUtils.toBean(trainInfoList, TrainInfoRespVO.class);
}
}

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.annotation.service.trainresult; package cn.iocoder.yudao.module.annotation.service.trainresult;
import java.util.*;
import jakarta.validation.*;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import jakarta.validation.Valid;
/** /**
* 识别结果 Service 接口 * 识别结果 Service 接口
@ -52,4 +51,5 @@ public interface TrainResultService {
*/ */
PageResult<TrainResultDO> getTrainResultPage(TrainResultPageReqVO pageReqVO); PageResult<TrainResultDO> getTrainResultPage(TrainResultPageReqVO pageReqVO);
TrainResultDO getStatus(Integer trainId);
} }

@ -1,20 +1,15 @@
package cn.iocoder.yudao.module.annotation.service.trainresult; package cn.iocoder.yudao.module.annotation.service.trainresult;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.*;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultPageReqVO;
import cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultSaveReqVO;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import cn.iocoder.yudao.module.annotation.dal.mysql.trainresult.TrainResultMapper; import cn.iocoder.yudao.module.annotation.dal.mysql.trainresult.TrainResultMapper;
import jakarta.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
//import static cn.iocoder.yudao.module.annotation.enums.ErrorCodeConstants.*; //import static cn.iocoder.yudao.module.annotation.enums.ErrorCodeConstants.*;
/** /**
@ -71,4 +66,10 @@ public class TrainResultServiceImpl implements TrainResultService {
return trainResultMapper.selectPage(pageReqVO); return trainResultMapper.selectPage(pageReqVO);
} }
public TrainResultDO getStatus(Integer trainId){
return trainResultMapper.selectOne(new LambdaQueryWrapperX<TrainResultDO>()
.eq(TrainResultDO::getTrainId, trainId)
.orderByDesc(TrainResultDO::getId).last("limit 1"));
}
} }

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.annotation.service.yolo;
import cn.iocoder.yudao.module.annotation.dal.yolo.YoloConfig;
import java.util.concurrent.CompletableFuture;
/**
* YOLO操作服务接口
*
* @author 管理员
*/
public interface YoloOperationService {
/**
* YOLO训练(异步)
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 训练配置
* @return 异步训练结果
*/
CompletableFuture<YoloConfig> trainAsync(String pythonPath, String envName, YoloConfig config);
/**
* YOLO验证
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 验证配置
* @return 验证结果配置
*/
YoloConfig validate(String pythonPath, String envName, YoloConfig config);
/**
* YOLO预测
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 预测配置
* @return 预测结果配置
*/
YoloConfig predict(String pythonPath, String envName, YoloConfig config);
/**
* YOLO分类
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 分类配置
* @return 分类结果配置
*/
YoloConfig classify(String pythonPath, String envName, YoloConfig config);
/**
* 导出YOLO模型
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 导出配置
* @return 导出结果配置
*/
YoloConfig export(String pythonPath, String envName, YoloConfig config);
/**
* 追踪任务
*
* @param pythonPath Python路径
* @param envName 虚拟环境名称
* @param config 追踪配置
* @return 追踪结果配置
*/
YoloConfig track(String pythonPath, String envName, YoloConfig config);
}

@ -0,0 +1,962 @@
package cn.iocoder.yudao.module.annotation.service.yolo;
import cn.iocoder.yudao.module.annotation.dal.dataobject.trainresult.TrainResultDO;
import cn.iocoder.yudao.module.annotation.dal.yolo.YoloConfig;
import cn.iocoder.yudao.module.annotation.service.python.PythonVirtualEnvService;
import cn.iocoder.yudao.module.annotation.service.python.vo.PythonVirtualEnvInfo;
import cn.iocoder.yudao.module.annotation.service.traininfo.TrainInfoService;
import cn.iocoder.yudao.module.annotation.service.trainresult.TrainResultService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* YOLO操作服务实现类
*
* @author 管理员
*/
@Slf4j
@Service
public class YoloOperationServiceImpl implements YoloOperationService {
@Resource
private PythonVirtualEnvService pythonVirtualEnvService;
@Resource
private TrainInfoService trainInfoService;
@Resource
private TrainResultService trainResultService;
// 异步执行器
private final Executor asyncExecutor = Executors.newCachedThreadPool();
@Override
public CompletableFuture<YoloConfig> trainAsync(String pythonPath, String envName, YoloConfig config) {
return CompletableFuture.supplyAsync(() -> {
config.setStatus("running");
config.setProgress(0);
config.setTaskId(UUID.randomUUID().toString());
// 生成训练ID
Integer trainId = generateTrainId(config);
// 记录上一轮的epoch,用于检测epoch完成
int[] lastSavedEpoch = {0};
try {
// 检测虚拟环境
PythonVirtualEnvInfo envInfo = pythonVirtualEnvService.activateVirtualEnv(pythonPath, envName);
if (!envInfo.getVirtualEnvExists() || !envInfo.getYoloInstalled()) {
config.setStatus("failed");
config.setErrorMessage("虚拟环境不存在或未安装YOLO");
return config;
}
// 构建训练命令
String command = buildTrainCommand(envInfo, config);
log.info("开始YOLO训练,命令: {}", command);
// 执行训练
ProcessBuilder processBuilder = new ProcessBuilder();
// 在Windows上使用cmd,在Linux/Mac上使用bash
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
// 设置环境变量确保正确处理中文路径
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
processBuilder.environment().put("LANG", "zh_CN.UTF-8");
} else {
processBuilder.command("/bin/bash", "-c", command);
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
}
Process process = processBuilder.start();
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder fullOutput = new StringBuilder();
String line;
// 同时读取标准输出和错误输出
String stdoutLine, stderrLine = null;
while ((stdoutLine = stdoutReader.readLine()) != null || (stderrLine = stderrReader.readLine()) != null) {
if (stdoutLine != null) {
log.info("YOLO训练日志: {}", stdoutLine);
fullOutput.append(stdoutLine).append("\n");
// 详细解析YOLO训练进度和结果
parseTrainingProgress(stdoutLine, config, trainId, lastSavedEpoch);
config.setLogMessage(stdoutLine);
}
if (stderrLine != null) {
log.info("YOLO训练日志: {}", stderrLine);
fullOutput.append(stderrLine).append("\n");
// 详细解析YOLO训练进度和结果
parseTrainingProgress(stderrLine, config, trainId, lastSavedEpoch);
config.setLogMessage(stderrLine);
}
}
int exitCode = process.waitFor();
stdoutReader.close();
stderrReader.close();
if (exitCode == 0) {
config.setStatus("completed");
config.setProgress(100);
config.setLogMessage("训练完成");
// 解析最终训练结果摘要
parseFinalResults(fullOutput.toString(), config);
// 保存最终训练结果
saveFinalTrainResult(trainId, config, fullOutput.toString());
} else {
config.setStatus("failed");
config.setErrorMessage("训练失败,退出码: " + exitCode);
}
} catch (Exception e) {
log.error("YOLO训练异常", e);
config.setStatus("failed");
config.setErrorMessage("训练异常: " + e.getMessage());
}
return config;
}, asyncExecutor);
}
@Override
public YoloConfig validate(String pythonPath, String envName, YoloConfig config) {
try {
PythonVirtualEnvInfo envInfo = pythonVirtualEnvService.activateVirtualEnv(pythonPath, envName);
if (!envInfo.getVirtualEnvExists() || !envInfo.getYoloInstalled()) {
config.setStatus("failed");
config.setErrorMessage("虚拟环境不存在或未安装YOLO");
return config;
}
String command = buildValidateCommand(envInfo, config);
log.info("开始YOLO验证,命令: {}", command);
ProcessBuilder processBuilder = new ProcessBuilder();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
// 设置环境变量确保正确处理中文路径
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
processBuilder.environment().put("LANG", "zh_CN.UTF-8");
} else {
processBuilder.command("/bin/bash", "-c", command);
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
}
Process process = processBuilder.start();
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
String line;
// 同时读取标准输出和错误输出
String stdoutLine, stderrLine = null;
while ((stdoutLine = stdoutReader.readLine()) != null || (stderrLine = stderrReader.readLine()) != null) {
if (stdoutLine != null) {
output.append(stdoutLine).append("\n");
log.info("YOLO验证日志: {}", stdoutLine);
}
if (stderrLine != null) {
output.append(stderrLine).append("\n");
log.info("YOLO验证日志: {}", stderrLine);
}
}
int exitCode = process.waitFor();
stdoutReader.close();
stderrReader.close();
if (exitCode == 0) {
config.setStatus("completed");
config.setLogMessage("验证完成");
config.setMetrics(output.toString());
} else {
config.setStatus("failed");
config.setErrorMessage("验证失败,退出码: " + exitCode);
config.setLogMessage(output.toString());
}
} catch (Exception e) {
log.error("YOLO验证异常", e);
config.setStatus("failed");
config.setErrorMessage("验证异常: " + e.getMessage());
}
return config;
}
@Override
public YoloConfig predict(String pythonPath, String envName, YoloConfig config) {
try {
PythonVirtualEnvInfo envInfo = pythonVirtualEnvService.activateVirtualEnv(pythonPath, envName);
if (!envInfo.getVirtualEnvExists() || !envInfo.getYoloInstalled()) {
config.setStatus("failed");
config.setErrorMessage("虚拟环境不存在或未安装YOLO");
return config;
}
String command = buildPredictCommand(envInfo, config);
log.info("开始YOLO预测,命令: {}", command);
ProcessBuilder processBuilder = new ProcessBuilder();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
// 设置环境变量确保正确处理中文路径
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
processBuilder.environment().put("LANG", "zh_CN.UTF-8");
} else {
processBuilder.command("/bin/bash", "-c", command);
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
}
Process process = processBuilder.start();
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
String line;
// 同时读取标准输出和错误输出
String stdoutLine, stderrLine = null;
while ((stdoutLine = stdoutReader.readLine()) != null || (stderrLine = stderrReader.readLine()) != null) {
if (stdoutLine != null) {
output.append(stdoutLine).append("\n");
log.info("YOLO预测日志: {}", stdoutLine);
}
if (stderrLine != null) {
output.append(stderrLine).append("\n");
log.info("YOLO预测日志: {}", stderrLine);
}
}
int exitCode = process.waitFor();
stdoutReader.close();
stderrReader.close();
if (exitCode == 0) {
config.setStatus("completed");
config.setLogMessage("预测完成");
config.setMetrics(output.toString());
} else {
config.setStatus("failed");
config.setErrorMessage("预测失败,退出码: " + exitCode);
config.setLogMessage(output.toString());
}
} catch (Exception e) {
log.error("YOLO预测异常", e);
config.setStatus("failed");
config.setErrorMessage("预测异常: " + e.getMessage());
}
return config;
}
@Override
public YoloConfig classify(String pythonPath, String envName, YoloConfig config) {
config.setTaskType("classify");
return predict(pythonPath, envName, config); // 分类和预测使用相同的逻辑,只是任务类型不同
}
@Override
public YoloConfig export(String pythonPath, String envName, YoloConfig config) {
try {
PythonVirtualEnvInfo envInfo = pythonVirtualEnvService.activateVirtualEnv(pythonPath, envName);
if (!envInfo.getVirtualEnvExists() || !envInfo.getYoloInstalled()) {
config.setStatus("failed");
config.setErrorMessage("虚拟环境不存在或未安装YOLO");
return config;
}
String command = buildExportCommand(envInfo, config);
log.info("开始YOLO导出,命令: {}", command);
ProcessBuilder processBuilder = new ProcessBuilder();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
// 设置环境变量确保正确处理中文路径
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
processBuilder.environment().put("LANG", "zh_CN.UTF-8");
} else {
processBuilder.command("/bin/bash", "-c", command);
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
log.info("YOLO导出日志: {}", line);
}
int exitCode = process.waitFor();
reader.close();
if (exitCode == 0) {
config.setStatus("completed");
config.setLogMessage("导出完成");
config.setMetrics(output.toString());
} else {
config.setStatus("failed");
config.setErrorMessage("导出失败,退出码: " + exitCode);
config.setLogMessage(output.toString());
}
} catch (Exception e) {
log.error("YOLO导出异常", e);
config.setStatus("failed");
config.setErrorMessage("导出异常: " + e.getMessage());
}
return config;
}
@Override
public YoloConfig track(String pythonPath, String envName, YoloConfig config) {
try {
PythonVirtualEnvInfo envInfo = pythonVirtualEnvService.activateVirtualEnv(pythonPath, envName);
if (!envInfo.getVirtualEnvExists() || !envInfo.getYoloInstalled()) {
config.setStatus("failed");
config.setErrorMessage("虚拟环境不存在或未安装YOLO");
return config;
}
String command = buildTrackCommand(envInfo, config);
log.info("开始YOLO追踪,命令: {}", command);
ProcessBuilder processBuilder = new ProcessBuilder();
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
processBuilder.command("cmd", "/c", command);
// 设置环境变量确保正确处理中文路径
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
processBuilder.environment().put("LANG", "zh_CN.UTF-8");
} else {
processBuilder.command("/bin/bash", "-c", command);
processBuilder.environment().put("PYTHONIOENCODING", "utf-8");
}
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
log.info("YOLO追踪日志: {}", line);
}
int exitCode = process.waitFor();
reader.close();
if (exitCode == 0) {
config.setStatus("completed");
config.setLogMessage("追踪完成");
config.setMetrics(output.toString());
} else {
config.setStatus("failed");
config.setErrorMessage("追踪失败,退出码: " + exitCode);
config.setLogMessage(output.toString());
}
} catch (Exception e) {
log.error("YOLO追踪异常", e);
config.setStatus("failed");
config.setErrorMessage("追踪异常: " + e.getMessage());
}
return config;
}
private String buildTrainCommand(PythonVirtualEnvInfo envInfo, YoloConfig config) {
StringBuilder command = new StringBuilder();
if ("conda".equals(envInfo.getVirtualEnvType())) {
command.append("conda run -n ").append(new File(envInfo.getVirtualEnvPath()).getName()).append(" ");
} else {
command.append(envInfo.getVirtualEnvPythonPath()).append(" ");
}
// 规范化路径,处理双斜杠和中文路径问题
String datasetPath = normalizePath(config.getDatasetPath());
String outputPath = normalizePath(config.getOutputPath());
String modelPath = config.getModelPath() != null ? normalizePath(config.getModelPath()) : null;
command.append("-c \"");
command.append("import sys; sys.path.insert(0, '.'); ");
command.append("from ultralytics import YOLO; ");
if (modelPath != null && !modelPath.trim().isEmpty()) {
command.append("model = YOLO(r'").append(escapePythonString(modelPath)).append("'); ");
} else {
command.append("model = YOLO('").append(config.getModelName()).append("'); ");
}
// 使用raw string来处理中文路径
command.append("model.train(data=r'").append(escapePythonString(datasetPath)).append("', ");
command.append("epochs=").append(config.getEpochs()).append(", ");
command.append("batch=").append(config.getBatchSize()).append(", ");
command.append("imgsz=").append(config.getImageSize()).append(", ");
// 取消注释设备和学习率配置
// command.append("device=").append(config.getDevice()).append(", ");
// command.append("lr0=").append(config.getLearningRate()).append(", ");
// command.append("workers=").append(config.getWorkers()).append(", ");
//
if (config.getSavePeriod() > 0) {
command.append("save_period=").append(config.getSavePeriod()).append(", ");
}
command.append("project=r'").append(escapePythonString(outputPath)).append("')\"");
return command.toString();
}
private String buildValidateCommand(PythonVirtualEnvInfo envInfo, YoloConfig config) {
StringBuilder command = new StringBuilder();
if ("conda".equals(envInfo.getVirtualEnvType())) {
command.append("conda run -n ").append(new File(envInfo.getVirtualEnvPath()).getName()).append(" ");
} else {
command.append(envInfo.getVirtualEnvPythonPath()).append(" ");
}
String datasetPath = normalizePath(config.getValDatasetPath() != null ?
config.getValDatasetPath() : config.getDatasetPath());
String modelPath = normalizePath(config.getModelPath());
command.append("-c \"");
command.append("import sys; sys.path.insert(0, '.'); ");
command.append("from ultralytics import YOLO; ");
command.append("model = YOLO(r'").append(escapePythonString(modelPath)).append("'); ");
command.append("results = model.val(data=r'").append(escapePythonString(datasetPath)).append("', ");
command.append("conf=").append(config.getConfThresh()).append(", ");
command.append("iou=").append(config.getIouThresh()).append(", ");
command.append("imgsz=").append(config.getImageSize()).append(", ");
command.append("device='").append(config.getDevice()).append("')\"");
return command.toString();
}
private String buildPredictCommand(PythonVirtualEnvInfo envInfo, YoloConfig config) {
StringBuilder command = new StringBuilder();
if ("conda".equals(envInfo.getVirtualEnvType())) {
command.append("conda run -n ").append(new File(envInfo.getVirtualEnvPath()).getName()).append(" ");
} else {
command.append(envInfo.getVirtualEnvPythonPath()).append(" ");
}
String inputPath = normalizePath(config.getInputPath());
String modelPath = normalizePath(config.getModelPath());
command.append("-c \"");
command.append("import sys; sys.path.insert(0, '.'); ");
command.append("from ultralytics import YOLO; ");
command.append("model = YOLO(r'").append(escapePythonString(modelPath)).append("'); ");
command.append("results = model.predict(source=r'").append(escapePythonString(inputPath)).append("', ");
command.append("conf=").append(config.getConfThresh()).append(", ");
command.append("iou=").append(config.getIouThresh()).append(", ");
command.append("imgsz=").append(config.getImageSize()).append(", ");
command.append("device='").append(config.getDevice()).append("', ");
command.append("save=").append(config.isSave()).append(", ");
command.append("show=").append(config.isShow()).append(", ");
command.append("save_txt=").append(config.isSaveTxt()).append(", ");
command.append("save_crop=").append(config.isSaveCrops()).append(")\"");
return command.toString();
}
private String buildExportCommand(PythonVirtualEnvInfo envInfo, YoloConfig config) {
StringBuilder command = new StringBuilder();
if ("conda".equals(envInfo.getVirtualEnvType())) {
command.append("conda run -n ").append(new File(envInfo.getVirtualEnvPath()).getName()).append(" ");
} else {
command.append(envInfo.getVirtualEnvPythonPath()).append(" ");
}
command.append("-c \"");
command.append("from ultralytics import YOLO; ");
command.append("model = YOLO('").append(config.getModelPath()).append("'); ");
command.append("model.export(format='").append(config.getFormat()).append("', ");
command.append("imgsz=").append(config.getImgsz()).append(", ");
command.append("optimize=").append(config.isOptimize()).append(")\"");
return command.toString();
}
private String buildTrackCommand(PythonVirtualEnvInfo envInfo, YoloConfig config) {
StringBuilder command = new StringBuilder();
if ("conda".equals(envInfo.getVirtualEnvType())) {
command.append("conda run -n ").append(new File(envInfo.getVirtualEnvPath()).getName()).append(" ");
} else {
command.append(envInfo.getVirtualEnvPythonPath()).append(" ");
}
command.append("-c \"");
command.append("from ultralytics import YOLO; ");
command.append("model = YOLO('").append(config.getModelPath()).append("'); ");
command.append("results = model.track(source='").append(config.getInputPath()).append("', ");
command.append("conf=").append(config.getTrackConf()).append(", ");
command.append("iou=").append(config.getTrackIou()).append(", ");
command.append("tracker='").append(config.getTracker()).append("', ");
command.append("device='").append(config.getDevice()).append("', ");
command.append("save=").append(config.isSave()).append(")\"");
return command.toString();
}
/**
* 解析训练进度详细信息
*/
private void parseTrainingProgress(String line, YoloConfig config, Integer trainId, int[] lastSavedEpoch) {
try {
String trimmedLine = line.trim();
// 解析Epoch信息: "Epoch 1/100" 或 "1/100"
if (trimmedLine.contains("Epoch") || (trimmedLine.matches("\\d+/\\d+"))) {
String[] parts = trimmedLine.split("\\s+");
for (String part : parts) {
if (part.contains("/")) {
String[] epochParts = part.split("/");
if (epochParts.length == 2) {
int currentEpoch = Integer.parseInt(epochParts[0]);
int totalEpochs = Integer.parseInt(epochParts[1]);
config.setCurrentEpoch(currentEpoch);
// 计算总体进度:epoch进度 + batch进度
int epochProgress = (int) ((double) currentEpoch / totalEpochs * 90); // 90%用于epoch,10%用于最终处理
int totalProgress = Math.min(epochProgress + (config.getBatchProgress() / 10), 95);
config.setProgress(totalProgress);
// 当epoch完成时保存训练信息
if (currentEpoch > lastSavedEpoch[0] && currentEpoch <= config.getEpochs()) {
saveTrainEpochInfo(trainId, currentEpoch, config.getEpochs(), config);
lastSavedEpoch[0] = currentEpoch;
}
}
}
}
}
// 解析批次信息: "1/100" 或 "batch 1/100"
if (trimmedLine.contains("batch") || trimmedLine.matches("\\d+/\\d+.*loss")) {
if (trimmedLine.contains("batch")) {
String[] parts = trimmedLine.split("\\s+");
for (int i = 0; i < parts.length; i++) {
if (parts[i].equals("batch") && i + 1 < parts.length && parts[i + 1].contains("/")) {
String[] batchParts = parts[i + 1].split("/");
if (batchParts.length == 2) {
int currentBatch = Integer.parseInt(batchParts[0]);
int totalBatches = Integer.parseInt(batchParts[1]);
config.setCurrentBatch(currentBatch);
config.setTotalBatches(totalBatches);
config.setBatchProgress((int) ((double) currentBatch / totalBatches * 100));
}
}
}
}
}
// 解析损失值: "loss: 0.1234", "box_loss: 0.1", "cls_loss: 0.05", "obj_loss: 0.02"
if (trimmedLine.contains("loss")) {
String[] lossParts = trimmedLine.split(",");
for (String lossPart : lossParts) {
lossPart = lossPart.trim();
if (lossPart.startsWith("loss:")) {
String value = lossPart.substring(5).trim();
config.setLoss(Double.parseDouble(value));
} else if (lossPart.startsWith("box_loss:")) {
// 可以添加更详细的损失记录
} else if (lossPart.startsWith("cls_loss:")) {
// 可以添加更详细的损失记录
} else if (lossPart.startsWith("obj_loss:")) {
// 可以添加更详细的损失记录
}
}
}
// 解析训练指标: "metrics/precision(B): 0.85", "metrics/recall(B): 0.78", "metrics/mAP50(B): 0.82", "metrics/mAP50-95(B): 0.65"
if (trimmedLine.contains("metrics/")) {
String[] metricParts = trimmedLine.split(",");
for (String metricPart : metricParts) {
metricPart = metricPart.trim();
if (metricPart.startsWith("metrics/precision")) {
String value = metricPart.split(":")[1].trim();
config.setPrecision(Double.parseDouble(value));
} else if (metricPart.startsWith("metrics/recall")) {
String value = metricPart.split(":")[1].trim();
config.setRecall(Double.parseDouble(value));
} else if (metricPart.startsWith("metrics/mAP50-95") || metricPart.startsWith("metrics/mAP")) {
String value = metricPart.split(":")[1].trim();
config.setMap(Double.parseDouble(value));
}
}
}
// 解析学习率: "lr: 0.001"
if (trimmedLine.contains("lr:")) {
// 可以记录学习率变化
}
} catch (Exception e) {
log.debug("解析训练进度时出错: {}", line, e);
// 忽略解析错误,继续处理下一行
}
}
/**
* 解析最终训练结果
*/
private void parseFinalResults(String fullOutput, YoloConfig config) {
try {
String[] lines = fullOutput.split("\n");
StringBuilder summary = new StringBuilder();
summary.append("训练完成!最终结果:\n");
boolean foundResults = false;
for (String line : lines) {
String trimmedLine = line.trim();
// 查找最终的验证结果
if (trimmedLine.contains("Class") || trimmedLine.contains("Images") ||
trimmedLine.contains("Instances") || trimmedLine.contains("Box")) {
// 这些是表格标题,跳过
continue;
}
// 查找最终的mAP结果
if (trimmedLine.contains("mAP50") || trimmedLine.contains("mAP50-95")) {
summary.append("• ").append(trimmedLine).append("\n");
foundResults = true;
}
// 查找精度和召回率
if (trimmedLine.contains("precision:") || trimmedLine.contains("recall:")) {
summary.append("• ").append(trimmedLine).append("\n");
foundResults = true;
}
// 查找模型保存路径
if (trimmedLine.contains("Results saved to") || trimmedLine.contains("Saved to")) {
summary.append("• ").append(trimmedLine).append("\n");
foundResults = true;
}
// 查找训练时间
if (trimmedLine.contains("Training completed in") || trimmedLine.contains("Time")) {
summary.append("• ").append(trimmedLine).append("\n");
foundResults = true;
}
}
if (!foundResults) {
summary.append("详细结果请查看日志输出和训练输出目录。\n");
}
// 添加训练参数摘要
summary.append("\n训练参数摘要:\n");
summary.append(String.format("• 模型: %s\n", config.getModelName()));
summary.append(String.format("• 训练轮数: %d\n", config.getEpochs()));
summary.append(String.format("• 批次大小: %d\n", config.getBatchSize()));
summary.append(String.format("• 学习率: %.4f\n", config.getLearningRate()));
summary.append(String.format("• 图像尺寸: %d\n", config.getImageSize()));
summary.append(String.format("• 设备: %s\n", config.getDevice()));
// 如果解析到了指标,添加最终指标摘要
if (config.getPrecision() > 0 || config.getRecall() > 0 || config.getMap() > 0) {
summary.append("\n最终性能指标:\n");
if (config.getPrecision() > 0) {
summary.append(String.format("• 精度(Precision): %.4f\n", config.getPrecision()));
}
if (config.getRecall() > 0) {
summary.append(String.format("• 召回率(Recall): %.4f\n", config.getRecall()));
}
if (config.getMap() > 0) {
summary.append(String.format("• mAP: %.4f\n", config.getMap()));
}
}
config.setTrainingSummary(summary.toString());
config.setMetrics(summary.toString());
} catch (Exception e) {
log.error("解析最终训练结果时出错", e);
config.setTrainingSummary("训练完成,但解析详细结果时出错。请检查日志和输出文件。");
}
}
/**
* 生成训练ID
*/
private Integer generateTrainId(YoloConfig config) {
// 这里可以根据业务需求生成训练ID
// 简单实现:使用当前时间戳的后8位
return (int) (System.currentTimeMillis() % 100000000);
}
/**
* 保存训练轮次信息
*/
private void saveTrainEpochInfo(Integer trainId, Integer currentEpoch, Integer totalEpochs, YoloConfig config) {
try {
// 构建训练信息
StringBuilder epochInfo = new StringBuilder();
epochInfo.append("Epoch ").append(currentEpoch).append("/").append(totalEpochs).append(" 完成\n");
if (config.getLoss() > 0) {
epochInfo.append("损失值: ").append(String.format("%.4f", config.getLoss())).append("\n");
}
if (config.getPrecision() > 0) {
epochInfo.append("精度: ").append(String.format("%.4f", config.getPrecision())).append("\n");
}
if (config.getRecall() > 0) {
epochInfo.append("召回率: ").append(String.format("%.4f", config.getRecall())).append("\n");
}
if (config.getMap() > 0) {
epochInfo.append("mAP: ").append(String.format("%.4f", config.getMap())).append("\n");
}
// 构建识别率字符串
StringBuilder rateStr = new StringBuilder();
if (config.getPrecision() > 0) {
rateStr.append("precision:").append(String.format("%.4f", config.getPrecision()));
}
if (config.getRecall() > 0) {
if (rateStr.length() > 0) rateStr.append(",");
rateStr.append("recall:").append(String.format("%.4f", config.getRecall()));
}
if (config.getMap() > 0) {
if (rateStr.length() > 0) rateStr.append(",");
rateStr.append("map:").append(String.format("%.4f", config.getMap()));
}
// 保存到数据库
trainInfoService.saveTrainRoundInfo(
trainId,
currentEpoch,
totalEpochs,
epochInfo.toString(),
rateStr.toString()
);
log.info("已保存训练轮次信息: trainId={}, epoch={}", trainId, currentEpoch);
} catch (Exception e) {
log.error("保存训练轮次信息失败: trainId={}, epoch={}", trainId, currentEpoch, e);
// 不影响训练继续进行
}
}
/**
* 保存最终训练结果
*/
private void saveFinalTrainResult(Integer trainId, YoloConfig config, String fullOutput) {
try {
// 构建结果路径(通常是训练输出目录)
String resultPath = config.getOutputPath();
if (resultPath == null || resultPath.trim().isEmpty()) {
resultPath = "runs/detect/train"; // YOLO默认输出路径
}
// 构建最终识别率
StringBuilder finalRate = new StringBuilder();
if (config.getPrecision() > 0) {
finalRate.append("precision:").append(String.format("%.4f", config.getPrecision()));
}
if (config.getRecall() > 0) {
if (finalRate.length() > 0) finalRate.append(",");
finalRate.append("recall:").append(String.format("%.4f", config.getRecall()));
}
if (config.getMap() > 0) {
if (finalRate.length() > 0) finalRate.append(",");
finalRate.append("map:").append(String.format("%.4f", config.getMap()));
}
// 创建TrainResultDO并保存
TrainResultDO trainResult = TrainResultDO.builder()
.trainId(trainId)
.path(resultPath)
.rate(finalRate.toString())
.dataId(null) // 可以根据需要设置数据集ID
.build();
// 由于TrainResultService可能没有直接的DO保存方法,我们可能需要使用VO
// 这里假设需要转换为SaveReqVO,如果没有这个方法,需要添加
saveTrainResultDO(trainResult);
log.info("已保存最终训练结果: trainId={}, path={}", trainId, resultPath);
} catch (Exception e) {
log.error("保存最终训练结果失败: trainId={}", trainId, e);
// 不影响训练完成状态
}
}
/**
* 保存TrainResultDO到数据库
*/
private void saveTrainResultDO(TrainResultDO trainResult) {
try {
// 创建SaveReqVO
cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultSaveReqVO saveReqVO =
new cn.iocoder.yudao.module.annotation.controller.admin.trainresult.vo.TrainResultSaveReqVO();
// 设置值
saveReqVO.setTrainId(trainResult.getTrainId());
saveReqVO.setPath(trainResult.getPath());
saveReqVO.setRate(trainResult.getRate());
saveReqVO.setDataId(trainResult.getDataId());
// 保存
trainResultService.createTrainResult(saveReqVO);
} catch (Exception e) {
log.error("保存训练结果失败: {}", trainResult, e);
// 不影响训练完成状态
}
}
/**
* 规范化路径,处理Windows/Linux路径分隔符、双斜杠等问题
*
* @param path 原始路径
* @return 规范化后的路径
*/
private String normalizePath(String path) {
if (path == null || path.trim().isEmpty()) {
return path;
}
try {
String normalized = path.trim();
// 在Windows系统中,特别处理中文路径和特殊字符
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// 对于Windows系统,需要确保路径能够正确处理中文
// 将反斜杠转换为正斜杠,但保持路径编码正确
normalized = normalized.replace("\\", "/");
// 处理双斜杠问题
while (normalized.contains("//")) {
normalized = normalized.replace("//", "/");
}
// 确保路径以正确的格式存在,避免额外的斜杠
if (normalized.startsWith("/") && normalized.length() > 1 && normalized.charAt(1) != '/') {
// 这是网络路径或绝对路径,保持原样
}
} else {
// 对于Linux/Mac系统,使用File类进行标准化
File file = new File(normalized);
normalized = file.getCanonicalPath();
}
log.debug("路径规范化: {} -> {}", path, normalized);
return normalized;
} catch (Exception e) {
log.warn("路径规范化失败,使用原始路径: {}", path, e);
return path;
}
}
/**
* 转义Python字符串中的特殊字符
*
* @param input 原始字符串
* @return 转义后的字符串
*/
private String escapePythonString(String input) {
if (input == null) {
return "";
}
// 首先规范化路径
String normalized = normalizePath(input);
// 转义Python字符串中的特殊字符
StringBuilder escaped = new StringBuilder();
for (int i = 0; i < normalized.length(); i++) {
char c = normalized.charAt(i);
switch (c) {
case '\\':
// 反斜杠需要转义
escaped.append("\\\\");
break;
case '"':
// 双引号需要转义(因为我们用双引号包围Python字符串)
escaped.append("\\\"");
break;
case '\'':
// 单引号也需要转义
escaped.append("\\\'");
break;
case '\n':
// 换行符
escaped.append("\\n");
break;
case '\r':
// 回车符
escaped.append("\\r");
break;
case '\t':
// 制表符
escaped.append("\\t");
break;
case '\b':
// 退格符
escaped.append("\\b");
break;
case '\f':
// 换页符
escaped.append("\\f");
break;
default:
// 对于其他控制字符(ASCII < 32),进行转义
if (c < 32) {
escaped.append(String.format("\\x%02x", (int) c));
} else {
escaped.append(c);
}
break;
}
}
log.debug("Python字符串转义: {} -> {}", input, escaped.toString());
return escaped.toString();
}
}

@ -9,4 +9,9 @@
文档可见:https://www.iocoder.cn/MyBatis/x-plugins/ 文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
--> -->
<update id="updateByIdSetStatus">
UPDATE annotation_mark
SET status = #{status}
WHERE id = #{id}
</update>
</mapper> </mapper>

@ -47,10 +47,10 @@ spring:
datasource: datasource:
master: master:
name: sy name: sy
url: jdbc:mysql://121.43.244.209:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 url: jdbc:mysql://192.168.1.21:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
username: root username: root
password: Sy+1234567 password: upright
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data: data:
redis: redis:
@ -62,7 +62,7 @@ spring:
max-wait: 5000ms # 增加获取连接的最大等待时间 max-wait: 5000ms # 增加获取连接的最大等待时间
shutdown-timeout: 1000ms # 增加关闭超时时间 shutdown-timeout: 1000ms # 增加关闭超时时间
timeout: 5000ms # 增加连接超时时间 timeout: 5000ms # 增加连接超时时间
host: 121.43.244.209 # 地址 host: 192.168.1.21 # 地址
port: 6379 # 端口 port: 6379 # 端口
database: 0 # 数据库索引 database: 0 # 数据库索引
# password: dev # 密码,建议生产环境开启 # password: dev # 密码,建议生产环境开启

Loading…
Cancel
Save