From 4ffafba1c0141a56c873669202422696fa8d158b Mon Sep 17 00:00:00 2001 From: Bingkun Li Date: Sun, 8 Feb 2026 14:54:06 +0800 Subject: [PATCH] Add to adjust brightness interface and functions to enhance image display --- .../jm/controller/JmJobExecController.java | 7 + .../com/zhangy/skyeye/jm/entity/JmImage.java | 3 + .../skyeye/jm/service/JmJobExecService.java | 3 + .../jm/service/impl/JmJobExecServiceImpl.java | 40 +++- .../sar/service/impl/SarImageServiceImpl.java | 5 + .../skyeye/sar/util/RadarDisplayOptions.java | 20 ++ .../skyeye/sar/util/RadarPseudoColorLUT.java | 22 ++ .../skyeye/sar/util/SarImageToneAdjuster.java | 202 ++++++++++++++++++ frontend/Skyeye-sys-ui/public/config.js | 1 + 9 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarDisplayOptions.java create mode 100644 backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarPseudoColorLUT.java diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/JmJobExecController.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/JmJobExecController.java index 7cf2a53..175fedc 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/JmJobExecController.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/JmJobExecController.java @@ -3,6 +3,7 @@ package com.zhangy.skyeye.jm.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.zhangy.skyeye.jm.dto.JmJobDTO; import com.zhangy.skyeye.jm.dto.JmJobPageDTO; +import com.zhangy.skyeye.jm.entity.JmImage; import com.zhangy.skyeye.jm.service.JmJobExecService; import com.zhangy.skyeye.jm.service.JmJobService; import com.zhangy.skyeye.jm.service.JmJobStatusService; @@ -55,4 +56,10 @@ public class JmJobExecController { }); return page; } + + @RequestMapping("/brightness") + public Object adjustBrightness(@Valid @RequestBody JmImage param) { + jobExecService.adjustBrightness(param); + return "成功调整亮度"; + } } \ No newline at end of file diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/entity/JmImage.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/entity/JmImage.java index 853368f..1963116 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/entity/JmImage.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/entity/JmImage.java @@ -82,6 +82,9 @@ public class JmImage extends GeoTiffDTO { @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Date imageTime; + /** 图像亮度 */ + private Integer brightness; + /** 图像识别结果 */ private List itemList; diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/JmJobExecService.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/JmJobExecService.java index 69afced..5bd9398 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/JmJobExecService.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/JmJobExecService.java @@ -2,6 +2,7 @@ package com.zhangy.skyeye.jm.service; import com.zhangy.skyeye.jm.dto.JmJobDTO; import com.zhangy.skyeye.jm.dto.JmJobQueryDTO; +import com.zhangy.skyeye.jm.entity.JmImage; import com.zhangy.skyeye.jm.entity.JmJobExec; import java.util.List; @@ -54,4 +55,6 @@ public interface JmJobExecService { * 按任务配置删除任务执行、航线执行 */ void deleteByJobConf(Long... jobConfId); + + void adjustBrightness(JmImage param); } \ No newline at end of file diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmJobExecServiceImpl.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmJobExecServiceImpl.java index 1cab92d..c05dc20 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmJobExecServiceImpl.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/service/impl/JmJobExecServiceImpl.java @@ -1,20 +1,16 @@ package com.zhangy.skyeye.jm.service.impl; import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson2.JSON; import com.zhangy.skyeye.common.extend.exception.ServiceException; import com.zhangy.skyeye.common.extend.util.ObjectUtil; import com.zhangy.skyeye.jm.dto.JmJobDTO; import com.zhangy.skyeye.jm.dto.JmJobQueryDTO; -import com.zhangy.skyeye.jm.entity.JmAirlineExec; -import com.zhangy.skyeye.jm.entity.JmJobExec; -import com.zhangy.skyeye.jm.entity.JmJobPoint; -import com.zhangy.skyeye.jm.entity.JmJobUav; +import com.zhangy.skyeye.jm.dto.JmUavStatusDTO; +import com.zhangy.skyeye.jm.entity.*; import com.zhangy.skyeye.jm.event.JmJobStatusInitEvent; import com.zhangy.skyeye.jm.mapper.JmJobExecMapper; -import com.zhangy.skyeye.jm.service.JmAirlineExecService; -import com.zhangy.skyeye.jm.service.JmJobExecService; -import com.zhangy.skyeye.jm.service.JmJobPointService; -import com.zhangy.skyeye.jm.service.JmJobUavService; +import com.zhangy.skyeye.jm.service.*; import com.zhangy.skyeye.publics.consts.ExecStatusEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +38,12 @@ public class JmJobExecServiceImpl implements JmJobExecService { @Autowired private ApplicationEventPublisher eventPublisher; + @Autowired + private JmJobStatusService jobStatusService; + + @Autowired + private JmJobService jobService; + @Override public void initExecutingJobs() { List executingJobs = this.selectWorking(); @@ -176,4 +178,26 @@ public class JmJobExecServiceImpl implements JmJobExecService { jmAirlineExecService.deleteByJob(jobConfId); jobExecMapper.deleteByConf(jobConfId); } + + @Override + public void adjustBrightness(JmImage param) { + // update cache in order that the next coming image uses the new brightness + JmUavStatusDTO execSar = jobStatusService.getUav(param.getUavId()); + execSar.setSarImageLight(param.getBrightness()); + + // update db in order that the next run of this job use the new brightness + JmJobDTO job = jobService.selectInfo(param.getJobId()); + JmJobUav theUav = job.getUavList().stream() + .filter(uav -> uav.getUavId().equals(param.getUavId())) + .findFirst().orElse(null); + if (theUav != null) { + JmJobPayload theSar = theUav.getPayloadList().stream() + .filter(sar -> sar.getPayloadId().equals(param.getPayloadId())) + .findFirst().orElse(null); + if (theSar != null) { + theSar.setImageLight(param.getBrightness()); + jobService.updateNotNull(job); + } + } + } } \ No newline at end of file diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/service/impl/SarImageServiceImpl.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/service/impl/SarImageServiceImpl.java index 62f4eb2..b08d0ea 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/service/impl/SarImageServiceImpl.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/service/impl/SarImageServiceImpl.java @@ -16,6 +16,7 @@ import com.zhangy.skyeye.publics.utils.OpenCVUtil; import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO; import com.zhangy.skyeye.sar.service.ISarImageService; import com.zhangy.skyeye.sar.service.SarWsAsyncService; +import com.zhangy.skyeye.sar.util.RadarDisplayOptions; import com.zhangy.skyeye.sar.util.SarImageToneAdjuster; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -212,6 +213,9 @@ public class SarImageServiceImpl implements ISarImageService { // 拆分多张图片,去掉自适应调整 if (IMG_MAX_WITH > 20000) { SarImageToneAdjuster.autoToneWithClipping(baseMat, 0.5f); +// RadarDisplayOptions options = new RadarDisplayOptions(); +// SarImageToneAdjuster.processForDisplayMat(baseMat, options); +// SarImageToneAdjuster.applyPseudoColorMat(baseMat, options); } // 5.保存后处理图(异步),用于前端显示 @@ -348,6 +352,7 @@ public class SarImageServiceImpl implements ISarImageService { image.setJobName(uav.getJobName()); image.setUavName(uav.getUavName()); image.setPayloadName(uav.getSarName()); + image.setBrightness(uav.getSarImageLight()); return image; } } diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarDisplayOptions.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarDisplayOptions.java new file mode 100644 index 0000000..0bf7469 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarDisplayOptions.java @@ -0,0 +1,20 @@ +package com.zhangy.skyeye.sar.util; + +public class RadarDisplayOptions { + + /** 是否启用 log 压缩 */ + public boolean enableLog = false; + + /** gamma 值(enableLog=false 时生效) */ + public double gamma16 = 0.75; + public double gamma8 = 0.6; + + /** 是否启用伪彩 */ + public boolean enablePseudoColor = true; + + /** 使用的 LUT */ + public RadarPseudoColorLUT lut = RadarPseudoColorLUT.IRON; + + /** clip 百分比,直接复用你原来的逻辑 */ + // public float clipPercentage = 0.1f; +} diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarPseudoColorLUT.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarPseudoColorLUT.java new file mode 100644 index 0000000..94a862e --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/RadarPseudoColorLUT.java @@ -0,0 +1,22 @@ +package com.zhangy.skyeye.sar.util; + +public enum RadarPseudoColorLUT { + + IRON(new int[]{ + 0x000000, 0x200030, 0x400060, 0x600090, + 0x8000C0, 0xA000E0, 0xC040FF, 0xE080FF, + 0xFFB000, 0xFFD000, 0xFFFF80 + }), + + JET(new int[]{ + 0x00007F, 0x0000FF, 0x007FFF, + 0x00FF7F, 0x7FFF00, 0xFF7F00, + 0xFF0000 + }); + + public final int[] colors; + + RadarPseudoColorLUT(int[] colors) { + this.colors = colors; + } +} diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/SarImageToneAdjuster.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/SarImageToneAdjuster.java index 47d84b4..a7f19eb 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/SarImageToneAdjuster.java +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/util/SarImageToneAdjuster.java @@ -48,10 +48,12 @@ public class SarImageToneAdjuster { // 处理8位图像 if (depth == CvType.CV_8U) { + // preStretch8Bit(image, channels); process8BitImage(image, clipPercentage, channels); } // 处理16位图像 else if (depth == CvType.CV_16U) { + // preStretch16Bit(image, channels); process16BitImage(image, clipPercentage, channels); } else { @@ -441,4 +443,204 @@ public class SarImageToneAdjuster { lut.put(0, 0, lutData); return lut; } + + public static void processForDisplayMat(Mat mat, RadarDisplayOptions options) { + int depth = mat.depth(); + + // 动态范围压缩(SAR 显示核心) + if (options.enableLog) { + applyLogCompressionMat(mat, depth == CvType.CV_16U); + if (depth == CvType.CV_16U) { + mat.convertTo(mat, CvType.CV_8U, 255.0 / 65535.0); + } + } else { + if (depth == CvType.CV_16U) { + applyGamma16U(mat, options.gamma16); + mat.convertTo(mat, CvType.CV_8U, 255.0 / 65535.0); + } else { + applyGamma8U(mat, options.gamma8); + } + } + } + + private static void applyLogCompressionMat(Mat mat, boolean is16Bit) { + + int rows = mat.rows(); + int cols = mat.cols(); + + double maxValue = is16Bit ? 65535.0 : 255.0; + double logMax = Math.log(maxValue + 1.0); + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + double v = mat.get(y, x)[0]; + double lv = Math.log(v + 1.0) / logMax; + mat.put(y, x, lv * maxValue); + } + } + } + + private static void applyGamma8U(Mat image, double gamma) { + int channels = image.channels(); + int total = (int) image.total() * channels; + + byte[] data = new byte[total]; + image.get(0, 0, data); + + // 预计算 LUT(非常重要,速度快) + byte[] lut = new byte[256]; + for (int i = 0; i < 256; i++) { + double normalized = i / 255.0; + double corrected = Math.pow(normalized, gamma); + lut[i] = (byte) Math.min(255, Math.max(0, (int) (corrected * 255.0 + 0.5))); + } + + for (int i = 0; i < total; i++) { + int v = data[i] & 0xFF; + data[i] = lut[v]; + } + + image.put(0, 0, data); + } + + private static void applyGamma16U(Mat image, double gamma) { + int channels = image.channels(); + int total = (int) image.total() * channels; + + short[] data = new short[total]; + image.get(0, 0, data); + + for (int i = 0; i < total; i++) { + int v = data[i] & 0xFFFF; + double normalized = v / 65535.0; + double corrected = Math.pow(normalized, gamma); + int out = (int) (corrected * 65535.0 + 0.5); + data[i] = (short) Math.min(65535, Math.max(0, out)); + } + + image.put(0, 0, data); + } + + + public static void applyPseudoColorMat(Mat gray, RadarDisplayOptions options) { + if (!options.enablePseudoColor) { + return; + } + + Mat color = new Mat(); + Imgproc.cvtColor(gray, color, Imgproc.COLOR_GRAY2BGR); + + for (int y = 0; y < gray.rows(); y++) { + for (int x = 0; x < gray.cols(); x++) { + int v = (int) gray.get(y, x)[0]; + if (gray.depth() == CvType.CV_16U) { + v = v >> 8; // 16bit → 8bit 映射(工业常用) + } + int rgb = mapToLUT(v, options.lut.colors); + + color.put(y, x, + (rgb >> 16) & 0xFF, + (rgb >> 8) & 0xFF, + rgb & 0xFF + ); + } + } + + // 用伪彩图替换原 Mat(显示专用) + gray.release(); + color.copyTo(gray); + } + + private static int mapToLUT(int v, int[] keyColors) { + if (v <= 0) return keyColors[0]; + if (v >= 255) return keyColors[keyColors.length - 1]; + + int n = keyColors.length; + + // 映射到 [0, n-1] + float pos = v * (n - 1) / 255.0f; + + int idx = (int) Math.floor(pos); + float t = pos - idx; + + // 边界保护 + if (idx >= n - 1) { + return keyColors[n - 1]; + } + + int c0 = keyColors[idx]; + int c1 = keyColors[idx + 1]; + + int r0 = (c0 >> 16) & 0xFF; + int g0 = (c0 >> 8) & 0xFF; + int b0 = c0 & 0xFF; + + int r1 = (c1 >> 16) & 0xFF; + int g1 = (c1 >> 8) & 0xFF; + int b1 = c1 & 0xFF; + + int r = (int) (r0 + t * (r1 - r0)); + int g = (int) (g0 + t * (g1 - g0)); + int b = (int) (b0 + t * (b1 - b0)); + + return (r << 16) | (g << 8) | b; + } + + private static void preStretch8Bit(Mat image, int channels) { + int totalValues = (int) image.total() * channels; + byte[] data = new byte[totalValues]; + image.get(0, 0, data); + + // 找 min/max(忽略 alpha) + int min = 255, max = 0; + for (int i = 0; i < totalValues; i++) { + if (channels == 4 && (i % 4) == 3) continue; // 跳过 alpha + int v = data[i] & 0xFF; + if (v < min) min = v; + if (v > max) max = v; + } + + if (max <= min) return; // 全黑或全白,跳过 + + float scale = 255.0f / (max - min); + + for (int i = 0; i < totalValues; i++) { + if (channels == 4 && (i % 4) == 3) continue; // alpha不变 + int v = data[i] & 0xFF; + int val = Math.round((v - min) * scale); + data[i] = (byte) Math.min(255, Math.max(0, val)); + } + + image.put(0, 0, data); + } + + /** + * 16bit 单通道/多通道预拉伸 + */ + private static void preStretch16Bit(Mat image, int channels) { + int totalValues = (int) image.total() * channels; + short[] data = new short[totalValues]; + image.get(0, 0, data); + + int min = 65535, max = 0; + for (int i = 0; i < totalValues; i++) { + if (channels == 4 && (i % 4) == 3) continue; + int v = data[i] & 0xFFFF; + if (v < min) min = v; + if (v > max) max = v; + } + + if (max <= min) return; + + float scale = 65535.0f / (max - min); + + for (int i = 0; i < totalValues; i++) { + if (channels == 4 && (i % 4) == 3) continue; + int v = data[i] & 0xFFFF; + int val = Math.round((v - min) * scale); + data[i] = (short) Math.min(65535, Math.max(0, val)); + } + + image.put(0, 0, data); + } } \ No newline at end of file diff --git a/frontend/Skyeye-sys-ui/public/config.js b/frontend/Skyeye-sys-ui/public/config.js index 4fc3a89..9130112 100644 --- a/frontend/Skyeye-sys-ui/public/config.js +++ b/frontend/Skyeye-sys-ui/public/config.js @@ -4,6 +4,7 @@ window.config = { api: 'http://127.0.0.1:9116', socket: 'http://127.0.0.1:9116', //外网服务器, imagePath: 'http://192.168.112.181:9000/files', + // imagePath: 'http://127.0.0.1:8080/files', arithmeticPath: 'http://127.0.0.1:18090/ktkx/UavPlanning/SAR', tokenKey: 'accessToken', refreshTokenKey: 'refreshToken',