diff --git a/config/local/zlmediakit.yml b/config/local/zlmediakit.yml
index e9988fe..5e7cc36 100644
--- a/config/local/zlmediakit.yml
+++ b/config/local/zlmediakit.yml
@@ -1,5 +1,5 @@
ZLMediaKit:
- ip: 192.168.2.128
+ ip: 192.168.2.129
port: 8080
app: live
vhost: __defaultVhost__
diff --git a/sz-common/sz-common-security/src/main/java/com/sz/security/config/SaTokenConfig.java b/sz-common/sz-common-security/src/main/java/com/sz/security/config/SaTokenConfig.java
index 0ea63c0..77b14f9 100644
--- a/sz-common/sz-common-security/src/main/java/com/sz/security/config/SaTokenConfig.java
+++ b/sz-common/sz-common-security/src/main/java/com/sz/security/config/SaTokenConfig.java
@@ -55,7 +55,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// D:/work/images/save/1.jpg
registry.addResourceHandler("/save/**")
- .addResourceLocations("file:D:/work/images/");
+ .addResourceLocations("file:D:/work/images/","file:D:/work/temp/");
}
}
diff --git a/sz-service/sz-service-admin/pom.xml b/sz-service/sz-service-admin/pom.xml
index 76db777..21d24d3 100644
--- a/sz-service/sz-service-admin/pom.xml
+++ b/sz-service/sz-service-admin/pom.xml
@@ -101,6 +101,11 @@
org.springframework.boot
spring-boot-starter-actuator
+
+ org.im4java
+ im4java
+ 1.4.0
+
cc.eguid
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/config/RestTemplateConfig.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/config/RestTemplateConfig.java
index 0c7e777..8ff5947 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/config/RestTemplateConfig.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/config/RestTemplateConfig.java
@@ -11,8 +11,8 @@ public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
- factory.setConnectTimeout(5000);
- factory.setReadTimeout(5000);
+ factory.setConnectTimeout(10000);
+ factory.setReadTimeout(10000);
return new RestTemplate(factory);
}
}
\ No newline at end of file
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/DeviceController.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/DeviceController.java
index 4724a3d..eb3d6b4 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/DeviceController.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/DeviceController.java
@@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
+
/**
* ClassName: DeviceController
* Package: com.sz.admin.monitor.controller
@@ -30,10 +32,18 @@ public class DeviceController {
return ApiResult.success(deviceService.refresh(id,deviceType));
}
// 设备登录
- @Operation(summary = "/设备登录")
+ @Operation(summary = "设备登录")
@GetMapping("/login")
public ApiResult> deviceLogin(@RequestParam("id") Long id)
{
return ApiResult.success(deviceService.deviceLogin(id));
}
+ // 整个NVR的历史视频的算法分析
+ @Operation(summary = "整个NVR的历史视频的算法分析")
+ @GetMapping("/history/analysis")
+ public ApiResult> captureHistoryAndCompose(@RequestParam("ids") List ids)
+ {
+ deviceService.captureHistoryAndCompose(ids);
+ return ApiResult.success();
+ }
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/IpChannelController.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/IpChannelController.java
index d2df090..0adcae6 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/IpChannelController.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/controller/IpChannelController.java
@@ -1,6 +1,7 @@
package com.sz.admin.monitor.controller;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
+import com.sz.admin.monitor.pojo.po.RecordFile;
import com.sz.admin.monitor.pojo.vo.watchful.WatchfulVO;
import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
@@ -11,6 +12,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
/**
* ClassName: IpChannelController
* Package: com.sz.admin.monitor.controller
@@ -97,4 +100,21 @@ public class IpChannelController {
return ApiResult.success(ipChannelService.configAlg(algTaskConfigDto));
}
+ // 抓取历史视频的图片
+ @Operation(summary = "抓取历史视频的图片并进行对比")
+ @GetMapping("/capture/history")
+ public ApiResult captureHistoryAndCompose(@RequestParam("id") Long id) {
+ ipChannelService.captureHistoryAndCompose(id);
+ return ApiResult.success();
+ }
+
+ // 按照时间范围获取录像
+ @Operation(summary = "按照时间范围获取录像")
+ @GetMapping("/getVideoByTime")
+ public ApiResult> getVideoByTime(@RequestParam("id") Long id,
+ @RequestParam("startTime") String startTime,
+ @RequestParam("endTime") String endTime) {
+ return ApiResult.success(ipChannelService.getVideoByTime(id, startTime, endTime));
+ }
+
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/pojo/dto/edgebox/BoxAlarmReportDto.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/pojo/dto/edgebox/BoxAlarmReportDto.java
index 18d9afc..0615424 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/pojo/dto/edgebox/BoxAlarmReportDto.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/pojo/dto/edgebox/BoxAlarmReportDto.java
@@ -23,17 +23,14 @@ public class BoxAlarmReportDto {
private String cameraNo;
@Schema(description = "摄像头ID")
private Long cameraId;
- @Schema(description = "报警的url")
- private String url;
@Schema(description = "报警的类型")
- private String alarmReportType;
+ private String alarmTypeDesc;
+ @Schema(description = "报警的枚举值")
+ private Integer alarmType;
@Schema(description = "报警的时间")
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime alarmReportTime;
+ private Long alarmTime;
@Schema(description = "基准图")
private String baseImage;
- @Schema(description = "抓拍图")
- private String captureImage;
- @Schema(description = "报警的原因")
- private String description;
+ @Schema(description = "报警图")
+ private String alarmImage;
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/AbstractNVR.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/AbstractNVR.java
index 147f6ca..6cddb3c 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/AbstractNVR.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/AbstractNVR.java
@@ -1,5 +1,7 @@
package com.sz.admin.monitor.sdk;
+import com.sz.admin.monitor.sdk.hkSdk.HCNetSDK;
+
/**
* ClassName: AbstractNVR
* Package: com.sz.admin.monitor.hik
@@ -39,4 +41,14 @@ public abstract class AbstractNVR {
* @return true成功 false失败
*/
public abstract String capturePic(int luerId,int iChannel,String fileName);
+
+ /**
+ * 抓取历史图像
+ */
+ public abstract boolean captureHistoryImage(int luerId, int iChannel,
+ HCNetSDK.NET_DVR_TIME startDvrTime,
+ HCNetSDK.NET_DVR_TIME stopDvrTime,
+ String tempVideoPath,
+ String finalImagePath);
}
+
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/ManageNVR.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/ManageNVR.java
index 1458be0..66f4871 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/ManageNVR.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/ManageNVR.java
@@ -1,6 +1,8 @@
package com.sz.admin.monitor.sdk;
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.RandomUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.sz.admin.monitor.pojo.po.Camera;
import com.sz.admin.monitor.pojo.po.Nvr;
@@ -34,6 +36,10 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* 所有NVR类型的管理类
@@ -56,6 +62,7 @@ public class ManageNVR {
private VcrlUserIdContainer vcrlUserIdContainer;
@Resource
private DataScopeProperties dataScopeProperties;
+ private final ExecutorService executor = Executors.newFixedThreadPool(5);
/**
* 项目启动注册所有sdk和登录所有nvr
@@ -749,4 +756,124 @@ public class ManageNVR {
Map map = hkNVR.NET_DVR_GetPTZPos(userId, camera.getChannelId());
return BeanCopyUtils.copy(map, PTZVO.class);
}
+
+
+ // 进行历史视频的抓图
+ public void captureHistoryImage(String taskDir, Long id) {
+ Camera camera = cameraService.getById(id);
+ Integer userId = (Integer) vcrlUserIdContainer.getlUserId(camera.getNvrId().intValue());
+ // 收集前一个月的每个小时的时间点
+ List points = new ArrayList<>();
+ LocalDateTime endTime = LocalDateTime.now().withMinute(0).withSecond(0);
+ LocalDateTime startTime = endTime.minusDays(7);
+ LocalDateTime current = startTime;
+ while (current.isBefore(endTime) || current.equals(endTime)) {
+ points.add(current);
+ current = current.plusHours(1);
+ }
+ int totalSizes = points.size();
+ // 创建固定大小的线程池
+ ExecutorService threadPool = Executors.newFixedThreadPool(5);
+ // 统计进度的原子类
+ AtomicInteger successCount = new AtomicInteger(0);
+ AtomicInteger failCount = new AtomicInteger(0);
+ AtomicInteger finishedCount = new AtomicInteger(0);
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
+ List> futures = new ArrayList<>();
+ // 将任务全部丢进线程池
+ for (LocalDateTime time : points) {
+ CompletableFuture future = CompletableFuture.runAsync(() -> {
+ String fileName = "history_"+camera.getId()+ time.format(fmt) + ".jpg";
+ File file = captureAtTime(userId, camera.getChannelId(), time, taskDir, fileName);
+ // 记录当前完成的次数
+ int currentFinished = finishedCount.incrementAndGet();
+ if (file != null) {
+ successCount.incrementAndGet();
+ // System.out.println(String.format("[进度 %d/%d] 成功: %s", currentFinished, totalSizes, fileName));
+ } else {
+ failCount.incrementAndGet();
+ // System.out.println(String.format("[进度 %d/%d] 失败: %s", currentFinished, totalSizes, fileName));
+ }
+ }, threadPool);
+ // 添加任务
+ futures.add(future);
+ }
+ // 阻塞主线程,等待所有720个任务全部跑完
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+ // 关闭线程池
+ executor.shutdown();
+ // System.out.println("总任务数量:" + totalSizes);
+ // System.out.println("成功抓取:" + successCount.get() + "张");
+ // System.out.println("失败:" + failCount.get() + "张");
+ // System.out.println("图片已全部保存在:" + "D:\\work\\temp\\");
+ }
+
+ /**
+ * 辅助方法:在指定的确切时间点,抓取一张监控图片
+ *
+ * @param userId 海康登录返回的 userId
+ * @param channelId 摄像头通道号
+ * @param time 想要抓图的具体时间 (Java 8 LocalDateTime)
+ * @param dir 图片和视频保存的本地目录 (必须以 / 或 \\ 结尾,例如 "D:/temp/")
+ * @param fileName 最终生成的图片文件名 (例如 "pic_120000.jpg")
+ * @return File 如果抓图成功,返回该图片的 File 对象;如果失败,返回 null
+ */
+ private File captureAtTime(int userId, int channelId, LocalDateTime time, String dir, String fileName) {
+ LocalDateTime stopTime = time.plusSeconds(5);
+ // 将 Java 的 LocalDateTime 翻译成海康 SDK 认识的底层结构体
+ HCNetSDK.NET_DVR_TIME startDvrTime = convertToDvrTime(time);
+ HCNetSDK.NET_DVR_TIME stopDvrTime = convertToDvrTime(stopTime);
+ String tempVideoPath = dir + "temp_slice_" + UUID.randomUUID() + ".mp4";
+ String finalImagePath = dir + fileName;
+ try {
+ hkNVR.captureHistoryImage(
+ userId,
+ channelId,
+ startDvrTime,
+ stopDvrTime,
+ tempVideoPath,
+ finalImagePath
+ );
+
+ // 抓图流程走完后,检查图片是否真的生成成功了
+ File resultImage = new File(finalImagePath);
+ if (resultImage.exists() && resultImage.length() > 0) {
+ return resultImage;
+ } else {
+ return null; // 抓图失败(可能是那个时间点设备断网或无录像)
+ }
+
+ } catch (Exception e) {
+ return null;
+ } finally {
+ File tempFile = new File(tempVideoPath);
+ if (tempFile.exists()) {
+ tempFile.delete();
+ }
+ }
+ }
+
+ private HCNetSDK.NET_DVR_TIME convertToDvrTime(LocalDateTime currentTime) {
+ HCNetSDK.NET_DVR_TIME netDvrTime = new HCNetSDK.NET_DVR_TIME();
+ netDvrTime.dwYear = (short) currentTime.getYear();
+ netDvrTime.dwMonth = (short) currentTime.getMonthValue();
+ netDvrTime.dwDay = (short) currentTime.getDayOfMonth();
+ netDvrTime.dwHour = (short) currentTime.getHour();
+ netDvrTime.dwMinute = (short) currentTime.getMinute();
+ netDvrTime.dwSecond = (short) currentTime.getSecond();
+ return netDvrTime;
+ }
+
+ // 按照时间范围获取录像文件
+ public List getRecordFileByTime(Long id, LocalDateTime startTime, LocalDateTime endTime) {
+ Camera camera = cameraService.getById(id);
+ List recordFile = new ArrayList<>();
+ Integer userId = (Integer) vcrlUserIdContainer.getlUserId(camera.getNvrId().intValue());
+ try {
+ recordFile = hkNVR.findRecordFile(userId, camera.getChannelId(), startTime, endTime);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ return recordFile;
+ }
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/hkSdk/HkNVR.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/hkSdk/HkNVR.java
index b508354..e8f0485 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/hkSdk/HkNVR.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/sdk/hkSdk/HkNVR.java
@@ -14,6 +14,8 @@ import com.sz.admin.monitor.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
+import java.io.File;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@@ -139,6 +141,81 @@ public class HkNVR extends AbstractNVR {
public String capturePic(int luerId, int iChannel,String fileName) {
return captureP(luerId, iChannel,fileName);
}
+
+ @Override
+ public boolean captureHistoryImage(int luerId, int iChannel,
+ HCNetSDK.NET_DVR_TIME startDvrTime,
+ HCNetSDK.NET_DVR_TIME stopDvrTime,
+ String tempVideoPath,
+ String finalImagePath) {
+ // 1. 构建下载条件结构体
+ HCNetSDK.NET_DVR_PLAYCOND downloadCond = new HCNetSDK.NET_DVR_PLAYCOND();
+ downloadCond.dwChannel = iChannel;
+ // 设置开始时间
+ downloadCond.struStartTime.dwYear = startDvrTime.dwYear;
+ downloadCond.struStartTime.dwMonth = startDvrTime.dwMonth;
+ downloadCond.struStartTime.dwDay = startDvrTime.dwDay;
+ downloadCond.struStartTime.dwHour = startDvrTime.dwHour;
+ downloadCond.struStartTime.dwMinute = startDvrTime.dwMinute;
+ downloadCond.struStartTime.dwSecond = startDvrTime.dwSecond;
+ // 设置结束时间
+ downloadCond.struStopTime.dwYear = stopDvrTime.dwYear;
+ downloadCond.struStopTime.dwMonth = stopDvrTime.dwMonth;
+ downloadCond.struStopTime.dwDay = stopDvrTime.dwDay;
+ downloadCond.struStopTime.dwHour = stopDvrTime.dwHour;
+ downloadCond.struStopTime.dwMinute = stopDvrTime.dwMinute;
+ downloadCond.struStopTime.dwSecond = stopDvrTime.dwSecond;
+ downloadCond.write();
+
+ // 2. 获取下载句柄
+ int lDownloadHandle = hCNetSDK.NET_DVR_GetFileByTime_V40(luerId, tempVideoPath, downloadCond);
+ if (lDownloadHandle == -1) {
+ System.out.println("建立下载通道失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
+ return false;
+ }
+
+ // 3. 发送下载控制指令
+ boolean playControl = hCNetSDK.NET_DVR_PlayBackControl_V40(lDownloadHandle, HCNetSDK.NET_DVR_PLAYSTART, null, 0, null, null);
+ if (!playControl) {
+ System.out.println("发送下载控制指令失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
+ hCNetSDK.NET_DVR_StopGetFile(lDownloadHandle); //下载用的接口,停止必须用 StopGetFile,不能用 StopPlayBack
+ return false; // 如果指令发送失败,必须 return 终止,不能往下走
+ }
+
+ // 给操作系统 1 秒钟的缓冲时间,确保视频文件彻底写入硬盘并解除占用
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ // 7. 调用 ffmpeg 进行抽取图片
+ ProcessBuilder processBuilder = new ProcessBuilder(
+ "ffmpeg", "-y", "-err_detect", "ignore_err", "-i", tempVideoPath, "-vframes", "1", "-q:v", "2", "-f", "image2", finalImagePath
+ );
+
+ // 重定向 FFmpeg 的输出流,防止缓冲区撑满导致 Java 进程死锁卡住
+ processBuilder.redirectErrorStream(true);
+ try {
+ Process process = processBuilder.start();
+ java.io.InputStream inputStream = process.getInputStream();
+ byte[] buffer = new byte[1024];
+ while (inputStream.read(buffer) != -1) {
+ // 静默读取,不打印,清空缓冲区
+ }
+ // 等待 FFmpeg 执行完毕
+ int exitCode = process.waitFor();
+ if (exitCode == 0) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (IOException | InterruptedException e) {
+ System.out.println("ffmpeg 执行失败:" + e);
+ return false;
+ }
+ }
+
/**
* 动态库加载
*
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/DeviceService.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/DeviceService.java
index 5ec18c8..27d1d41 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/DeviceService.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/DeviceService.java
@@ -3,6 +3,8 @@ package com.sz.admin.monitor.service;
import com.mybatisflex.core.service.IService;
import com.sz.admin.monitor.pojo.po.Nvr;
+import java.util.List;
+
/**
* ClassName: DeviceService
* Package: com.sz.admin.monitor.service
@@ -13,4 +15,7 @@ public interface DeviceService extends IService {
// 设备登录
Object deviceLogin(Long id);
+ // 历史视频的算法分析
+ void captureHistoryAndCompose(List ids);
+
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/ForwardService.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/ForwardService.java
index 3048826..edff629 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/ForwardService.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/ForwardService.java
@@ -13,5 +13,5 @@ public interface ForwardService {
/**
* 转发报警方法
*/
- void enrichAndForward(BoxAlarmReportDto boxAlarm);
+ void enrichAndForward(String url,BoxAlarmReportDto boxAlarm);
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/IpChannelService.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/IpChannelService.java
index 8e5a760..1fe76bc 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/IpChannelService.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/IpChannelService.java
@@ -3,9 +3,12 @@ package com.sz.admin.monitor.service;
import com.mybatisflex.core.service.IService;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
import com.sz.admin.monitor.pojo.po.Camera;
+import com.sz.admin.monitor.pojo.po.RecordFile;
import com.sz.admin.monitor.pojo.vo.watchful.WatchfulVO;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
+import java.util.List;
+
/**
* ClassName: IpChannelService
* Package: com.sz.admin.monitor.service
@@ -35,5 +38,9 @@ public interface IpChannelService extends IService {
// 配置算法任务
AlgMediaConfigResponse configAlg(AlgorithmTaskDTO algTaskConfigDto);
+ void captureHistoryAndCompose(Long id);
+
+ List getVideoByTime(Long id, String startTime, String endTime);
+
// void resetSentinel(int channelId, int watchTime, int presetIndex);
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/AlgorithmTaskServiceImpl.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/AlgorithmTaskServiceImpl.java
index fce8632..920d27d 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/AlgorithmTaskServiceImpl.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/AlgorithmTaskServiceImpl.java
@@ -39,6 +39,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
@@ -142,17 +143,22 @@ public class AlgorithmTaskServiceImpl extends ServiceImpl implements De
@Resource
private NvrMapper nvrMapper;
@Resource
- private SocketService socketService;
- @Resource
- private WebsocketRedisService websocketRedisService;
- @Resource
- private AiBoxRequestUtil aiBoxRequestUtil;
+ private IpChannelService ipChannelService;
+
@Override
public Object deviceLogin(Long id) {
@@ -68,6 +70,37 @@ public class DeviceServiceImpl extends ServiceImpl implements De
return result;
}
+ @Override
+ public void captureHistoryAndCompose(List ids) {
+ if(ids==null || ids.isEmpty())
+ {
+ throw new BusinessException(AdminResponseEnum.OPERATION_FAIL,null,"数据为空");
+ }
+ ExecutorService threadPool = Executors.newFixedThreadPool(1);
+ CompletableFuture.runAsync(()->{
+ try {
+ List nvrs = nvrMapper.selectListByIds(ids);
+ // 对每个NVR进行分析
+ for (Nvr nvr : nvrs) {
+ // 查询出所有的通道
+ List cameraList = cameraMapper.selectByNvrId(nvr.getId());
+ // 遍历所有的通道,进行分析
+ for (Camera camera : cameraList) {
+ try {
+ ipChannelService.captureHistoryAndCompose(camera.getId());
+ }catch (Exception e)
+ {
+ log.error("处理camera异常,NVRID:{},CameraID:{}",nvr.getId(),camera.getId());
+ }
+ }
+ }
+ }catch (Exception e)
+ {
+ log.error("执行批量分析任务发生全局异常", e);
+ }
+ },threadPool);
+ }
+
/**
* 通道刷新
* @param id NVR的id
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/ForwardServiceImpl.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/ForwardServiceImpl.java
index 824b3b0..3d96b9e 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/ForwardServiceImpl.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/ForwardServiceImpl.java
@@ -19,7 +19,7 @@ public class ForwardServiceImpl implements ForwardService {
@Resource
private RestTemplate restTemplate;
@Override
- public void enrichAndForward(BoxAlarmReportDto boxAlarm) {
- ResponseEntity response = restTemplate.postForEntity(boxAlarm.getUrl(), boxAlarm, String.class);
+ public void enrichAndForward(String url,BoxAlarmReportDto boxAlarm) {
+ ResponseEntity response = restTemplate.postForEntity(url, boxAlarm, String.class);
}
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/IpChannelServiceImpl.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/IpChannelServiceImpl.java
index 63b468a..2ba90e6 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/IpChannelServiceImpl.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/service/impl/IpChannelServiceImpl.java
@@ -1,5 +1,6 @@
package com.sz.admin.monitor.service.impl;
+import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@@ -8,16 +9,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.sz.admin.monitor.enums.HCPlayControlEnum;
-import com.sz.admin.monitor.mapper.AlgorithmTaskMapper;
-import com.sz.admin.monitor.mapper.CameraMapper;
-import com.sz.admin.monitor.mapper.EdgeBoxMapper;
-import com.sz.admin.monitor.mapper.NvrMapper;
+import com.sz.admin.monitor.mapper.*;
import com.sz.admin.monitor.pojo.dto.Ipchannel.ControlDTO;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
-import com.sz.admin.monitor.pojo.po.AlgorithmTask;
-import com.sz.admin.monitor.pojo.po.Camera;
-import com.sz.admin.monitor.pojo.po.EdgeBox;
-import com.sz.admin.monitor.pojo.po.Nvr;
+import com.sz.admin.monitor.pojo.po.*;
import com.sz.admin.monitor.pojo.vo.camerasnapshot.CameraSnapshotVO;
import com.sz.admin.monitor.pojo.vo.ptz.PTZVO;
import com.sz.admin.monitor.pojo.vo.watchful.WatchfulVO;
@@ -25,10 +20,7 @@ import com.sz.admin.monitor.sdk.ManageNVR;
import com.sz.admin.monitor.service.CameraSnapshotService;
import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.service.PresetService;
-import com.sz.admin.monitor.utils.AiBoxRequestUtil;
-import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
-import com.sz.admin.monitor.utils.RtspUtil;
-import com.sz.admin.monitor.utils.ZLMediaKitUtils;
+import com.sz.admin.monitor.utils.*;
import com.sz.core.common.entity.TransferMessage;
import com.sz.core.common.exception.common.BusinessException;
import com.sz.core.util.BeanCopyUtils;
@@ -42,7 +34,9 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.io.File;
import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -67,7 +61,7 @@ public class IpChannelServiceImpl extends ServiceImpl impl
private AlgorithmTaskMapper algorithmTaskMapper;
@Resource
- private PresetService presetService;
+ private CameraAlarmMapper cameraAlarmMapper;
@Resource
private AiBoxRequestUtil aiBoxRequestUtil;
@@ -378,6 +372,98 @@ public class IpChannelServiceImpl extends ServiceImpl impl
}
return response;
}
+
+ @Override
+ public void captureHistoryAndCompose(Long id) {
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
+ // 生成本次任务的专属批次号
+ String batchId=LocalDateTime.now().format(fmt);
+ // 拼接出本次任务的专属目录
+ String baseDir="D:/work/temp/";
+ String taskDir=baseDir+"task_"+batchId+"/";
+ File taskDirect = new File(taskDir);
+ if (!taskDirect.exists()) {
+ boolean created = taskDirect.mkdirs();
+ if (!created) {
+ log.error("创建专属工作目录失败,请检查 D 盘权限!路径: {}", taskDir);
+ return;
+ }
+ }
+ log.info("保存文件的路径:{}",taskDir);
+ manageNVR.captureHistoryImage(taskDir,id);
+ startSequentialComparison(taskDir,id);
+ }
+
+ // 执行相邻图片的滑动窗口对比任务
+ private void startSequentialComparison(String imageDirPath,Long id) {
+ // 图片存储目录文件夹
+ File dir = new File(imageDirPath);
+ Camera camera = this.getById(id);
+ Nvr nvr = nvrMapper.selectOneById(camera.getNvrId());
+ // 获取该目录下所有的.jpg图片
+ File[] imageFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".jpg") && name.contains("history_"+camera.getId()));
+ if (imageFiles == null || imageFiles.length < 2) {
+ log.error("图片数据不足,无法对比");
+ return;
+ }
+ // 将图片进行排序
+ Arrays.sort(imageFiles, Comparator.comparing(File::getName));
+ List cameraAlarmList=new ArrayList<>();
+ for (int i = 0; i < imageFiles.length - 1; i++) {
+ File currentImg = imageFiles[i];
+ File nextImg = imageFiles[i + 1];
+ // System.out.println("正在对比:" + currentImg.getName() + "VS :" + nextImg.getName());
+ // 调用算法进行对比
+ // 1. 移位对比
+ Map map = RobustImageMatcherUtil.calculateOffset(currentImg.getPath(), nextImg.getPath());
+ // 如果出现差异化,直接生成一条记录,保存到报警记录表中
+ int value = (int) map.get("value");
+ if (value == 1) {
+ CameraAlarm cameraAlarm = new CameraAlarm();
+ cameraAlarm.setCameraId(camera.getId());
+ cameraAlarm.setCameraName(camera.getName());
+ cameraAlarm.setCameraNo(camera.getCameraNo());
+ cameraAlarm.setChannelId(camera.getChannelId());
+ cameraAlarm.setAlarmAreaId(nvr.getStationId());
+ cameraAlarm.setAlarmType(1);
+ cameraAlarm.setStatus(0);
+ cameraAlarm.setBaseImage(currentImg.getPath());
+ cameraAlarm.setCaptureImage(nextImg.getPath());
+ cameraAlarm.setAlgoResult((String) map.get("description"));
+ cameraAlarmList.add(cameraAlarm);
+ }
+ // 2. 模糊对比
+ BlurDetectorV4.DetectResult detectResult = BlurDetectorV4.doubleDetectBlur(currentImg.getPath(), nextImg.getPath());
+ if ("1".equals(detectResult.value)){
+ CameraAlarm cameraAlarm = new CameraAlarm();
+ cameraAlarm.setCameraId(camera.getId());
+ cameraAlarm.setCameraName(camera.getName());
+ cameraAlarm.setCameraNo(camera.getCameraNo());
+ cameraAlarm.setChannelId(camera.getChannelId());
+ cameraAlarm.setAlarmAreaId(nvr.getStationId());
+ cameraAlarm.setAlarmType(6);
+ cameraAlarm.setStatus(0);
+ cameraAlarm.setBaseImage(currentImg.getPath());
+ cameraAlarm.setCaptureImage(nextImg.getPath());
+ cameraAlarm.setAlgoResult((String) map.get("description"));
+ cameraAlarmList.add(cameraAlarm);
+ }
+ }
+ // 保存到数据库
+ cameraAlarmMapper.insertBatch(cameraAlarmList);
+ }
+
+ @Override
+ public List getVideoByTime(Long id, String startTime, String endTime) {
+ List recordFiles = new ArrayList<>();
+ try {
+ recordFiles = manageNVR.getRecordFileByTime(id, LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+ } catch (Exception e) {
+ throw new RuntimeException("获取视频失败", e);
+ }
+ return recordFiles;
+ }
+
// 在这里接收透传过来的 websocket信息,进行业务处理
@Override
public void handlerMsg(TransferMessage transferMessage) {
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/task/ScheduledTask.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/task/ScheduledTask.java
index 44b8e24..b7fd2fc 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/task/ScheduledTask.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/task/ScheduledTask.java
@@ -33,6 +33,8 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
@@ -133,17 +135,24 @@ public class ScheduledTask {
}
count++;
// 进行单一背景检测
- BackgroundDetector.DetectResult result = BackgroundDetector.detectBackground(imagePath2);
- if("1".equals(result.value))
- {
- // 当前摄像头面对的是白墙,直接进行报警
- handleAnalysisResult(blurDetection, imagePath2, result);
- continue;
- }
+// BackgroundDetector.DetectResult result = BackgroundDetector.detectBackground(imagePath2);
+// if("1".equals(result.value))
+// {
+// // 当前摄像头面对的是白墙,直接进行报警
+// handleAnalysisResult(blurDetection, imagePath2, result);
+// continue;
+// }
// 进行模糊图片对比
BlurDetectorV4.DetectResult detectResult = null;
if (isExists) {
// 基准图存在,使用双图对比功能
+ // 检查是否对着墙壁或者特征不明显的地方
+ BlurrinessComparator.DetectResult result = BlurrinessComparator.compareBlurriness(imagePath1, imagePath2);
+ if (result.value.equals("1")) {
+ // 当前摄像头面对的是白墙,直接进行报警
+ handleAnalysisResult(blurDetection, imagePath2, result);
+ continue;
+ }
detectResult = BlurDetectorV4.doubleDetectBlur(imagePath1, imagePath2);
} else {
// 基准图不存在,使用单图对比功能
@@ -162,7 +171,7 @@ public class ScheduledTask {
public record DetectResult(String desc,double score) {
}
// 分析背景检测结果并且报警
- private void handleAnalysisResult(BlurDetection blurDetection, String imagePath, BackgroundDetector.DetectResult result) throws IOException {
+ private void handleAnalysisResult(BlurDetection blurDetection, String imagePath, BlurrinessComparator.DetectResult result) throws IOException {
Camera camera = cameraService.getById(blurDetection.getCameraId());
CameraAlarm cameraAlarm = new CameraAlarm();
cameraAlarm.setCameraId(camera.getId());
@@ -174,7 +183,7 @@ public class ScheduledTask {
cameraAlarm.setCameraName(camera.getName());
cameraAlarm.setCaptureImage(imagePath);
DetectResult backgroundResult = new DetectResult(result.desc, result.score);
- cameraAlarm.setAlgoResult(JsonUtils.toJsonString(result));
+ cameraAlarm.setAlgoResult(JsonUtils.toJsonString(backgroundResult));
cameraAlarm.setStatus(0);
cameraAlarmMapper.insert(cameraAlarm);
SocketMessage bean = new SocketMessage();
@@ -190,17 +199,22 @@ public class ScheduledTask {
msg.setToPushAll(true);
websocketRedisService.sendServiceToWs(msg);
BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
- reportDto.setAlarmReportTime(LocalDateTime.now());
- reportDto.setAlarmReportType(AlarmReportEnums.BLUR_ALARM.getAlarmDescription());
- reportDto.setDescription("摄像头拍摄的图片出现模糊");
+ // 获取时间字符串
+ String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ LocalDateTime localDateTime = LocalDateTime.parse(timeStr, timeFormatter);
+ // 转成时间戳(毫秒)
+ long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
+ reportDto.setAlarmTime(timestamp);
+ reportDto.setAlarmTypeDesc(AlarmReportEnums.BLUR_ALARM.getAlarmDescription());
byte[] CaptureFileContent = Files.readAllBytes(Paths.get(imagePath));
- reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent));
- reportDto.setUrl(blurDetection.getUrl());
+ reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setCameraName(camera.getName());
+ reportDto.setAlarmType(AlarmReportEnums.BLUR_ALARM.getAlarmCode());
reportDto.setCameraId(camera.getId());
reportDto.setCameraNo(camera.getCameraNo());
try {
- forwardService.enrichAndForward(reportDto);
+ forwardService.enrichAndForward(blurDetection.getUrl(),reportDto);
}catch (Exception e)
{
log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}",
@@ -248,21 +262,26 @@ public class ScheduledTask {
websocketRedisService.sendServiceToWs(msg);
// 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器
BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
- reportDto.setAlarmReportTime(LocalDateTime.now());
- reportDto.setAlarmReportType(AlarmReportEnums.BLUR_ALARM.getAlarmDescription());
- reportDto.setDescription("摄像头拍摄的图片出现模糊");
+ // 获取时间字符串
+ String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ LocalDateTime localDateTime = LocalDateTime.parse(timeStr, timeFormatter);
+ // 转成时间戳(毫秒)
+ long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
+ reportDto.setAlarmTime(timestamp);
+ reportDto.setAlarmTypeDesc(AlarmReportEnums.BLUR_ALARM.getAlarmDescription());
if(isExists && imagePath1 !=null) {
byte[] BaseFileContent = Files.readAllBytes(Paths.get(imagePath1));
reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent));
}
byte[] CaptureFileContent = Files.readAllBytes(Paths.get(imagePath2));
- reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent));
- reportDto.setUrl(blurDetection.getUrl());
+ reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setCameraName(camera.getName());
+ reportDto.setAlarmType(AlarmReportEnums.BLUR_ALARM.getAlarmCode());
reportDto.setCameraId(camera.getId());
reportDto.setCameraNo(camera.getCameraNo());
try {
- forwardService.enrichAndForward(reportDto);
+ forwardService.enrichAndForward(blurDetection.getUrl(),reportDto);
}catch (Exception e)
{
log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}",
@@ -304,7 +323,7 @@ public class ScheduledTask {
if (ok) {
String filename = ImageNameUtils.generateFileName("CAPTURE", cameraId);
String image2 = manageNVR.capturePic(cameraId.intValue(), filename);
- // String image2="D:\\work\\images\\CAPTURE_258_20260320170313838_4680425d.jpg";
+ //String image2="D:\\work\\images\\CAPTURE_258_20260331104152476_de86220b.jpg";
// 抓图合法性校验
if (image2 == null || image2.isBlank()) {
log.error("摄像机[{}] 抓拍失败,无法获取当前图片", cameraId);
@@ -372,19 +391,24 @@ public class ScheduledTask {
websocketRedisService.sendServiceToWs(msg);
// 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器
BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
- reportDto.setAlarmReportTime(LocalDateTime.now());
- reportDto.setAlarmReportType(AlarmReportEnums.DISPLACEMENT_ALARM.getAlarmDescription());
- reportDto.setDescription("摄像头发生移位");
+ // 获取时间字符串
+ String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ LocalDateTime localDateTime = LocalDateTime.parse(timeStr, timeFormatter);
+ // 转成时间戳(毫秒)
+ long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
+ reportDto.setAlarmTime(timestamp);
+ reportDto.setAlarmTypeDesc(AlarmReportEnums.DISPLACEMENT_ALARM.getAlarmDescription());
byte[] BaseFileContent = Files.readAllBytes(Paths.get(imagePath));
reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent));
byte[] CaptureFileContent = Files.readAllBytes(Paths.get(image2));
- reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent));
- reportDto.setUrl(preset.getPresetUrl());
+ reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setCameraName(camera.getName());
reportDto.setCameraId(camera.getId());
+ reportDto.setAlarmType(AlarmReportEnums.DISPLACEMENT_ALARM.getAlarmCode());
reportDto.setCameraNo(camera.getCameraNo());
try {
- forwardService.enrichAndForward(reportDto);
+ forwardService.enrichAndForward(preset.getPresetUrl(),reportDto);
}catch (Exception e)
{
log.error("移位报警上报失败,摄像头 ID: {}, 相机编号:{}",
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurDetectorV4.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurDetectorV4.java
index a5b9d74..03df27b 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurDetectorV4.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurDetectorV4.java
@@ -264,7 +264,7 @@ public class BlurDetectorV4 {
}
double ratio = scoreRef / scoreImg;
- System.out.println("scoreImg: " + scoreImg + ", scoreRef: " + scoreRef);
+
DetectResult finalResult;
if (ratio < ratioThreshold) {
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurrinessComparator.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurrinessComparator.java
new file mode 100644
index 0000000..a83f209
--- /dev/null
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurrinessComparator.java
@@ -0,0 +1,136 @@
+package com.sz.admin.monitor.utils;
+
+import org.im4java.core.IMOperation;
+import org.im4java.core.ImageCommand;
+
+import java.io.File;
+import java.util.UUID;
+
+public class BlurrinessComparator {
+
+ // 强制指定你的 IM7 安装路径
+ private static final String IM_PATH = "D:\\work\\imageMagic\\ImageMagick-7.1.2-Q16-HDRI";
+
+ public static void main(String[] args) {
+ String imagePath1 = "D:\\work\\images\\CAPTURE_258_20260320093827912_bcf0b101.jpg"; // 基准图 (清晰图标准)
+ String imagePath2 = "D:\\work\\images\\CAPTURE_258_20260319160020647_ffbc4f66.jpg"; // 当前图 (待检测图)
+
+ try {
+ //Result{type='blur_detection', value='1', code='BLURRY', desc='检测到图片模糊',
+ // conf=1.0, score=580.00, ratio=0.47}
+ DetectResult result = compareBlurriness(imagePath1, imagePath2);
+ // 打印最终返回的标准结果对象
+ System.out.println("检测完成,返回结果: " + result.toString());
+ } catch (Exception e) {
+ System.err.println("处理图片时发生错误: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 内部结果类封装
+ */
+ public static class DetectResult {
+ public String type; // 检测类型
+ public String value; // 结果值 (1: 模糊, 0: 正常)
+ public String code; // 结果代码
+ public String desc; // 描述信息
+ public double conf; // 置信度 (此处默认给 1.0)
+ public double score; // 得分 (此处记录目标图压缩后的大小)
+ public double ratio; // 比例 (目标图大小 / 基准图大小)
+
+ public DetectResult(String type, String value, String code, String desc, double conf, double score, double ratio) {
+ this.type = type;
+ this.value = value;
+ this.code = code;
+ this.desc = desc;
+ this.conf = conf;
+ this.score = score;
+ this.ratio = ratio;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Result{type='%s', value='%s', code='%s', desc='%s', conf=%.1f, score=%.2f, ratio=%.2f}",
+ type, value, code, desc, conf, score, ratio);
+ }
+ }
+
+ /**
+ * 对比两张图片的模糊度,并返回标准 DetectResult 结果
+ */
+ public static DetectResult compareBlurriness(String path1, String path2) {
+ System.out.println("正在处理:ImageMagick 缩放压缩 -> 计算文件体积...");
+
+ // 注意:尺寸设为 64x48 非常小,可能会导致压缩后体积差异微弱。如果效果不明显建议调大(如 800x600)。
+ long size1 = getProcessedImageSize(path1, 64, 48, 80.0);
+ long size2 = getProcessedImageSize(path2, 64, 48, 80.0);
+ System.out.println("图片1大小: " + size1 + " 字节, 图片2大小: " + size2 + " 字节");
+ // x为图片的大小 y = 90.733x - 35.474;
+ // 90.733*1.234-35.474 = 76.490522
+ // 90.733*0.58-35.474 = 17.15114
+ // 90.733*0.695-35.474 = 27.585435
+ // size1/size2
+ // size1>size2 则出现了模糊,因为特征点变小了,体积也变小了
+ // 计算体积比例 (防止除以 0 的异常)
+ double ratio = (size1 == 0) ? 1.0 : (double) size2 / size1;
+ String value;
+ String code;
+ String desc;
+ // 判断逻辑:当前图体积小于基准图,说明丢失了细节,判定为模糊
+ if (size2 < size1) {
+ value = "1"; // 模糊 Value 为 1
+ code = "BLURRY";
+ desc = "检测到图片模糊";
+ } else {
+ value = "0"; // 正常 Value 为 0
+ code = "NORMAL";
+ desc = "图片清晰度正常";
+ }
+
+ // 构造并返回标准实体类
+ // type: 设为 "blur_detection"
+ // conf: 置信度设为 1.0
+ // score: 存入 size2 (目标图片的压缩字节数) 作为参考得分
+ return new DetectResult("blur_detection", value, code, desc, 1.0, size2, ratio);
+ }
+
+ /**
+ * 核心逻辑:纯 ImageMagick 物理瘦身并获取压缩后体积
+ */
+ private static long getProcessedImageSize(String imagePath, int width, int height, double quality) {
+ // 生成一个临时文件路径,用于存放 ImageMagick 处理后的图片
+ String tempImagePath = System.getProperty("java.io.tmpdir") + File.separator + "im_temp_" + UUID.randomUUID() + ".jpg";
+ File tempFile = new File(tempImagePath);
+
+ try {
+ // 构建 ImageMagick 压缩指令
+ IMOperation op = new IMOperation();
+ op.addImage(imagePath);
+ op.strip(); // 剥离 EXIF 等多余元数据信息
+ op.resize(width, height); // 缩放为标准大小
+ op.quality(quality); // 设置 JPEG 压缩质量
+ op.addImage(tempImagePath); // 输出到临时文件
+
+ // 执行命令
+ ImageCommand cmd = new ImageCommand("magick");
+ cmd.setSearchPath(IM_PATH);
+ cmd.run(op);
+
+ // 获取临时文件的体积 (字节)
+ if (!tempFile.exists()) {
+ throw new RuntimeException("ImageMagick 未能成功生成临时文件!");
+ }
+
+ return tempFile.length();
+
+ } catch (Exception e) {
+ throw new RuntimeException("ImageMagick 处理流水线执行失败", e);
+ } finally {
+ // 务必清理临时文件
+ if (tempFile.exists()) {
+ tempFile.delete();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/RobustImageMatcherUtil.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/RobustImageMatcherUtil.java
index 88fb97d..beaffe2 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/RobustImageMatcherUtil.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/RobustImageMatcherUtil.java
@@ -28,7 +28,7 @@ import static org.bytedeco.opencv.global.opencv_video.calcOpticalFlowFarneback;
/**
* ClassName: RobustImageMatcherUtil
* Package: com.sz.admin.monitor.utils
- * Description: 图片移位和检测工具类(生产环境优化版)
+ * Description: 图片移位和检测工具类
*/
@Slf4j
public class RobustImageMatcherUtil {
@@ -321,7 +321,6 @@ public class RobustImageMatcherUtil {
private static Map formatResult(MatchResult result, Mat img1, Mat img2) {
Map map = new LinkedHashMap<>();
map.put("type", "tx_yzwpy");
-
if (!result.success || result.confidence < 0.2) {
log.warn("图像配准失败或置信度过低");
map.put("value", 1);
@@ -388,10 +387,16 @@ public class RobustImageMatcherUtil {
Mat[] preprocessed = preprocess(smallImg1, smallImg2);
Mat gray1 = preprocessed[0];
Mat gray2 = preprocessed[1];
-
+ Map map = new LinkedHashMap<>();
+ map.put("type", "tx_yzwpy");
if (!isSameScene(gray1, gray2)) {
log.warn("图像差异过大,非同一场景: path1={}, path2={}", path1, path2);
- return null;
+ map.put("value", 1);
+ map.put("code", "4000");
+ OffsetVO offsetVO = new OffsetVO();
+ offsetVO.setDescription("图像存在偏移");
+ map.put("description", JsonUtils.toJsonString(offsetVO));
+ return map;
}
MatchResult flowResult = calcOpticalFlow(gray1, gray2);
@@ -416,8 +421,8 @@ public class RobustImageMatcherUtil {
// String img1Path = "D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
// String img2Path = "D:\\work\\images\\CAPTURE_118_20260306140555121_0a31347e.jpg";
// 出现移位的图片 value 1
- String img1Path="D:\\work\\images\\CAPTURE_118_20260306140328178_7f050439.jpg";
- String img2Path="D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
+ String img1Path = "D:\\work\\temp\\history_20260324_180000.jpg";
+ String img2Path = "D:\\work\\temp\\history_20260324_190000.jpg";
Map map = RobustImageMatcherUtil.calculateOffset(img1Path, img2Path);
System.out.println(map);
}
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/UrlConvert.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/UrlConvert.java
index 8872072..a5da466 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/UrlConvert.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/UrlConvert.java
@@ -2,24 +2,41 @@ package com.sz.admin.monitor.utils;
import java.io.File;
-/**
- * ClassName: UrlConvert
- * Package: com.sz.admin.monitor.utils
- * Description:
- */
public class UrlConvert {
private static final String RESOURCE_PREFIX = "/save/";
+
+ // 定义我们配置的两个静态资源根目录 (注意统一用正斜杠 / )
+ private static final String BASE_DIR_IMAGES = "D:/work/images/";
+ private static final String BASE_DIR_TEMP = "D:/work/temp/";
+
/**
* 将本地绝对路径转换为网络相对路径
- * @param localPath D:\work\sz-admin\images\test.jpg
- * @return /save/test.jpg
+ * @param localPath 例如: D:\work\temp\task_123\test.jpg
+ * @return /save/task_123/test.jpg
*/
public static String convertToUrl(String localPath) {
if (localPath == null || localPath.isEmpty()) {
return null;
}
- String fileName = new File(localPath).getName();
- // 2. 拼接映射前缀
- return RESOURCE_PREFIX + fileName;
+
+ // 1. 统一路径分隔符:将 Windows 的反斜杠 \ 全部替换为标准的正斜杠 /
+ String normalizedPath = localPath.replace("\\", "/");
+
+ String relativePath = "";
+
+ // 2. 剥离根目录,保留后面的子文件夹和文件名
+ if (normalizedPath.startsWith(BASE_DIR_IMAGES)) {
+ // 截取掉 D:/work/images/ 这一部分
+ relativePath = normalizedPath.substring(BASE_DIR_IMAGES.length());
+ } else if (normalizedPath.startsWith(BASE_DIR_TEMP)) {
+ // 截取掉 D:/work/temp/ 这一部分,保留 task_123/test.jpg
+ relativePath = normalizedPath.substring(BASE_DIR_TEMP.length());
+ } else {
+ // 兜底方案:如果传进来的路径不在这两个目录里,退回到只取文件名
+ relativePath = new File(localPath).getName();
+ }
+
+ // 3. 拼接映射前缀
+ return RESOURCE_PREFIX + relativePath;
}
-}
+}
\ No newline at end of file
diff --git a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/ZLMediaKitUtils.java b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/ZLMediaKitUtils.java
index dae5292..5b0a91d 100644
--- a/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/ZLMediaKitUtils.java
+++ b/sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/ZLMediaKitUtils.java
@@ -98,6 +98,7 @@ public class ZLMediaKitUtils {
public JSONObject sendPost(String api, Map param, RequestCallback callback) {
OkHttpClient okHttpClient = getClient();
String url = String.format(MediaConstant.HTTP_MEDIA_URL, ip, port, api);
+ log.info("url:{}", url);
JSONObject responseJSON = new JSONObject();
responseJSON.put("code", CommonResponseEnum.MEDIA_HTTP_FAIL.getCode());
responseJSON.put("msg", CommonResponseEnum.MEDIA_HTTP_FAIL.getMessage());