新增抓取历史图片和分析功能

dev_xq_0.0.1-2
刘政 1 day ago
parent c1b99b4d84
commit 65e5982524

@ -1,5 +1,5 @@
ZLMediaKit: ZLMediaKit:
ip: 192.168.2.128 ip: 192.168.2.129
port: 8080 port: 8080
app: live app: live
vhost: __defaultVhost__ vhost: __defaultVhost__

@ -55,7 +55,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
// D:/work/images/save/1.jpg // D:/work/images/save/1.jpg
registry.addResourceHandler("/save/**") registry.addResourceHandler("/save/**")
.addResourceLocations("file:D:/work/images/"); .addResourceLocations("file:D:/work/images/","file:D:/work/temp/");
} }
} }

@ -101,6 +101,11 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.im4java</groupId>
<artifactId>im4java</artifactId>
<version>1.4.0</version>
</dependency>
<!-- jna,ffmpeg的相关依赖--> <!-- jna,ffmpeg的相关依赖-->
<dependency> <dependency>
<groupId>cc.eguid</groupId> <groupId>cc.eguid</groupId>

@ -11,8 +11,8 @@ public class RestTemplateConfig {
@Bean @Bean
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000); factory.setConnectTimeout(10000);
factory.setReadTimeout(5000); factory.setReadTimeout(10000);
return new RestTemplate(factory); return new RestTemplate(factory);
} }
} }

@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/** /**
* ClassName: DeviceController * ClassName: DeviceController
* Package: com.sz.admin.monitor.controller * Package: com.sz.admin.monitor.controller
@ -30,10 +32,18 @@ public class DeviceController {
return ApiResult.success(deviceService.refresh(id,deviceType)); return ApiResult.success(deviceService.refresh(id,deviceType));
} }
// 设备登录 // 设备登录
@Operation(summary = "/设备登录") @Operation(summary = "设备登录")
@GetMapping("/login") @GetMapping("/login")
public ApiResult<?> deviceLogin(@RequestParam("id") Long id) public ApiResult<?> deviceLogin(@RequestParam("id") Long id)
{ {
return ApiResult.success(deviceService.deviceLogin(id)); return ApiResult.success(deviceService.deviceLogin(id));
} }
// 整个NVR的历史视频的算法分析
@Operation(summary = "整个NVR的历史视频的算法分析")
@GetMapping("/history/analysis")
public ApiResult<?> captureHistoryAndCompose(@RequestParam("ids") List<Long> ids)
{
deviceService.captureHistoryAndCompose(ids);
return ApiResult.success();
}
} }

@ -1,6 +1,7 @@
package com.sz.admin.monitor.controller; package com.sz.admin.monitor.controller;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO; 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.pojo.vo.watchful.WatchfulVO;
import com.sz.admin.monitor.service.IpChannelService; import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse; import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
@ -11,6 +12,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
/** /**
* ClassName: IpChannelController * ClassName: IpChannelController
* Package: com.sz.admin.monitor.controller * Package: com.sz.admin.monitor.controller
@ -97,4 +100,21 @@ public class IpChannelController {
return ApiResult.success(ipChannelService.configAlg(algTaskConfigDto)); return ApiResult.success(ipChannelService.configAlg(algTaskConfigDto));
} }
// 抓取历史视频的图片
@Operation(summary = "抓取历史视频的图片并进行对比")
@GetMapping("/capture/history")
public ApiResult<Void> captureHistoryAndCompose(@RequestParam("id") Long id) {
ipChannelService.captureHistoryAndCompose(id);
return ApiResult.success();
}
// 按照时间范围获取录像
@Operation(summary = "按照时间范围获取录像")
@GetMapping("/getVideoByTime")
public ApiResult<List<RecordFile>> getVideoByTime(@RequestParam("id") Long id,
@RequestParam("startTime") String startTime,
@RequestParam("endTime") String endTime) {
return ApiResult.success(ipChannelService.getVideoByTime(id, startTime, endTime));
}
} }

@ -23,17 +23,14 @@ public class BoxAlarmReportDto {
private String cameraNo; private String cameraNo;
@Schema(description = "摄像头ID") @Schema(description = "摄像头ID")
private Long cameraId; private Long cameraId;
@Schema(description = "报警的url")
private String url;
@Schema(description = "报警的类型") @Schema(description = "报警的类型")
private String alarmReportType; private String alarmTypeDesc;
@Schema(description = "报警的枚举值")
private Integer alarmType;
@Schema(description = "报警的时间") @Schema(description = "报警的时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Long alarmTime;
private LocalDateTime alarmReportTime;
@Schema(description = "基准图") @Schema(description = "基准图")
private String baseImage; private String baseImage;
@Schema(description = "抓拍图") @Schema(description = "报警图")
private String captureImage; private String alarmImage;
@Schema(description = "报警的原因")
private String description;
} }

@ -1,5 +1,7 @@
package com.sz.admin.monitor.sdk; package com.sz.admin.monitor.sdk;
import com.sz.admin.monitor.sdk.hkSdk.HCNetSDK;
/** /**
* ClassName: AbstractNVR * ClassName: AbstractNVR
* Package: com.sz.admin.monitor.hik * Package: com.sz.admin.monitor.hik
@ -39,4 +41,14 @@ public abstract class AbstractNVR {
* @return true false * @return true false
*/ */
public abstract String capturePic(int luerId,int iChannel,String fileName); 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);
} }

@ -1,6 +1,8 @@
package com.sz.admin.monitor.sdk; 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.mybatisflex.core.query.QueryWrapper;
import com.sz.admin.monitor.pojo.po.Camera; import com.sz.admin.monitor.pojo.po.Camera;
import com.sz.admin.monitor.pojo.po.Nvr; import com.sz.admin.monitor.pojo.po.Nvr;
@ -34,6 +36,10 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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 * NVR
@ -56,6 +62,7 @@ public class ManageNVR {
private VcrlUserIdContainer vcrlUserIdContainer; private VcrlUserIdContainer vcrlUserIdContainer;
@Resource @Resource
private DataScopeProperties dataScopeProperties; private DataScopeProperties dataScopeProperties;
private final ExecutorService executor = Executors.newFixedThreadPool(5);
/** /**
* sdknvr * sdknvr
@ -749,4 +756,124 @@ public class ManageNVR {
Map<String, Integer> map = hkNVR.NET_DVR_GetPTZPos(userId, camera.getChannelId()); Map<String, Integer> map = hkNVR.NET_DVR_GetPTZPos(userId, camera.getChannelId());
return BeanCopyUtils.copy(map, PTZVO.class); 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<LocalDateTime> 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<CompletableFuture<Void>> futures = new ArrayList<>();
// 将任务全部丢进线程池
for (LocalDateTime time : points) {
CompletableFuture<Void> 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<RecordFile> getRecordFileByTime(Long id, LocalDateTime startTime, LocalDateTime endTime) {
Camera camera = cameraService.getById(id);
List<RecordFile> 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;
}
} }

@ -14,6 +14,8 @@ import com.sz.admin.monitor.utils.*;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -139,6 +141,81 @@ public class HkNVR extends AbstractNVR {
public String capturePic(int luerId, int iChannel,String fileName) { public String capturePic(int luerId, int iChannel,String fileName) {
return captureP(luerId, iChannel,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;
}
}
/** /**
* *
* *

@ -3,6 +3,8 @@ package com.sz.admin.monitor.service;
import com.mybatisflex.core.service.IService; import com.mybatisflex.core.service.IService;
import com.sz.admin.monitor.pojo.po.Nvr; import com.sz.admin.monitor.pojo.po.Nvr;
import java.util.List;
/** /**
* ClassName: DeviceService * ClassName: DeviceService
* Package: com.sz.admin.monitor.service * Package: com.sz.admin.monitor.service
@ -13,4 +15,7 @@ public interface DeviceService extends IService<Nvr> {
// 设备登录 // 设备登录
Object deviceLogin(Long id); Object deviceLogin(Long id);
// 历史视频的算法分析
void captureHistoryAndCompose(List<Long> ids);
} }

@ -13,5 +13,5 @@ public interface ForwardService {
/** /**
* *
*/ */
void enrichAndForward(BoxAlarmReportDto boxAlarm); void enrichAndForward(String url,BoxAlarmReportDto boxAlarm);
} }

@ -3,9 +3,12 @@ package com.sz.admin.monitor.service;
import com.mybatisflex.core.service.IService; import com.mybatisflex.core.service.IService;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO; import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
import com.sz.admin.monitor.pojo.po.Camera; 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.pojo.vo.watchful.WatchfulVO;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse; import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
import java.util.List;
/** /**
* ClassName: IpChannelService * ClassName: IpChannelService
* Package: com.sz.admin.monitor.service * Package: com.sz.admin.monitor.service
@ -35,5 +38,9 @@ public interface IpChannelService extends IService<Camera> {
// 配置算法任务 // 配置算法任务
AlgMediaConfigResponse configAlg(AlgorithmTaskDTO algTaskConfigDto); AlgMediaConfigResponse configAlg(AlgorithmTaskDTO algTaskConfigDto);
void captureHistoryAndCompose(Long id);
List<RecordFile> getVideoByTime(Long id, String startTime, String endTime);
// void resetSentinel(int channelId, int watchTime, int presetIndex); // void resetSentinel(int channelId, int watchTime, int presetIndex);
} }

@ -39,6 +39,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
@ -142,17 +143,22 @@ public class AlgorithmTaskServiceImpl extends ServiceImpl<AlgorithmTaskMapper, A
websocketRedisService.sendServiceToWs(msg); websocketRedisService.sendServiceToWs(msg);
// 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器 // 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器
BoxAlarmReportDto reportDto = new BoxAlarmReportDto(); BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
reportDto.setAlarmReportTime(LocalDateTime.parse(alarmReportDto.getTime(), formatter)); // 1. 获取你的时间字符串
reportDto.setAlarmReportType(alarmReportEnums.getAlarmDescription()); String timeStr = alarmReportDto.getTime();
reportDto.setDescription(alarmReportDto.getResult().getDescription()); 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.getAlarmDescription());
reportDto.setAlarmType(alarmReportEnums.getAlarmCode());
reportDto.setBaseImage(imageData); reportDto.setBaseImage(imageData);
reportDto.setCaptureImage(imageDataLabeled); reportDto.setAlarmImage(imageDataLabeled);
reportDto.setUrl(algorithmTask.getUrl());
reportDto.setCameraName(camera.getName()); reportDto.setCameraName(camera.getName());
reportDto.setCameraId(cameraId); reportDto.setCameraId(cameraId);
reportDto.setCameraNo(camera.getCameraNo()); reportDto.setCameraNo(camera.getCameraNo());
try { try {
forwardService.enrichAndForward(reportDto); forwardService.enrichAndForward(algorithmTask.getUrl(),reportDto);
}catch ( Exception e) }catch ( Exception e)
{ {
log.error("报警信息上报失败", e); log.error("报警信息上报失败", e);

@ -8,6 +8,7 @@ import com.sz.admin.monitor.pojo.po.Camera;
import com.sz.admin.monitor.pojo.po.Nvr; import com.sz.admin.monitor.pojo.po.Nvr;
import com.sz.admin.monitor.sdk.ManageNVR; import com.sz.admin.monitor.sdk.ManageNVR;
import com.sz.admin.monitor.service.DeviceService; import com.sz.admin.monitor.service.DeviceService;
import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.utils.AiBoxRequestUtil; import com.sz.admin.monitor.utils.AiBoxRequestUtil;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse; import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
import com.sz.admin.monitor.utils.RtspUtil; import com.sz.admin.monitor.utils.RtspUtil;
@ -30,6 +31,10 @@ import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -49,11 +54,8 @@ public class DeviceServiceImpl extends ServiceImpl<NvrMapper, Nvr> implements De
@Resource @Resource
private NvrMapper nvrMapper; private NvrMapper nvrMapper;
@Resource @Resource
private SocketService socketService; private IpChannelService ipChannelService;
@Resource
private WebsocketRedisService websocketRedisService;
@Resource
private AiBoxRequestUtil aiBoxRequestUtil;
@Override @Override
public Object deviceLogin(Long id) { public Object deviceLogin(Long id) {
@ -68,6 +70,37 @@ public class DeviceServiceImpl extends ServiceImpl<NvrMapper, Nvr> implements De
return result; return result;
} }
@Override
public void captureHistoryAndCompose(List<Long> ids) {
if(ids==null || ids.isEmpty())
{
throw new BusinessException(AdminResponseEnum.OPERATION_FAIL,null,"数据为空");
}
ExecutorService threadPool = Executors.newFixedThreadPool(1);
CompletableFuture.runAsync(()->{
try {
List<Nvr> nvrs = nvrMapper.selectListByIds(ids);
// 对每个NVR进行分析
for (Nvr nvr : nvrs) {
// 查询出所有的通道
List<Camera> 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 NVRid * @param id NVRid

@ -19,7 +19,7 @@ public class ForwardServiceImpl implements ForwardService {
@Resource @Resource
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Override @Override
public void enrichAndForward(BoxAlarmReportDto boxAlarm) { public void enrichAndForward(String url,BoxAlarmReportDto boxAlarm) {
ResponseEntity<String> response = restTemplate.postForEntity(boxAlarm.getUrl(), boxAlarm, String.class); ResponseEntity<String> response = restTemplate.postForEntity(url, boxAlarm, String.class);
} }
} }

@ -1,5 +1,6 @@
package com.sz.admin.monitor.service.impl; package com.sz.admin.monitor.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID; import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -8,16 +9,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl; import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.sz.admin.monitor.enums.HCPlayControlEnum; import com.sz.admin.monitor.enums.HCPlayControlEnum;
import com.sz.admin.monitor.mapper.AlgorithmTaskMapper; import com.sz.admin.monitor.mapper.*;
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.pojo.dto.Ipchannel.ControlDTO; import com.sz.admin.monitor.pojo.dto.Ipchannel.ControlDTO;
import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO; import com.sz.admin.monitor.pojo.dto.edgebox.AlgorithmTaskDTO;
import com.sz.admin.monitor.pojo.po.AlgorithmTask; import com.sz.admin.monitor.pojo.po.*;
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.vo.camerasnapshot.CameraSnapshotVO; 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.ptz.PTZVO;
import com.sz.admin.monitor.pojo.vo.watchful.WatchfulVO; 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.CameraSnapshotService;
import com.sz.admin.monitor.service.IpChannelService; import com.sz.admin.monitor.service.IpChannelService;
import com.sz.admin.monitor.service.PresetService; import com.sz.admin.monitor.service.PresetService;
import com.sz.admin.monitor.utils.AiBoxRequestUtil; import com.sz.admin.monitor.utils.*;
import com.sz.admin.monitor.utils.AlgMediaConfigResponse;
import com.sz.admin.monitor.utils.RtspUtil;
import com.sz.admin.monitor.utils.ZLMediaKitUtils;
import com.sz.core.common.entity.TransferMessage; import com.sz.core.common.entity.TransferMessage;
import com.sz.core.common.exception.common.BusinessException; import com.sz.core.common.exception.common.BusinessException;
import com.sz.core.util.BeanCopyUtils; import com.sz.core.util.BeanCopyUtils;
@ -42,7 +34,9 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -67,7 +61,7 @@ public class IpChannelServiceImpl extends ServiceImpl<CameraMapper, Camera> impl
private AlgorithmTaskMapper algorithmTaskMapper; private AlgorithmTaskMapper algorithmTaskMapper;
@Resource @Resource
private PresetService presetService; private CameraAlarmMapper cameraAlarmMapper;
@Resource @Resource
private AiBoxRequestUtil aiBoxRequestUtil; private AiBoxRequestUtil aiBoxRequestUtil;
@ -378,6 +372,98 @@ public class IpChannelServiceImpl extends ServiceImpl<CameraMapper, Camera> impl
} }
return response; 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<CameraAlarm> 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<String, Object> 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<RecordFile> getVideoByTime(Long id, String startTime, String endTime) {
List<RecordFile> 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信息进行业务处理 // 在这里接收透传过来的 websocket信息进行业务处理
@Override @Override
public void handlerMsg(TransferMessage transferMessage) { public void handlerMsg(TransferMessage transferMessage) {

@ -33,6 +33,8 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -133,17 +135,24 @@ public class ScheduledTask {
} }
count++; count++;
// 进行单一背景检测 // 进行单一背景检测
BackgroundDetector.DetectResult result = BackgroundDetector.detectBackground(imagePath2); // BackgroundDetector.DetectResult result = BackgroundDetector.detectBackground(imagePath2);
if("1".equals(result.value)) // if("1".equals(result.value))
{ // {
// 当前摄像头面对的是白墙,直接进行报警 // // 当前摄像头面对的是白墙,直接进行报警
handleAnalysisResult(blurDetection, imagePath2, result); // handleAnalysisResult(blurDetection, imagePath2, result);
continue; // continue;
} // }
// 进行模糊图片对比 // 进行模糊图片对比
BlurDetectorV4.DetectResult detectResult = null; BlurDetectorV4.DetectResult detectResult = null;
if (isExists) { if (isExists) {
// 基准图存在,使用双图对比功能 // 基准图存在,使用双图对比功能
// 检查是否对着墙壁或者特征不明显的地方
BlurrinessComparator.DetectResult result = BlurrinessComparator.compareBlurriness(imagePath1, imagePath2);
if (result.value.equals("1")) {
// 当前摄像头面对的是白墙,直接进行报警
handleAnalysisResult(blurDetection, imagePath2, result);
continue;
}
detectResult = BlurDetectorV4.doubleDetectBlur(imagePath1, imagePath2); detectResult = BlurDetectorV4.doubleDetectBlur(imagePath1, imagePath2);
} else { } else {
// 基准图不存在,使用单图对比功能 // 基准图不存在,使用单图对比功能
@ -162,7 +171,7 @@ public class ScheduledTask {
public record DetectResult(String desc,double score) { 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()); Camera camera = cameraService.getById(blurDetection.getCameraId());
CameraAlarm cameraAlarm = new CameraAlarm(); CameraAlarm cameraAlarm = new CameraAlarm();
cameraAlarm.setCameraId(camera.getId()); cameraAlarm.setCameraId(camera.getId());
@ -174,7 +183,7 @@ public class ScheduledTask {
cameraAlarm.setCameraName(camera.getName()); cameraAlarm.setCameraName(camera.getName());
cameraAlarm.setCaptureImage(imagePath); cameraAlarm.setCaptureImage(imagePath);
DetectResult backgroundResult = new DetectResult(result.desc, result.score); DetectResult backgroundResult = new DetectResult(result.desc, result.score);
cameraAlarm.setAlgoResult(JsonUtils.toJsonString(result)); cameraAlarm.setAlgoResult(JsonUtils.toJsonString(backgroundResult));
cameraAlarm.setStatus(0); cameraAlarm.setStatus(0);
cameraAlarmMapper.insert(cameraAlarm); cameraAlarmMapper.insert(cameraAlarm);
SocketMessage bean = new SocketMessage(); SocketMessage bean = new SocketMessage();
@ -190,17 +199,22 @@ public class ScheduledTask {
msg.setToPushAll(true); msg.setToPushAll(true);
websocketRedisService.sendServiceToWs(msg); websocketRedisService.sendServiceToWs(msg);
BoxAlarmReportDto reportDto = new BoxAlarmReportDto(); BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
reportDto.setAlarmReportTime(LocalDateTime.now()); // 获取时间字符串
reportDto.setAlarmReportType(AlarmReportEnums.BLUR_ALARM.getAlarmDescription()); String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
reportDto.setDescription("摄像头拍摄的图片出现模糊"); 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)); byte[] CaptureFileContent = Files.readAllBytes(Paths.get(imagePath));
reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent)); reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setUrl(blurDetection.getUrl());
reportDto.setCameraName(camera.getName()); reportDto.setCameraName(camera.getName());
reportDto.setAlarmType(AlarmReportEnums.BLUR_ALARM.getAlarmCode());
reportDto.setCameraId(camera.getId()); reportDto.setCameraId(camera.getId());
reportDto.setCameraNo(camera.getCameraNo()); reportDto.setCameraNo(camera.getCameraNo());
try { try {
forwardService.enrichAndForward(reportDto); forwardService.enrichAndForward(blurDetection.getUrl(),reportDto);
}catch (Exception e) }catch (Exception e)
{ {
log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}", log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}",
@ -248,21 +262,26 @@ public class ScheduledTask {
websocketRedisService.sendServiceToWs(msg); websocketRedisService.sendServiceToWs(msg);
// 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器 // 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器
BoxAlarmReportDto reportDto = new BoxAlarmReportDto(); BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
reportDto.setAlarmReportTime(LocalDateTime.now()); // 获取时间字符串
reportDto.setAlarmReportType(AlarmReportEnums.BLUR_ALARM.getAlarmDescription()); String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
reportDto.setDescription("摄像头拍摄的图片出现模糊"); 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) { if(isExists && imagePath1 !=null) {
byte[] BaseFileContent = Files.readAllBytes(Paths.get(imagePath1)); byte[] BaseFileContent = Files.readAllBytes(Paths.get(imagePath1));
reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent)); reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent));
} }
byte[] CaptureFileContent = Files.readAllBytes(Paths.get(imagePath2)); byte[] CaptureFileContent = Files.readAllBytes(Paths.get(imagePath2));
reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent)); reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setUrl(blurDetection.getUrl());
reportDto.setCameraName(camera.getName()); reportDto.setCameraName(camera.getName());
reportDto.setAlarmType(AlarmReportEnums.BLUR_ALARM.getAlarmCode());
reportDto.setCameraId(camera.getId()); reportDto.setCameraId(camera.getId());
reportDto.setCameraNo(camera.getCameraNo()); reportDto.setCameraNo(camera.getCameraNo());
try { try {
forwardService.enrichAndForward(reportDto); forwardService.enrichAndForward(blurDetection.getUrl(),reportDto);
}catch (Exception e) }catch (Exception e)
{ {
log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}", log.error("模糊报警上报失败,摄像头 ID: {}, 相机编号:{}",
@ -304,7 +323,7 @@ public class ScheduledTask {
if (ok) { if (ok) {
String filename = ImageNameUtils.generateFileName("CAPTURE", cameraId); String filename = ImageNameUtils.generateFileName("CAPTURE", cameraId);
String image2 = manageNVR.capturePic(cameraId.intValue(), filename); 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()) { if (image2 == null || image2.isBlank()) {
log.error("摄像机[{}] 抓拍失败,无法获取当前图片", cameraId); log.error("摄像机[{}] 抓拍失败,无法获取当前图片", cameraId);
@ -372,19 +391,24 @@ public class ScheduledTask {
websocketRedisService.sendServiceToWs(msg); websocketRedisService.sendServiceToWs(msg);
// 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器 // 统一处理报警信息后,将摄像头信息+报警信息上报给别人服务器
BoxAlarmReportDto reportDto = new BoxAlarmReportDto(); BoxAlarmReportDto reportDto = new BoxAlarmReportDto();
reportDto.setAlarmReportTime(LocalDateTime.now()); // 获取时间字符串
reportDto.setAlarmReportType(AlarmReportEnums.DISPLACEMENT_ALARM.getAlarmDescription()); String timeStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
reportDto.setDescription("摄像头发生移位"); 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)); byte[] BaseFileContent = Files.readAllBytes(Paths.get(imagePath));
reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent)); reportDto.setBaseImage(Base64.getEncoder().encodeToString(BaseFileContent));
byte[] CaptureFileContent = Files.readAllBytes(Paths.get(image2)); byte[] CaptureFileContent = Files.readAllBytes(Paths.get(image2));
reportDto.setCaptureImage(Base64.getEncoder().encodeToString(CaptureFileContent)); reportDto.setAlarmImage(Base64.getEncoder().encodeToString(CaptureFileContent));
reportDto.setUrl(preset.getPresetUrl());
reportDto.setCameraName(camera.getName()); reportDto.setCameraName(camera.getName());
reportDto.setCameraId(camera.getId()); reportDto.setCameraId(camera.getId());
reportDto.setAlarmType(AlarmReportEnums.DISPLACEMENT_ALARM.getAlarmCode());
reportDto.setCameraNo(camera.getCameraNo()); reportDto.setCameraNo(camera.getCameraNo());
try { try {
forwardService.enrichAndForward(reportDto); forwardService.enrichAndForward(preset.getPresetUrl(),reportDto);
}catch (Exception e) }catch (Exception e)
{ {
log.error("移位报警上报失败,摄像头 ID: {}, 相机编号:{}", log.error("移位报警上报失败,摄像头 ID: {}, 相机编号:{}",

@ -264,7 +264,7 @@ public class BlurDetectorV4 {
} }
double ratio = scoreRef / scoreImg; double ratio = scoreRef / scoreImg;
System.out.println("scoreImg: " + scoreImg + ", scoreRef: " + scoreRef);
DetectResult finalResult; DetectResult finalResult;
if (ratio < ratioThreshold) { if (ratio < ratioThreshold) {

@ -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();
}
}
}
}

@ -28,7 +28,7 @@ import static org.bytedeco.opencv.global.opencv_video.calcOpticalFlowFarneback;
/** /**
* ClassName: RobustImageMatcherUtil * ClassName: RobustImageMatcherUtil
* Package: com.sz.admin.monitor.utils * Package: com.sz.admin.monitor.utils
* Description: * Description:
*/ */
@Slf4j @Slf4j
public class RobustImageMatcherUtil { public class RobustImageMatcherUtil {
@ -321,7 +321,6 @@ public class RobustImageMatcherUtil {
private static Map<String, Object> formatResult(MatchResult result, Mat img1, Mat img2) { private static Map<String, Object> formatResult(MatchResult result, Mat img1, Mat img2) {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "tx_yzwpy"); map.put("type", "tx_yzwpy");
if (!result.success || result.confidence < 0.2) { if (!result.success || result.confidence < 0.2) {
log.warn("图像配准失败或置信度过低"); log.warn("图像配准失败或置信度过低");
map.put("value", 1); map.put("value", 1);
@ -388,10 +387,16 @@ public class RobustImageMatcherUtil {
Mat[] preprocessed = preprocess(smallImg1, smallImg2); Mat[] preprocessed = preprocess(smallImg1, smallImg2);
Mat gray1 = preprocessed[0]; Mat gray1 = preprocessed[0];
Mat gray2 = preprocessed[1]; Mat gray2 = preprocessed[1];
Map<String, Object> map = new LinkedHashMap<>();
map.put("type", "tx_yzwpy");
if (!isSameScene(gray1, gray2)) { if (!isSameScene(gray1, gray2)) {
log.warn("图像差异过大,非同一场景: path1={}, path2={}", path1, path2); 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); MatchResult flowResult = calcOpticalFlow(gray1, gray2);
@ -416,8 +421,8 @@ public class RobustImageMatcherUtil {
// String img1Path = "D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg"; // String img1Path = "D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg";
// String img2Path = "D:\\work\\images\\CAPTURE_118_20260306140555121_0a31347e.jpg"; // String img2Path = "D:\\work\\images\\CAPTURE_118_20260306140555121_0a31347e.jpg";
// 出现移位的图片 value 1 // 出现移位的图片 value 1
String img1Path="D:\\work\\images\\CAPTURE_118_20260306140328178_7f050439.jpg"; String img1Path = "D:\\work\\temp\\history_20260324_180000.jpg";
String img2Path="D:\\work\\images\\CAPTURE_118_20260306140433490_7c323ce1.jpg"; String img2Path = "D:\\work\\temp\\history_20260324_190000.jpg";
Map<String, Object> map = RobustImageMatcherUtil.calculateOffset(img1Path, img2Path); Map<String, Object> map = RobustImageMatcherUtil.calculateOffset(img1Path, img2Path);
System.out.println(map); System.out.println(map);
} }

@ -2,24 +2,41 @@ package com.sz.admin.monitor.utils;
import java.io.File; import java.io.File;
/**
* ClassName: UrlConvert
* Package: com.sz.admin.monitor.utils
* Description:
*/
public class UrlConvert { public class UrlConvert {
private static final String RESOURCE_PREFIX = "/save/"; 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 * @param localPath : D:\work\temp\task_123\test.jpg
* @return /save/test.jpg * @return /save/task_123/test.jpg
*/ */
public static String convertToUrl(String localPath) { public static String convertToUrl(String localPath) {
if (localPath == null || localPath.isEmpty()) { if (localPath == null || localPath.isEmpty()) {
return null; return null;
} }
String fileName = new File(localPath).getName();
// 2. 拼接映射前缀 // 1. 统一路径分隔符:将 Windows 的反斜杠 \ 全部替换为标准的正斜杠 /
return RESOURCE_PREFIX + fileName; 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;
} }
} }

@ -98,6 +98,7 @@ public class ZLMediaKitUtils {
public JSONObject sendPost(String api, Map<String, Object> param, RequestCallback callback) { public JSONObject sendPost(String api, Map<String, Object> param, RequestCallback callback) {
OkHttpClient okHttpClient = getClient(); OkHttpClient okHttpClient = getClient();
String url = String.format(MediaConstant.HTTP_MEDIA_URL, ip, port, api); String url = String.format(MediaConstant.HTTP_MEDIA_URL, ip, port, api);
log.info("url:{}", url);
JSONObject responseJSON = new JSONObject(); JSONObject responseJSON = new JSONObject();
responseJSON.put("code", CommonResponseEnum.MEDIA_HTTP_FAIL.getCode()); responseJSON.put("code", CommonResponseEnum.MEDIA_HTTP_FAIL.getCode());
responseJSON.put("msg", CommonResponseEnum.MEDIA_HTTP_FAIL.getMessage()); responseJSON.put("msg", CommonResponseEnum.MEDIA_HTTP_FAIL.getMessage());

Loading…
Cancel
Save