From 7af8982f27181025d1800cf6f8da1d005e44dc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Sat, 26 Jul 2025 20:24:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E6=8B=8D=E7=85=A7=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/common/utils/ImageAnalysisUtil.java | 105 +++++++++++++-------- .../app/controller/PhotoController.java | 7 +- .../colortitration/app/model/vo/PhotoTakeVO.java | 22 +++++ .../common/service/PhotosService.java | 29 +++--- 4 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/iflytop/colortitration/app/model/vo/PhotoTakeVO.java diff --git a/src/main/java/com/iflytop/colortitration/app/common/utils/ImageAnalysisUtil.java b/src/main/java/com/iflytop/colortitration/app/common/utils/ImageAnalysisUtil.java index 0fbe303..65bae06 100644 --- a/src/main/java/com/iflytop/colortitration/app/common/utils/ImageAnalysisUtil.java +++ b/src/main/java/com/iflytop/colortitration/app/common/utils/ImageAnalysisUtil.java @@ -90,47 +90,7 @@ public class ImageAnalysisUtil { public static boolean analyzeRegionColorMatch(File imageFile, Color targetColor, double tolerancePercent, Rectangle region) throws IOException { BufferedImage image = loadImage(imageFile); BufferedImage smoothed = smoothImage(image); - Rectangle validRegion = getValidRegion(smoothed, region); - - int width = validRegion.width; - int height = validRegion.height; - int area = width * height; - double[] brightness = new double[area]; - int idx = 0; - for (int x = validRegion.x; x < validRegion.x + width; x++) { - for (int y = validRegion.y; y < validRegion.y + height; y++) { - Color c = new Color(smoothed.getRGB(x, y), true); - brightness[idx++] = (c.getRed() + c.getGreen() + c.getBlue()) / 3.0; - } - } - double sum = 0; - for (double v : brightness) sum += v; - double mean = sum / area; - double var = 0; - for (double v : brightness) var += Math.pow(v - mean, 2); - double std = Math.sqrt(var / area); - - long sumR = 0, sumG = 0, sumB = 0; - int validCount = 0; - idx = 0; - for (int x = validRegion.x; x < validRegion.x + width; x++) { - for (int y = validRegion.y; y < validRegion.y + height; y++) { - if (Math.abs(brightness[idx++] - mean) <= BUBBLE_STD_MULTIPLIER * std) { - Color c = new Color(smoothed.getRGB(x, y), true); - sumR += c.getRed(); - sumG += c.getGreen(); - sumB += c.getBlue(); - validCount++; - } - } - } - Color avgColor = validCount == 0 - ? new Color(0, 0, 0) - : new Color( - (int) (sumR / validCount), - (int) (sumG / validCount), - (int) (sumB / validCount) - ); + Color avgColor = computeFilteredAverageColor(smoothed, region); double distance = computeDistance(avgColor, targetColor); return distance <= convertTolerance(tolerancePercent); } @@ -191,6 +151,21 @@ public class ImageAnalysisUtil { return aspect >= minAspectRatio; } + /** + * 返回指定图像文件区域的平均颜色的16进制字符串 + * 使用高斯平滑 + 泡沫过滤处理后计算区域平均颜色,格式 "#RRGGBB" + * + * @param imageFile 待分析的图像文件 + * @param region 指定区域(x, y, width, height) + * @return 区域平均颜色的16进制代码 + * @throws IOException 如果读取图像文件失败 + */ + public static String getRegionAverageHexColor(File imageFile, Rectangle region) throws IOException { + BufferedImage image = loadImage(imageFile); + BufferedImage smoothed = smoothImage(image); + Color avg = computeFilteredAverageColor(smoothed, region); + return String.format("#%02X%02X%02X", avg.getRed(), avg.getGreen(), avg.getBlue()); + } // --------------- 私有辅助方法 --------------- /** @@ -252,6 +227,54 @@ public class ImageAnalysisUtil { } /** + * 先对指定区域像素的亮度进行统计,然后使用亮度均值和标准差过滤泡沫噪声,再计算剩余像素的平均颜色。 + * + * @param img 已平滑的图像对象 + * @param region 分析区域 + * @return 过滤后的区域平均颜色 + */ + private static Color computeFilteredAverageColor(BufferedImage img, Rectangle region) { + int w = region.width, h = region.height, area = w * h; + double[] bright = new double[area]; + int idx = 0; + for (int x = region.x; x < region.x + w; x++) + for (int y = region.y; y < region.y + h; y++) { + bright[idx++] = getGray(img.getRGB(x, y)); + } + double sum = 0; + for (double v : bright) sum += v; + double mean = sum / area; + double var = 0; + for (double v : bright) var += Math.pow(v - mean, 2); + double std = Math.sqrt(var / area); + long sumR = 0, sumG = 0, sumB = 0; + int cnt = 0; + idx = 0; + for (int x = region.x; x < region.x + w; x++) + for (int y = region.y; y < region.y + h; y++) { + if (Math.abs(bright[idx++] - mean) <= BUBBLE_STD_MULTIPLIER * std) { + Color c = new Color(img.getRGB(x, y), true); + sumR += c.getRed(); + sumG += c.getGreen(); + sumB += c.getBlue(); + cnt++; + } + } + return cnt == 0 ? new Color(0, 0, 0) : new Color((int) (sumR / cnt), (int) (sumG / cnt), (int) (sumB / cnt)); + } + + /** + * 计算单像素的灰度值(简单平均法)。 + * + * @param rgb 像素的 RGB 整数值 + * @return 灰度值 + */ + private static double getGray(int rgb) { + Color c = new Color(rgb, true); + return (c.getRed() + c.getGreen() + c.getBlue()) / 3.0; + } + + /** * 将百分比容差转换为在 RGB 空间中的绝对距离阈值 * * @param percent 容差百分比(0–100) diff --git a/src/main/java/com/iflytop/colortitration/app/controller/PhotoController.java b/src/main/java/com/iflytop/colortitration/app/controller/PhotoController.java index a995ab6..b0e53e3 100644 --- a/src/main/java/com/iflytop/colortitration/app/controller/PhotoController.java +++ b/src/main/java/com/iflytop/colortitration/app/controller/PhotoController.java @@ -2,6 +2,7 @@ package com.iflytop.colortitration.app.controller; import com.iflytop.colortitration.app.model.dto.PhotoSaveDTO; import com.iflytop.colortitration.app.model.dto.PhotoTakeDTO; +import com.iflytop.colortitration.app.model.vo.PhotoTakeVO; import com.iflytop.colortitration.common.base.BasePageQuery; import com.iflytop.colortitration.common.model.vo.PhotoListVO; import com.iflytop.colortitration.common.model.vo.PhotoVO; @@ -38,12 +39,10 @@ public class PhotoController { return Result.success(photosService.get(id)); } - @Operation(summary = "拍摄一张照片") @PostMapping("/take") - public Result take(@RequestBody PhotoTakeDTO photoTakeDTO) throws Exception { - photosService.take(photoTakeDTO.getModuleCode()); - return Result.success(); + public Result take(@RequestBody PhotoTakeDTO photoTakeDTO) throws Exception { + return Result.success(photosService.take(photoTakeDTO.getModuleCode())); } @Operation(summary = "保存照片") diff --git a/src/main/java/com/iflytop/colortitration/app/model/vo/PhotoTakeVO.java b/src/main/java/com/iflytop/colortitration/app/model/vo/PhotoTakeVO.java new file mode 100644 index 0000000..7e9d69d --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/model/vo/PhotoTakeVO.java @@ -0,0 +1,22 @@ +package com.iflytop.colortitration.app.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class PhotoTakeVO { + @Schema(description = "照片名称") + String fileName; + + @Schema(description = "16进制颜色") + String color; + + @Schema(description = "图像的Url") + String imageUrl; + + public PhotoTakeVO(String fileName, String color,String imageUrl) { + this.fileName = fileName; + this.color = color; + this.imageUrl = imageUrl; + } +} diff --git a/src/main/java/com/iflytop/colortitration/common/service/PhotosService.java b/src/main/java/com/iflytop/colortitration/common/service/PhotosService.java index 6381e11..664e5cb 100644 --- a/src/main/java/com/iflytop/colortitration/common/service/PhotosService.java +++ b/src/main/java/com/iflytop/colortitration/common/service/PhotosService.java @@ -5,9 +5,10 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.iflytop.colortitration.app.common.enums.MultipleModuleCode; +import com.iflytop.colortitration.app.common.utils.ImageAnalysisUtil; import com.iflytop.colortitration.app.core.state.DeviceState; import com.iflytop.colortitration.app.model.dto.PhotoSaveDTO; -import com.iflytop.colortitration.app.websocket.server.WebSocketMessageType; +import com.iflytop.colortitration.app.model.vo.PhotoTakeVO; import com.iflytop.colortitration.app.websocket.server.WebSocketSender; import com.iflytop.colortitration.common.base.BasePageQuery; import com.iflytop.colortitration.common.enums.PhotoModeType; @@ -25,6 +26,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.util.StreamUtils; +import java.awt.*; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; @@ -32,7 +34,9 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; /** @@ -88,21 +92,18 @@ public class PhotosService extends ServiceImpl { return null; } - public void take(MultipleModuleCode moduleCode) throws IOException { - File file = getStaticFile("1.png"); + public PhotoTakeVO take(MultipleModuleCode moduleCode) throws IOException { + String fileName = "1.png"; + File file = getStaticFile(fileName); deviceState.getTitrationModuleStateMap().get(moduleCode).setCurrentPhoto(file); - // 1. 读取图片并转 Base64 - byte[] bytes = Files.readAllBytes(file.toPath()); - String b64 = Base64.getEncoder().encodeToString(bytes); - String dataUrl = "data:image/png;base64," + b64; - // 2. 构造要发送的对象 - Map payload = new HashMap<>(); - payload.put("moduleCode", moduleCode); - payload.put("image", dataUrl); - - webSocketSender.push(WebSocketMessageType.PHOTO, payload); +// Map payload = new HashMap<>(); +// payload.put("moduleCode", moduleCode); +// payload.put("image", dataUrl); +// webSocketSender.push(WebSocketMessageType.PHOTO, payload); + String color = ImageAnalysisUtil.getRegionAverageHexColor(file, new Rectangle(1315, 1315, 40, 40)); + return new PhotoTakeVO(fileName, color, fileName); } public File getStaticFile(String fileName) throws IOException {