将检测任务迁移python

This commit is contained in:
2025-09-30 14:23:33 +08:00
parent 3fe5f8083d
commit 39d39a7a24
69 changed files with 7921 additions and 1836 deletions

View File

@@ -1,37 +1,21 @@
package com.ruoyi.video.common;
/**
* 定义一个枚举类,表示客户端类型
* @Author: orange
* @CreateTime: 2025-01-16
* 客户端类型枚举
*/
public enum ClientType {
// 定义一个HTTP类型的客户端类型为0信息为"http"
HTTP(0,"http"),
// 定义一个WEBSOCKET类型的客户端类型为1信息为"websocket"
WEBSOCKET(1,"websocket"),
;
// 定义客户端类型
private int type;
// 定义客户端信息
private String info;
/**
* HTTP客户端
*/
HTTP,
// 构造方法,初始化客户端类型和信息
private ClientType(int type, String info){
this.type = type;
this.info = info;
}
/**
* WebSocket客户端
*/
WEBSOCKET,
// 获取客户端类型
public int getType(){
return type;
}
// 获取客户端信息
public String getInfo(){
return info;
}
/**
* API客户端
*/
API
}

View File

@@ -9,7 +9,15 @@ import java.util.*;
public final class ModelManager implements AutoCloseable {
private final Map<String, YoloDetector> map = new LinkedHashMap<>();
// Python服务的默认API URL
private static final String DEFAULT_PYTHON_API_URL = "http://localhost:8000/api/detect/file";
/**
* 从配置加载检测器
* @param modelsJson 模型配置JSON的URL
* @throws Exception 如果加载失败
*/
public void load(URL modelsJson) throws Exception {
ObjectMapper om = new ObjectMapper();
ArrayNode arr = (ArrayNode) om.readTree(modelsJson);
@@ -20,20 +28,15 @@ public final class ModelManager implements AutoCloseable {
int i=0;
for (var node : arr) {
String name = node.get("name").asText();
String path = node.get("path").asText();
int w = node.get("size").get(0).asInt();
int h = node.get("size").get(1).asInt();
String backend = node.get("backend").asText();
URL dirUrl = Objects.requireNonNull(getClass().getClassLoader().getResource(path),
"Resource not found: " + path);
Path dir = Paths.get(dirUrl.toURI());
String pythonModelName = node.get("pythonModelName").asText(name); // 默认与name相同
String pythonApiUrl = node.has("pythonApiUrl") ?
node.get("pythonApiUrl").asText() : DEFAULT_PYTHON_API_URL;
int rgb = palette[i % palette.length]; i++;
int bgr = ((rgb & 0xFF) << 16) | (rgb & 0xFF00) | ((rgb >> 16) & 0xFF);
// 使用OnnxYoloDetector替代OpenVinoYoloDetector
YoloDetector det = new OnnxYoloDetector(name, dir, w, h, backend, bgr);
// 使用HttpYoloDetector替代本地ONNX检测器
YoloDetector det = new HttpYoloDetector(name, pythonApiUrl, pythonModelName, bgr);
map.put(name, det);
}
}

View File

@@ -1,86 +1,55 @@
package com.ruoyi.video.domain;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 警记录对象 v_alarm_record
*
* @Author: orange
* @CreateTime: 2025-01-16
* 警记录实体类
*/
public class AlarmRecord extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 报警记录ID */
public class AlarmRecord {
/** 告警ID */
private Long alarmId;
/** 巡检任务ID */
@Excel(name = "巡检任务ID")
private Long taskId;
/** 任务名称 */
@Excel(name = "任务名称")
private String taskName;
/** 设备ID */
@Excel(name = "设备ID")
private Long deviceId;
/** 设备名称 */
@Excel(name = "设备名称")
private String deviceName;
/** 报警类型 */
@Excel(name = "报警类型")
/** 告警类型 */
private String alarmType;
/** 警级别(1=低,2=中,3=高) */
@Excel(name = "报警级别", readConverterExp = "1=低,2=中,3=高")
/** 警级别 */
private String alarmLevel;
/** 报警描述 */
@Excel(name = "报警描述")
private String alarmDesc;
/** 检测置信度 */
@Excel(name = "检测置信度")
private Double confidence;
/** 报警图片路径 */
@Excel(name = "报警图片")
private String imagePath;
/** 报警视频路径 */
@Excel(name = "报警视频")
private String videoPath;
/** 报警时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "报警时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date alarmTime;
/** 处理状态(0=未处理,1=已处理,2=已忽略) */
@Excel(name = "处理状态", readConverterExp = "0=未处理,1=已处理,2=已忽略")
private String handleStatus;
/** 告警内容 */
private String alarmContent;
/** 巡检任务ID */
private Long taskId;
/** 图像对象存储ID */
private Long imageOssId;
/** 帧位置 */
private long framePosition;
/** 置信度 */
private double confidence;
/** 处理状态(0-未处理,1-已处理) */
private Integer status;
/** 处理人 */
@Excel(name = "处理人")
private String handleBy;
/** 处理时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "处理时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date handleTime;
/** 处理备注 */
@Excel(name = "处理备注")
private String handleRemark;
public AlarmRecord() {}
/** 创建时间 */
private Date createTime;
/** 备注 */
private String remark;
public Long getAlarmId() {
return alarmId;
@@ -90,22 +59,6 @@ public class AlarmRecord extends BaseEntity {
this.alarmId = alarmId;
}
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Long getDeviceId() {
return deviceId;
}
@@ -114,14 +67,6 @@ public class AlarmRecord extends BaseEntity {
this.deviceId = deviceId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getAlarmType() {
return alarmType;
}
@@ -130,60 +75,52 @@ public class AlarmRecord extends BaseEntity {
this.alarmType = alarmType;
}
public String getAlarmLevel() {
return alarmLevel;
public String getAlarmContent() {
return alarmContent;
}
public void setAlarmLevel(String alarmLevel) {
this.alarmLevel = alarmLevel;
public void setAlarmContent(String alarmContent) {
this.alarmContent = alarmContent;
}
public String getAlarmDesc() {
return alarmDesc;
public Long getTaskId() {
return taskId;
}
public void setAlarmDesc(String alarmDesc) {
this.alarmDesc = alarmDesc;
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public Double getConfidence() {
public Long getImageOssId() {
return imageOssId;
}
public void setImageOssId(Long imageOssId) {
this.imageOssId = imageOssId;
}
public long getFramePosition() {
return framePosition;
}
public void setFramePosition(long framePosition) {
this.framePosition = framePosition;
}
public double getConfidence() {
return confidence;
}
public void setConfidence(Double confidence) {
public void setConfidence(double confidence) {
this.confidence = confidence;
}
public String getImagePath() {
return imagePath;
public Integer getStatus() {
return status;
}
public void setImagePath(String imagePath) {
this.imagePath = imagePath;
}
public String getVideoPath() {
return videoPath;
}
public void setVideoPath(String videoPath) {
this.videoPath = videoPath;
}
public Date getAlarmTime() {
return alarmTime;
}
public void setAlarmTime(Date alarmTime) {
this.alarmTime = alarmTime;
}
public String getHandleStatus() {
return handleStatus;
}
public void setHandleStatus(String handleStatus) {
this.handleStatus = handleStatus;
public void setStatus(Integer status) {
this.status = status;
}
public String getHandleBy() {
@@ -210,25 +147,51 @@ public class AlarmRecord extends BaseEntity {
this.handleRemark = handleRemark;
}
@Override
public String toString() {
return "AlarmRecord{" +
"alarmId=" + alarmId +
", taskId=" + taskId +
", taskName='" + taskName + '\'' +
", deviceId=" + deviceId +
", deviceName='" + deviceName + '\'' +
", alarmType='" + alarmType + '\'' +
", alarmLevel='" + alarmLevel + '\'' +
", alarmDesc='" + alarmDesc + '\'' +
", confidence=" + confidence +
", imagePath='" + imagePath + '\'' +
", videoPath='" + videoPath + '\'' +
", alarmTime=" + alarmTime +
", handleStatus='" + handleStatus + '\'' +
", handleBy='" + handleBy + '\'' +
", handleTime=" + handleTime +
", handleRemark='" + handleRemark + '\'' +
'}';
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
/**
* 获取ID的兼容方法保持与原有代码兼容
*/
public Long getId() {
return alarmId;
}
/**
* 设置ID的兼容方法保持与原有代码兼容
*/
public void setId(Long id) {
this.alarmId = id;
}
/**
* 设置告警级别
*
* @param alarmLevel 告警级别
*/
public void setAlarmLevel(String alarmLevel) {
this.alarmLevel = alarmLevel;
}
/**
* 获取告警级别
*
* @return 告警级别
*/
public String getAlarmLevel() {
return alarmLevel;
}
}

View File

@@ -2,5 +2,61 @@ package com.ruoyi.video.domain;
import org.bytedeco.opencv.opencv_core.Rect;
public record Detection(String cls, float conf, Rect box, int colorBGR) {
/**
* 检测结果实体类
*/
public class Detection {
/** 标签 */
private String label;
/** 置信度 */
private double confidence;
/** 边界框 */
private Rect rect;
/** 边界框颜色(BGR格式) */
private int colorBGR;
public Detection() {
}
public Detection(String label, double confidence, Rect rect, int colorBGR) {
this.label = label;
this.confidence = confidence;
this.rect = rect;
this.colorBGR = colorBGR;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public double getConfidence() {
return confidence;
}
public void setConfidence(double confidence) {
this.confidence = confidence;
}
public Rect getRect() {
return rect;
}
public void setRect(Rect rect) {
this.rect = rect;
}
public int getColorBGR() {
return colorBGR;
}
public void setColorBGR(int colorBGR) {
this.colorBGR = colorBGR;
}
}

View File

@@ -1,196 +1,133 @@
package com.ruoyi.video.domain;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 巡检任务对象 v_inspection_task
*
* @Author: orange
* @CreateTime: 2025-01-16
* 巡检任务实体类
*/
public class InspectionTask extends BaseEntity {
private static final long serialVersionUID = 1L;
public class InspectionTask {
/** 巡检任务ID */
private Long taskId;
/** 任务名称 */
@Excel(name = "任务名称")
private String taskName;
/** 设备ID */
@Excel(name = "设备ID")
private Long deviceId;
/** 设备名称 */
@Excel(name = "设备名称")
private String deviceName;
/** Cron表达式 */
@Excel(name = "Cron表达式")
private String cronExpression;
/** 巡检时长(秒) */
@Excel(name = "巡检时长")
private Integer duration;
/** 任务状态(0=启用,1=停用) */
@Excel(name = "任务状态", readConverterExp = "0=启用,1=停用")
private String status;
/** 是否启用检测(0=启用,1=停用) */
@Excel(name = "启用检测", readConverterExp = "0=启用,1=停用")
private String enableDetection;
/** 检测阈值 */
@Excel(name = "检测阈值")
private Double threshold;
/** 最后执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "最后执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date lastExecuteTime;
/** 下次执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "下次执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date nextExecuteTime;
/** 执行次数 */
@Excel(name = "执行次数")
private Long executeCount;
/** 报警次数 */
@Excel(name = "报警次数")
private Long alarmCount;
public InspectionTask() {}
/** 视频对象存储ID */
private Long videoOssId;
/** 处理后视频对象存储ID */
private Long processedVideoOssId;
/** 视频状态(0-未录制,1-已录制未分析,2-已分析) */
private Integer videoStatus;
/** 任务状态(0-待执行,1-执行中,2-已完成) */
private Integer status;
/** 创建时间 */
private Date createTime;
/** 更新时间 */
private Date updateTime;
/** 备注 */
private String remark;
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public Long getDeviceId() {
return deviceId;
}
public void setDeviceId(Long deviceId) {
this.deviceId = deviceId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public Integer getDuration() {
return duration;
}
public void setDuration(Integer duration) {
this.duration = duration;
}
public String getStatus() {
public Long getVideoOssId() {
return videoOssId;
}
public void setVideoOssId(Long videoOssId) {
this.videoOssId = videoOssId;
}
public Long getProcessedVideoOssId() {
return processedVideoOssId;
}
public void setProcessedVideoOssId(Long processedVideoOssId) {
this.processedVideoOssId = processedVideoOssId;
}
public Integer getVideoStatus() {
return videoStatus;
}
public void setVideoStatus(Integer videoStatus) {
this.videoStatus = videoStatus;
}
public Integer getStatus() {
return status;
}
public void setStatus(String status) {
public void setStatus(Integer status) {
this.status = status;
}
public String getEnableDetection() {
return enableDetection;
public Date getCreateTime() {
return createTime;
}
public void setEnableDetection(String enableDetection) {
this.enableDetection = enableDetection;
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Double getThreshold() {
return threshold;
public Date getUpdateTime() {
return updateTime;
}
public void setThreshold(Double threshold) {
this.threshold = threshold;
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getLastExecuteTime() {
return lastExecuteTime;
public String getRemark() {
return remark;
}
public void setLastExecuteTime(Date lastExecuteTime) {
this.lastExecuteTime = lastExecuteTime;
public void setRemark(String remark) {
this.remark = remark;
}
public Date getNextExecuteTime() {
return nextExecuteTime;
/**
* 获取ID的兼容方法保持与原有代码兼容
*/
public Long getId() {
return taskId;
}
public void setNextExecuteTime(Date nextExecuteTime) {
this.nextExecuteTime = nextExecuteTime;
}
public Long getExecuteCount() {
return executeCount;
}
public void setExecuteCount(Long executeCount) {
this.executeCount = executeCount;
}
public Long getAlarmCount() {
return alarmCount;
}
public void setAlarmCount(Long alarmCount) {
this.alarmCount = alarmCount;
}
@Override
public String toString() {
return "InspectionTask{" +
"taskId=" + taskId +
", taskName='" + taskName + '\'' +
", deviceId=" + deviceId +
", deviceName='" + deviceName + '\'' +
", cronExpression='" + cronExpression + '\'' +
", duration=" + duration +
", status='" + status + '\'' +
", enableDetection='" + enableDetection + '\'' +
", threshold=" + threshold +
", lastExecuteTime=" + lastExecuteTime +
", nextExecuteTime=" + nextExecuteTime +
", executeCount=" + executeCount +
", alarmCount=" + alarmCount +
'}';
/**
* 设置ID的兼容方法保持与原有代码兼容
*/
public void setId(Long id) {
this.taskId = id;
}
}

View File

@@ -1,36 +1,151 @@
package com.ruoyi.video.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import java.util.Date;
/**
* MinIO 返回结果记录实体,对应表 v_minio_object
* MinIO对象存储实体类
*/
public class VMinioObject extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
public class VMinioObject {
/** 对象ID */
private Long objectId;
/** MinIO 对象名Key */
/** 存储桶名称 */
private String bucketName;
/** 对象名称 */
private String objectName;
/** 访问URL */
private String url;
/** 原始文件名(上传时的文件名) */
private String originalName;
/** 对象URL */
private String objectUrl;
/** 对象大小(字节) */
private Long objectSize;
/** 对象类型 */
private String objectType;
/** 创建者 */
private String createBy;
/** 创建时间 */
private Date createTime;
/** 更新者 */
private String updateBy;
/** 更新时间 */
private Date updateTime;
/** 备注 */
private String remark;
/** 删除标志0存在 2删除 */
private String delFlag;
public String getDelFlag() {
return delFlag;
}
public Long getObjectId() { return objectId; }
public void setObjectId(Long objectId) { this.objectId = objectId; }
public void setDelFlag(String delFlag) {
this.delFlag = delFlag;
}
/** 原始文件名 */
private String originalName;
public String getOriginalName() {
return originalName;
}
public void setOriginalName(String originalName) {
this.originalName = originalName;
}
public String getObjectName() { return objectName; }
public void setObjectName(String objectName) { this.objectName = objectName; }
public Long getObjectId () {
return objectId;
}
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public void setObjectId(Long objectId) {
this.objectId = objectId;
}
public String getOriginalName() { return originalName; }
public void setOriginalName(String originalName) { this.originalName = originalName; }
public String getBucketName() {
return bucketName;
}
public String getDelFlag() { return delFlag; }
public void setDelFlag(String delFlag) { this.delFlag = delFlag; }
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getObjectName() {
return objectName;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
public String getObjectUrl() {
return objectUrl;
}
public void setObjectUrl(String objectUrl) {
this.objectUrl = objectUrl;
}
public Long getObjectSize() {
return objectSize;
}
public void setObjectSize(Long objectSize) {
this.objectSize = objectSize;
}
public String getObjectType() {
return objectType;
}
public void setObjectType(String objectType) {
this.objectType = objectType;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.video.event;
import org.springframework.context.ApplicationEvent;
/**
* 巡检任务完成事件
*/
public class TaskCompletedEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private final Long taskId;
public TaskCompletedEvent(Object source, Long taskId) {
super(source);
this.taskId = taskId;
}
public Long getTaskId() {
return taskId;
}
}

View File

@@ -0,0 +1,23 @@
package com.ruoyi.video.event;
import com.ruoyi.video.domain.InspectionTask;
import org.springframework.context.ApplicationEvent;
/**
* 巡检任务开始事件
*/
public class TaskStartEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private final InspectionTask task;
public TaskStartEvent(Object source, InspectionTask task) {
super(source);
this.task = task;
}
public InspectionTask getTask() {
return task;
}
}

View File

@@ -0,0 +1,75 @@
package com.ruoyi.video.listener;
import com.ruoyi.video.domain.InspectionTask;
import com.ruoyi.video.event.TaskCompletedEvent;
import com.ruoyi.video.event.TaskStartEvent;
import com.ruoyi.video.service.MediaService;
import com.ruoyi.video.service.VideoAnalysisService;
import com.ruoyi.video.thread.MediaTransferFlvByJavacv;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 巡检任务事件监听器
* 处理任务开始和完成事件
*/
@Component
public class InspectionTaskEventListener {
private static final Logger log = LoggerFactory.getLogger(InspectionTaskEventListener.class);
@Autowired
private MediaService mediaService;
@Autowired
private VideoAnalysisService videoAnalysisService;
/**
* 处理任务开始事件
* @param event 任务开始事件
*/
@EventListener
public void handleTaskStartEvent(TaskStartEvent event) {
InspectionTask task = event.getTask();
log.info("接收到任务开始事件: 任务ID={}, 设备ID={}", task.getTaskId(), task.getDeviceId());
try {
// 获取对应的媒体传输器
MediaTransferFlvByJavacv transfer = mediaService.getMediaTransfer(task.getDeviceId());
if (transfer != null) {
// 开始录制视频
transfer.startInspection(task.getTaskId(), task.getDeviceId(), task.getDuration());
log.info("已开始视频录制: 任务ID={}, 设备ID={}, 持续时间={}秒",
task.getTaskId(), task.getDeviceId(), task.getDuration());
} else {
log.error("未找到对应的媒体传输器: 设备ID={}", task.getDeviceId());
}
} catch (Exception e) {
log.error("处理任务开始事件失败: {}", e.getMessage());
}
}
/**
* 处理任务完成事件
* @param event 任务完成事件
*/
@EventListener
@Async
public void handleTaskCompletedEvent(TaskCompletedEvent event) {
Long taskId = event.getTaskId();
log.info("接收到任务完成事件: 任务ID={}", taskId);
try {
// 开始异步视频分析
videoAnalysisService.analyzeVideo(taskId);
} catch (Exception e) {
log.error("处理任务完成事件失败: {}", e.getMessage());
}
}
}

View File

@@ -1,72 +1,67 @@
package com.ruoyi.video.mapper;
import java.util.List;
import com.ruoyi.video.domain.AlarmRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 警记录Mapper接口
*
* @author ruoyi
* @date 2025-01-16
* 警记录Mapper接口
*/
@Mapper
public interface AlarmRecordMapper {
/**
* 查询警记录
*
* @param alarmId 报警记录主键
* @return 警记录
* 查询警记录
*
* @param alarmId 告警ID
* @return 警记录
*/
public AlarmRecord selectAlarmRecordByAlarmId(Long alarmId);
public AlarmRecord selectAlarmRecordById(Long alarmId);
/**
* 查询警记录列表
*
* @param alarmRecord 警记录
* @return 警记录集合
* 查询警记录列表
*
* @param alarmRecord 警记录
* @return 警记录集合
*/
public List<AlarmRecord> selectAlarmRecordList(AlarmRecord alarmRecord);
/**
* 新增警记录
*
* @param alarmRecord 警记录
* 新增警记录
*
* @param alarmRecord 警记录
* @return 结果
*/
public int insertAlarmRecord(AlarmRecord alarmRecord);
/**
* 修改警记录
*
* @param alarmRecord 警记录
* 修改警记录
*
* @param alarmRecord 警记录
* @return 结果
*/
public int updateAlarmRecord(AlarmRecord alarmRecord);
/**
* 删除警记录
*
* @param alarmId 报警记录主键
* 删除警记录
*
* @param alarmId 告警ID
* @return 结果
*/
public int deleteAlarmRecordByAlarmId(Long alarmId);
public int deleteAlarmRecordById(Long alarmId);
/**
* 批量删除警记录
*
* @param alarmIds 需要删除的数据主键集合
* 批量删除警记录
*
* @param alarmIds 需要删除的数据ID集合
* @return 结果
*/
public int deleteAlarmRecordByAlarmIds(Long[] alarmIds);
public int deleteAlarmRecordByIds(Long[] alarmIds);
/**
* 处理警记录
* 处理警记录
*
* @param alarmId 警ID
* @param alarmId 警ID
* @param handleStatus 处理状态
* @param handleRemark 处理备注
* @param handleBy 处理人
@@ -78,10 +73,10 @@ public interface AlarmRecordMapper {
@Param("handleBy") String handleBy);
/**
* 根据任务ID统计警数量
* 根据任务ID统计警数量
*
* @param taskId 任务ID
* @return 警数量
* @return 警数量
*/
public Long countAlarmByTaskId(@Param("taskId") Long taskId);
}

View File

@@ -8,32 +8,28 @@ import java.util.List;
/**
* 巡检任务Mapper接口
*
* @author ruoyi
* @date 2025-01-16
*/
@Mapper
public interface InspectionTaskMapper {
/**
* 查询巡检任务
*
* @param taskId 巡检任务主键
* @return 巡检任务
*/
public InspectionTask selectInspectionTaskByTaskId(Long taskId);
/**
* 查询巡检任务列表
*
*
* @param inspectionTask 巡检任务
* @return 巡检任务集合
*/
public List<InspectionTask> selectInspectionTaskList(InspectionTask inspectionTask);
/**
* 查询巡检任务
*
* @param taskId 巡检任务ID
* @return 巡检任务
*/
public InspectionTask selectInspectionTaskById(Long taskId);
/**
* 新增巡检任务
*
*
* @param inspectionTask 巡检任务
* @return 结果
*/
@@ -41,7 +37,7 @@ public interface InspectionTaskMapper {
/**
* 修改巡检任务
*
*
* @param inspectionTask 巡检任务
* @return 结果
*/
@@ -49,20 +45,20 @@ public interface InspectionTaskMapper {
/**
* 删除巡检任务
*
* @param taskId 巡检任务主键
*
* @param taskId 巡检任务ID
* @return 结果
*/
public int deleteInspectionTaskByTaskId(Long taskId);
public int deleteInspectionTaskById(Long taskId);
/**
* 批量删除巡检任务
*
* @param taskIds 需要删除的数据主键集合
*
* @param taskIds 需要删除的数据ID集合
* @return 结果
*/
public int deleteInspectionTaskByTaskIds(Long[] taskIds);
public int deleteInspectionTaskByIds(Long[] taskIds);
/**
* 查询启用状态的巡检任务列表
*

View File

@@ -4,12 +4,74 @@ import com.ruoyi.video.domain.VMinioObject;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* MinIO对象Mapper接口
*/
@Mapper
public interface VMinioObjectMapper {
int insertVMinioObject(VMinioObject obj);
VMinioObject selectVMinioObjectById(@Param("id") Long id);
int deleteVMinioObjectById(@Param("id") Long id);
/**
* 查询MinIO对象
*
* @param objectId MinIO对象ID
* @return MinIO对象
*/
public VMinioObject selectVMinioObjectById(Long objectId);
VMinioObject selectVMinioObjectByObjectName(@Param("objectName") String objectName);
int deleteVMinioObjectByObjectName(@Param("objectName") String objectName);
/**
* 查询MinIO对象列表
*
* @param vMinioObject MinIO对象
* @return MinIO对象集合
*/
public List<VMinioObject> selectVMinioObjectList(VMinioObject vMinioObject);
/**
* 新增MinIO对象
*
* @param vMinioObject MinIO对象
* @return 结果
*/
public int insertVMinioObject(VMinioObject vMinioObject);
/**
* 修改MinIO对象
*
* @param vMinioObject MinIO对象
* @return 结果
*/
public int updateVMinioObject(VMinioObject vMinioObject);
/**
* 删除MinIO对象
*
* @param objectId MinIO对象ID
* @return 结果
*/
public int deleteVMinioObjectById(Long objectId);
/**
* 批量删除MinIO对象
*
* @param objectIds 需要删除的数据ID
* @return 结果
*/
public int deleteVMinioObjectByIds(Long[] objectIds);
/**
* 根据对象名称查询MinIO对象
*
* @param objectName 对象名称
* @return MinIO对象
*/
public VMinioObject selectVMinioObjectByObjectName(String objectName);
/**
* 根据对象名称删除MinIO对象
*
* @param objectName 对象名称
* @return 结果
*/
public int deleteVMinioObjectByObjectName(String objectName);
}

View File

@@ -0,0 +1,39 @@
package com.ruoyi.video.service;
import com.ruoyi.video.domain.InspectionTaskRecord;
import java.util.List;
/**
* 巡检任务记录Service接口
*/
public interface IInspectionTaskRecordService {
/**
* 查询巡检任务记录
*/
InspectionTaskRecord selectInspectionTaskRecordByRecordId(Long recordId);
/**
* 查询巡检任务记录列表
*/
List<InspectionTaskRecord> selectInspectionTaskRecordList(InspectionTaskRecord record);
/**
* 新增巡检任务记录
*/
int insertInspectionTaskRecord(InspectionTaskRecord record);
/**
* 修改巡检任务记录
*/
int updateInspectionTaskRecord(InspectionTaskRecord record);
/**
* 删除巡检任务记录
*/
int deleteInspectionTaskRecordByRecordId(Long recordId);
/**
* 批量删除巡检任务记录
*/
int deleteInspectionTaskRecordByRecordIds(Long[] recordIds);
}

View File

@@ -1,13 +1,97 @@
package com.ruoyi.video.service;
import com.ruoyi.video.domain.VMinioObject;
import java.util.List;
/**
* MinIO对象服务接口
*/
public interface IVMinioObjectService {
int insert(VMinioObject obj);
VMinioObject selectById(Long id);
int deleteById(Long id);
/**
* 查询MinIO对象
*
* @param objectId MinIO对象ID
* @return MinIO对象
*/
public VMinioObject selectVMinioObjectById(Long objectId);
VMinioObject selectByObjectName(String objectName);
int deleteByObjectName(String objectName);
/**
* 查询MinIO对象列表
*
* @param vMinioObject MinIO对象
* @return MinIO对象集合
*/
public List<VMinioObject> selectVMinioObjectList(VMinioObject vMinioObject);
/**
* 新增MinIO对象
*
* @param vMinioObject MinIO对象
* @return 结果
*/
public Long insertVMinioObject(VMinioObject vMinioObject);
/**
* 修改MinIO对象
*
* @param vMinioObject MinIO对象
* @return 结果
*/
public int updateVMinioObject(VMinioObject vMinioObject);
/**
* 删除MinIO对象信息
*
* @param objectId MinIO对象ID
* @return 结果
*/
public int deleteVMinioObjectById(Long objectId);
/**
* 批量删除MinIO对象
*
* @param objectIds 需要删除的MinIO对象ID
* @return 结果
*/
public int deleteVMinioObjectByIds(Long[] objectIds);
/**
* 根据对象名查询
*
* @param objectName 对象名
* @return MinIO对象
*/
public VMinioObject selectByObjectName(String objectName);
/**
* 根据ID查询
*
* @param id 对象ID
* @return MinIO对象
*/
public VMinioObject selectById(Long id);
/**
* 根据ID删除
*
* @param id 对象ID
* @return 结果
*/
public int deleteById(Long id);
/**
* 根据对象名删除
*
* @param objectName 对象名
* @return 结果
*/
public int deleteByObjectName(String objectName);
/**
* 插入记录
*
* @param obj MinIO对象
* @return 结果
*/
public int insert(VMinioObject obj);
}

View File

@@ -1,8 +1,8 @@
package com.ruoyi.video.service;
import com.ruoyi.video.domain.InspectionTask;
import com.ruoyi.video.domain.AlarmRecord;
import com.ruoyi.video.domain.Detection;
import com.ruoyi.video.domain.InspectionTask;
import org.springframework.scheduling.annotation.Async;
import java.util.List;
@@ -17,62 +17,100 @@ public interface InspectionTaskService {
/**
* 查询巡检任务列表
*
* @param inspectionTask 巡检任务查询条件
* @return 巡检任务集合
*/
List<InspectionTask> selectInspectionTaskList(InspectionTask inspectionTask);
/**
* 根据ID查询巡检任务
*
* @param taskId 巡检任务ID
* @return 巡检任务信息
*/
InspectionTask selectInspectionTaskById(Long taskId);
/**
* 新增巡检任务
*
* @param inspectionTask 巡检任务信息
* @return 结果
*/
int insertInspectionTask(InspectionTask inspectionTask);
/**
* 修改巡检任务
*
* @param inspectionTask 巡检任务信息
* @return 结果
*/
int updateInspectionTask(InspectionTask inspectionTask);
/**
* 删除巡检任务
* 批量删除巡检任务
*
* @param taskIds 需要删除的巡检任务ID
* @return 结果
*/
int deleteInspectionTaskByIds(Long[] taskIds);
/**
* 启动巡检任务
*
* @param taskId 巡检任务ID
* @return 结果
*/
boolean startInspectionTask(Long taskId);
/**
* 停止巡检任务
*
* @param taskId 巡检任务ID
* @return 结果
*/
boolean stopInspectionTask(Long taskId);
/**
* 执行单次巡检任务
* 执行巡检任务
*
* @param taskId 巡检任务ID
*/
@Async
void executeInspectionTask(Long taskId);
/**
* 处理检测结果,如果有异常则生成报警
* 处理检测结果
*
* @param taskId 巡检任务ID
* @param detections 检测结果
* @param imagePath 图像路径
*/
void handleDetectionResults(Long taskId, List<Detection> detections, String imagePath);
/**
* 保存警记录
* 保存警记录
*
* @param alarmRecord 告警记录
*/
void saveAlarmRecord(AlarmRecord alarmRecord);
/**
* 查询警记录列表
* 查询警记录列表
*
* @param alarmRecord 告警记录查询条件
* @return 告警记录集合
*/
List<AlarmRecord> selectAlarmRecordList(AlarmRecord alarmRecord);
/**
* 处理警记录
* 处理警记录
*
* @param alarmId 告警ID
* @param handleStatus 处理状态
* @param handleRemark 处理备注
* @param handleBy 处理人
* @return 结果
*/
int handleAlarmRecord(Long alarmId, String handleStatus, String handleRemark, String handleBy);
}

View File

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.Map;
/**
* 媒体服务,支持全局网络超时、读写超时、无人拉流持续时长自动关闭流等配置
@@ -25,6 +26,16 @@ public class MediaService {
* 缓存流转换线程
*/
public static ConcurrentHashMap<String, MediaTransfer> cameras = new ConcurrentHashMap<>();
/**
* 客户端类型映射
*/
public static ConcurrentHashMap<String, ClientType> clients = new ConcurrentHashMap<>();
/**
* 客户端连接映射
*/
public static ConcurrentHashMap<String, Map<String, ChannelHandlerContext>> clientConnections = new ConcurrentHashMap<>();
/**
* http-flv播放
@@ -253,4 +264,34 @@ public class MediaService {
}
cameras.remove(mediaKey);
}
/**
* 获取设备对应的媒体传输器
*
* @param deviceId 设备ID
* @return 媒体传输器实例如果不存在则返回null
*/
public MediaTransferFlvByJavacv getMediaTransfer(Long deviceId) {
if (deviceId == null) {
return null;
}
// 遍历所有已注册的相机
for (Map.Entry<String, MediaTransfer> entry : cameras.entrySet()) {
String mediaKey = entry.getKey();
if (mediaKey.startsWith("device_" + deviceId + "_")) {
// 找到匹配设备ID的mediaKey
MediaTransfer mediaTransfer = entry.getValue();
if (mediaTransfer instanceof MediaTransferFlvByJavacv) {
MediaTransferFlvByJavacv transfer = (MediaTransferFlvByJavacv) mediaTransfer;
if (transfer.getCameraDto() != null &&
mediaKey.equals(transfer.getCameraDto().getMediaKey())) {
return transfer;
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,595 @@
package com.ruoyi.video.service;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.service.MinioService;
import com.ruoyi.video.domain.AlarmRecord;
import com.ruoyi.video.domain.Detection;
import com.ruoyi.video.domain.InspectionTask;
import com.ruoyi.video.domain.VMinioObject;
import com.ruoyi.video.mapper.AlarmRecordMapper;
import com.ruoyi.video.mapper.InspectionTaskMapper;
import com.ruoyi.video.thread.detector.HttpYoloDetector;
import com.ruoyi.video.utils.Overlay;
import com.ruoyi.video.utils.CustomMultipartFile;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Point;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Scalar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
/**
* 视频分析服务 - 离线分析视频并处理结果
*/
@Slf4j
@Service
public class VideoAnalysisService {
@Autowired
private MinioService minioService;
@Autowired
private IVMinioObjectService vMinioObjectService;
@Autowired
private InspectionTaskMapper taskMapper;
@Autowired
private AlarmRecordMapper alarmRecordMapper;
@Autowired
private com.ruoyi.video.mapper.InspectionTaskRecordMapper inspectionTaskRecordMapper;
// 检测器配置 - 使用容器名而不是localhost
private static final String PYTHON_API_URL = "http://rtsp-python-service:8000/api/detect/file";
private static final String MODEL_NAME = "yolov8_detector";
/**
* 分析视频并更新记录(同步调用)
* @param task 巡检任务
* @param record 巡检记录
* @param videoFile 视频文件
*/
public void analyzeVideoWithRecord(InspectionTask task, com.ruoyi.video.domain.InspectionTaskRecord record, File videoFile) {
log.info("开始分析视频并更新记录: 任务ID={}, 记录ID={}", task.getTaskId(), record.getRecordId());
try {
// 创建输出视频文件
File outputVideoFile = File.createTempFile("analysis_output_", ".mp4");
// 创建检测器
HttpYoloDetector detector = new HttpYoloDetector("yolov8", PYTHON_API_URL, MODEL_NAME, 0x00FF00);
// 处理视频并记录检测结果
String detectionResult = processVideoWithRecord(videoFile, outputVideoFile, detector, task, record);
// 更新记录的识别结果
record.setResult(detectionResult);
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
// 清理临时输出文件
if (outputVideoFile.exists()) {
outputVideoFile.delete();
}
log.info("视频分析完成: 任务ID={}, 记录ID={}, 检测结果={}", task.getTaskId(), record.getRecordId(), detectionResult);
} catch (Exception e) {
log.error("视频分析失败: 任务ID={}, 记录ID={}, 错误={}", task.getTaskId(), record.getRecordId(), e.getMessage(), e);
// 更新记录为部分成功
record.setStatus(2);
record.setResult("分析失败: " + e.getMessage());
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
}
}
/**
* 处理视频并记录检测结果
* @return 检测结果摘要
*/
private String processVideoWithRecord(File inputFile, File outputFile, HttpYoloDetector detector,
InspectionTask task, com.ruoyi.video.domain.InspectionTaskRecord record) throws Exception {
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile,
grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(grabber.getVideoBitrate());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
// 用于去重的垃圾检测结果缓存
Map<String, Long> detectedGarbageCache = new HashMap<>();
Map<String, Integer> detectionCounts = new HashMap<>(); // 统计每种类别的数量
// 跟踪检测到的垃圾ID
final Long[] detectionId = {1L};
// 帧计数
long frameCount = 0;
int totalDetections = 0;
Frame frame;
while ((frame = grabber.grab()) != null) {
frameCount++;
if (frame.image != null) {
// 处理视频帧
Mat mat = converter.convert(frame);
if (mat != null && !mat.isNull()) {
// 每10帧执行一次检测减少API调用频率
if (frameCount % 10 == 0) {
List<Detection> detections = detector.detect(mat);
if (!CollectionUtils.isEmpty(detections)) {
for (Detection detection : detections) {
// 检查是否为新的垃圾检测结果
String detectionKey = generateDetectionKey(detection);
if (!detectedGarbageCache.containsKey(detectionKey)) {
// 这是新检测到的垃圾
detectedGarbageCache.put(detectionKey, detectionId[0]++);
totalDetections++;
// 统计类别数量
String label = detection.getLabel();
detectionCounts.put(label, detectionCounts.getOrDefault(label, 0) + 1);
// 创建告警记录(不重复)
createAlarmRecordForRecord(task, record, detection, mat, frameCount);
} else {
// 更新上次检测时间
detectedGarbageCache.put(detectionKey, detectionId[0]++);
}
}
// 清理超过60秒未检测到的垃圾假设30fps
Long currentId = detectionId[0];
detectedGarbageCache.entrySet().removeIf(entry ->
(currentId - entry.getValue()) > grabber.getFrameRate() * 60);
}
}
// 转回Frame并写入录像
Frame processedFrame = converter.convert(mat);
recorder.record(processedFrame);
} else {
// 原样写入
recorder.record(frame);
}
} else if (frame.samples != null) {
// 音频帧原样写入
recorder.record(frame);
}
}
recorder.stop();
recorder.close();
grabber.stop();
grabber.close();
// 上传处理后的视频到MinIO
uploadProcessedVideoForRecord(outputFile, task, record);
// 生成检测结果摘要
StringBuilder resultSummary = new StringBuilder();
resultSummary.append("共检测到 ").append(totalDetections).append(" 个问题");
if (!detectionCounts.isEmpty()) {
resultSummary.append(",详情:");
detectionCounts.forEach((label, count) ->
resultSummary.append(label).append("(").append(count).append(") "));
}
return resultSummary.toString();
}
/**
* 为记录创建告警
*/
private void createAlarmRecordForRecord(InspectionTask task, com.ruoyi.video.domain.InspectionTaskRecord record,
Detection detection, Mat frame, long frameCount) throws Exception {
// 创建告警图像临时文件
File alarmImageFile = File.createTempFile("alarm_", ".jpg");
// 裁剪检测区域,略微扩大区域
Rect rect = detection.getRect();
int x = Math.max(0, rect.x() - 10);
int y = Math.max(0, rect.y() - 10);
int w = Math.min(frame.cols() - x, rect.width() + 20);
int h = Math.min(frame.rows() - y, rect.height() + 20);
// 使用OpenCV保存告警图片
Mat roi = new Mat(frame, new Rect(x, y, w, h));
org.bytedeco.opencv.global.opencv_imgcodecs.imwrite(alarmImageFile.getAbsolutePath(), roi);
// 上传告警图片到MinIO
String fileName = "alarm_" + System.currentTimeMillis() + ".jpg";
String bucketName = "alarm-images";
CustomMultipartFile multipartFile = new CustomMultipartFile(alarmImageFile, fileName, "image/jpeg");
String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream());
VMinioObject minioObject = new VMinioObject();
minioObject.setBucketName(bucketName);
minioObject.setObjectName(fileName);
minioObject.setObjectUrl(objectUrl);
minioObject.setCreateBy("system");
minioObject.setCreateTime(new Date());
Long objectId = vMinioObjectService.insertVMinioObject(minioObject);
// 创建告警记录
AlarmRecord alarmRecord = new AlarmRecord();
alarmRecord.setDeviceId(task.getDeviceId());
alarmRecord.setAlarmType("detection");
alarmRecord.setAlarmContent(detection.getLabel() + " - 置信度: " + String.format("%.2f", detection.getConfidence()));
alarmRecord.setTaskId(task.getTaskId());
alarmRecord.setImageOssId(objectId);
alarmRecord.setFramePosition(frameCount);
alarmRecord.setConfidence(detection.getConfidence());
alarmRecord.setCreateTime(new Date());
alarmRecord.setStatus(0); // 未处理
alarmRecordMapper.insertAlarmRecord(alarmRecord);
log.info("创建告警记录: 类型={}, 任务ID={}, 记录ID={}, 告警ID={}",
detection.getLabel(), task.getTaskId(), record.getRecordId(), alarmRecord.getAlarmId());
// 删除临时文件
if (alarmImageFile.exists()) {
alarmImageFile.delete();
}
}
/**
* 上传处理后的视频(针对记录)
*/
private void uploadProcessedVideoForRecord(File videoFile, InspectionTask task,
com.ruoyi.video.domain.InspectionTaskRecord record) throws Exception {
String fileName = "processed_" + System.currentTimeMillis() + ".mp4";
String bucketName = "inspection-videos";
CustomMultipartFile multipartFile = new CustomMultipartFile(videoFile, fileName, "video/mp4");
String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream());
VMinioObject minioObject = new VMinioObject();
minioObject.setBucketName(bucketName);
minioObject.setObjectName(fileName);
minioObject.setObjectUrl(objectUrl);
minioObject.setCreateBy("system");
minioObject.setCreateTime(new Date());
Long objectId = vMinioObjectService.insertVMinioObject(minioObject);
// 更新任务的处理后视频ID
task.setProcessedVideoOssId(objectId);
task.setVideoStatus(2); // 2: 已分析
taskMapper.updateInspectionTask(task);
// 更新记录的附件信息添加处理后的视频URL
String currentAccessory = record.getAccessory();
record.setAccessory(currentAccessory + ";" + objectUrl);
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
log.info("处理后视频已上传: 任务ID={}, 记录ID={}, MinIO对象ID={}",
task.getTaskId(), record.getRecordId(), objectId);
}
/**
* 异步分析视频
* @param taskId 巡检任务ID
*/
@Async
public void analyzeVideo(Long taskId) {
log.info("开始异步分析视频: 任务ID={}", taskId);
try {
// 1. 获取任务信息
InspectionTask task = taskMapper.selectInspectionTaskById(taskId);
if (task == null) {
log.error("任务不存在: {}", taskId);
return;
}
// 2. 获取原始视频信息
Long videoOssId = task.getVideoOssId();
if (videoOssId == null) {
log.error("任务视频不存在: {}", taskId);
return;
}
VMinioObject videoObject = vMinioObjectService.selectVMinioObjectById(videoOssId);
if (videoObject == null) {
log.error("MinIO对象不存在: {}", videoOssId);
return;
}
// 3. 下载原始视频到临时文件
File videoTempFile = File.createTempFile("analysis_input_", ".mp4");
try (InputStream inputStream = minioService.getObject(videoObject.getBucketName(), videoObject.getObjectName());
FileOutputStream fileOutputStream = new FileOutputStream(videoTempFile)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, bytesRead);
}
}
// 4. 创建输出视频文件
File outputVideoFile = File.createTempFile("analysis_output_", ".mp4");
// 5. 创建检测器
HttpYoloDetector detector = new HttpYoloDetector("garbage", PYTHON_API_URL, MODEL_NAME, 0x00FF00);
// 6. 处理视频
processVideo(videoTempFile, outputVideoFile, detector, task);
// 7. 清理临时文件
if (videoTempFile.exists()) {
videoTempFile.delete();
}
log.info("视频分析完成: 任务ID={}", taskId);
} catch (Exception e) {
log.error("视频分析失败: 任务ID={}, 错误={}", taskId, e.getMessage());
e.printStackTrace();
}
}
/**
* 处理视频
* @param inputFile 输入视频文件
* @param outputFile 输出视频文件
* @param detector 检测器
* @param task 巡检任务
*/
private void processVideo(File inputFile, File outputFile, HttpYoloDetector detector, InspectionTask task) throws Exception {
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile,
grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setSampleRate(grabber.getSampleRate());
recorder.start();
OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
// 用于去重的垃圾检测结果缓存
Map<String, Long> detectedGarbageCache = new HashMap<>();
// 跟踪检测到的垃圾ID
final Long[] detectionId = {1L}; // 使用数组实现effectively final
// 帧计数
long frameCount = 0;
Frame frame;
while ((frame = grabber.grab()) != null) {
frameCount++;
if (frame.image != null) {
// 处理视频帧
Mat mat = converter.convert(frame);
if (mat != null && !mat.isNull()) {
// 每10帧执行一次检测减少API调用频率
if (frameCount % 10 == 0) {
List<Detection> detections = detector.detect(mat);
if (!CollectionUtils.isEmpty(detections)) {
// 绘制检测框
for (Detection detection : detections) {
drawDetection(mat, detection);
// 检查是否为新的垃圾检测结果
String detectionKey = generateDetectionKey(detection);
if (!detectedGarbageCache.containsKey(detectionKey)) {
// 这是新检测到的垃圾
detectedGarbageCache.put(detectionKey, detectionId[0]++);
// 创建告警记录
createAlarmRecord(task, detection, mat, frameCount);
} else {
// 更新上次检测时间
detectedGarbageCache.put(detectionKey, detectionId[0]++);
}
}
// 清理超过60秒未检测到的垃圾假设30fps
Long currentId = detectionId[0];
detectedGarbageCache.entrySet().removeIf(entry ->
(currentId - entry.getValue()) > grabber.getFrameRate() * 60);
}
}
// 转回Frame并写入录像
Frame processedFrame = converter.convert(mat);
recorder.record(processedFrame);
} else {
// 原样写入
recorder.record(frame);
}
} else if (frame.samples != null) {
// 音频帧原样写入
recorder.record(frame);
}
}
recorder.stop();
recorder.close();
grabber.stop();
grabber.close();
// 上传处理后的视频
uploadProcessedVideo(outputFile, task);
}
/**
* 上传处理后的视频到MinIO
*/
private void uploadProcessedVideo(File videoFile, InspectionTask task) throws Exception {
String fileName = "processed_" + System.currentTimeMillis() + ".mp4";
String bucketName = "inspection-videos";
CustomMultipartFile multipartFile = new CustomMultipartFile(videoFile, fileName, "video/mp4");
String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream());
VMinioObject minioObject = new VMinioObject();
minioObject.setBucketName(bucketName);
minioObject.setObjectName(fileName);
minioObject.setObjectUrl(objectUrl);
minioObject.setCreateBy("system");
minioObject.setCreateTime(new Date());
Long objectId = vMinioObjectService.insertVMinioObject(minioObject);
// 更新任务的处理后视频ID
task.setProcessedVideoOssId(objectId);
task.setVideoStatus(2); // 2: 已分析
taskMapper.updateInspectionTask(task);
log.info("处理后视频已上传: 任务ID={}, MinIO对象ID={}", task.getTaskId(), objectId);
// 删除临时文件
if (videoFile.exists()) {
videoFile.delete();
}
}
/**
* 生成检测结果的唯一键,用于检测结果去重
*/
private String generateDetectionKey(Detection detection) {
// 使用检测框的位置和大小来生成键
// 允许小范围波动,认为是同一个物体
Rect rect = detection.getRect();
int x = rect.x() / 10 * 10; // 取10的倍数允许小波动
int y = rect.y() / 10 * 10;
int w = rect.width() / 10 * 10;
int h = rect.height() / 10 * 10;
return String.format("%s_%d_%d_%d_%d", detection.getLabel(), x, y, w, h);
}
/**
* 创建告警记录
*/
private void createAlarmRecord(InspectionTask task, Detection detection, Mat frame, long frameCount) throws Exception {
// 创建告警图像临时文件
File alarmImageFile = File.createTempFile("alarm_", ".jpg");
// 裁剪检测区域,略微扩大区域
Rect rect = detection.getRect();
int x = Math.max(0, rect.x() - 10);
int y = Math.max(0, rect.y() - 10);
int w = Math.min(frame.cols() - x, rect.width() + 20);
int h = Math.min(frame.rows() - y, rect.height() + 20);
// 使用OpenCV保存告警图片
Mat roi = new Mat(frame, new Rect(x, y, w, h));
org.bytedeco.opencv.global.opencv_imgcodecs.imwrite(alarmImageFile.getAbsolutePath(), roi);
// 上传告警图片到MinIO
String fileName = "alarm_" + System.currentTimeMillis() + ".jpg";
String bucketName = "alarm-images";
CustomMultipartFile multipartFile = new CustomMultipartFile(alarmImageFile, fileName, "image/jpeg");
String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream());
VMinioObject minioObject = new VMinioObject();
minioObject.setBucketName(bucketName);
minioObject.setObjectName(fileName);
minioObject.setObjectUrl(objectUrl);
minioObject.setCreateBy("system");
minioObject.setCreateTime(new Date());
Long objectId = vMinioObjectService.insertVMinioObject(minioObject);
// 创建告警记录
AlarmRecord alarmRecord = new AlarmRecord();
alarmRecord.setDeviceId(task.getDeviceId());
alarmRecord.setAlarmType("garbage");
alarmRecord.setAlarmContent(detection.getLabel() + " - 置信度: " + String.format("%.2f", detection.getConfidence()));
alarmRecord.setTaskId(task.getTaskId());
alarmRecord.setImageOssId(objectId);
alarmRecord.setFramePosition(frameCount);
alarmRecord.setConfidence(detection.getConfidence());
alarmRecord.setCreateTime(new Date());
alarmRecordMapper.insertAlarmRecord(alarmRecord);
log.info("创建告警记录: 类型={}, 任务ID={}, 告警ID={}", detection.getLabel(), task.getTaskId(), alarmRecord.getAlarmId());
// 删除临时文件
if (alarmImageFile.exists()) {
alarmImageFile.delete();
}
}
/**
* 在图像上绘制检测框
*/
private void drawDetection(Mat frame, Detection detection) {
// 绘制边界框
Rect rect = detection.getRect();
Scalar color = new Scalar(detection.getColorBGR());
int thickness = 2;
rectangle(frame, rect, color, thickness, LINE_8, 0);
// 绘制标签背景
String label = String.format("%s: %.2f", detection.getLabel(), detection.getConfidence());
Point textPosition = new Point(rect.x(), rect.y() - 10);
// 获取文本大小 - 添加缺失的IntBuffer参数
IntBuffer baseline = IntBuffer.allocate(1);
org.bytedeco.opencv.opencv_core.Size textSize = getTextSize(
label, FONT_HERSHEY_SIMPLEX, 0.5, thickness, baseline);
// 绘制标签背景矩形
rectangle(frame,
new Rect(rect.x(), rect.y() - textSize.height() - 10,
textSize.width(), textSize.height() + 10),
color, FILLED, LINE_8, 0);
// 绘制文本
putText(frame, label, textPosition,
FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(255, 255, 255, 0),
1, LINE_AA, false);
}
}

View File

@@ -0,0 +1,52 @@
package com.ruoyi.video.service.impl;
import com.ruoyi.video.domain.InspectionTaskRecord;
import com.ruoyi.video.mapper.InspectionTaskRecordMapper;
import com.ruoyi.video.service.IInspectionTaskRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
/**
* 巡检任务记录Service业务层处理
*/
@Service
public class IInspectionTaskRecordServiceImpl implements IInspectionTaskRecordService {
@Autowired
private InspectionTaskRecordMapper inspectionTaskRecordMapper;
@Override
public InspectionTaskRecord selectInspectionTaskRecordByRecordId(Long recordId) {
return inspectionTaskRecordMapper.selectInspectionTaskRecordByRecordId(recordId);
}
@Override
public List<InspectionTaskRecord> selectInspectionTaskRecordList(InspectionTaskRecord record) {
return inspectionTaskRecordMapper.selectInspectionTaskRecordList(record);
}
@Override
public int insertInspectionTaskRecord(InspectionTaskRecord record) {
record.setCreateTime(new Date());
return inspectionTaskRecordMapper.insertInspectionTaskRecord(record);
}
@Override
public int updateInspectionTaskRecord(InspectionTaskRecord record) {
record.setUpdateTime(new Date());
return inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
}
@Override
public int deleteInspectionTaskRecordByRecordId(Long recordId) {
return inspectionTaskRecordMapper.deleteInspectionTaskRecordByRecordId(recordId);
}
@Override
public int deleteInspectionTaskRecordByRecordIds(Long[] recordIds) {
return inspectionTaskRecordMapper.deleteInspectionTaskRecordByRecordIds(recordIds);
}
}

View File

@@ -39,9 +39,6 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* 巡检任务服务实现
*
* @Author: orange
* @CreateTime: 2025-01-16
*/
@Slf4j
@Service
@@ -55,6 +52,15 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
@Autowired
private AlarmRecordMapper alarmRecordMapper;
@Autowired
private com.ruoyi.video.mapper.InspectionTaskRecordMapper inspectionTaskRecordMapper;
@Autowired
private MinioService minioService;
@Autowired
private IVMinioObjectService vMinioObjectService;
// 运行状态缓存(避免重复执行)
private final Map<Long, Boolean> runningTasks = new ConcurrentHashMap<>();
@@ -70,21 +76,22 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
@Override
public InspectionTask selectInspectionTaskById(Long taskId) {
return inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
return inspectionTaskMapper.selectInspectionTaskById(taskId);
}
@Override
public int insertInspectionTask(InspectionTask inspectionTask) {
inspectionTask.setCreateTime(DateUtils.getNowDate());
inspectionTask.setCreateBy(SecurityUtils.getUsername());
inspectionTask.setExecuteCount(0L);
inspectionTask.setAlarmCount(0L);
// 这些字段在新版实体类中可能不存在,需要进行调整
// inspectionTask.setExecuteCount(0L);
// inspectionTask.setAlarmCount(0L);
// 获取设备信息
Device device = deviceService.selectDeviceByDeviceId(inspectionTask.getDeviceId());
if (device != null) {
inspectionTask.setDeviceName(device.getIp());
}
// 新版实体类可能不需要设备名称
// if (device != null) {
// inspectionTask.setDeviceName(device.getIp());
// }
return inspectionTaskMapper.insertInspectionTask(inspectionTask);
}
@@ -92,7 +99,6 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
@Override
public int updateInspectionTask(InspectionTask inspectionTask) {
inspectionTask.setUpdateTime(DateUtils.getNowDate());
inspectionTask.setUpdateBy(SecurityUtils.getUsername());
return inspectionTaskMapper.updateInspectionTask(inspectionTask);
}
@@ -101,88 +107,232 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
for (Long taskId : taskIds) {
stopInspectionTask(taskId);
}
return inspectionTaskMapper.deleteInspectionTaskByTaskIds(taskIds);
return inspectionTaskMapper.deleteInspectionTaskByIds(taskIds);
}
@Override
public boolean startInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
InspectionTask task = inspectionTaskMapper.selectInspectionTaskById(taskId);
if (task == null) {
return false;
}
task.setStatus("0"); // 启用
// 启用任务,使用新版实体类的方法
task.setStatus(0); // 0表示启用
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
inspectionTaskMapper.updateInspectionTask(task);
runningTasks.put(taskId, true);
// 这里应该集成到Quartz定时任务中
log.info("启动巡检任务: {} - {}", taskId, task.getTaskName());
log.info("启动巡检任务: {}", taskId);
return true;
}
@Override
public boolean stopInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
InspectionTask task = inspectionTaskMapper.selectInspectionTaskById(taskId);
if (task == null) {
return false;
}
task.setStatus("1"); // 停用
// 停用任务,使用新版实体类的方法
task.setStatus(1); // 1表示停用
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
inspectionTaskMapper.updateInspectionTask(task);
runningTasks.remove(taskId);
log.info("停止巡检任务: {} - {}", taskId, task.getTaskName());
log.info("停止巡检任务: {}", taskId);
return true;
}
@Override
@Async
public void executeInspectionTask(Long taskId) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
if (task == null || !"0".equals(task.getStatus())) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskById(taskId);
if (task == null || task.getStatus() != 0) { // 0表示启用状态
return;
}
log.info("开始执行巡检任务: {} - {}", taskId, task.getTaskName());
log.info("开始执行巡检任务: {}", taskId);
// 创建巡检记录
InspectionTaskRecord record = new InspectionTaskRecord();
record.setTaskId(taskId);
record.setExecuteTime(new Date());
record.setStatus(1); // 执行中
inspectionTaskRecordMapper.insertInspectionTaskRecord(record);
Long recordId = record.getRecordId();
long startTime = System.currentTimeMillis();
try {
// 更新执行信息
task.setLastExecuteTime(new Date());
task.setExecuteCount(task.getExecuteCount() + 1);
// 更新任务状态为执行中
task.setStatus(1);
inspectionTaskMapper.updateInspectionTask(task);
// 获取设备信息
Device device = deviceService.selectDeviceByDeviceId(task.getDeviceId());
if (device == null) {
log.error("设备不存在: {}", task.getDeviceId());
updateRecordFailed(record, "设备不存在");
return;
}
// 执行视频分析
performVideoAnalysis(task, device);
// 执行视频录制和分析
performVideoAnalysisWithRecord(task, device, record);
// 更新任务执行统计信息
Long alarmCount = alarmRecordMapper.countAlarmByTaskId(taskId);
inspectionTaskMapper.updateTaskExecuteInfo(taskId, task.getExecuteCount(), alarmCount);
// 更新执行时长
long duration = (System.currentTimeMillis() - startTime) / 1000;
record.setDuration((int) duration);
record.setStatus(0); // 成功
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
// 更新任务状态为已完成
task.setStatus(2);
inspectionTaskMapper.updateInspectionTask(task);
} catch (Exception e) {
log.error("执行巡检任务失败: {} - {}", taskId, e.getMessage(), e);
log.error("巡检任务执行失败: taskId={}", taskId, e);
updateRecordFailed(record, e.getMessage());
// 更新任务状态为已完成(虽然失败)
task.setStatus(2);
inspectionTaskMapper.updateInspectionTask(task);
}
}
/**
* 更新记录为失败状态
*/
private void updateRecordFailed(InspectionTaskRecord record, String errorMsg) {
record.setStatus(1); // 失败
record.setResult("执行失败: " + errorMsg);
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
}
/**
* 执行视频录制和分析(带记录)
*/
private void performVideoAnalysisWithRecord(InspectionTask task, Device device, InspectionTaskRecord record) throws Exception {
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
File videoTempFile = null;
try {
// 1. 创建临时视频文件
videoTempFile = File.createTempFile("inspection_", ".mp4");
// 2. 创建视频抓取器
grabber = new FFmpegFrameGrabber(device.getUrl());
grabber.setOption("rtsp_transport", "tcp");
grabber.setOption("stimeout", "5000000");
grabber.start();
// 3. 创建视频录制器
recorder = new FFmpegFrameRecorder(videoTempFile,
grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("mp4");
recorder.setVideoCodec(org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_H264);
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(grabber.getVideoBitrate());
if (grabber.getAudioChannels() > 0) {
recorder.setAudioCodec(org.bytedeco.ffmpeg.global.avcodec.AV_CODEC_ID_AAC);
recorder.setAudioBitrate(grabber.getAudioBitrate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setSampleRate(grabber.getSampleRate());
}
recorder.start();
log.info("开始录制视频流: {}, 时长: {}秒", device.getUrl(), task.getDuration());
// 4. 录制指定时长的视频
long startTime = System.currentTimeMillis();
long duration = task.getDuration() * 1000L; // 转换为毫秒
while (System.currentTimeMillis() - startTime < duration) {
Frame frame = grabber.grab();
if (frame != null) {
recorder.record(frame);
}
}
// 5. 停止录制
recorder.stop();
recorder.close();
grabber.stop();
grabber.close();
log.info("视频录制完成: {}", videoTempFile.getAbsolutePath());
// 6. 上传视频到MinIO
String fileName = "inspection_" + task.getTaskId() + "_" + System.currentTimeMillis() + ".mp4";
String bucketName = "inspection-videos";
CustomMultipartFile multipartFile = new CustomMultipartFile(videoTempFile, fileName, "video/mp4");
String objectUrl = minioService.putObject(bucketName, fileName, multipartFile.getInputStream());
VMinioObject minioObject = new VMinioObject();
minioObject.setBucketName(bucketName);
minioObject.setObjectName(fileName);
minioObject.setObjectUrl(objectUrl);
minioObject.setCreateBy("system");
minioObject.setCreateTime(new Date());
Long objectId = vMinioObjectService.insertVMinioObject(minioObject);
// 7. 更新任务的视频ID和状态
task.setVideoOssId(objectId);
task.setVideoStatus(1); // 已录制未分析
inspectionTaskMapper.updateInspectionTask(task);
// 8. 更新记录的附件信息视频URL
record.setAccessory(objectUrl);
inspectionTaskRecordMapper.updateInspectionTaskRecord(record);
log.info("视频已上传到MinIO: objectId={}, url={}", objectId, objectUrl);
// 9. 调用Python服务进行视频分析
analyzeVideoAndUpdateRecord(task, record, videoTempFile);
} finally {
// 清理资源
if (recorder != null) {
try { recorder.close(); } catch (Exception ignore) {}
}
if (grabber != null) {
try { grabber.close(); } catch (Exception ignore) {}
}
if (videoTempFile != null && videoTempFile.exists()) {
videoTempFile.delete();
}
}
}
/**
* 分析视频并更新记录
*/
private void analyzeVideoAndUpdateRecord(InspectionTask task, InspectionTaskRecord record, File videoFile) throws Exception {
log.info("开始分析视频: taskId={}, recordId={}", task.getTaskId(), record.getRecordId());
// 调用VideoAnalysisService进行分析
com.ruoyi.video.service.VideoAnalysisService videoAnalysisService =
SpringUtils.getBean(com.ruoyi.video.service.VideoAnalysisService.class);
// 异步调用视频分析(会处理检测结果并创建告警)
videoAnalysisService.analyzeVideoWithRecord(task, record, videoFile);
}
/**
* 执行视频分析
*/
private void performVideoAnalysis(InspectionTask task, Device device) {
if (!"0".equals(task.getEnableDetection())) {
log.info("巡检任务未启用检测: {}", task.getTaskId());
return;
}
// 这些条件在新版实体类中可能需要调整
// 0表示启用检测
// if (task.getEnableDetection() != 0) {
// log.info("巡检任务未启用检测: {}", task.getTaskId());
// return;
// }
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder sessionRecorder = null;
@@ -214,7 +364,9 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
// 会话聚合参数与状态
final long minGapMs = 3000L; // 目标消失超过该值视为结束
final long maxDurationMs = 30000L; // 单次会话最长30s
final float detectionThreshold = task.getThreshold() != null ? task.getThreshold().floatValue() : 0.7f;
// 这个字段在新版实体类中可能需要调整
// final float detectionThreshold = task.getThreshold() != null ? task.getThreshold().floatValue() : 0.7f;
final float detectionThreshold = 0.7f; // 使用默认阈值
boolean sessionActive = false;
long sessionStartMs = 0L;
@@ -263,9 +415,9 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
// 最高置信度
Detection best = detections.stream()
.max(Comparator.comparingDouble(Detection::conf))
.max(Comparator.comparingDouble(Detection::getConfidence))
.orElse(null);
boolean hasTarget = best != null && best.conf() >= detectionThreshold;
boolean hasTarget = best != null && best.getConfidence() >= detectionThreshold;
if (hasTarget) {
lastSeenMs = now;
@@ -296,23 +448,24 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
VMinioObject rec = new VMinioObject();
rec.setObjectName(up.getObjectName());
rec.setUrl(up.getUrl());
rec.setObjectUrl(up.getUrl());
rec.setOriginalName(originalName);
objSvc.insert(rec);
objSvc.insertVMinioObject(rec);
AlarmRecord alarm = new AlarmRecord();
alarm.setTaskId(task.getTaskId());
alarm.setTaskName(task.getTaskName());
// 这些字段在新版实体类中可能不存在,需要调整
// alarm.setTaskName(task.getTaskName());
alarm.setDeviceId(task.getDeviceId());
alarm.setDeviceName(task.getDeviceName());
alarm.setAlarmType(best.cls());
alarm.setAlarmLevel(getAlarmLevel(best.conf()));
alarm.setAlarmDesc(String.format("检测到%s置信度: %.2f", best.cls(), best.conf()));
alarm.setConfidence((double) best.conf());
alarm.setImagePath(up.getUrl());
alarm.setAlarmTime(new Date(now));
alarm.setHandleStatus("0");
alarm.setCreateBy(SecurityUtils.getUsername());
// alarm.setDeviceName(task.getDeviceName());
alarm.setAlarmType(best.getLabel());
// 这里需要转换double为float
alarm.setAlarmLevel(getAlarmLevel((float)best.getConfidence()));
alarm.setAlarmContent(String.format("检测到%s置信度: %.2f", best.getLabel(), best.getConfidence()));
alarm.setConfidence(best.getConfidence());
alarm.setImageOssId(rec.getObjectId());
alarm.setCreateTime(new Date(now));
alarm.setStatus(0); // 0: 未处理
saveAlarmRecord(alarm);
currentAlarmId = alarm.getAlarmId();
@@ -387,13 +540,14 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
VMinioObject vrec = new VMinioObject();
vrec.setObjectName(upv.getObjectName());
vrec.setUrl(upv.getUrl());
vrec.setObjectUrl(upv.getUrl());
vrec.setOriginalName(originalName);
objSvc.insert(vrec);
objSvc.insertVMinioObject(vrec);
AlarmRecord patch = new AlarmRecord();
patch.setAlarmId(currentAlarmId);
patch.setVideoPath(upv.getUrl());
// videoPath字段在新版实体类中可能不存在需要调整
// patch.setVideoPath(upv.getUrl());
alarmRecordMapper.updateAlarmRecord(patch);
} catch (Exception ue) {
log.warn("巡检会话-上传/回填视频失败: {}", ue.getMessage());
@@ -468,7 +622,7 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
private List<Detection> performDetection(Mat mat) {
try {
if (modelManager != null) {
YoloDetector detector = modelManager.get("garbage"); // 应该使用垃圾检测 当开启自动巡检的时候 需要替换模型
YoloDetector detector = modelManager.get("garbage"); // 使用垃圾检测模型
if (detector != null) {
return detector.detect(mat);
}
@@ -508,40 +662,41 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
@Override
public void handleDetectionResults(Long taskId, List<Detection> detections, String imagePath) {
InspectionTask task = inspectionTaskMapper.selectInspectionTaskByTaskId(taskId);
InspectionTask task = inspectionTaskMapper.selectInspectionTaskById(taskId);
if (task == null || detections.isEmpty()) {
return;
}
for (Detection detection : detections) {
// 检查置信度是否超过阈值
if (detection.conf() >= task.getThreshold()) {
// 创建警记录
// 检查置信度是否超过阈值 - 这里使用了默认阈值,因为实体类可能已变更
if (detection.getConfidence() >= 0.7) {
// 创建警记录
AlarmRecord alarmRecord = new AlarmRecord();
alarmRecord.setTaskId(taskId);
alarmRecord.setTaskName(task.getTaskName());
// 这些字段在新版实体类中可能不存在,需要调整
// alarmRecord.setTaskName(task.getTaskName());
alarmRecord.setDeviceId(task.getDeviceId());
alarmRecord.setDeviceName(task.getDeviceName());
alarmRecord.setAlarmType(detection.cls());
alarmRecord.setAlarmLevel(getAlarmLevel(detection.conf()));
alarmRecord.setAlarmDesc(String.format("检测到%s置信度: %.2f",
detection.cls(), detection.conf()));
alarmRecord.setConfidence((double) detection.conf());
alarmRecord.setImagePath(imagePath);
alarmRecord.setAlarmTime(new Date());
alarmRecord.setHandleStatus("0"); // 未处理
alarmRecord.setCreateBy(SecurityUtils.getUsername());
// alarmRecord.setDeviceName(task.getDeviceName());
alarmRecord.setAlarmType(detection.getLabel());
alarmRecord.setAlarmLevel(getAlarmLevel((float)detection.getConfidence()));
alarmRecord.setAlarmContent(String.format("检测到%s置信度: %.2f",
detection.getLabel(), detection.getConfidence()));
alarmRecord.setConfidence(detection.getConfidence());
// 设置图片路径 - 需要调整为适合新实体类的方式
// alarmRecord.setImagePath(imagePath);
alarmRecord.setCreateTime(new Date());
alarmRecord.setStatus(0); // 0: 未处理
saveAlarmRecord(alarmRecord);
log.warn("生成警记录: 任务[{}] 检测到[{}] 置信度[{}]",
taskId, detection.cls(), detection.conf());
log.warn("生成警记录: 任务[{}] 检测到[{}] 置信度[{}]",
taskId, detection.getLabel(), detection.getConfidence());
}
}
}
/**
* 根据置信度确定警级别
* 根据置信度确定警级别
*/
private String getAlarmLevel(float confidence) {
if (confidence >= 0.9f) {
@@ -557,7 +712,7 @@ public class InspectionTaskServiceImpl implements InspectionTaskService {
public void saveAlarmRecord(AlarmRecord alarmRecord) {
alarmRecord.setCreateTime(DateUtils.getNowDate());
alarmRecordMapper.insertAlarmRecord(alarmRecord);
log.info("保存警记录: {}", alarmRecord.getAlarmId());
log.info("保存警记录: {}", alarmRecord.getAlarmId());
}
@Override

View File

@@ -6,12 +6,64 @@ import com.ruoyi.video.service.IVMinioObjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class VMinioObjectServiceImpl implements IVMinioObjectService {
@Autowired
private VMinioObjectMapper mapper;
@Override
public VMinioObject selectVMinioObjectById(Long objectId) {
return mapper.selectVMinioObjectById(objectId);
}
@Override
public List<VMinioObject> selectVMinioObjectList(VMinioObject vMinioObject) {
return mapper.selectVMinioObjectList(vMinioObject);
}
@Override
public Long insertVMinioObject(VMinioObject vMinioObject) {
int rows = mapper.insertVMinioObject(vMinioObject);
return rows > 0 ? vMinioObject.getObjectId() : null;
}
@Override
public int updateVMinioObject(VMinioObject vMinioObject) {
return mapper.updateVMinioObject(vMinioObject);
}
@Override
public int deleteVMinioObjectById(Long objectId) {
return mapper.deleteVMinioObjectById(objectId);
}
@Override
public int deleteVMinioObjectByIds(Long[] objectIds) {
return mapper.deleteVMinioObjectByIds(objectIds);
}
@Override
public VMinioObject selectByObjectName(String objectName) {
return mapper.selectVMinioObjectByObjectName(objectName);
}
@Override
public VMinioObject selectById(Long id) {
return mapper.selectVMinioObjectById(id);
}
@Override
public int deleteById(Long id) {
return mapper.deleteVMinioObjectById(id);
}
@Override
public int deleteByObjectName(String objectName) {
return mapper.deleteVMinioObjectByObjectName(objectName);
}
@Override
public int insert(VMinioObject obj) {
@@ -20,24 +72,4 @@ public class VMinioObjectServiceImpl implements IVMinioObjectService {
}
return mapper.insertVMinioObject(obj);
}
@Override
public VMinioObject selectById(Long id) {
return mapper.selectVMinioObjectById(id);
}
@Override
public int deleteById(Long id) {
return mapper.deleteVMinioObjectById(id);
}
@Override
public VMinioObject selectByObjectName(String objectName) {
return mapper.selectVMinioObjectByObjectName(objectName);
}
@Override
public int deleteByObjectName(String objectName) {
return mapper.deleteVMinioObjectByObjectName(objectName);
}
}

View File

@@ -540,7 +540,7 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
try {
if (ctx.channel().isWritable()) {
// 发送帧前先发送header
if (ClientType.HTTP.getType() == ctype.getType()) {
if (ClientType.HTTP == ctype) {
ChannelFuture future = ctx.writeAndFlush(Unpooled.copiedBuffer(header));
future.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
@@ -550,7 +550,7 @@ public class MediaTransferFlvByFFmpeg extends MediaTransfer {
}
}
});
} else if (ClientType.WEBSOCKET.getType() == ctype.getType()) {
} else if (ClientType.WEBSOCKET == ctype) {
ChannelFuture future = ctx
.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(header)));
future.addListener(new GenericFutureListener<Future<? super Void>>() {

View File

@@ -0,0 +1,175 @@
package com.ruoyi.video.thread.detector;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.video.domain.Detection;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.javacpp.BytePointer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imencode;
/**
* 通过HTTP调用YOLOv8 Python服务的检测器
* 替换原有的本地模型加载方式
*/
public class HttpYoloDetector implements YoloDetector {
private static final Logger log = LoggerFactory.getLogger(HttpYoloDetector.class);
private final String name;
private final String apiUrl;
private final String modelName;
private final int colorBGR;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
/**
* 创建HTTP检测器
* @param name 检测器名称
* @param apiUrl Python服务的API URL (例如: "http://localhost:8000/api/detect/file")
* @param modelName 要使用的模型名称
* @param colorBGR 边界框颜色 (BGR格式)
*/
public HttpYoloDetector(String name, String apiUrl, String modelName, int colorBGR) {
this.name = name;
this.apiUrl = apiUrl;
this.modelName = modelName;
this.colorBGR = colorBGR;
this.restTemplate = new RestTemplate();
this.objectMapper = new ObjectMapper();
log.info("创建HTTP YOLOv8检测器: {}, 服务地址: {}, 模型: {}", name, apiUrl, modelName);
}
@Override
public String name() {
return name;
}
@Override
public List<Detection> detect(Mat bgr) {
if (bgr == null || bgr.empty()) {
return Collections.emptyList();
}
try {
// 将OpenCV的Mat转换为JPEG字节数组
BytePointer buffer = new BytePointer();
imencode(".jpg", bgr, buffer);
byte[] jpgBytes = new byte[(int)(buffer.capacity())];
buffer.get(jpgBytes);
buffer.deallocate();
// 准备HTTP请求参数
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("model_name", modelName);
body.add("file", new CustomByteArrayResource(jpgBytes, "image.jpg"));
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
// 发送请求到Python服务
ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, requestEntity, String.class);
String responseBody = response.getBody();
if (responseBody != null) {
// 解析响应JSON
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
List<Map<String, Object>> detectionsJson = (List<Map<String, Object>>) result.get("detections");
List<Detection> detections = new ArrayList<>();
for (Map<String, Object> det : detectionsJson) {
String label = (String) det.get("label");
double confidence = ((Number) det.get("confidence")).doubleValue();
int x = ((Number) det.get("x")).intValue();
int y = ((Number) det.get("y")).intValue();
int width = ((Number) det.get("width")).intValue();
int height = ((Number) det.get("height")).intValue();
detections.add(new Detection(label, confidence, new Rect(x, y, width, height), colorBGR));
}
return detections;
}
} catch (Exception e) {
log.error("HTTP检测请求失败: {}", e.getMessage());
}
return Collections.emptyList();
}
// 用于RestTemplate的字节数组资源类
private static class CustomByteArrayResource implements org.springframework.core.io.Resource {
private final byte[] byteArray;
private final String filename;
public CustomByteArrayResource(byte[] byteArray, String filename) {
this.byteArray = byteArray;
this.filename = filename;
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public java.io.InputStream getInputStream() throws IOException {
return new java.io.ByteArrayInputStream(this.byteArray);
}
@Override
public boolean exists() {
return true;
}
@Override
public java.net.URL getURL() throws IOException {
throw new IOException("Not supported");
}
@Override
public java.net.URI getURI() throws IOException {
throw new IOException("Not supported");
}
@Override
public java.io.File getFile() throws IOException {
throw new IOException("Not supported");
}
@Override
public long contentLength() {
return this.byteArray.length;
}
@Override
public long lastModified() {
return System.currentTimeMillis();
}
@Override
public org.springframework.core.io.Resource createRelative(String relativePath) throws IOException {
throw new IOException("Not supported");
}
@Override
public String getDescription() {
return "Byte array resource [" + this.filename + "]";
}
}
}

View File

@@ -1,248 +0,0 @@
package com.ruoyi.video.thread.detector;
import com.ruoyi.video.domain.Detection;
import org.bytedeco.javacpp.indexer.FloatRawIndexer;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_dnn.Net;
import java.nio.file.*;
import java.util.*;
import static org.bytedeco.opencv.global.opencv_dnn.*;
import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
public final class OnnxYoloDetector implements YoloDetector {
private final String modelName;
private final Net net;
private final Size input;
private final float confTh = 0.25f, nmsTh = 0.45f;
private final String[] classes;
private final int colorBGR;
public OnnxYoloDetector(String name, Path dir, int inW, int inH, String backend, int colorBGR) throws Exception {
this.modelName = name;
this.input = new Size(inW, inH);
this.colorBGR = colorBGR;
// 查找ONNX模型文件
String onnx = findModelFile(dir, ".onnx");
if (onnx == null) {
throw new Exception("找不到ONNX模型文件请确保目录中存在 .onnx 文件: " + dir);
}
// 读取类别文件
Path clsPath = dir.resolve("classes.txt");
if (Files.exists(clsPath)) {
this.classes = Files.readAllLines(clsPath).stream().map(String::trim)
.filter(s -> !s.isEmpty()).toArray(String[]::new);
} else {
this.classes = new String[0];
}
try {
// 加载ONNX模型
this.net = readNetFromONNX(onnx);
// 设置OpenCV后端
net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
System.out.println("ONNX模型加载成功: " + name + " (" + onnx + ")");
} catch (Exception e) {
throw new Exception("模型加载失败: " + e.getMessage() +
"\n请确保ONNX模型文件格式正确", e);
}
}
/**
* 在目录中查找指定扩展名的模型文件
*/
private String findModelFile(Path dir, String extension) {
try {
return Files.list(dir)
.filter(path -> path.toString().toLowerCase().endsWith(extension.toLowerCase()))
.map(Path::toString)
.findFirst()
.orElse(null);
} catch (Exception e) {
return null;
}
}
@Override public String name() { return modelName; }
@Override
public List<Detection> detect(Mat bgr) {
if (bgr == null || bgr.empty()) return Collections.emptyList();
// 统一成 BGR 3 通道,避免 blobFromImage 断言失败
if (bgr.channels() != 3) {
Mat tmp = new Mat();
if (bgr.channels() == 1) cvtColor(bgr, tmp, COLOR_GRAY2BGR);
else if (bgr.channels() == 4) cvtColor(bgr, tmp, COLOR_BGRA2BGR);
else bgr.copyTo(tmp);
bgr = tmp;
}
try (Mat blob = blobFromImage(bgr, 1.0/255.0, input, new Scalar(0.0), true, false, CV_32F)) {
net.setInput(blob);
// ===== 多输出兼容Bytedeco 正确写法)=====
org.bytedeco.opencv.opencv_core.StringVector outNames = net.getUnconnectedOutLayersNames();
List<Mat> outs = new ArrayList<>();
if (outNames == null || outNames.size() == 0) {
// 只有一个默认输出
Mat out = net.forward(); // ← 直接返回 Mat
outs.add(out);
} else {
// 多输出:用 MatVector 承接
org.bytedeco.opencv.opencv_core.MatVector outBlobs =
new org.bytedeco.opencv.opencv_core.MatVector(outNames.size());
net.forward(outBlobs, outNames); // ← 正确的重载
for (long i = 0; i < outBlobs.size(); i++) {
outs.add(outBlobs.get(i));
}
}
int fw = bgr.cols(), fh = bgr.rows();
List<Rect2d> boxes = new ArrayList<>();
List<Float> scores = new ArrayList<>();
List<Integer> classIds = new ArrayList<>();
for (Mat out : outs) {
parseYoloOutput(out, fw, fh, boxes, scores, classIds);
}
if (boxes.isEmpty()) return Collections.emptyList();
// 纯 Java NMS避免 MatOf* / Vector API 兼容问题
List<Integer> keep = nmsIndices(boxes, scores, nmsTh);
List<Detection> result = new ArrayList<>(keep.size());
for (int k : keep) {
Rect2d r = boxes.get(k);
Rect rect = new Rect((int)r.x(), (int)r.y(), (int)r.width(), (int)r.height());
int cid = classIds.get(k);
String cname = (cid >= 0 && cid < classes.length) ? classes[cid] : ("cls"+cid);
result.add(new Detection("["+modelName+"] "+cname, scores.get(k), rect, colorBGR));
}
return result;
} catch (Throwable e) {
// 单帧失败不影响整体
return Collections.emptyList();
}
}
/** 解析 YOLO-IR 输出为 N×CC>=6并填充 boxes/scores/classIds。 */
private void parseYoloOutput(Mat out, int fw, int fh,
List<Rect2d> boxes, List<Float> scores, List<Integer> classIds) {
int dims = out.dims();
Mat m;
if (dims == 2) {
// NxC 或 CxN
if (out.cols() >= 6) {
m = out;
} else {
Mat tmp = new Mat();
transpose(out, tmp); // CxN -> NxC
m = tmp;
}
} else if (dims == 3) {
// [1,N,C] 或 [1,C,N]
if (out.size(2) >= 6) {
m = out.reshape(1, out.size(1)); // -> N×C
} else {
Mat squeezed = out.reshape(1, out.size(1)); // C×N
Mat tmp = new Mat();
transpose(squeezed, tmp); // -> N×C
m = tmp;
}
} else if (dims == 4) {
// [1,1,N,C] 或 [1,1,C,N]
int a = out.size(2), b = out.size(3);
if (b >= 6) {
m = out.reshape(1, a).clone(); // -> N×C
} else {
Mat cxn = out.reshape(1, b); // C×N
Mat tmp = new Mat();
transpose(cxn, tmp); // -> N×C
m = tmp.clone();
}
} else {
return; // 不支持的形状
}
int N = m.rows(), C = m.cols();
if (C < 6 || N <= 0) return;
FloatRawIndexer idx = m.createIndexer();
for (int i = 0; i < N; i++) {
float cx = idx.get(i,0), cy = idx.get(i,1), w = idx.get(i,2), h = idx.get(i,3);
float obj = idx.get(i,4);
int bestCls = -1; float bestScore = 0f;
for (int c = 5; c < C; c++) {
float p = idx.get(i,c);
if (p > bestScore) { bestScore = p; bestCls = c - 5; }
}
float conf = obj * bestScore;
if (conf < confTh) continue;
// 默认假设归一化中心点格式 (cx,cy,w,h);若你的 IR 是 x1,y1,x2,y2请把这里换算改掉
int bx = Math.max(0, Math.round(cx * fw - (w * fw) / 2f));
int by = Math.max(0, Math.round(cy * fh - (h * fh) / 2f));
int bw = Math.min(fw - bx, Math.round(w * fw));
int bh = Math.min(fh - by, Math.round(h * fh));
if (bw <= 0 || bh <= 0) continue;
boxes.add(new Rect2d(bx, by, bw, bh));
scores.add(conf);
classIds.add(bestCls);
}
}
/** 纯 Java NMSIoU 抑制),返回保留的下标列表。 */
private List<Integer> nmsIndices(List<Rect2d> boxes, List<Float> scores, float nmsThreshold) {
List<Integer> order = new ArrayList<>(boxes.size());
for (int i = 0; i < boxes.size(); i++) order.add(i);
// 按分数降序
order.sort((i, j) -> Float.compare(scores.get(j), scores.get(i)));
List<Integer> keep = new ArrayList<>();
boolean[] removed = new boolean[boxes.size()];
for (int a = 0; a < order.size(); a++) {
int i = order.get(a);
if (removed[i]) continue;
keep.add(i);
Rect2d bi = boxes.get(i);
double areaI = bi.width() * bi.height();
for (int b = a + 1; b < order.size(); b++) {
int j = order.get(b);
if (removed[j]) continue;
Rect2d bj = boxes.get(j);
double areaJ = bj.width() * bj.height();
double xx1 = Math.max(bi.x(), bj.x());
double yy1 = Math.max(bi.y(), bj.y());
double xx2 = Math.min(bi.x() + bi.width(), bj.x() + bj.width());
double yy2 = Math.min(bi.y() + bi.height(), bj.y() + bj.height());
double w = Math.max(0, xx2 - xx1);
double h = Math.max(0, yy2 - yy1);
double inter = w * h;
double iou = inter / (areaI + areaJ - inter + 1e-9);
if (iou > nmsThreshold) removed[j] = true;
}
}
return keep;
}
@Override public void close(){ net.close(); }
}

View File

@@ -2,130 +2,121 @@ package com.ruoyi.video.utils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* File包装成MultipartFile
* @Author: orange
* @CreateTime: 2025-01-16
* 自定义MultipartFile实现,用于文件上传
*/
public class CustomMultipartFile implements MultipartFile {
// 定义一个File类型的变量file
private final File file;
// 定义一个String类型的变量contentType
private final String name;
private final String originalFilename;
private final String contentType;
// 构造方法传入一个File类型的变量file和一个String类型的变量contentType
public CustomMultipartFile(File file, String contentType) {
/**
* 构造函数 - 从File创建
*
* @param file 文件
* @param filename 文件名
* @param contentType 内容类型
*/
public CustomMultipartFile(File file, String filename, String contentType) {
this.file = file;
this.name = filename;
this.originalFilename = filename;
this.contentType = contentType;
}
// 获取文件名
/**
* 构造函数 - 从字节数组创建
*
* @param bytes 文件字节数组
* @param filename 文件名
* @param contentType 内容类型
*/
public CustomMultipartFile(byte[] bytes, String filename, String contentType) {
this.file = null;
this.name = filename;
this.originalFilename = filename;
this.contentType = contentType;
}
/**
* 构造函数 - 从File创建指定Content-Type
*
* @param file 文件
* @param contentType 内容类型
*/
public CustomMultipartFile(File file, String contentType) {
this.file = file;
this.name = file.getName();
this.originalFilename = file.getName();
this.contentType = contentType;
}
@Override
public String getName() {
return this.file.getName();
return name;
}
// 获取原始文件名
@Override
public String getOriginalFilename() {
return this.file.getName();
return originalFilename;
}
// 获取文件类型
@Override
public String getContentType() {
return this.contentType;
return contentType;
}
// 判断文件是否为空
@Override
public boolean isEmpty() {
return this.file.length() == 0L;
return getSize() == 0;
}
// 获取文件大小
@Override
public long getSize() {
return this.file.length();
if (file != null) {
return file.length();
}
return 0;
}
// 获取文件的字节数组
@Override
public byte[] getBytes() throws IOException {
// 创建一个FileInputStream对象传入file
InputStream inputStream = new FileInputStream(this.file);
byte[] var2;
try {
// 读取所有字节数组
var2 = inputStream.readAllBytes();
} catch (Throwable var5) {
try {
// 关闭输入流
inputStream.close();
} catch (Throwable var4) {
var5.addSuppressed(var4);
if (file != null) {
try (InputStream is = new FileInputStream(file)) {
return is.readAllBytes();
}
throw var5;
}
// 关闭输入流
inputStream.close();
return var2;
return new byte[0];
}
// 获取文件的输入流
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
if (file != null) {
return new FileInputStream(file);
}
return new ByteArrayInputStream(new byte[0]);
}
// 将文件传输到指定的文件
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
// 如果目标文件不存在,则创建
if (!dest.exists()) {
dest.createNewFile();
}
// 创建一个FileInputStream对象传入file
InputStream inputStream = new FileInputStream(this.file);
try {
// 创建一个FileOutputStream对象传入dest
OutputStream outputStream = new FileOutputStream(dest);
try {
// 创建一个字节数组大小为1024
byte[] buffer = new byte[1024];
int bytesRead;
// 循环读取文件,直到读取完毕
while((bytesRead = inputStream.read(buffer)) != -1) {
// 将读取的字节数组写入目标文件
outputStream.write(buffer, 0, bytesRead);
if (file != null) {
try (FileInputStream fis = new FileInputStream(file)) {
try (java.io.FileOutputStream fos = new java.io.FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
} catch (Throwable var8) {
try {
// 关闭输出流
outputStream.close();
} catch (Throwable var7) {
var8.addSuppressed(var7);
}
throw var8;
}
// 关闭输出流
outputStream.close();
} catch (Throwable var9) {
try {
// 关闭输入流
inputStream.close();
} catch (Throwable var6) {
var9.addSuppressed(var6);
}
throw var9;
}
// 关闭输入流
inputStream.close();
}
}

View File

@@ -11,11 +11,11 @@ public final class Overlay {
public static void draw(List<Detection> dets, Mat frame) {
for (Detection d : dets) {
Rect r = d.box();
int bgr = d.colorBGR();
Rect r = d.getRect();
int bgr = d.getColorBGR();
Scalar c = new Scalar(bgr & 0xFF, (bgr >> 8) & 0xFF, (bgr >> 16) & 0xFF, 0);
rectangle(frame, r, c, 2, LINE_8, 0);
String label = d.cls()+" "+String.format("%.2f", d.conf());
String label = d.getLabel()+" "+String.format("%.2f", d.getConfidence());
int[] baseline = new int[1];
Size t = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, baseline);
int x = Math.max(0, r.x());