From 65e5982524b23d7daf9cf93591560b0267654f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=94=BF?= <3443290081@qq.com> Date: Tue, 31 Mar 2026 14:25:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8A=93=E5=8F=96=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=9B=BE=E7=89=87=E5=92=8C=E5=88=86=E6=9E=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/local/zlmediakit.yml | 2 +- .../com/sz/security/config/SaTokenConfig.java | 2 +- sz-service/sz-service-admin/pom.xml | 5 + .../monitor/config/RestTemplateConfig.java | 4 +- .../monitor/controller/DeviceController.java | 12 +- .../controller/IpChannelController.java | 20 +++ .../pojo/dto/edgebox/BoxAlarmReportDto.java | 15 +- .../com/sz/admin/monitor/sdk/AbstractNVR.java | 12 ++ .../com/sz/admin/monitor/sdk/ManageNVR.java | 127 ++++++++++++++++ .../com/sz/admin/monitor/sdk/hkSdk/HkNVR.java | 77 ++++++++++ .../admin/monitor/service/DeviceService.java | 5 + .../admin/monitor/service/ForwardService.java | 2 +- .../monitor/service/IpChannelService.java | 7 + .../impl/AlgorithmTaskServiceImpl.java | 18 ++- .../service/impl/DeviceServiceImpl.java | 43 +++++- .../service/impl/ForwardServiceImpl.java | 4 +- .../service/impl/IpChannelServiceImpl.java | 112 +++++++++++++-- .../sz/admin/monitor/task/ScheduledTask.java | 80 +++++++---- .../admin/monitor/utils/BlurDetectorV4.java | 2 +- .../monitor/utils/BlurrinessComparator.java | 136 ++++++++++++++++++ .../monitor/utils/RobustImageMatcherUtil.java | 17 ++- .../sz/admin/monitor/utils/UrlConvert.java | 39 +++-- .../admin/monitor/utils/ZLMediaKitUtils.java | 1 + 23 files changed, 655 insertions(+), 87 deletions(-) create mode 100644 sz-service/sz-service-admin/src/main/java/com/sz/admin/monitor/utils/BlurrinessComparator.java 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());