diff --git a/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/config/I18nConfig.java b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/config/I18nConfig.java new file mode 100644 index 0000000..b6a7127 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/config/I18nConfig.java @@ -0,0 +1,21 @@ +package com.zhangy.skyeye.common.extend.config; + +import com.zhangy.skyeye.common.extend.core.I18nLocaleResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; + +/** + * 国际化配置 + * + * @author zhangy + */ +@Configuration +public class I18nConfig { + + @Bean + public LocaleResolver localeResolver() { + return new I18nLocaleResolver(); + } + +} diff --git a/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/core/I18nLocaleResolver.java b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/core/I18nLocaleResolver.java new file mode 100644 index 0000000..d7ebed3 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/core/I18nLocaleResolver.java @@ -0,0 +1,35 @@ +package com.zhangy.skyeye.common.extend.core; + +import org.springframework.web.servlet.LocaleResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Locale; + +/** + * 获取请求头国际化信息 + * + * @author zhangy + */ +public class I18nLocaleResolver implements LocaleResolver { + + @Override + public Locale resolveLocale(HttpServletRequest httpServletRequest) { + String language = httpServletRequest.getHeader("content-language"); + Locale locale = Locale.ENGLISH; + if (language != null && language.length() > 0) { + String[] split = language.split("_"); + if (split.length > 1) { + locale = new Locale(split[0], split[1]); + } else { + locale = new Locale(split[0]); + } + } + return locale; + } + + @Override + public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { + + } +} diff --git a/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/MessageUtils.java b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/MessageUtils.java new file mode 100644 index 0000000..2362556 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/MessageUtils.java @@ -0,0 +1,31 @@ +package com.zhangy.skyeye.common.extend.util; + +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContextHolder; + +import java.util.Locale; + +/** + * 获取i18n资源文件 + * + * @author zhangy + */ +public class MessageUtils { + + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + try { + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } catch (NoSuchMessageException e) { + return code; + } + } +} diff --git a/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/SpringUtils.java b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/SpringUtils.java new file mode 100644 index 0000000..14a2d44 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-common/skyeye-common-extend/src/main/java/com/zhangy/skyeye/common/extend/util/SpringUtils.java @@ -0,0 +1,66 @@ +package com.zhangy.skyeye.common.extend.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author zhangy + */ +@Component +public class SpringUtils implements ApplicationContextAware { + + /** + * 当前IOC上下文对象 + */ + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取applicationContext + */ + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * 通过name获取 Bean. + * + * @param name bean名称 + * @return bean对象 + */ + public static Object getBean(String name) { + return getApplicationContext().getBean(name); + } + + /** + * 通过class获取Bean. + * + * @param clazz bean类型 + * @param 泛型 + * @return bean对象 + */ + public static T getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + /** + * 通过name,以及Clazz返回指定的Bean + * + * @param name bean名称 + * @param clazz bean类型 + * @param 泛型 + * @return bean对象 + */ + public static T getBean(String name, Class clazz) { + return getApplicationContext().getBean(name, clazz); + } + +} diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/TestI18nController.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/TestI18nController.java new file mode 100644 index 0000000..a6e38b2 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/jm/controller/TestI18nController.java @@ -0,0 +1,23 @@ +package com.zhangy.skyeye.jm.controller; + +import com.zhangy.skyeye.common.extend.anno.IgnoreAuth; +import com.zhangy.skyeye.common.extend.util.MessageUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * i18n 测试 + * + * @author zhangy + */ +@RestController +@RequestMapping("/test/i18n") +public class TestI18nController { + + @IgnoreAuth + @GetMapping + public String test(String code) { + return MessageUtils.message(code); + } +} diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/image/enhancer/ImageEnhancer.java b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/image/enhancer/ImageEnhancer.java new file mode 100644 index 0000000..4871b2b --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/java/com/zhangy/skyeye/sar/image/enhancer/ImageEnhancer.java @@ -0,0 +1,311 @@ +package com.zhangy.skyeye.sar.image.enhancer; + +import org.opencv.core.*; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.opencv.imgproc.CLAHE; +import org.opencv.photo.Photo; +import org.opencv.photo.TonemapReinhard; + +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * ImageEnhancer.java + * ================== + * 两步组合图像亮度增强脚本(自适应参数 + 多格式兼容 Java 版) + * + * 支持的输入格式: + * - JPG : 8 bit BGR,直接兼容 + * - PNG : 8/16 bit,RGB/RGBA,自动处理透明通道与位深 + * - TIF : 8/16/32 bit,灰度/彩色/含透明通道,自动处理所有组合 + * + * 增强步骤: + * 1. Reinhard 局部色调映射 —— 压缩动态范围,统一全局亮度 + * 2. CLAHE 局部对比度增强 —— 在 LAB 空间对 L 通道进行局部直方图均衡化 + * + * 所有关键参数均根据输入图像的统计特征自动计算,无需手动调参。 + * + * 依赖: + * - OpenCV for Java (e.g., opencv-4.x.x.jar) + * + * 编译与运行: + * 1. 确保 opencv-4.x.x.jar 在 classpath 中。 + * 2. 确保 OpenCV native library (e.g., opencv_java4xx.dll/so) 在 java.library.path 中。 + * javac -cp .:/path/to/opencv-4.x.x.jar ImageEnhancer.java + * java -cp .:/path/to/opencv-4.x.x.jar -Djava.library.path=/path/to/native/libs ImageEnhancer [output_dir] + */ +public class ImageEnhancer { + + static { + try { + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + } catch (UnsatisfiedLinkError e) { + System.err.println("Native code library failed to load.\n" + e); + System.exit(1); + } + } + + /** + * 存放自适应计算出的参数 + */ + public static class AdaptiveParams { + double gamma, intensity, lightAdapt, colorAdapt = 0.0, clipLimit; + Size tileGrid; + + @Override + public String toString() { + return String.format( + " gamma = %.3f%n" + + " intensity = %.3f%n" + + " light_adapt = %.3f%n" + + " clip_limit = %.3f%n" + + " tile_grid = %s", + gamma, intensity, lightAdapt, clipLimit, tileGrid + ); + } + } + + /** + * 存放归一化结果和日志 + */ + public static class NormalizedImage { + Mat image; + String log; + } + + /** + * 对 uint16 图像做百分位截断线性拉伸后转为 uint8。 + */ + private static Mat uint16ToUint8Stretch(Mat imgU16, double lowPct, double highPct) { + // 将所有通道像素合并后统一计算 lo/hi,避免各通道独立拉伸导致色偏 + int totalPixels = (int) (imgU16.total() * imgU16.channels()); + short[] pixels = new short[totalPixels]; + imgU16.get(0, 0, pixels); + + // 转换为 int 以避免排序时的符号问题 + int[] pixelsInt = new int[totalPixels]; + for (int i = 0; i < totalPixels; i++) { + pixelsInt[i] = pixels[i] & 0xFFFF; + } + Arrays.sort(pixelsInt); + + // 使用全局统一的 lo/hi(所有通道共用),保持色彩比例 + double lo = pixelsInt[(int) (totalPixels * lowPct / 100.0)]; + double hi = pixelsInt[(int) (totalPixels * highPct / 100.0)]; + + // 使用 convertTo(alpha, beta) 做线性变换:dst = src * alpha + beta + // 等价于 (src - lo) * 255/(hi-lo) = src * (255/(hi-lo)) + (-lo * 255/(hi-lo)) + double alpha = 255.0 / (hi - lo + 1e-6); + double beta = -lo * alpha; + Mat result = new Mat(); + // convertTo 对所有通道均匀应用相同的 alpha/beta,不存在 Scalar 多通道问题 + imgU16.convertTo(result, CvType.CV_8U, alpha, beta); + return result; + } + + /** + * 将任意常见格式的图像统一转换为 BGR uint8。 + */ + public static NormalizedImage normalizeToBgrUint8(Mat img) { + NormalizedImage result = new NormalizedImage(); + StringBuilder log = new StringBuilder(); + log.append(String.format("原始格式:dtype=%s, shape=(%d, %d, %d)%n", + CvType.typeToString(img.type()), img.rows(), img.cols(), img.channels())); + + Mat currentImg = img.clone(); + List steps = new ArrayList<>(); + + // 1. 位深归一化 + if (img.depth() == CvType.CV_16U) { + currentImg = uint16ToUint8Stretch(img, 2.0, 98.0); + steps.add("uint16 → uint8 (百分位拉伸 2%~98%)"); + } else if (img.depth() == CvType.CV_32F || img.depth() == CvType.CV_64F) { + img.convertTo(currentImg, CvType.CV_8U, 255.0); + steps.add(CvType.typeToString(img.type()) + " → uint8 (×255 缩放)"); + } + + // 2. 通道数归一化 + if (currentImg.channels() == 1) { + Imgproc.cvtColor(currentImg, currentImg, Imgproc.COLOR_GRAY2BGR); + steps.add("灰度(1ch) → BGR(3ch)"); + } else if (currentImg.channels() == 4) { + Imgproc.cvtColor(currentImg, currentImg, Imgproc.COLOR_BGRA2BGR); + steps.add("BGRA(4ch) → BGR(3ch, 丢弃 Alpha)"); + } + + log.append(" 转换步骤:").append(steps.isEmpty() ? "无需转换 (已是 BGR uint8)" : String.join(" | ", steps)).append("\n"); + log.append(String.format(" 归一化后:dtype=%s, shape=(%d, %d, %d)", + CvType.typeToString(currentImg.type()), currentImg.rows(), currentImg.cols(), currentImg.channels())); + + result.image = currentImg; + result.log = log.toString(); + return result; + } + + /** + * 根据输入图像的统计特征自适应计算增强参数。 + */ + public static AdaptiveParams computeAdaptiveParams(Mat imgBgr) { + Mat gray = new Mat(); + Imgproc.cvtColor(imgBgr, gray, Imgproc.COLOR_BGR2GRAY); + Mat grayF = new Mat(); + gray.convertTo(grayF, CvType.CV_32F); + + int H = gray.rows(), W = gray.cols(), totalPixels = H * W; + AdaptiveParams params = new AdaptiveParams(); + + // ── Reinhard gamma 语义说明 ──────────────────────────────────────────── + // OpenCV Reinhard 中 gamma 越大 → 输出越亮(与传统 gamma 校正语义相反) + // 由实验扫描拟合(la=0.8, intensity=0):mean_out ≈ 79.2 × gamma + // target_mean 根据图像均值自适应:图越暗 → 目标越高(最大提亮到 128) + // 1. 图像均值 → gamma + double meanVal = Core.mean(grayF).val[0]; + double meanNorm = meanVal / 255.0; + double targetMean = Math.min(128.0, 128.0 * Math.pow(1.0 - meanNorm, 0.3)); + params.gamma = Math.max(0.8, Math.min(3.0, targetMean / 79.2)); + + // 2. intensity 固定为 0:亮度控制完全由 gamma 承担 + // 避免 intensity 与 gamma 叠加导致方向混乱 + params.intensity = 0.0; + + // 3. 全局标准差 → light_adapt + // std 越小(对比度越低)→ 越需要局部自适应 → light_adapt 越大 + // 上限 0.9(1.0 会产生 NaN),下限 0.5 + MatOfDouble mean = new MatOfDouble(), stddev = new MatOfDouble(); + Core.meanStdDev(grayF, mean, stddev); + double stdGlobal = stddev.get(0, 0)[0] / 255.0; + params.lightAdapt = Math.max(0.5, Math.min(0.9, 0.9 - stdGlobal)); + + // 4. 局部标准差均值 → clipLimit + int tile = 8; + List localStds = new ArrayList<>(); + for (int y = 0; y < H - tile; y += tile) { + for (int x = 0; x < W - tile; x += tile) { + Mat patch = new Mat(grayF, new Rect(x, y, tile, tile)); + Core.meanStdDev(patch, mean, stddev); + localStds.add(stddev.get(0, 0)[0]); + } + } + double meanLocalStd = localStds.stream().mapToDouble(d -> d).average().orElse(20.0); + double clipLimit = 2.0 * (30.0 / (meanLocalStd + 1e-6)); + params.clipLimit = Math.max(1.0, Math.min(6.0, clipLimit)); + + // 5. 图像短边分辨率 → tileGridSize + int tileSize = Math.max(8, Math.min(H, W) / 16); + params.tileGrid = new Size(tileSize, tileSize); + + gray.release(); grayF.release(); mean.release(); stddev.release(); + return params; + } + + /** + * 第一步:Reinhard 局部色调映射。 + */ + public static Mat step1Reinhard(Mat imgBgr, AdaptiveParams params) { + Mat imgF32 = new Mat(); + imgBgr.convertTo(imgF32, CvType.CV_32FC3, 1.0 / 255.0); + + TonemapReinhard tonemap = Photo.createTonemapReinhard( + (float) params.gamma, (float) params.intensity, (float) params.lightAdapt, (float) params.colorAdapt); + Mat mapped = new Mat(); + tonemap.process(imgF32, mapped); + + // 将 NaN/Inf 替换为 0,再裁剪到合法范围 + Core.patchNaNs(mapped, 0.0); + + Mat result = new Mat(); + mapped.convertTo(result, CvType.CV_8UC3, 255.0); + + imgF32.release(); mapped.release(); + return result; + } + + /** + * 第二步:CLAHE 局部对比度增强。 + */ + public static Mat step2Clahe(Mat imgBgr, AdaptiveParams params) { + Mat lab = new Mat(); + Imgproc.cvtColor(imgBgr, lab, Imgproc.COLOR_BGR2Lab); + + List labPlanes = new ArrayList<>(3); + Core.split(lab, labPlanes); + + CLAHE clahe = Imgproc.createCLAHE(params.clipLimit, params.tileGrid); + Mat lEnhanced = new Mat(); + clahe.apply(labPlanes.get(0), lEnhanced); + + labPlanes.set(0, lEnhanced); + Mat resultLab = new Mat(); + Core.merge(labPlanes, resultLab); + + Mat result = new Mat(); + Imgproc.cvtColor(resultLab, result, Imgproc.COLOR_Lab2BGR); + + lab.release(); lEnhanced.release(); resultLab.release(); + for(Mat p : labPlanes) p.release(); + return result; + } + + /** + * 主流程:对输入图像执行两步增强并保存结果。 + */ + public static void enhance(String inputPath, String outputDir) { + Mat imgRaw = Imgcodecs.imread(inputPath, Imgcodecs.IMREAD_UNCHANGED); + if (imgRaw.empty()) { + System.err.println("无法读取图像: " + inputPath); + return; + } + + String baseName = inputPath.substring( + inputPath.lastIndexOf("/") >= 0 ? inputPath.lastIndexOf("/") + 1 : 0, + inputPath.lastIndexOf(".") + ); + + // 1. 格式归一化 + System.out.println("── 格式归一化 ──────────────────────────────"); + NormalizedImage normResult = normalizeToBgrUint8(imgRaw); + Mat imgBgr = normResult.image; + System.out.println(normResult.log); + + // 2. 计算自适应参数 + System.out.println("── 自适应参数 ──────────────────────────────"); + AdaptiveParams params = computeAdaptiveParams(imgBgr); + System.out.println(params); + System.out.println("────────────────────────────────────────────"); + + // 3. 第一步:Reinhard 色调映射 + Mat resultStep1 = step1Reinhard(imgBgr, params); + String pathStep1 = outputDir + "/" + baseName + "_step1_tonemap.png"; + Imgcodecs.imwrite(pathStep1, resultStep1); + System.out.println("第一步结果已保存: " + pathStep1); + + // 4. 第二步:CLAHE 局部增强 + Mat resultStep2 = step2Clahe(resultStep1, params); + String pathStep2 = outputDir + "/" + baseName + "_step2_clahe.png"; + Imgcodecs.imwrite(pathStep2, resultStep2); + System.out.println("第二步结果已保存: " + pathStep2); + + imgRaw.release(); imgBgr.release(); resultStep1.release(); resultStep2.release(); + } + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("用法: java ImageEnhancer [output_dir]"); + String inputPath = "/home/ubuntu/upload/pasted_file_a5uhAu_8986130c06b1e8661387e859b5d2ac93.png"; + String outputDir = "/home/ubuntu"; + System.out.println("\n使用默认路径进行处理:"); + enhance(inputPath, outputDir); + return; + } + + String inputPath = args[0]; + String outputDir = (args.length > 1) ? args[1] : new java.io.File(inputPath).getParent(); + if (outputDir == null) outputDir = "."; + new java.io.File(outputDir).mkdirs(); + + enhance(inputPath, outputDir); + } +} diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/application-dev.yml b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/application-dev.yml index 583287c..abc1274 100644 --- a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/application-dev.yml +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/application-dev.yml @@ -14,6 +14,9 @@ spring: allow-circular-references: true application: name: @artifactId@ + messages: + basename: i18n/messages + fallback-to-system-locale: false profiles: # active: dev config: diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages.properties b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..90c7ca7 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages.properties @@ -0,0 +1,3 @@ +# Default messages +user.login.success=Login success +user.login.error=Login error diff --git a/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages_zh_CN.properties b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..2feaff4 --- /dev/null +++ b/backend/Skyeye-sys-dev/skyeye-service-manager/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,3 @@ +# 中文消息 +user.login.success=登录成功 +user.login.error=登录失败