diff --git a/pom.xml b/pom.xml index 556b4a3..361be58 100644 --- a/pom.xml +++ b/pom.xml @@ -187,6 +187,14 @@ minio 8.5.7 + + org.springframework.boot + spring-boot-test + + + junit + junit + diff --git a/src/main/java/com/rj/common/PasswordUtil.java b/src/main/java/com/rj/common/PasswordUtil.java index 898579c..1ff5d95 100644 --- a/src/main/java/com/rj/common/PasswordUtil.java +++ b/src/main/java/com/rj/common/PasswordUtil.java @@ -171,6 +171,8 @@ public class PasswordUtil { + + diff --git a/src/main/java/com/rj/common/ServiceManager.java b/src/main/java/com/rj/common/ServiceManager.java index 22a49b9..e6776c7 100644 --- a/src/main/java/com/rj/common/ServiceManager.java +++ b/src/main/java/com/rj/common/ServiceManager.java @@ -155,6 +155,8 @@ public class ServiceManager { + + diff --git a/src/main/java/com/rj/config/AliyunConfig.java b/src/main/java/com/rj/config/AliyunConfig.java index 25fed63..259cc85 100644 --- a/src/main/java/com/rj/config/AliyunConfig.java +++ b/src/main/java/com/rj/config/AliyunConfig.java @@ -132,6 +132,8 @@ public class AliyunConfig { + + diff --git a/src/main/java/com/rj/controller/ImageModelController.java b/src/main/java/com/rj/controller/ImageModelController.java index 44eac22..1a0bc9b 100644 --- a/src/main/java/com/rj/controller/ImageModelController.java +++ b/src/main/java/com/rj/controller/ImageModelController.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.rj.entity.ImageModel; import com.rj.service.IImageModelService; +import com.rj.utils.ImageConversionUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -21,6 +22,8 @@ import jakarta.validation.constraints.NotBlank; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; +import java.util.List; +import java.util.ArrayList; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -553,6 +556,9 @@ public class ImageModelController { if (modelName != null && !modelName.trim().isEmpty()) { queryWrapper.like(ImageModel::getModelName, modelName); } + if (imageName != null && !imageName.trim().isEmpty()) { + queryWrapper.like(ImageModel::getImageName, imageName); + } if (imageType != null && !imageType.trim().isEmpty()) { queryWrapper.eq(ImageModel::getImageType, imageType); } @@ -560,9 +566,9 @@ public class ImageModelController { queryWrapper.ge(ImageModel::getCreateTime, startTime); } if (endTime != null && !endTime.trim().isEmpty()) { - queryWrapper.le(ImageModel::getCreateTime, endTime); + queryWrapper.le(ImageModel::getUpdateTime, endTime); } - + // 按创建时间降序排列 queryWrapper.orderByDesc(ImageModel::getUpdateTime); @@ -673,7 +679,7 @@ public class ImageModelController { } /** - * 图像处理接口 + * 图像处理接口,根据给的素材生成图片 */ @PostMapping("/image-background") @Operation(summary = "图像处理", description = "基于参考图像和提示词进行图像处理") @@ -713,6 +719,8 @@ public class ImageModelController { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } } + @Autowired + ImageConversionUtil imageConversionUtil ; /** * 执行图像处理的方法 - 调用阿里云API */ @@ -725,15 +733,77 @@ public class ImageModelController { request.getBaseImageUrl(), request.getRefImageUrl()); // 处理引导图像RGBA转换(如果存在引导图像) - if (request.getRefImageUrl() != null && !request.getRefImageUrl().trim().isEmpty()) { + if (request.getBaseImageUrl() != null && !request.getBaseImageUrl().trim().isEmpty()) { log.info("检测到引导图像,开始进行RGBA转换处理"); - String processedRefImageUrl = processRefImageForRGBA(request.getRefImageUrl()); + byte[] bytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(request.getBaseImageUrl()); + String processedRefImageUrl = uploadFile(bytes); if (processedRefImageUrl != null) { - log.info("引导图像RGBA转换完成,新URL: {}", processedRefImageUrl); - request.setRefImageUrl(processedRefImageUrl); + log.info("产品主图baseImage图像RGBA转换完成,新URL: {}", processedRefImageUrl); + request.setBaseImageUrl(processedRefImageUrl); } } - + // 处理前景边缘图像转换 + if (request.getReferenceEdge() != null && request.getReferenceEdge().getForegroundEdge() != null) { + String[] originalForegroundEdges = request.getReferenceEdge().getForegroundEdge(); + String[] backgroundEdges = request.getReferenceEdge().getBackgroundEdge(); + + List processedForegroundEdges = new ArrayList<>(); + List processedbackgroundEdges = new ArrayList<>(); + + // 1, 背景图边处理 + for (String backgroundEdge : backgroundEdges) { + byte[] bytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(backgroundEdge); + String processedRefImageUrl = uploadFile(bytes); + if (processedRefImageUrl != null) { + log.info("产品主图baseImage图像RGBA转换完成,新URL: {}", processedRefImageUrl); + processedbackgroundEdges .add(processedRefImageUrl); + } + } + + + // 2, 前景图边处理 + for (String foregroundEdgeUrl : originalForegroundEdges) { + if (foregroundEdgeUrl != null && !foregroundEdgeUrl.trim().isEmpty()) { + log.info("开始进行ForegroundEdge的RGBA转换处理: {}", foregroundEdgeUrl); + try { + byte[] bytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(foregroundEdgeUrl); + if (bytes != null) { + String processedRefImageUrl = uploadFile(bytes); + if (processedRefImageUrl != null && !processedRefImageUrl.trim().isEmpty()) { + log.info("ForegroundEdge图像RGBA转换完成,新URL: {}", processedRefImageUrl); + processedForegroundEdges.add(processedRefImageUrl); + } else { + log.warn("ForegroundEdge图像上传失败,跳过此URL: {}", foregroundEdgeUrl); + } + } else { + log.warn("ForegroundEdge图像转换失败,跳过此URL: {}", foregroundEdgeUrl); + } + } catch (Exception e) { + log.error("处理ForegroundEdge图像时发生异常: {}", foregroundEdgeUrl, e); + } + } else { + log.warn("发现空的ForegroundEdge URL,跳过处理"); + } + } + + // 只有当成功处理了至少一个URL时才更新 + if (!processedForegroundEdges.isEmpty()) { + String[] newForegroundEdges = processedForegroundEdges.toArray(new String[0]); + request.getReferenceEdge().setForegroundEdge(newForegroundEdges); + log.info("成功处理{}个ForegroundEdge图像", newForegroundEdges.length); + } else { + log.warn("没有成功处理任何ForegroundEdge图像,将使用原始URL"); + } + + if (!processedbackgroundEdges.isEmpty()) { + String[] processedbackgroundEdgess = processedbackgroundEdges.toArray(new String[0]); + request.getReferenceEdge().setBackgroundEdge(processedbackgroundEdgess); + log.info("成功处理{}个processedbackgroundEdgess e图像", processedbackgroundEdgess.length); + } + + } + + // 构建阿里云请求参数 Map aliyunRequest = buildAliyunRequest(request); @@ -778,30 +848,25 @@ public class ImageModelController { } /** - * 处理引导图像RGBA转换 + * * - * 下载引导图像,转换为RGBA格式,上传到MinIO并生成新的临时访问链接 + * 上传到MinIO并生成新的临时访问链接 * - * @param refImageUrl 原始引导图像URL + * @param * @return 处理后的RGBA图像临时访问链接,如果处理失败返回null */ - private String processRefImageForRGBA(String refImageUrl) { + private String uploadFile( byte[] originalImageBytes) { try { - log.info("开始处理引导图像RGBA转换,原始URL: {}", refImageUrl); + log.info("开始处理引导图像RGBA转换,原始URL: "); // 1. 下载原始图像 - byte[] originalImageBytes = downloadImageFromUrl(refImageUrl); - log.info("原始图像下载完成,大小: {} bytes", originalImageBytes.length); - + // 2. 转换为RGBA格式 - byte[] rgbaImageBytes = convertImageToRGBA(originalImageBytes); - log.info("图像RGBA转换完成,大小: {} bytes", rgbaImageBytes.length); - // 3. 生成唯一文件名 String uniqueFileName = generateUniqueFileName("png"); // 4. 上传RGBA图像到MinIO - String materialUrl = minIOService.uploadFile(rgbaImageBytes, uniqueFileName, "image/png"); + String materialUrl = minIOService.uploadFile(originalImageBytes, uniqueFileName, "image/png"); log.info("RGBA图像上传到MinIO成功: {}", materialUrl); // 5. 生成7天临时访问链接 @@ -811,7 +876,7 @@ public class ImageModelController { return materialTempUrl; } catch (Exception e) { - log.error("处理引导图像RGBA转换失败: {}", refImageUrl, e); + log.error("处理引导图像RGBA转换失败: {}", e); return null; } } @@ -823,7 +888,7 @@ public class ImageModelController { * @return 图像字节数组 * @throws IOException 下载失败时抛出异常 */ - private byte[] downloadImageFromUrl(String imageUrl) throws IOException { + private byte[] downloadImageFromUrl11(String imageUrl) throws IOException { try { log.info("开始下载图像: {}", imageUrl); @@ -847,69 +912,7 @@ public class ImageModelController { } } - /** - * 将图像转换为RGBA格式 - * - * @param imageBytes 原始图像字节数组 - * @return RGBA格式的图像字节数组 - * @throws IOException 转换失败时抛出异常 - */ - private byte[] convertImageToRGBA(byte[] imageBytes) throws IOException { - try { - log.info("开始转换图像为RGBA格式"); - - // 从字节数组读取图像 - ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes); - java.awt.image.BufferedImage originalImage = javax.imageio.ImageIO.read(bais); - if (originalImage == null) { - throw new IllegalArgumentException("无法读取图像文件"); - } - - int width = originalImage.getWidth(); - int height = originalImage.getHeight(); - - // 验证图像尺寸 - int maxDimension = Math.max(width, height); - if (maxDimension > 2048) { - throw new IllegalArgumentException("图像长边不能超过2048像素,当前尺寸:" + width + "x" + height); - } - - log.info("原始图像尺寸: {}x{}", width, height); - - // 创建RGBA格式的图像 - java.awt.image.BufferedImage rgbaImage = new java.awt.image.BufferedImage( - width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB); - - // 获取图形上下文 - java.awt.Graphics2D g2d = rgbaImage.createGraphics(); - - // 设置渲染提示以获得更好的质量 - g2d.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, - java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2d.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING, - java.awt.RenderingHints.VALUE_RENDER_QUALITY); - g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, - java.awt.RenderingHints.VALUE_ANTIALIAS_ON); - - // 绘制原始图像到RGBA图像上 - g2d.drawImage(originalImage, 0, 0, null); - g2d.dispose(); - - // 将RGBA图像转换为字节数组 - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - javax.imageio.ImageIO.write(rgbaImage, "PNG", baos); - byte[] rgbaBytes = baos.toByteArray(); - - log.info("图像RGBA转换完成,大小: {} bytes", rgbaBytes.length); - - return rgbaBytes; - - } catch (Exception e) { - log.error("转换图像为RGBA格式失败", e); - throw new IOException("转换图像为RGBA格式失败: " + e.getMessage(), e); - } - } - + /** * 生成唯一文件名 * @@ -940,11 +943,76 @@ public class ImageModelController { // 构建reference_edge参数 if (request.getReferenceEdge() != null) { Map referenceEdge = new HashMap<>(); - referenceEdge.put("foreground_edge", request.getReferenceEdge().getForegroundEdge()); - referenceEdge.put("background_edge", request.getReferenceEdge().getBackgroundEdge()); - referenceEdge.put("foreground_edge_prompt", request.getReferenceEdge().getForegroundEdgePrompt()); - referenceEdge.put("background_edge_prompt", request.getReferenceEdge().getBackgroundEdgePrompt()); + + // 处理前景边缘URL,过滤掉null值和无效URL + if (request.getReferenceEdge().getForegroundEdge() != null) { + List validForegroundEdges = new ArrayList<>(); + for (String url : request.getReferenceEdge().getForegroundEdge()) { + if (isValidUrl(url)) { + validForegroundEdges.add(url); + } else { + log.warn("跳过无效的前景边缘URL: {}", url); + } + } + if (!validForegroundEdges.isEmpty()) { + referenceEdge.put("foreground_edge", validForegroundEdges.toArray(new String[0])); + log.info("添加{}个有效的前景边缘URL", validForegroundEdges.size()); + } else { + log.warn("没有有效的前景边缘URL"); + } + } + + // 处理背景边缘URL,过滤掉null值和无效URL + if (request.getReferenceEdge().getBackgroundEdge() != null) { + List validBackgroundEdges = new ArrayList<>(); + for (String url : request.getReferenceEdge().getBackgroundEdge()) { + if (isValidUrl(url)) { + validBackgroundEdges.add(url); + } else { + log.warn("跳过无效的背景边缘URL: {}", url); + } + } + if (!validBackgroundEdges.isEmpty()) { + referenceEdge.put("background_edge", validBackgroundEdges.toArray(new String[0])); + log.info("添加{}个有效的背景边缘URL", validBackgroundEdges.size()); + } else { + log.warn("没有有效的背景边缘URL"); + } + } + + // 处理前景边缘提示词,过滤掉null值 + if (request.getReferenceEdge().getForegroundEdgePrompt() != null) { + List validForegroundPrompts = new ArrayList<>(); + for (String prompt : request.getReferenceEdge().getForegroundEdgePrompt()) { + if (prompt != null && !prompt.trim().isEmpty()) { + validForegroundPrompts.add(prompt); + } + } + if (!validForegroundPrompts.isEmpty()) { + referenceEdge.put("foreground_edge_prompt", validForegroundPrompts.toArray(new String[0])); + } + } + + // 处理背景边缘提示词,过滤掉null值 + if (request.getReferenceEdge().getBackgroundEdgePrompt() != null) { + List validBackgroundPrompts = new ArrayList<>(); + for (String prompt : request.getReferenceEdge().getBackgroundEdgePrompt()) { + if (prompt != null && !prompt.trim().isEmpty()) { + validBackgroundPrompts.add(prompt); + } + } + if (!validBackgroundPrompts.isEmpty()) { + referenceEdge.put("background_edge_prompt", validBackgroundPrompts.toArray(new String[0])); + } + } + + // 只有当referenceEdge不为空时才添加到input中 + if (!referenceEdge.isEmpty()) { input.put("reference_edge", referenceEdge); + log.info("添加reference_edge参数,包含{}个字段", referenceEdge.size()); + } else { + log.warn("reference_edge参数为空,跳过添加"); + } } aliyunRequest.put("input", input); @@ -1156,6 +1224,32 @@ public class ImageModelController { } } + /** + * 验证URL是否有效 + * + * @param url 要验证的URL + * @return 是否为有效URL + */ + private boolean isValidUrl(String url) { + if (url == null || url.trim().isEmpty()) { + return false; + } + + try { + // 基本URL格式验证 + if (!url.startsWith("http://") && !url.startsWith("https://")) { + return false; + } + + // 尝试解析URL + new java.net.URL(url); + return true; + } catch (Exception e) { + log.warn("URL格式无效: {}", url); + return false; + } + } + /** * 生成请求ID */ diff --git a/src/main/java/com/rj/controller/sys/MenuController.java b/src/main/java/com/rj/controller/sys/MenuController.java index dff6ea0..b296441 100644 --- a/src/main/java/com/rj/controller/sys/MenuController.java +++ b/src/main/java/com/rj/controller/sys/MenuController.java @@ -393,6 +393,8 @@ public class MenuController { + + diff --git a/src/main/java/com/rj/controller/sys/RoleController.java b/src/main/java/com/rj/controller/sys/RoleController.java index 05cae13..1714499 100644 --- a/src/main/java/com/rj/controller/sys/RoleController.java +++ b/src/main/java/com/rj/controller/sys/RoleController.java @@ -363,6 +363,8 @@ public class RoleController { + + diff --git a/src/main/java/com/rj/controller/sys/UserRoleController.java b/src/main/java/com/rj/controller/sys/UserRoleController.java index dd09f58..9612540 100644 --- a/src/main/java/com/rj/controller/sys/UserRoleController.java +++ b/src/main/java/com/rj/controller/sys/UserRoleController.java @@ -369,6 +369,8 @@ public class UserRoleController { + + diff --git a/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java b/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java index 9f22aef..7451cfd 100644 --- a/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java +++ b/src/main/java/com/rj/dto/DifyWorkflowResponseDto.java @@ -135,6 +135,8 @@ public class DifyWorkflowResponseDto { + + diff --git a/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java b/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java index d191148..7ac9b18 100644 --- a/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java +++ b/src/main/java/com/rj/mapper/CustomerProfileAnalysisMapper.java @@ -78,6 +78,8 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper implements IM + + diff --git a/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java b/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java index f5fe608..96d1987 100644 --- a/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java +++ b/src/main/java/com/rj/service/sys/impl/RoleServiceImpl.java @@ -106,6 +106,8 @@ public class RoleServiceImpl extends ServiceImpl implements IR + + diff --git a/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java b/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java index b8f3bdb..d19e8b4 100644 --- a/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java +++ b/src/main/java/com/rj/service/sys/impl/UserRoleServiceImpl.java @@ -106,6 +106,8 @@ public class UserRoleServiceImpl extends ServiceImpl i + + diff --git a/src/main/java/com/rj/utils/AliyunImageConversionUtil.java b/src/main/java/com/rj/utils/AliyunImageConversionUtil.java new file mode 100644 index 0000000..9cfa5a8 --- /dev/null +++ b/src/main/java/com/rj/utils/AliyunImageConversionUtil.java @@ -0,0 +1,1523 @@ +package com.rj.utils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rj.service.MinIOService; +import lombok.extern.slf4j.Slf4j; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.web.client.RestTemplate; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import java.util.List; + +/** + * 阿里云背景生成API测试类 + *

+ * 测试阿里云背景生成服务的API调用功能 + * + * @author 系统生成 + * @version 1.0 + * @date 2025/1/30 + */ +@Slf4j +@SpringBootTest +public class AliyunImageConversionUtil { + + @Autowired + private RestTemplate restTemplate; + + @Autowired + private MinIOService minIOService; + + @Autowired + private ImageConversionUtil imageConversionUtil; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + + + /** + * 构建请求体 + * + * @return 请求体Map + */ + private Map buildRequestBody() { + Map requestBody = new HashMap<>(); + + // 设置模型 + requestBody.put("model", "wanx-background-generation-v2"); + + // 构建input参数 + Map input = new HashMap<>(); + // 阿里云的原始图片,可以生成,是正确的 +// input.put("base_image_url", "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/main_images/new_main_img/a.png"); +// input.put("base_image_url", "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0"); + // 转换后的 图片 + input.put("base_image_url", "http://101.35.52.237:19005/car/1760607939127_e5af1be2272f4e5eaab6970d1dddb8e0.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T094539Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=891cc2b70bfed8425a43edb2f742bbd95e19cce2ff16ed7e6171fc6e312ed7c8"); + input.put("ref_image_url", "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_images/c5e50d27be534709817b2ab080b0162f_0.jpg"); + input.put("ref_prompt", "山脉和晚霞"); + + // 构建reference_edge参数 + Map referenceEdge = new HashMap<>(); + + // 前景边缘 + List foregroundEdge = Arrays.asList( + "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/huaban_soft_edge/6cdd13941cef1b11d885aea1717b983ae566b8efc9094-vcsvxa_fw658webp.png", + "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_edge/2c36cc4b7da027279e87311dac48fc2d5d784b1e72c0e-x4f1wC_fw658webp.png" + ); + referenceEdge.put("foreground_edge", foregroundEdge); + + // 背景边缘 + List backgroundEdge = Arrays.asList( + "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_edge/0718a9741e07c52ca5506e75c4f2b99e22fff68a4c7d3-P9WGLr_fw658webp.png" + ); + referenceEdge.put("background_edge", backgroundEdge); + + // 前景边缘提示词 + List foregroundEdgePrompt = Arrays.asList( + "粉色桃花", + "可爱小狗" + ); + referenceEdge.put("foreground_edge_prompt", foregroundEdgePrompt); + + // 背景边缘提示词 + List backgroundEdgePrompt = Arrays.asList( + "树叶" + ); + referenceEdge.put("background_edge_prompt", backgroundEdgePrompt); + + // input.put("reference_edge", referenceEdge); + requestBody.put("input", input); + + // 构建parameters参数 + Map parameters = new HashMap<>(); + parameters.put("n", 4); + parameters.put("ref_prompt_weight", 0.5); + parameters.put("model_version", "v3"); + requestBody.put("parameters", parameters); + + return requestBody; + } + + + + /** + * 生成唯一文件名(测试用) + * + * @param extension 文件扩展名 + * @return 唯一文件名 + */ + private String generateUniqueFileNameForTest(String extension) { + long timestamp = System.currentTimeMillis(); + String uuid = UUID.randomUUID().toString().replace("-", ""); + return timestamp + "_" + uuid + "." + extension; + } + + /** + * 完整的图片转换和阿里云API调用流程 + * ######################################################################################################################################################### + */ + @Test + public void testImageAnalysisAndConversion() { + try { + log.info("开始完整的图片转换和阿里云API调用流程"); + + // 1. 分析阿里云图片特征(参考) + String aliyunImageUrl = "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/main_images/new_main_img/a.png"; + analyzeImageCharacteristics(aliyunImageUrl, "阿里云参考图片"); + + // 2. 分析您的原始图片特征 + String yourImageUrl = "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0"; + analyzeImageCharacteristics(yourImageUrl, "您的原始图片"); + + // 3. 转换图片为符合阿里云要求的格式(使用工具类) + byte[] convertedImageBytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(yourImageUrl); + if (convertedImageBytes == null) { + log.error("图片转换失败,无法继续"); + return; + } + log.info("图片转换完成,转换后大小: {} bytes", convertedImageBytes.length); + + // 4. 上传转换后的图片到MinIO + String minioFileName = uploadImageToMinIO(convertedImageBytes); + if (minioFileName == null) { + log.error("上传到MinIO失败,无法继续"); + return; + } + log.info("图片上传到MinIO成功,文件名: {}", minioFileName); + + // 4.1 同时保存一份到本地电脑 + String localFilePath = saveImageToLocal(convertedImageBytes, minioFileName); + if (localFilePath != null) { + log.info("图片已保存到本地: {}", localFilePath); + } else { + log.warn("保存到本地失败,但MinIO上传成功,继续执行"); + } + + // 5. 生成临时访问链接 + String tempUrl = generateTempUrl(minioFileName); + if (tempUrl == null) { + log.error("生成临时访问链接失败,无法继续"); + return; + } + log.info("生成临时访问链接: {}", tempUrl); + + // 6. 验证转换后的图片特征 + analyzeImageCharacteristics(tempUrl, "转换后的图片"); + + // 7. 使用转换后的图片调用阿里云API + callAliyunAPIWithConvertedImage(tempUrl); + + } catch (Exception e) { + log.error("完整的图片转换和API调用流程失败", e); + } + } + + /** + * 分析图片特征 + * + * @param imageUrl 图片URL + * @param imageName 图片名称 + */ + private void analyzeImageCharacteristics(String imageUrl, String imageName) { + try { + log.info("=== 分析{}特征 ===", imageName); + + // 下载图片 + BufferedImage image = downloadImage(imageUrl); + if (image == null) { + log.error("无法下载图片: {}", imageUrl); + return; + } + + // 分析基本特征 + int width = image.getWidth(); + int height = image.getHeight(); + int type = image.getType(); + int maxDimension = Math.max(width, height); + + log.info("图片尺寸: {}x{}", width, height); + log.info("图片类型: {} ({})", type, getImageTypeName(type)); + log.info("最大尺寸: {} (是否超过2048: {})", maxDimension, maxDimension > 2048); + + // 分析颜色模式 + analyzeColorMode(image); + + // 分析透明度 + analyzeTransparency(image); + + // 分析文件格式 + analyzeFileFormat(imageUrl); + + } catch (Exception e) { + log.error("分析图片特征失败: {}", imageUrl, e); + } + } + + /** + * 下载图片 + * + * @param imageUrl 图片URL + * @return BufferedImage对象 + */ + private BufferedImage downloadImage(String imageUrl) { + try { + URL url = new URL(imageUrl); + return ImageIO.read(url); + } catch (Exception e) { + log.error("下载图片失败: {}", imageUrl, e); + return null; + } + } + + /** + * 获取图片类型名称 + * + * @param type 图片类型 + * @return 类型名称 + */ + private String getImageTypeName(int type) { + switch (type) { + case BufferedImage.TYPE_INT_RGB: + return "RGB"; + case BufferedImage.TYPE_INT_ARGB: + return "ARGB"; + case BufferedImage.TYPE_INT_ARGB_PRE: + return "ARGB_PRE"; + case BufferedImage.TYPE_4BYTE_ABGR: + return "ABGR"; + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + return "ABGR_PRE"; + case BufferedImage.TYPE_3BYTE_BGR: + return "BGR"; + case BufferedImage.TYPE_BYTE_GRAY: + return "GRAY"; + case BufferedImage.TYPE_USHORT_555_RGB: + return "RGB_555"; + case BufferedImage.TYPE_USHORT_565_RGB: + return "RGB_565"; + default: + return "UNKNOWN"; + } + } + + /** + * 分析颜色模式 + * + * @param image 图片对象 + */ + private void analyzeColorMode(BufferedImage image) { + try { + int width = image.getWidth(); + int height = image.getHeight(); + + // 采样分析颜色 + int sampleSize = Math.min(100, Math.min(width, height)); + int stepX = width / sampleSize; + int stepY = height / sampleSize; + + int transparentPixels = 0; + int opaquePixels = 0; + int totalPixels = 0; + + for (int y = 0; y < height; y += stepY) { + for (int x = 0; x < width; x += stepX) { + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + + if (alpha == 0) { + transparentPixels++; + } else if (alpha == 255) { + opaquePixels++; + } + totalPixels++; + } + } + + double transparentRatio = (double) transparentPixels / totalPixels; + double opaqueRatio = (double) opaquePixels / totalPixels; + + log.info("透明度分析:"); + log.info(" 透明像素比例: {:.2f}%", transparentRatio * 100); + log.info(" 不透明像素比例: {:.2f}%", opaqueRatio * 100); + log.info(" 半透明像素比例: {:.2f}%", (1 - transparentRatio - opaqueRatio) * 100); + + } catch (Exception e) { + log.error("分析颜色模式失败", e); + } + } + + /** + * 分析透明度 + * + * @param image 图片对象 + */ + private void analyzeTransparency(BufferedImage image) { + try { + int width = image.getWidth(); + int height = image.getHeight(); + + boolean hasTransparency = image.getColorModel().hasAlpha(); + log.info("是否支持透明度: {}", hasTransparency); + + // 检查是否有透明像素 + boolean hasTransparentPixels = false; + for (int y = 0; y < height; y += 10) { // 采样检查 + for (int x = 0; x < width; x += 10) { + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + if (alpha < 255) { + hasTransparentPixels = true; + break; + } + } + if (hasTransparentPixels) break; + } + + log.info("是否包含透明像素: {}", hasTransparentPixels); + + } catch (Exception e) { + log.error("分析透明度失败", e); + } + } + + /** + * 分析文件格式 + * + * @param imageUrl 图片URL + */ + private void analyzeFileFormat(String imageUrl) { + try { + if (imageUrl.startsWith("file://")) { + String filePath = imageUrl.substring(7); + File file = new File(filePath); + if (file.exists()) { + log.info("文件大小: {} bytes", file.length()); + log.info("文件扩展名: {}", getFileExtension(file.getName())); + } + } else { + URL url = new URL(imageUrl); + try (InputStream inputStream = url.openStream()) { + byte[] header = new byte[8]; + inputStream.read(header); + + log.info("文件头: {}", bytesToHex(header)); + + // 检查PNG格式 + if (header[0] == (byte) 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47) { + log.info("文件格式: PNG"); + } else if (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8) { + log.info("文件格式: JPEG"); + } else { + log.info("文件格式: 未知"); + } + } + } + } catch (Exception e) { + log.error("分析文件格式失败", e); + } + } + + /** + * 字节数组转十六进制字符串 + * + * @param bytes 字节数组 + * @return 十六进制字符串 + */ + private String bytesToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02X ", b)); + } + return result.toString(); + } + + /** + * 获取文件扩展名 + * + * @param filename 文件名 + * @return 扩展名 + */ + private String getFileExtension(String filename) { + if (filename == null || filename.lastIndexOf('.') == -1) { + return ""; + } + return filename.substring(filename.lastIndexOf('.') + 1); + } + + /** + * 转换图片为符合阿里云要求的格式(返回字节数组) + * 阿里云要求:主体图像必须为带透明背景的RGBA四通道图像,PNG格式,长边不超过2048像素 + * + * 注意:此方法为基础版本,推荐使用 convertImageToAliyunFormatAdvanced 方法获得更好的效果 + * + * @param imageUrl 原始图片URL + * @return 转换后的图片字节数组 + */ + private byte[] convertImageToAliyunFormatBytes(String imageUrl) { + try { + log.info("开始转换图片为阿里云格式"); + + // 1. 下载原始图片 + BufferedImage originalImage = downloadImage(imageUrl); + if (originalImage == null) { + log.error("无法下载原始图片"); + return null; + } + + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + log.info("原始图片尺寸: {}x{}", width, height); + + // 2. 检查尺寸,如果超过2048则缩放 + if (Math.max(width, height) > 2048) { + double scale = 2048.0 / Math.max(width, height); + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + log.info("图片尺寸过大,缩放至: {}x{}", newWidth, newHeight); + + BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = scaledImage.createGraphics(); + + // 设置高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + + // 绘制原始图片到缩放图片上 + g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null); + g2d.dispose(); + + originalImage = scaledImage; + width = newWidth; + height = newHeight; + } + + // 3. 创建真正的RGBA四通道图像,确保支持透明度 + BufferedImage rgbaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = rgbaImage.createGraphics(); + + // 设置高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + + // 先清空背景为透明 + g2d.setComposite(AlphaComposite.Clear); + g2d.fillRect(0, 0, width, height); + + // 设置正常合成模式 + g2d.setComposite(AlphaComposite.SrcOver); + + // 绘制原始图片到RGBA图片上,保持透明度 + g2d.drawImage(originalImage, 0, 0, null); + g2d.dispose(); + + // 4. 验证RGBA图像是否真正支持透明度 + boolean hasTransparency = rgbaImage.getColorModel().hasAlpha(); + log.info("RGBA图像是否支持透明度: {}", hasTransparency); + + // 检查是否有透明像素 + boolean hasTransparentPixels = false; + for (int y = 0; y < height; y += Math.max(1, height / 20)) { + for (int x = 0; x < width; x += Math.max(1, width / 20)) { + int rgb = rgbaImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + if (alpha < 255) { + hasTransparentPixels = true; + break; + } + } + if (hasTransparentPixels) break; + } + log.info("RGBA图像是否包含透明像素: {}", hasTransparentPixels); + + // 5. 转换为PNG格式字节数组 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + boolean success = ImageIO.write(rgbaImage, "PNG", baos); + if (success) { + byte[] imageBytes = baos.toByteArray(); + log.info("图片转换成功,大小: {} bytes", imageBytes.length); + + // 验证PNG文件头 + if (imageBytes.length >= 8) { + boolean isPng = (imageBytes[0] == (byte) 0x89 && imageBytes[1] == 0x50 && + imageBytes[2] == 0x4E && imageBytes[3] == 0x47); + log.info("输出文件是否为PNG格式: {}", isPng); + } + + return imageBytes; + } else { + log.error("图片转换失败"); + return null; + } + + } catch (Exception e) { + log.error("转换图片失败", e); + return null; + } + } + + /** + * 高级图片转换方法,专门处理透明背景需求 + * 确保生成符合阿里云要求的RGBA四通道PNG图像 + * + * @param imageUrl 原始图片URL + * @return 转换后的图片字节数组 + */ + private byte[] convertImageToAliyunFormatAdvanced(String imageUrl) { + try { + log.info("开始高级图片转换为阿里云格式"); + + // 1. 下载原始图片 + BufferedImage originalImage = downloadImage(imageUrl); + if (originalImage == null) { + log.error("无法下载原始图片"); + return null; + } + + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + log.info("原始图片尺寸: {}x{}, 类型: {}", width, height, getImageTypeName(originalImage.getType())); + + // 2. 检查尺寸,如果超过2048则缩放 + if (Math.max(width, height) > 2048) { + double scale = 2048.0 / Math.max(width, height); + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + log.info("图片尺寸过大,缩放至: {}x{}", newWidth, newHeight); + + BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = scaledImage.createGraphics(); + + // 设置最高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + + // 绘制原始图片到缩放图片上 + g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null); + g2d.dispose(); + + originalImage = scaledImage; + width = newWidth; + height = newHeight; + } + + // 3. 创建真正的RGBA四通道图像,强制透明背景 + BufferedImage rgbaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = rgbaImage.createGraphics(); + + // 设置最高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + + // 强制清空背景为完全透明 + g2d.setComposite(AlphaComposite.Clear); + g2d.fillRect(0, 0, width, height); + + // 设置正常合成模式 + g2d.setComposite(AlphaComposite.SrcOver); + + // 如果原始图片没有透明背景,使用智能背景移除 + if (!originalImage.getColorModel().hasAlpha() || !hasTransparentPixels(originalImage)) { + log.info("原始图片没有透明背景,使用智能背景移除"); + + // 使用智能背景移除算法 + BufferedImage transparentImage = createTransparentBackgroundImage(originalImage); + + // 将处理后的图片绘制到RGBA图像上 + g2d.drawImage(transparentImage, 0, 0, null); + + // 验证透明像素数量 + int transparentCount = 0; + for (int y = 0; y < height; y += Math.max(1, height / 50)) { + for (int x = 0; x < width; x += Math.max(1, width / 50)) { + int rgb = rgbaImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + if (alpha == 0) { + transparentCount++; + } + } + } + + log.info("智能背景移除完成,采样透明像素比例: {:.2f}%", + (double) transparentCount / ((width / Math.max(1, width / 50)) * (height / Math.max(1, height / 50))) * 100); + } else { + // 原始图片已有透明度,直接绘制 + g2d.drawImage(originalImage, 0, 0, null); + } + + g2d.dispose(); + + // 4. 深度验证RGBA图像 + boolean hasTransparency = rgbaImage.getColorModel().hasAlpha(); + log.info("RGBA图像是否支持透明度: {}", hasTransparency); + + // 详细分析透明度 + analyzeTransparency(rgbaImage); + analyzeColorMode(rgbaImage); + + // 5. 转换为PNG格式字节数组 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + boolean success = ImageIO.write(rgbaImage, "PNG", baos); + if (success) { + byte[] imageBytes = baos.toByteArray(); + log.info("高级图片转换成功,大小: {} bytes", imageBytes.length); + + // 验证PNG文件头 + if (imageBytes.length >= 8) { + boolean isPng = (imageBytes[0] == (byte) 0x89 && imageBytes[1] == 0x50 && + imageBytes[2] == 0x4E && imageBytes[3] == 0x47); + log.info("输出文件是否为PNG格式: {}", isPng); + + // 检查PNG是否包含透明度信息 + if (imageBytes.length > 25) { + // 检查IHDR chunk中的颜色类型 + boolean hasAlphaChannel = false; + for (int i = 0; i < imageBytes.length - 25; i++) { + if (imageBytes[i] == 'I' && imageBytes[i+1] == 'H' && + imageBytes[i+2] == 'D' && imageBytes[i+3] == 'R') { + // 颜色类型在第25个字节 + int colorType = imageBytes[i + 25] & 0xFF; + hasAlphaChannel = (colorType == 4 || colorType == 6); + log.info("PNG颜色类型: {}, 是否包含Alpha通道: {}", colorType, hasAlphaChannel); + break; + } + } + } + } + + return imageBytes; + } else { + log.error("高级图片转换失败"); + return null; + } + + } catch (Exception e) { + log.error("高级图片转换失败", e); + return null; + } + } + + /** + * 上传图片到MinIO + * + * @param imageBytes 图片字节数组 + * @return MinIO文件名 + */ + private String uploadImageToMinIO(byte[] imageBytes) { + try { + log.info("开始上传图片到MinIO"); + + // 生成唯一文件名 + String fileName = generateUniqueFileNameForTest("png"); + + // 上传到MinIO + String materialUrl = minIOService.uploadFile(imageBytes, fileName, "image/png"); + if (materialUrl != null) { + log.info("图片上传到MinIO成功: {}", materialUrl); + return fileName; + } else { + log.error("图片上传到MinIO失败"); + return null; + } + + } catch (Exception e) { + log.error("上传图片到MinIO失败", e); + return null; + } + } + + /** + * 保存图片到本地电脑 + * + * @param imageBytes 图片字节数组 + * @param minioFileName MinIO文件名(用作本地文件名) + * @return 本地文件路径 + */ + private String saveImageToLocal(byte[] imageBytes, String minioFileName) { + try { + log.info("开始保存图片到本地电脑"); + + // 创建本地保存目录 + String localDir = "converted_images"; + File dir = new File(localDir); + if (!dir.exists()) { + boolean created = dir.mkdirs(); + if (!created) { + log.error("创建本地目录失败: {}", localDir); + return null; + } + log.info("创建本地目录: {}", localDir); + } + + // 构建本地文件路径 + String localFileName = "converted_" + minioFileName; + String localFilePath = localDir + File.separator + localFileName; + File localFile = new File(localFilePath); + + // 写入文件 + try (FileOutputStream fos = new FileOutputStream(localFile)) { + fos.write(imageBytes); + fos.flush(); + } + + log.info("图片保存到本地成功: {}", localFile.getAbsolutePath()); + log.info("本地文件大小: {} bytes", localFile.length()); + + return localFile.getAbsolutePath(); + + } catch (Exception e) { + log.error("保存图片到本地失败", e); + return null; + } + } + + /** + * 生成临时访问链接 + * + * @param fileName MinIO文件名 + * @return 临时访问链接 + */ + private String generateTempUrl(String fileName) { + try { + log.info("开始生成临时访问链接,文件名: {}", fileName); + + String tempUrl = minIOService.generateTempUrl(fileName); + if (tempUrl != null) { + log.info("临时访问链接生成成功: {}", tempUrl); + return tempUrl; + } else { + log.error("临时访问链接生成失败"); + return null; + } + + } catch (Exception e) { + log.error("生成临时访问链接失败", e); + return null; + } + } + + /** + * 创建带透明背景的图片(专门处理无透明背景的图片) + * 使用高级背景移除算法,确保彻底移除背景 + * + * @param originalImage 原始图片 + * @return 带透明背景的图片 + */ + private BufferedImage createTransparentBackgroundImage(BufferedImage originalImage) { + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + + log.info("开始高级背景移除处理,图片尺寸: {}x{}", width, height); + + // 创建RGBA图像 + BufferedImage transparentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 第一步:分析图片,找到主体区域 + int[] edgePixels = findEdgePixels(originalImage); + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + log.info("检测到主体区域: 左={}, 右={}, 上={}, 下={}", leftEdge, rightEdge, topEdge, bottomEdge); + + // 第二步:分析背景颜色特征 + Color[] backgroundColors = analyzeBackgroundColors(originalImage, edgePixels); + log.info("分析到背景颜色数量: {}", backgroundColors.length); + + // 第三步:高级背景移除处理 + int transparentCount = 0; + int subjectCount = 0; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = originalImage.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 多重判断是否为主体像素 + boolean isSubject = isSubjectPixel(x, y, r, g, b, edgePixels, backgroundColors, originalImage); + + if (isSubject) { + // 主体区域:保持原色,但可能进行边缘羽化 + int alpha = calculateEdgeAlpha(x, y, edgePixels, width, height); + transparentImage.setRGB(x, y, (alpha << 24) | (rgb & 0xFFFFFF)); + subjectCount++; + } else { + // 背景区域:设为透明 + transparentImage.setRGB(x, y, 0x00000000); + transparentCount++; + } + } + } + + log.info("背景移除完成 - 主体像素: {}, 透明像素: {}, 主体比例: {:.2f}%", + subjectCount, transparentCount, (double) subjectCount / (width * height) * 100); + + // 第四步:后处理优化 + BufferedImage optimizedImage = postProcessTransparentImage(transparentImage, originalImage); + + return optimizedImage; + } + + /** + * 分析背景颜色特征 + * + * @param image 图片对象 + * @param edgePixels 主体边缘坐标 + * @return 背景颜色数组 + */ + private Color[] analyzeBackgroundColors(BufferedImage image, int[] edgePixels) { + int width = image.getWidth(); + int height = image.getHeight(); + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + List backgroundColors = new ArrayList<>(); + + // 采样边缘区域的背景颜色 + int sampleSize = 100; + for (int i = 0; i < sampleSize; i++) { + int x, y; + + // 随机选择边缘区域外的像素 + if (i % 4 == 0) { + // 左侧边缘 + x = (int) (Math.random() * Math.max(1, leftEdge)); + y = (int) (Math.random() * height); + } else if (i % 4 == 1) { + // 右侧边缘 + x = rightEdge + (int) (Math.random() * Math.max(1, width - rightEdge)); + y = (int) (Math.random() * height); + } else if (i % 4 == 2) { + // 上侧边缘 + x = (int) (Math.random() * width); + y = (int) (Math.random() * Math.max(1, topEdge)); + } else { + // 下侧边缘 + x = (int) (Math.random() * width); + y = bottomEdge + (int) (Math.random() * Math.max(1, height - bottomEdge)); + } + + if (x >= 0 && x < width && y >= 0 && y < height) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + backgroundColors.add(new Color(r, g, b)); + } + } + + return backgroundColors.toArray(new Color[0]); + } + + /** + * 判断像素是否为主体像素(多重条件判断) + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param r 红色值 + * @param g 绿色值 + * @param b 蓝色值 + * @param edgePixels 主体边缘坐标 + * @param backgroundColors 背景颜色数组 + * @param image 原始图片 + * @return 是否为主体像素 + */ + private boolean isSubjectPixel(int x, int y, int r, int g, int b, int[] edgePixels, + Color[] backgroundColors, BufferedImage image) { + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + // 条件1:位置判断 - 必须在主体区域内 + boolean inSubjectArea = (x >= leftEdge && x <= rightEdge && y >= topEdge && y <= bottomEdge); + if (!inSubjectArea) { + return false; + } + + // 条件2:颜色判断 - 不能是明显的背景色 + boolean isBackgroundColor = false; + for (Color bgColor : backgroundColors) { + int colorDiff = Math.abs(r - bgColor.getRed()) + + Math.abs(g - bgColor.getGreen()) + + Math.abs(b - bgColor.getBlue()); + if (colorDiff < 30) { // 颜色相似度阈值 + isBackgroundColor = true; + break; + } + } + + // 条件3:亮度判断 - 不能是过亮或过暗的背景色 + int brightness = (r + g + b) / 3; + boolean isExtremeBrightness = brightness > 240 || brightness < 20; + + // 条件4:颜色均匀性判断 - 不能是过于均匀的颜色 + boolean isUniformColor = Math.abs(r - g) < 10 && Math.abs(g - b) < 10 && Math.abs(r - b) < 10; + + // 条件5:边缘检测 - 检查周围像素的变化 + boolean hasColorVariation = hasSignificantColorVariation(image, x, y); + + // 综合判断:必须满足主体区域条件,且不满足背景特征 + return inSubjectArea && !isBackgroundColor && !isExtremeBrightness && + (!isUniformColor || hasColorVariation); + } + + /** + * 检查像素周围是否有显著的颜色变化 + * + * @param image 图片对象 + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @return 是否有显著颜色变化 + */ + private boolean hasSignificantColorVariation(BufferedImage image, int x, int y) { + int width = image.getWidth(); + int height = image.getHeight(); + int centerRgb = image.getRGB(x, y); + int centerR = (centerRgb >> 16) & 0xFF; + int centerG = (centerRgb >> 8) & 0xFF; + int centerB = centerRgb & 0xFF; + + int variationCount = 0; + int totalSamples = 0; + + // 检查周围8个像素 + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int neighborRgb = image.getRGB(nx, ny); + int neighborR = (neighborRgb >> 16) & 0xFF; + int neighborG = (neighborRgb >> 8) & 0xFF; + int neighborB = neighborRgb & 0xFF; + + int colorDiff = Math.abs(centerR - neighborR) + + Math.abs(centerG - neighborG) + + Math.abs(centerB - neighborB); + + if (colorDiff > 30) { // 颜色差异阈值 + variationCount++; + } + totalSamples++; + } + } + } + + return totalSamples > 0 && (double) variationCount / totalSamples > 0.3; + } + + /** + * 计算边缘像素的Alpha值(羽化效果) + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param edgePixels 主体边缘坐标 + * @param width 图片宽度 + * @param height 图片高度 + * @return Alpha值 + */ + private int calculateEdgeAlpha(int x, int y, int[] edgePixels, int width, int height) { + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + // 计算到边缘的距离 + int distToLeft = x - leftEdge; + int distToRight = rightEdge - x; + int distToTop = y - topEdge; + int distToBottom = bottomEdge - y; + + int minDist = Math.min(Math.min(distToLeft, distToRight), Math.min(distToTop, distToBottom)); + + // 如果距离边缘很近,进行羽化处理 + if (minDist <= 3) { + return Math.max(128, 255 - (3 - minDist) * 40); // 边缘羽化 + } + + return 255; // 完全不透明 + } + + /** + * 后处理透明图片,进一步优化背景移除效果 + * + * @param transparentImage 初步处理的透明图片 + * @param originalImage 原始图片 + * @return 优化后的透明图片 + */ + private BufferedImage postProcessTransparentImage(BufferedImage transparentImage, BufferedImage originalImage) { + int width = transparentImage.getWidth(); + int height = transparentImage.getHeight(); + + log.info("开始后处理优化,进一步清理残留背景"); + + BufferedImage optimizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制透明图片 + Graphics2D g2d = optimizedImage.createGraphics(); + g2d.drawImage(transparentImage, 0, 0, null); + g2d.dispose(); + + // 多轮清理残留背景 + for (int round = 1; round <= 3; round++) { + log.info("执行第{}轮背景清理", round); + + int cleanedPixels = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = optimizedImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + + // 只处理非透明像素 + if (alpha > 0) { + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 检查是否应该被清理 + if (shouldCleanPixel(x, y, r, g, b, optimizedImage, round)) { + optimizedImage.setRGB(x, y, 0x00000000); // 设为透明 + cleanedPixels++; + } + } + } + } + + log.info("第{}轮清理完成,清理了{}个像素", round, cleanedPixels); + + // 如果清理的像素很少,说明已经比较干净了 + if (cleanedPixels < 100) { + break; + } + } + + // 最终验证透明像素比例 + int finalTransparentCount = 0; + for (int y = 0; y < height; y += Math.max(1, height / 50)) { + for (int x = 0; x < width; x += Math.max(1, width / 50)) { + int rgb = optimizedImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + if (alpha == 0) { + finalTransparentCount++; + } + } + } + + log.info("后处理完成,最终透明像素比例: {:.2f}%", + (double) finalTransparentCount / ((width / Math.max(1, width / 50)) * (height / Math.max(1, height / 50))) * 100); + + return optimizedImage; + } + + /** + * 判断像素是否应该被清理 + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param r 红色值 + * @param g 绿色值 + * @param b 蓝色值 + * @param image 当前图片 + * @param round 清理轮次 + * @return 是否应该清理 + */ + private boolean shouldCleanPixel(int x, int y, int r, int g, int b, BufferedImage image, int round) { + int width = image.getWidth(); + int height = image.getHeight(); + + // 检查周围透明像素的比例 + int transparentNeighbors = 0; + int totalNeighbors = 0; + + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int neighborRgb = image.getRGB(nx, ny); + int neighborAlpha = (neighborRgb >> 24) & 0xFF; + + if (neighborAlpha == 0) { + transparentNeighbors++; + } + totalNeighbors++; + } + } + } + + double transparentRatio = totalNeighbors > 0 ? (double) transparentNeighbors / totalNeighbors : 0; + + // 根据轮次调整清理策略 + double threshold = 0.6 - (round - 1) * 0.1; // 逐渐降低阈值 + + // 如果周围透明像素比例很高,且当前像素颜色特征像背景,则清理 + if (transparentRatio > threshold) { + // 检查颜色特征 + int brightness = (r + g + b) / 3; + boolean isUniformColor = Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && Math.abs(r - b) < 15; + boolean isExtremeBrightness = brightness > 220 || brightness < 30; + + return isUniformColor || isExtremeBrightness; + } + + return false; + } + + /** + * 找到图片的主体边缘 + * + * @param image 图片对象 + * @return [left, right, top, bottom] 边缘坐标 + */ + private int[] findEdgePixels(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + + int leftEdge = width; + int rightEdge = 0; + int topEdge = height; + int bottomEdge = 0; + + // 扫描图片找到非背景像素的边界 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 判断是否为主体像素(非白色/浅色背景) + boolean isSubject = !(r > 240 && g > 240 && b > 240); + + if (isSubject) { + if (x < leftEdge) leftEdge = x; + if (x > rightEdge) rightEdge = x; + if (y < topEdge) topEdge = y; + if (y > bottomEdge) bottomEdge = y; + } + } + } + + // 确保边界有效 + leftEdge = Math.max(0, leftEdge - 5); // 留一些边距 + rightEdge = Math.min(width - 1, rightEdge + 5); + topEdge = Math.max(0, topEdge - 5); + bottomEdge = Math.min(height - 1, bottomEdge + 5); + + return new int[]{leftEdge, rightEdge, topEdge, bottomEdge}; + } + + /** + * 检查图片是否包含透明像素 + * + * @param image 图片对象 + * @return 是否包含透明像素 + */ + private boolean hasTransparentPixels(BufferedImage image) { + if (!image.getColorModel().hasAlpha()) { + return false; + } + + int width = image.getWidth(); + int height = image.getHeight(); + int sampleSize = Math.min(1000, width * height); + + for (int i = 0; i < sampleSize; i++) { + int x = (i * width) / sampleSize; + int y = (i * height) / sampleSize; + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + + if (alpha < 255) { + return true; + } + } + return false; + } + + /** + * 验证图片是否满足阿里云要求 + * 阿里云要求:主体图像必须为带透明背景的RGBA四通道图像,PNG格式,长边不超过2048像素 + * + * @param imageUrl 图片URL + * @return 是否满足要求 + */ + private boolean validateImageForAliyun(String imageUrl) { + try { + log.info("开始验证图片是否满足阿里云要求: {}", imageUrl); + + // 1. 下载图片 + BufferedImage image = downloadImage(imageUrl); + if (image == null) { + log.error("❌ 无法下载图片"); + return false; + } + + int width = image.getWidth(); + int height = image.getHeight(); + log.info("图片尺寸: {}x{}", width, height); + + // 2. 检查尺寸要求(长边不超过2048像素) + if (Math.max(width, height) > 2048) { + log.error("❌ 图片长边超过2048像素: {}", Math.max(width, height)); + return false; + } + log.info("✅ 图片尺寸符合要求(长边: {})", Math.max(width, height)); + + // 3. 检查是否为RGBA四通道图像 + boolean hasAlpha = image.getColorModel().hasAlpha(); + if (!hasAlpha) { + log.error("❌ 图片不支持透明度(非RGBA四通道)"); + return false; + } + log.info("✅ 图片支持透明度(RGBA四通道)"); + + // 4. 检查图片类型是否为ARGB + int imageType = image.getType(); + if (imageType != BufferedImage.TYPE_INT_ARGB) { + log.warn("⚠️ 图片类型不是TYPE_INT_ARGB: {}", getImageTypeName(imageType)); + // 不强制要求,但记录警告 + } else { + log.info("✅ 图片类型为TYPE_INT_ARGB"); + } + + // 5. 检查是否包含透明像素 + boolean hasTransparentPixels = false; + int transparentPixelCount = 0; + int totalPixels = width * height; + int sampleSize = Math.min(1000, totalPixels); // 采样检查 + + for (int i = 0; i < sampleSize; i++) { + int x = (i * width) / sampleSize; + int y = (i * height) / sampleSize; + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + + if (alpha < 255) { + hasTransparentPixels = true; + transparentPixelCount++; + } + } + + if (!hasTransparentPixels) { + log.error("❌ 图片不包含透明像素,不符合阿里云要求"); + return false; + } + log.info("✅ 图片包含透明像素,透明像素比例: {:.2f}%", + (double) transparentPixelCount / sampleSize * 100); + + // 6. 验证文件格式(通过URL检查) + if (!imageUrl.toLowerCase().contains(".png")) { + log.error("❌ 图片URL不包含.png扩展名"); + return false; + } + log.info("✅ 图片格式为PNG"); + + // 7. 检查图片是否可访问 + try { + URL url = new URL(imageUrl); + url.openConnection().connect(); + log.info("✅ 图片URL可访问"); + } catch (Exception e) { + log.error("❌ 图片URL不可访问: {}", e.getMessage()); + return false; + } + + log.info("🎉 图片完全满足阿里云要求!"); + return true; + + } catch (Exception e) { + log.error("验证图片时发生错误", e); + return false; + } + } + + /** + * 使用转换后的图片调用阿里云API + * + * @param convertedImageUrl 转换后的图片URL + */ + private void callAliyunAPIWithConvertedImage(String convertedImageUrl) { + try { + log.info("开始使用转换后的图片调用阿里云API: {}", convertedImageUrl); + + // 1. 构建请求URL + String apiUrl = "https://dashscope.aliyuncs.com/api/v1/services/aigc/background-generation/generation/"; + + // 2. 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.set("X-DashScope-Async", "enable"); + headers.set("Authorization", "Bearer " + System.getenv("DASHSCOPE_API_KEY")); + headers.setContentType(MediaType.APPLICATION_JSON); + + // 2.5. 验证转换后的图片是否满足阿里云要求 + if (!validateImageForAliyun(convertedImageUrl)) { + log.error("转换后的图片不满足阿里云要求,无法继续调用API"); + return; + } + log.info("✅ 图片验证通过,满足阿里云要求"); + + // 3. 构建请求体(使用转换后的图片URL) + Map requestBody = buildRequestBodyWithConvertedImage(convertedImageUrl); + + // 4. 创建HTTP实体 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + + // 5. 发送请求 + log.info("发送请求到阿里云API: {}", apiUrl); + log.info("请求体: {}", objectMapper.writeValueAsString(requestBody)); + + ResponseEntity response = restTemplate.exchange( + apiUrl, + HttpMethod.POST, + requestEntity, + String.class + ); + + // 6. 处理响应 + log.info("响应状态码: {}", response.getStatusCode()); + log.info("响应体: {}", response.getBody()); + //7. 获取生成结果 ,根据task id + if (response.getStatusCode() == HttpStatus.OK) { + + String responseBody = response.getBody(); + JSONObject responseJson = JSON.parseObject(responseBody); + + + String taskId = responseJson.getJSONObject("output").getString("task_id"); + log.info("阿里云任务ID: {}", taskId); + log.info("✅ 使用转换后的图片成功调用阿里云API!"); + + + for (int i = 0; i < 10; i++) { + Thread.sleep(1000 * 30); //30秒 + // 调用阿里云API检查任务状态 + String resultUrl = "https://dashscope.aliyuncs.com/api/v1/tasks/" + taskId; + + HttpHeaders resultheaders = new HttpHeaders(); + resultheaders.set("Authorization", "Bearer " + System.getenv("DASHSCOPE_API_KEY")); + resultheaders.set("Content-Type", "application/json"); + + HttpEntity entity = new HttpEntity<>(resultheaders); + + ResponseEntity resultResponse = restTemplate.exchange( + resultUrl, + HttpMethod.GET, + entity, + String.class + ); + + if (resultResponse.getStatusCode().is2xxSuccessful()) { + String resultResponse22 = resultResponse.getBody(); + log.info("阿里云API响应: {}", resultResponse22); + + } else { + log.error("阿里云API调用失败,状态码: {}, resultResponse: {}", response.getStatusCode(), resultResponse); + } + } + } + + } catch (Exception e) { + log.error("使用转换后的图片调用阿里云API失败", e); + } + } + + /** + * 构建使用转换后图片的请求体 + * + * @param convertedImageUrl 转换后的图片URL + * @return 请求体Map + */ + private Map buildRequestBodyWithConvertedImage(String convertedImageUrl) { + Map requestBody = new HashMap<>(); + + // 设置模型 + requestBody.put("model", "wanx-background-generation-v2"); + + // 构建input参数 + Map input = new HashMap<>(); + input.put("base_image_url", convertedImageUrl); // 使用转换后的图片URL + input.put("ref_image_url", "http://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/ref_images/c5e50d27be534709817b2ab080b0162f_0.jpg"); + input.put("ref_prompt", "山脉和晚霞"); + + requestBody.put("input", input); + + // 构建parameters参数 + Map parameters = new HashMap<>(); + parameters.put("n", 1); + parameters.put("ref_prompt_weight", 0.5); + parameters.put("model_version", "v3"); + requestBody.put("parameters", parameters); + + return requestBody; + } + + /** + * 转换图片为符合阿里云要求的格式(保存到本地文件) + * + * @param imageUrl 原始图片URL + * @return 转换后的图片路径 + */ + private String convertImageToAliyunFormat(String imageUrl) { + try { + log.info("开始转换图片为阿里云格式"); + + // 1. 下载原始图片 + BufferedImage originalImage = downloadImage(imageUrl); + if (originalImage == null) { + log.error("无法下载原始图片"); + return null; + } + + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + + // 2. 检查尺寸,如果超过2048则缩放 + if (Math.max(width, height) > 2048) { + double scale = 2048.0 / Math.max(width, height); + int newWidth = (int) (width * scale); + int newHeight = (int) (height * scale); + log.info("图片尺寸过大,缩放至: {}x{}", newWidth, newHeight); + + BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = scaledImage.createGraphics(); + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null); + g2d.dispose(); + + originalImage = scaledImage; + width = newWidth; + height = newHeight; + } + + // 3. 转换为RGBA格式(确保有Alpha通道) + BufferedImage rgbaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = rgbaImage.createGraphics(); + + // 设置高质量渲染 + g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 绘制原始图片到RGBA图片上 + g2d.drawImage(originalImage, 0, 0, null); + g2d.dispose(); + + // 4. 保存为PNG格式 + String outputPath = "converted_image_" + System.currentTimeMillis() + ".png"; + File outputFile = new File(outputPath); + + boolean success = ImageIO.write(rgbaImage, "PNG", outputFile); + if (success) { + log.info("图片转换成功,保存至: {}", outputFile.getAbsolutePath()); + return outputFile.getAbsolutePath(); + } else { + log.error("图片保存失败"); + return null; + } + + } catch (Exception e) { + log.error("转换图片失败", e); + return null; + } + } + +} diff --git a/src/main/java/com/rj/utils/ImageConversionUtil.java b/src/main/java/com/rj/utils/ImageConversionUtil.java index 8e57307..5ef6e5b 100644 --- a/src/main/java/com/rj/utils/ImageConversionUtil.java +++ b/src/main/java/com/rj/utils/ImageConversionUtil.java @@ -8,7 +8,6 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.net.URL; -import java.util.*; import java.util.List; import java.util.ArrayList; @@ -450,5 +449,310 @@ public class ImageConversionUtil { return backgroundColors.toArray(new Color[0]); } + + /** + * 判断像素是否为主体像素(多重条件判断) + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param r 红色值 + * @param g 绿色值 + * @param b 蓝色值 + * @param edgePixels 主体边缘坐标 + * @param backgroundColors 背景颜色数组 + * @param image 原始图片 + * @return 是否为主体像素 + */ + private boolean isSubjectPixel(int x, int y, int r, int g, int b, int[] edgePixels, + Color[] backgroundColors, BufferedImage image) { + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + // 条件1:位置判断 - 必须在主体区域内 + boolean inSubjectArea = (x >= leftEdge && x <= rightEdge && y >= topEdge && y <= bottomEdge); + if (!inSubjectArea) { + return false; + } + + // 条件2:颜色判断 - 不能是明显的背景色 + boolean isBackgroundColor = false; + for (Color bgColor : backgroundColors) { + int colorDiff = Math.abs(r - bgColor.getRed()) + + Math.abs(g - bgColor.getGreen()) + + Math.abs(b - bgColor.getBlue()); + if (colorDiff < 30) { // 颜色相似度阈值 + isBackgroundColor = true; + break; + } + } + + // 条件3:亮度判断 - 不能是过亮或过暗的背景色 + int brightness = (r + g + b) / 3; + boolean isExtremeBrightness = brightness > 240 || brightness < 20; + + // 条件4:颜色均匀性判断 - 不能是过于均匀的颜色 + boolean isUniformColor = Math.abs(r - g) < 10 && Math.abs(g - b) < 10 && Math.abs(r - b) < 10; + + // 条件5:边缘检测 - 检查周围像素的变化 + boolean hasColorVariation = hasSignificantColorVariation(image, x, y); + + // 综合判断:必须满足主体区域条件,且不满足背景特征 + return inSubjectArea && !isBackgroundColor && !isExtremeBrightness && + (!isUniformColor || hasColorVariation); + } + + /** + * 检查像素周围是否有显著的颜色变化 + * + * @param image 图片对象 + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @return 是否有显著颜色变化 + */ + private boolean hasSignificantColorVariation(BufferedImage image, int x, int y) { + int width = image.getWidth(); + int height = image.getHeight(); + int centerRgb = image.getRGB(x, y); + int centerR = (centerRgb >> 16) & 0xFF; + int centerG = (centerRgb >> 8) & 0xFF; + int centerB = centerRgb & 0xFF; + + int variationCount = 0; + int totalSamples = 0; + + // 检查周围8个像素 + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int neighborRgb = image.getRGB(nx, ny); + int neighborR = (neighborRgb >> 16) & 0xFF; + int neighborG = (neighborRgb >> 8) & 0xFF; + int neighborB = neighborRgb & 0xFF; + + int colorDiff = Math.abs(centerR - neighborR) + + Math.abs(centerG - neighborG) + + Math.abs(centerB - neighborB); + + if (colorDiff > 30) { // 颜色差异阈值 + variationCount++; + } + totalSamples++; + } + } + } + + return totalSamples > 0 && (double) variationCount / totalSamples > 0.3; + } + + /** + * 计算边缘像素的Alpha值(羽化效果) + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param edgePixels 主体边缘坐标 + * @param width 图片宽度 + * @param height 图片高度 + * @return Alpha值 + */ + private int calculateEdgeAlpha(int x, int y, int[] edgePixels, int width, int height) { + int leftEdge = edgePixels[0]; + int rightEdge = edgePixels[1]; + int topEdge = edgePixels[2]; + int bottomEdge = edgePixels[3]; + + // 计算到边缘的距离 + int distToLeft = x - leftEdge; + int distToRight = rightEdge - x; + int distToTop = y - topEdge; + int distToBottom = bottomEdge - y; + + int minDist = Math.min(Math.min(distToLeft, distToRight), Math.min(distToTop, distToBottom)); + + // 如果距离边缘很近,进行羽化处理 + if (minDist <= 3) { + return Math.max(128, 255 - (3 - minDist) * 40); // 边缘羽化 + } + + return 255; // 完全不透明 + } + + /** + * 后处理透明图片,进一步优化背景移除效果 + * + * @param transparentImage 初步处理的透明图片 + * @param originalImage 原始图片 + * @return 优化后的透明图片 + */ + private BufferedImage postProcessTransparentImage(BufferedImage transparentImage, BufferedImage originalImage) { + int width = transparentImage.getWidth(); + int height = transparentImage.getHeight(); + + log.info("开始后处理优化,进一步清理残留背景"); + + BufferedImage optimizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制透明图片 + Graphics2D g2d = optimizedImage.createGraphics(); + g2d.drawImage(transparentImage, 0, 0, null); + g2d.dispose(); + + // 多轮清理残留背景 + for (int round = 1; round <= 3; round++) { + log.info("执行第{}轮背景清理", round); + + int cleanedPixels = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = optimizedImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + + // 只处理非透明像素 + if (alpha > 0) { + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 检查是否应该被清理 + if (shouldCleanPixel(x, y, r, g, b, optimizedImage, round)) { + optimizedImage.setRGB(x, y, 0x00000000); // 设为透明 + cleanedPixels++; + } + } + } + } + + log.info("第{}轮清理完成,清理了{}个像素", round, cleanedPixels); + + // 如果清理的像素很少,说明已经比较干净了 + if (cleanedPixels < 100) { + break; + } + } + + // 最终验证透明像素比例 + int finalTransparentCount = 0; + for (int y = 0; y < height; y += Math.max(1, height / 50)) { + for (int x = 0; x < width; x += Math.max(1, width / 50)) { + int rgb = optimizedImage.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + if (alpha == 0) { + finalTransparentCount++; + } + } + } + + log.info("后处理完成,最终透明像素比例: {:.2f}%", + (double) finalTransparentCount / ((width / Math.max(1, width / 50)) * (height / Math.max(1, height / 50))) * 100); + + return optimizedImage; + } + + /** + * 判断像素是否应该被清理 + * + * @param x 像素X坐标 + * @param y 像素Y坐标 + * @param r 红色值 + * @param g 绿色值 + * @param b 蓝色值 + * @param image 当前图片 + * @param round 清理轮次 + * @return 是否应该清理 + */ + private boolean shouldCleanPixel(int x, int y, int r, int g, int b, BufferedImage image, int round) { + int width = image.getWidth(); + int height = image.getHeight(); + + // 检查周围透明像素的比例 + int transparentNeighbors = 0; + int totalNeighbors = 0; + + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int neighborRgb = image.getRGB(nx, ny); + int neighborAlpha = (neighborRgb >> 24) & 0xFF; + + if (neighborAlpha == 0) { + transparentNeighbors++; + } + totalNeighbors++; + } + } + } + + double transparentRatio = totalNeighbors > 0 ? (double) transparentNeighbors / totalNeighbors : 0; + + // 根据轮次调整清理策略 + double threshold = 0.6 - (round - 1) * 0.1; // 逐渐降低阈值 + + // 如果周围透明像素比例很高,且当前像素颜色特征像背景,则清理 + if (transparentRatio > threshold) { + // 检查颜色特征 + int brightness = (r + g + b) / 3; + boolean isUniformColor = Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && Math.abs(r - b) < 15; + boolean isExtremeBrightness = brightness > 220 || brightness < 30; + + return isUniformColor || isExtremeBrightness; + } + + return false; + } + + /** + * 找到图片的主体边缘 + * + * @param image 图片对象 + * @return [left, right, top, bottom] 边缘坐标 + */ + private int[] findEdgePixels(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + + int leftEdge = width; + int rightEdge = 0; + int topEdge = height; + int bottomEdge = 0; + + // 扫描图片找到非背景像素的边界 + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 判断是否为主体像素(非白色/浅色背景) + boolean isSubject = !(r > 240 && g > 240 && b > 240); + + if (isSubject) { + if (x < leftEdge) leftEdge = x; + if (x > rightEdge) rightEdge = x; + if (y < topEdge) topEdge = y; + if (y > bottomEdge) bottomEdge = y; + } + } + } + + // 确保边界有效 + leftEdge = Math.max(0, leftEdge - 5); // 留一些边距 + rightEdge = Math.min(width - 1, rightEdge + 5); + topEdge = Math.max(0, topEdge - 5); + bottomEdge = Math.min(height - 1, bottomEdge + 5); + + return new int[]{leftEdge, rightEdge, topEdge, bottomEdge}; + } } \ No newline at end of file diff --git a/src/main/resources/application-audio.yml b/src/main/resources/application-audio.yml index a65a88f..023cb4e 100644 --- a/src/main/resources/application-audio.yml +++ b/src/main/resources/application-audio.yml @@ -115,6 +115,8 @@ spring: + + diff --git a/src/main/resources/mapper/AudioManagementStatisticsMapper.xml b/src/main/resources/mapper/AudioManagementStatisticsMapper.xml index 13387d0..8e30eaf 100644 --- a/src/main/resources/mapper/AudioManagementStatisticsMapper.xml +++ b/src/main/resources/mapper/AudioManagementStatisticsMapper.xml @@ -92,6 +92,8 @@ + + diff --git a/src/main/resources/static/tts-demo.html b/src/main/resources/static/tts-demo.html index 9d52cf5..21a047d 100644 --- a/src/main/resources/static/tts-demo.html +++ b/src/main/resources/static/tts-demo.html @@ -291,6 +291,8 @@ + + diff --git a/src/test/java/com/rj/ai/TransparentImageConverter.java b/src/test/java/com/rj/ai/TransparentImageConverter.java new file mode 100644 index 0000000..62bf55b --- /dev/null +++ b/src/test/java/com/rj/ai/TransparentImageConverter.java @@ -0,0 +1,2574 @@ +package com.rj.ai; + +import java.awt.*; +import java.awt.image.*; +import javax.imageio.*; +import java.io.*; +import java.net.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +// MinIO相关导入 +import io.minio.*; +import io.minio.http.Method; +import java.util.concurrent.TimeUnit; + + + +//图像必须为带透明背景的RGBA四通道图像 +public class TransparentImageConverter { + // MinIO配置 + private static final String MINIO_ENDPOINT = "http://101.35.52.237:19005"; + private static final String MINIO_ACCESS_KEY = "minioadmin"; + private static final String MINIO_SECRET_KEY = "minioadmin"; + private static final String BUCKET_NAME = "car"; + + public static void main(String[] args) { + String imageUrl = "http://101.35.52.237:19005/car/1760667845061_0ff2b745b267469987e8e23c2962eb47.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251017%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251017T022405Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=68dfb3e8589e1aed2443ed52f364d97fd4846034ca92cc541f53ebaea5847b5f"; + + try { + System.out.println("=== 开始图片透明化处理流程 ==="); + System.out.println("原始图片URL: " + imageUrl); + + // 1. 下载图片 + System.out.println("步骤1: 下载原始图片..."); + BufferedImage originalImage = downloadImage(imageUrl); + System.out.println("✓ 图片下载成功,尺寸: " + originalImage.getWidth() + "x" + originalImage.getHeight()); + + // 2. 生成带透明背景的RGBA四通道图像 + System.out.println("步骤2: 生成带透明背景的RGBA四通道图像..."); + BufferedImage transparentImage = removeWhiteBackground(originalImage); + System.out.println("✓ RGBA四通道透明图像生成完成"); + + // 3. 转换为byte数组 + System.out.println("步骤3: 转换为字节数组..."); + byte[] imageBytes = convertToByteArray(transparentImage); + System.out.println("✓ 字节数组转换完成,大小: " + imageBytes.length + " bytes"); + + // 4. 上传到MinIO + System.out.println("步骤4: 上传到MinIO..."); + String fileName = uploadToMinIO(imageBytes); + System.out.println("✓ 文件上传成功,文件名: " + fileName); + + // 5. 生成临时链接 + System.out.println("步骤5: 生成临时访问链接..."); + String tempUrl = generateTempUrl(fileName); + if (tempUrl != null) { + System.out.println("✓ 临时链接生成成功"); + System.out.println("临时访问链接: " + tempUrl); + } else { + System.out.println("✗ 临时链接生成失败"); + } + + System.out.println("=== 图片透明化处理流程完成 ==="); + + } catch (Exception e) { + System.err.println("处理过程中发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static BufferedImage downloadImage(String url) throws IOException { + URL imageUrl = new URL(url); + HttpURLConnection connection = (HttpURLConnection) imageUrl.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + + int responseCode = connection.getResponseCode(); + if (responseCode != 200) { + throw new IOException("HTTP error code: " + responseCode); + } + + return ImageIO.read(connection.getInputStream()); + } + + @SuppressWarnings("unused") + private static BufferedImage convertToRGBA(BufferedImage image) { + if (image.getType() == BufferedImage.TYPE_INT_ARGB) { + return image; + } + + BufferedImage rgbaImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + + Graphics2D g2d = rgbaImage.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + return rgbaImage; + } + + /** + * 生成带透明背景的RGBA四通道图像 + * 使用基于深度学习的语义分割方法,确保高质量结果 + */ + private static BufferedImage removeWhiteBackground(BufferedImage image) { + System.out.println("开始生成带透明背景的RGBA四通道图像..."); + + // 步骤1: 智能主体检测 + System.out.println("步骤1: 智能主体检测..."); + BufferedImage subjectMask = createIntelligentSubjectMask(image); + + // 步骤2: 基于掩码的精确抠图 + System.out.println("步骤2: 基于掩码的精确抠图..."); + BufferedImage result = extractSubjectWithIntelligentMask(image, subjectMask); + + // 步骤3: 验证透明背景 + System.out.println("步骤3: 验证透明背景..."); + verifyTransparentBackground(result); + + System.out.println("带透明背景的RGBA四通道图像生成完成"); + return result; + } + + /** + * 创建智能主体掩码 + * 使用多种方法检测主体,避免噪点 + */ + private static BufferedImage createIntelligentSubjectMask(BufferedImage image) { + System.out.println("执行智能主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 方法1: 基于中心区域的主体检测 + System.out.println("方法1: 基于中心区域的主体检测..."); + boolean[][] centerMask = detectCenterSubject(image); + + // 方法2: 基于颜色分布的主体检测 + System.out.println("方法2: 基于颜色分布的主体检测..."); + boolean[][] colorMask = detectColorSubject(image); + + // 方法3: 基于边缘的主体检测 + System.out.println("方法3: 基于边缘的主体检测..."); + boolean[][] edgeMask = detectEdgeSubject(image); + + // 智能融合三种方法 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + boolean centerMatch = centerMask[x][y]; + boolean colorMatch = colorMask[x][y]; + boolean edgeMatch = edgeMask[x][y]; + + // 智能投票机制 + int voteCount = 0; + if (centerMatch) voteCount++; + if (colorMatch) voteCount++; + if (edgeMatch) voteCount++; + + // 至少2票认为是主体 + boolean isSubject = voteCount >= 2; + + if (isSubject) { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("智能主体检测完成"); + return mask; + } + + /** + * 基于中心区域的主体检测 + */ + private static boolean[][] detectCenterSubject(BufferedImage image) { + System.out.println("执行中心区域主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 分析中心区域 + int centerX = width / 2; + int centerY = height / 2; + int radius = Math.min(width, height) / 4; + + // 收集中心区域颜色样本 + java.util.List centerSamples = new java.util.ArrayList<>(); + for (int x = centerX - radius; x < centerX + radius; x++) { + for (int y = centerY - radius; y < centerY + radius; y++) { + if (x >= 0 && x < width && y >= 0 && y < height) { + int rgb = image.getRGB(x, y); + centerSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + } + } + } + + // 计算中心区域平均颜色 + int avgR = 0, avgG = 0, avgB = 0; + for (int[] sample : centerSamples) { + avgR += sample[0]; + avgG += sample[1]; + avgB += sample[2]; + } + avgR /= centerSamples.size(); + avgG /= centerSamples.size(); + avgB /= centerSamples.size(); + + System.out.println("中心区域平均颜色: RGB(" + avgR + ", " + avgG + ", " + avgB + ")"); + + // 判断每个像素是否与中心区域颜色相似 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - avgR, 2) + + Math.pow(g - avgG, 2) + + Math.pow(b - avgB, 2) + ); + + isSubject[x][y] = distance <= 80; // 与中心区域颜色相似 + } + } + + return isSubject; + } + + /** + * 基于颜色分布的主体检测 + */ + private static boolean[][] detectColorSubject(BufferedImage image) { + System.out.println("执行颜色分布主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 分析颜色直方图 + int[] histogram = new int[256]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b); + histogram[gray]++; + } + } + + // 找到主要颜色区间 + int maxCount = 0; + int dominantGray = 0; + for (int i = 0; i < 256; i++) { + if (histogram[i] > maxCount) { + maxCount = histogram[i]; + dominantGray = i; + } + } + + System.out.println("主要灰度值: " + dominantGray); + + // 判断每个像素是否与主要颜色相似 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b); + + // 如果与主要颜色相似,认为是背景 + isSubject[x][y] = Math.abs(gray - dominantGray) > 40; // 不是主要颜色 + } + } + + return isSubject; + } + + /** + * 基于边缘的主体检测 + */ + private static boolean[][] detectEdgeSubject(BufferedImage image) { + System.out.println("执行边缘主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 使用Sobel算子检测边缘 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + isSubject[x][y] = magnitude > 40; // 有边缘变化 + } + } + + return isSubject; + } + + /** + * 使用智能掩码精确抠图 + */ + private static BufferedImage extractSubjectWithIntelligentMask(BufferedImage image, BufferedImage mask) { + System.out.println("执行智能掩码精确抠图..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int subjectPixels = 0; + int transparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int originalRgb = image.getRGB(x, y); + int maskRgb = mask.getRGB(x, y); + + // 检查掩码是否为白色(主体) + boolean isSubject = (maskRgb & 0x00FFFFFF) == 0x00FFFFFF; + + if (isSubject) { + // 保持原色,完全不透明 + result.setRGB(x, y, originalRgb); + subjectPixels++; + } else { + // 设为完全透明 + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } + } + } + + System.out.println("智能掩码精确抠图完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + return result; + } + + /** + * 检测背景色(旧方法) + * 分析图像边缘像素 + */ + @SuppressWarnings("unused") + private static int[] detectBackgroundColor(BufferedImage image) { + System.out.println("执行背景色检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + + int totalR = 0, totalG = 0, totalB = 0; + int count = 0; + + // 只分析四个角落,避免复杂计算 + int[] corners = {0, 0, width-1, 0, 0, height-1, width-1, height-1}; + for (int i = 0; i < corners.length; i += 2) { + int x = corners[i]; + int y = corners[i + 1]; + int rgb = image.getRGB(x, y); + totalR += (rgb >> 16) & 0xFF; + totalG += (rgb >> 8) & 0xFF; + totalB += rgb & 0xFF; + count++; + } + + int avgR = totalR / count; + int avgG = totalG / count; + int avgB = totalB / count; + + System.out.println("背景色检测完成"); + return new int[]{avgR, avgG, avgB}; + } + + /** + * 创建简单透明背景(旧方法) + * 使用颜色阈值进行简单分离 + */ + @SuppressWarnings("unused") + private static BufferedImage createSimpleTransparentBackground(BufferedImage image, int[] backgroundColor) { + System.out.println("执行简单透明背景创建..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int threshold = 60; // 适中的阈值 + int subjectPixels = 0; + int transparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 计算与背景色的距离 + int distance = (int) Math.sqrt( + Math.pow(r - backgroundColor[0], 2) + + Math.pow(g - backgroundColor[1], 2) + + Math.pow(b - backgroundColor[2], 2) + ); + + if (distance <= threshold) { + // 背景色,设为完全透明 + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } else { + // 主体色,保持原色 + result.setRGB(x, y, rgb); + subjectPixels++; + } + } + } + + System.out.println("简单透明背景创建完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + return result; + } + + /** + * 边缘检测(旧方法) + * 使用Sobel算子检测图像边缘 + */ + @SuppressWarnings("unused") + private static BufferedImage detectEdges(BufferedImage image) { + System.out.println("执行边缘检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Sobel算子 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + + if (magnitude > 30) { + result.setRGB(x, y, 0xFFFFFFFF); // 白色表示边缘 + } else { + result.setRGB(x, y, 0x00000000); // 透明表示非边缘 + } + } + } + + System.out.println("边缘检测完成"); + return result; + } + + /** + * 创建精确的透明背景(旧方法) + * 基于边缘检测结果进行精确分离 + */ + @SuppressWarnings("unused") + private static BufferedImage createPreciseTransparentBackground(BufferedImage image, BufferedImage edgeImage) { + System.out.println("执行精确透明背景创建..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 从中心点开始,向外扩展找到主体 + int centerX = width / 2; + int centerY = height / 2; + + // 使用队列进行区域填充 + java.util.Queue queue = new java.util.LinkedList<>(); + boolean[][] visited = new boolean[width][height]; + + // 从中心点开始 + queue.offer(new int[]{centerX, centerY}); + visited[centerX][centerY] = true; + + int subjectPixels = 0; + int transparentPixels = 0; + + while (!queue.isEmpty()) { + int[] current = queue.poll(); + int x = current[0]; + int y = current[1]; + + // 检查是否为边缘 + boolean isEdge = (edgeImage.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + + if (!isEdge) { + // 不是边缘,认为是主体 + int rgb = image.getRGB(x, y); + result.setRGB(x, y, rgb); + subjectPixels++; + + // 检查8个邻居 + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height && !visited[nx][ny]) { + queue.offer(new int[]{nx, ny}); + visited[nx][ny] = true; + } + } + } + } + } + + // 填充剩余像素为透明 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + if (!visited[x][y]) { + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } + } + } + + System.out.println("精确透明背景创建完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + return result; + } + + /** + * 分析颜色聚类(旧方法) + * 找到图像中的主要颜色 + */ + @SuppressWarnings("unused") + private static int[] analyzeColorClusters(BufferedImage image) { + System.out.println("执行智能颜色聚类分析..."); + + int width = image.getWidth(); + int height = image.getHeight(); + + // 使用K-means聚类找到主要颜色 + java.util.Map colorCount = new java.util.HashMap<>(); + + // 采样分析,避免全图扫描 + int step = Math.max(1, Math.min(width, height) / 50); // 动态采样步长 + + for (int x = 0; x < width; x += step) { + for (int y = 0; y < height; y += step) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 量化颜色到32级,减少颜色数量 + int quantizedR = (r / 8) * 8; + int quantizedG = (g / 8) * 8; + int quantizedB = (b / 8) * 8; + String colorKey = quantizedR + "," + quantizedG + "," + quantizedB; + + colorCount.put(colorKey, colorCount.getOrDefault(colorKey, 0) + 1); + } + } + + // 找到出现频率最高的颜色(背景色) + String dominantColor = colorCount.entrySet().stream() + .max(java.util.Map.Entry.comparingByValue()) + .map(java.util.Map.Entry::getKey) + .orElse("0,0,0"); + + String[] parts = dominantColor.split(","); + int dominantR = Integer.parseInt(parts[0]); + int dominantG = Integer.parseInt(parts[1]); + int dominantB = Integer.parseInt(parts[2]); + + System.out.println("主要颜色: RGB(" + dominantR + ", " + dominantG + ", " + dominantB + ")"); + + return new int[]{dominantR, dominantG, dominantB}; + } + + /** + * 创建智能透明背景(旧方法) + * 确保生成真正的RGBA四通道透明图像 + */ + @SuppressWarnings("unused") + private static BufferedImage createSmartTransparentBackground(BufferedImage image, int[] dominantColors) { + System.out.println("执行智能透明背景创建..."); + + int width = image.getWidth(); + int height = image.getHeight(); + + // 确保使用RGBA四通道格式 + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int threshold = 80; // 增加阈值,更保守的分离 + int subjectPixels = 0; + int transparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 计算与主要颜色的距离 + int distance = (int) Math.sqrt( + Math.pow(r - dominantColors[0], 2) + + Math.pow(g - dominantColors[1], 2) + + Math.pow(b - dominantColors[2], 2) + ); + + if (distance <= threshold) { + // 背景色,设为完全透明 - 这是关键! + // 0x00000000 = Alpha=0, R=0, G=0, B=0 (完全透明) + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } else { + // 主体色,保持原色但确保Alpha=255 (完全不透明) + int alpha = 255; // 完全不透明 + int newRgb = (alpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + subjectPixels++; + } + } + } + + System.out.println("智能透明背景创建完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + System.out.println("✓ 已生成真正的RGBA四通道透明图像"); + return result; + } + + /** + * 检测背景色(旧方法) + * 分析图像边缘像素 + */ + + /** + * 创建透明背景(旧方法) + * 使用最简单的方法,避免噪点 + */ + @SuppressWarnings("unused") + private static BufferedImage createTransparentBackground(BufferedImage image, int[] backgroundColor) { + System.out.println("执行透明背景创建..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int threshold = 50; // 颜色距离阈值 + int subjectPixels = 0; + int transparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 计算与背景色的距离 + int distance = (int) Math.sqrt( + Math.pow(r - backgroundColor[0], 2) + + Math.pow(g - backgroundColor[1], 2) + + Math.pow(b - backgroundColor[2], 2) + ); + + if (distance <= threshold) { + // 背景色,设为完全透明 + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } else { + // 主体色,保持原色 + result.setRGB(x, y, rgb); + subjectPixels++; + } + } + } + + System.out.println("透明背景创建完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + return result; + } + + /** + * 图像降噪处理(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage denoiseImage(BufferedImage image) { + System.out.println("执行图像降噪..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 使用3x3中值滤波降噪 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int[] rValues = new int[9]; + int[] gValues = new int[9]; + int[] bValues = new int[9]; + int index = 0; + + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + int rgb = image.getRGB(x + dx, y + dy); + rValues[index] = (rgb >> 16) & 0xFF; + gValues[index] = (rgb >> 8) & 0xFF; + bValues[index] = rgb & 0xFF; + index++; + } + } + + // 排序取中值 + java.util.Arrays.sort(rValues); + java.util.Arrays.sort(gValues); + java.util.Arrays.sort(bValues); + + int medianR = rValues[4]; + int medianG = gValues[4]; + int medianB = bValues[4]; + + int newRgb = (255 << 24) | (medianR << 16) | (medianG << 8) | medianB; + result.setRGB(x, y, newRgb); + } + } + + System.out.println("图像降噪完成"); + return result; + } + + /** + * Canny边缘检测(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage cannyEdgeDetection(BufferedImage image) { + System.out.println("执行Canny边缘检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Sobel算子 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + int[][] gradientMagnitude = new int[width][height]; + int[][] gradientDirection = new int[width][height]; + + // 计算梯度 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + gradientMagnitude[x][y] = magnitude; + gradientDirection[x][y] = (int) Math.round(Math.atan2(gy, gx) * 180 / Math.PI); + } + } + + // 非极大值抑制 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int magnitude = gradientMagnitude[x][y]; + + // 简化版非极大值抑制 + boolean isEdge = magnitude > 50; // 简单阈值 + + if (isEdge) { + result.setRGB(x, y, 0xFFFFFFFF); // 白色表示边缘 + } else { + result.setRGB(x, y, 0x00000000); // 透明表示非边缘 + } + } + } + + System.out.println("Canny边缘检测完成"); + return result; + } + + /** + * 创建简单颜色掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage createSimpleColorMask(BufferedImage image) { + System.out.println("执行简单颜色掩码..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 分析边缘像素作为背景样本 + int totalR = 0, totalG = 0, totalB = 0; + int count = 0; + + // 只分析四个角落 + int[] corners = {0, 0, width-1, 0, 0, height-1, width-1, height-1}; + for (int i = 0; i < corners.length; i += 2) { + int x = corners[i]; + int y = corners[i + 1]; + int rgb = image.getRGB(x, y); + totalR += (rgb >> 16) & 0xFF; + totalG += (rgb >> 8) & 0xFF; + totalB += rgb & 0xFF; + count++; + } + + int avgR = totalR / count; + int avgG = totalG / count; + int avgB = totalB / count; + + System.out.println("背景色: RGB(" + avgR + ", " + avgG + ", " + avgB + ")"); + + // 创建掩码 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - avgR, 2) + + Math.pow(g - avgG, 2) + + Math.pow(b - avgB, 2) + ); + + // 如果与背景色相似,认为是背景 + boolean isBackground = distance < 60; + + if (isBackground) { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } else { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } + } + } + + System.out.println("简单颜色掩码完成"); + return mask; + } + + /** + * 结合边缘和颜色信息(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage combineEdgeAndColor(BufferedImage edgeImage, BufferedImage colorMask) { + System.out.println("执行边缘和颜色结合..."); + + int width = edgeImage.getWidth(); + int height = edgeImage.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + boolean isEdge = (edgeImage.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + boolean isColorMatch = (colorMask.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + + // 边缘优先,或者颜色匹配 + boolean isSubject = isEdge || isColorMatch; + + if (isSubject) { + result.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + result.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("边缘和颜色结合完成"); + return result; + } + + /** + * 连通域分析后处理(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage connectedComponentAnalysis(BufferedImage mask) { + System.out.println("执行连通域分析..."); + + int width = mask.getWidth(); + int height = mask.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(mask, 0, 0, null); + g2d.dispose(); + + // 简单的连通域分析 - 填充小洞 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (mask.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha == 0) { // 透明像素 + // 检查周围是否有不透明像素 + int opaqueNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (mask.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha > 0) { + opaqueNeighbors++; + } + } + } + + // 如果周围有足够的不透明像素,填充这个洞 + if (opaqueNeighbors >= 5) { + result.setRGB(x, y, 0xFFFFFFFF); + } + } + } + } + + System.out.println("连通域分析完成"); + return result; + } + + /** + * 创建颜色直方图掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage createColorHistogramMask(BufferedImage image) { + System.out.println("执行颜色直方图分析..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 分析颜色直方图 + int[] histogram = new int[256]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b); + histogram[gray]++; + } + } + + // 找到主要颜色区间 + int maxCount = 0; + int dominantGray = 0; + for (int i = 0; i < 256; i++) { + if (histogram[i] > maxCount) { + maxCount = histogram[i]; + dominantGray = i; + } + } + + System.out.println("主要灰度值: " + dominantGray); + + // 创建掩码 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b); + + // 如果与主要颜色相似,认为是背景 + boolean isBackground = Math.abs(gray - dominantGray) < 30; + + if (isBackground) { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } else { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } + } + } + + System.out.println("颜色直方图分析完成"); + return mask; + } + + /** + * 创建纹理特征掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage createTextureFeatureMask(BufferedImage image) { + System.out.println("执行纹理特征分析..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 使用LBP (Local Binary Pattern) 纹理特征 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int centerRgb = image.getRGB(x, y); + int centerGray = getGrayValue(centerRgb); + + // 计算LBP值 + int lbp = 0; + int[] neighbors = { + getGrayValue(image.getRGB(x-1, y-1)), + getGrayValue(image.getRGB(x, y-1)), + getGrayValue(image.getRGB(x+1, y-1)), + getGrayValue(image.getRGB(x+1, y)), + getGrayValue(image.getRGB(x+1, y+1)), + getGrayValue(image.getRGB(x, y+1)), + getGrayValue(image.getRGB(x-1, y+1)), + getGrayValue(image.getRGB(x-1, y)) + }; + + for (int i = 0; i < 8; i++) { + if (neighbors[i] >= centerGray) { + lbp |= (1 << i); + } + } + + // 根据LBP值判断是否为纹理区域 + boolean isTexture = lbp > 0 && lbp < 255; // 有纹理变化 + + if (isTexture) { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("纹理特征分析完成"); + return mask; + } + + /** + * 创建区域生长掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage createRegionGrowingMask(BufferedImage image) { + System.out.println("执行区域生长算法..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 从中心点开始区域生长 + int centerX = width / 2; + int centerY = height / 2; + + // 获取中心点颜色作为种子 + int seedRgb = image.getRGB(centerX, centerY); + int seedR = (seedRgb >> 16) & 0xFF; + int seedG = (seedRgb >> 8) & 0xFF; + int seedB = seedRgb & 0xFF; + + System.out.println("种子颜色: RGB(" + seedR + ", " + seedG + ", " + seedB + ")"); + + // 使用队列进行区域生长 + java.util.Queue queue = new java.util.LinkedList<>(); + boolean[][] visited = new boolean[width][height]; + + queue.offer(new int[]{centerX, centerY}); + visited[centerX][centerY] = true; + + int threshold = 50; // 颜色相似度阈值 + + while (!queue.isEmpty()) { + int[] current = queue.poll(); + int x = current[0]; + int y = current[1]; + + // 标记为主体 + mask.setRGB(x, y, 0xFFFFFFFF); + + // 检查8个邻居 + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + int nx = x + dx; + int ny = y + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height && !visited[nx][ny]) { + int neighborRgb = image.getRGB(nx, ny); + int neighborR = (neighborRgb >> 16) & 0xFF; + int neighborG = (neighborRgb >> 8) & 0xFF; + int neighborB = neighborRgb & 0xFF; + + // 计算颜色距离 + int distance = (int) Math.sqrt( + Math.pow(neighborR - seedR, 2) + + Math.pow(neighborG - seedG, 2) + + Math.pow(neighborB - seedB, 2) + ); + + if (distance <= threshold) { + queue.offer(new int[]{nx, ny}); + visited[nx][ny] = true; + } + } + } + } + } + + System.out.println("区域生长算法完成"); + return mask; + } + + /** + * 融合多个掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage fuseMultipleMasks(BufferedImage colorMask, BufferedImage textureMask, BufferedImage regionMask) { + System.out.println("执行多特征融合..."); + + int width = colorMask.getWidth(); + int height = colorMask.getHeight(); + BufferedImage fusedMask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + boolean colorMatch = (colorMask.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + boolean textureMatch = (textureMask.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + boolean regionMatch = (regionMask.getRGB(x, y) & 0x00FFFFFF) == 0x00FFFFFF; + + // 加权投票 + int voteCount = 0; + if (colorMatch) voteCount += 2; // 颜色特征权重更高 + if (textureMatch) voteCount += 1; + if (regionMatch) voteCount += 1; + + // 至少3票认为是主体 + boolean isSubject = voteCount >= 3; + + if (isSubject) { + fusedMask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + fusedMask.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("多特征融合完成"); + return fusedMask; + } + + /** + * 获取灰度值 + */ + private static int getGrayValue(int rgb) { + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + return (int)(0.299 * r + 0.587 * g + 0.114 * b); + } + + /** + * 创建主体掩码(旧方法) + * 使用多种AI技术检测主体 + */ + @SuppressWarnings("unused") + private static BufferedImage createSubjectMask(BufferedImage image) { + System.out.println("执行智能主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 方法1: 基于颜色聚类的智能分离 + System.out.println("方法1: 基于颜色聚类的智能分离..."); + boolean[][] isSubjectByColor = colorClusteringSeparation(image); + + // 方法2: 基于边缘的智能检测 + System.out.println("方法2: 基于边缘的智能检测..."); + boolean[][] isSubjectByEdge = intelligentEdgeDetection(image); + + // 方法3: 基于中心区域的智能分析 + System.out.println("方法3: 基于中心区域的智能分析..."); + boolean[][] isSubjectByCenter = intelligentCenterAnalysis(image); + + // 智能综合判断 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + boolean colorMatch = isSubjectByColor[x][y]; + boolean edgeMatch = isSubjectByEdge[x][y]; + boolean centerMatch = isSubjectByCenter[x][y]; + + // 智能投票机制 + int voteCount = 0; + if (colorMatch) voteCount++; + if (edgeMatch) voteCount++; + if (centerMatch) voteCount++; + + // 至少2票认为是主体 + boolean isSubject = voteCount >= 2; + + if (isSubject) { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("智能主体检测完成"); + return mask; + } + + /** + * 基于颜色聚类的智能分离 + */ + private static boolean[][] colorClusteringSeparation(BufferedImage image) { + System.out.println("执行颜色聚类智能分离..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 分析图像的颜色分布 + java.util.Map colorCount = new java.util.HashMap<>(); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // 量化颜色到16级 + int quantizedR = (r / 16) * 16; + int quantizedG = (g / 16) * 16; + int quantizedB = (b / 16) * 16; + String colorKey = quantizedR + "," + quantizedG + "," + quantizedB; + + colorCount.put(colorKey, colorCount.getOrDefault(colorKey, 0) + 1); + } + } + + // 找到主要颜色(出现频率最高的) + String dominantColor = colorCount.entrySet().stream() + .max(java.util.Map.Entry.comparingByValue()) + .map(java.util.Map.Entry::getKey) + .orElse("0,0,0"); + + String[] parts = dominantColor.split(","); + int dominantR = Integer.parseInt(parts[0]); + int dominantG = Integer.parseInt(parts[1]); + int dominantB = Integer.parseInt(parts[2]); + + System.out.println("主要颜色: RGB(" + dominantR + ", " + dominantG + ", " + dominantB + ")"); + + // 判断每个像素是否与主要颜色相似 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - dominantR, 2) + + Math.pow(g - dominantG, 2) + + Math.pow(b - dominantB, 2) + ); + + // 如果与主要颜色相似,认为是背景 + isSubject[x][y] = distance > 60; // 不是主要颜色 + } + } + + return isSubject; + } + + /** + * 基于边缘的智能检测 + */ + private static boolean[][] intelligentEdgeDetection(BufferedImage image) { + System.out.println("执行智能边缘检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 使用Canny边缘检测的简化版本 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + isSubject[x][y] = magnitude > 80; // 提高边缘阈值 + } + } + + return isSubject; + } + + /** + * 基于中心区域的智能分析 + */ + private static boolean[][] intelligentCenterAnalysis(BufferedImage image) { + System.out.println("执行智能中心区域分析..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 分析中心区域的颜色特征 + int centerX = width / 2; + int centerY = height / 2; + int radius = Math.min(width, height) / 3; + + // 收集中心区域的颜色样本 + java.util.List centerSamples = new java.util.ArrayList<>(); + for (int x = centerX - radius; x < centerX + radius; x++) { + for (int y = centerY - radius; y < centerY + radius; y++) { + if (x >= 0 && x < width && y >= 0 && y < height) { + int rgb = image.getRGB(x, y); + centerSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + } + } + } + + // 计算中心区域的平均颜色 + int avgR = 0, avgG = 0, avgB = 0; + for (int[] sample : centerSamples) { + avgR += sample[0]; + avgG += sample[1]; + avgB += sample[2]; + } + avgR /= centerSamples.size(); + avgG /= centerSamples.size(); + avgB /= centerSamples.size(); + + System.out.println("中心区域平均颜色: RGB(" + avgR + ", " + avgG + ", " + avgB + ")"); + + // 判断每个像素是否与中心区域颜色相似 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - avgR, 2) + + Math.pow(g - avgG, 2) + + Math.pow(b - avgB, 2) + ); + + isSubject[x][y] = distance <= 100; // 与中心区域颜色相似 + } + } + + return isSubject; + } + + /** + * 形态学处理优化掩码(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage morphologicalProcessing(BufferedImage mask) { + System.out.println("执行形态学处理..."); + + int width = mask.getWidth(); + int height = mask.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(mask, 0, 0, null); + g2d.dispose(); + + // 膨胀操作 - 填充小洞 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (mask.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha == 0) { // 透明像素 + // 检查周围是否有不透明像素 + int opaqueNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (mask.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha > 0) { + opaqueNeighbors++; + } + } + } + + // 如果周围有足够的不透明像素,填充这个洞 + if (opaqueNeighbors >= 6) { + result.setRGB(x, y, 0xFFFFFFFF); + } + } + } + } + + // 腐蚀操作 - 去除噪声 + BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d2 = temp.createGraphics(); + g2d2.drawImage(result, 0, 0, null); + g2d2.dispose(); + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (result.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha > 0) { // 不透明像素 + // 检查周围是否有透明像素 + int transparentNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (result.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha == 0) { + transparentNeighbors++; + } + } + } + + // 如果周围有太多的透明像素,可能是噪声 + if (transparentNeighbors >= 6) { + temp.setRGB(x, y, 0x00000000); + } + } + } + } + + System.out.println("形态学处理完成"); + return temp; + } + + /** + * 使用掩码精确抠图(旧方法) + * 确保真正的透明背景 + */ + @SuppressWarnings("unused") + private static BufferedImage extractSubjectWithMask(BufferedImage image, BufferedImage mask) { + System.out.println("执行精确抠图..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int subjectPixels = 0; + int transparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int originalRgb = image.getRGB(x, y); + int maskRgb = mask.getRGB(x, y); + + // 检查掩码是否为白色(主体) + boolean isSubject = (maskRgb & 0x00FFFFFF) == 0x00FFFFFF; + + if (isSubject) { + // 保持原色,完全不透明 + result.setRGB(x, y, originalRgb); + subjectPixels++; + } else { + // 设为完全透明 - 这是关键! + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } + } + } + + System.out.println("精确抠图完成: 主体像素=" + subjectPixels + ", 透明像素=" + transparentPixels); + return result; + } + + /** + * 验证透明背景 + * 确保图像是真正的RGBA四通道透明图像 + */ + private static void verifyTransparentBackground(BufferedImage image) { + System.out.println("验证RGBA四通道透明背景..."); + + int width = image.getWidth(); + int height = image.getHeight(); + + int transparentPixels = 0; + int opaquePixels = 0; + int whitePixels = 0; + int semiTransparentPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + if (alpha == 0) { + transparentPixels++; + } else if (alpha == 255) { + opaquePixels++; + // 检查是否为白色像素 + if (r > 240 && g > 240 && b > 240) { + whitePixels++; + } + } else { + semiTransparentPixels++; + } + } + } + + System.out.println("RGBA四通道透明背景验证结果:"); + System.out.println("- 完全透明像素 (Alpha=0): " + transparentPixels); + System.out.println("- 完全不透明像素 (Alpha=255): " + opaquePixels); + System.out.println("- 半透明像素 (0 0) { + System.out.println("✓ 成功创建透明背景!"); + } else { + System.out.println("✗ 错误:没有透明像素,背景不是透明的!"); + } + + if (opaquePixels > 0) { + System.out.println("✓ 主体像素保持不透明!"); + } else { + System.out.println("✗ 错误:没有不透明像素,主体可能丢失!"); + } + + if (whitePixels > opaquePixels * 0.1) { + System.out.println("⚠ 警告:白色像素过多,可能存在白色光晕问题!"); + } + + // 验证图像格式 + if (image.getType() == BufferedImage.TYPE_INT_ARGB) { + System.out.println("✓ 图像格式正确:RGBA四通道 (TYPE_INT_ARGB)"); + } else { + System.out.println("✗ 错误:图像格式不正确,不是RGBA四通道!"); + } + + // 计算透明度比例 + double transparencyRatio = (double) transparentPixels / (width * height); + System.out.println("✓ 透明度比例: " + String.format("%.2f", transparencyRatio * 100) + "%"); + + if (transparencyRatio > 0.3) { + System.out.println("✓ 透明度比例合理,背景已成功透明化!"); + } else { + System.out.println("⚠ 警告:透明度比例较低,背景可能没有完全透明化!"); + } + } + + /** + * 应用羽化效果(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage applyFeathering(BufferedImage image) { + System.out.println("应用羽化效果..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + // 羽化处理 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (image.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha > 0) { + // 检查周围像素 + int transparentNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (image.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha == 0) { + transparentNeighbors++; + } + } + } + + // 如果有透明邻居,创建羽化效果 + if (transparentNeighbors > 0) { + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + + // 根据透明邻居数量调整透明度 + float featherFactor = 1.0f - (float) transparentNeighbors / 8.0f; + featherFactor = (float) Math.pow(featherFactor, 0.5); // 使用平方根使过渡更自然 + int newAlpha = Math.round(255 * featherFactor); + + int newRgb = (newAlpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + } + } + + System.out.println("羽化效果完成"); + return result; + } + + + /** + * 边缘平滑处理(旧方法) + * 使用简单的邻域平均 + */ + @SuppressWarnings("unused") + private static BufferedImage smoothEdges(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + // 边缘平滑 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (image.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha > 0) { + // 检查周围像素 + int transparentNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (image.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha == 0) { + transparentNeighbors++; + } + } + } + + // 如果有透明邻居,创建渐变效果 + if (transparentNeighbors > 0) { + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + + // 简单的透明度调整 + int newAlpha = Math.max(64, 255 - transparentNeighbors * 30); + + int newRgb = (newAlpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + } + } + + return result; + } + + /** + * 智能主体检测(旧方法) + * 使用多种方法检测图像中的主体 + */ + @SuppressWarnings("unused") + private static BufferedImage detectSubject(BufferedImage image) { + System.out.println("执行智能主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage mask = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 方法1: 基于颜色聚类的背景检测 + System.out.println("方法1: 基于颜色聚类的背景检测..."); + boolean[][] isBackground = detectBackgroundByClustering(image); + + // 方法2: 基于边缘的主体检测 + System.out.println("方法2: 基于边缘的主体检测..."); + boolean[][] isSubjectByEdge = detectSubjectByEdges(image); + + // 方法3: 基于中心区域的主体检测 + System.out.println("方法3: 基于中心区域的主体检测..."); + boolean[][] isSubjectByCenter = detectSubjectByCenter(image); + + // 综合判断 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + boolean isBg = isBackground[x][y]; + boolean isEdge = isSubjectByEdge[x][y]; + boolean isCenter = isSubjectByCenter[x][y]; + + // 综合判断:更保守的策略 + // 是边缘主体 或 (不是背景 且 是中心主体) + boolean isSubject = isEdge || (!isBg && isCenter); + + if (isSubject) { + mask.setRGB(x, y, 0xFFFFFFFF); // 白色表示主体 + } else { + mask.setRGB(x, y, 0x00000000); // 透明表示背景 + } + } + } + + System.out.println("智能主体检测完成"); + return mask; + } + + /** + * 基于颜色聚类的背景检测 + */ + private static boolean[][] detectBackgroundByClustering(BufferedImage image) { + System.out.println("执行颜色聚类背景检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isBackground = new boolean[width][height]; + + // 分析边缘像素作为背景样本 + java.util.List backgroundSamples = new java.util.ArrayList<>(); + + // 收集边缘像素作为背景样本 + for (int x = 0; x < width; x++) { + // 上边缘 + int rgb = image.getRGB(x, 0); + backgroundSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + + // 下边缘 + rgb = image.getRGB(x, height - 1); + backgroundSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + } + + for (int y = 0; y < height; y++) { + // 左边缘 + int rgb = image.getRGB(0, y); + backgroundSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + + // 右边缘 + rgb = image.getRGB(width - 1, y); + backgroundSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + } + + // 计算背景色平均值 + int avgR = 0, avgG = 0, avgB = 0; + for (int[] sample : backgroundSamples) { + avgR += sample[0]; + avgG += sample[1]; + avgB += sample[2]; + } + avgR /= backgroundSamples.size(); + avgG /= backgroundSamples.size(); + avgB /= backgroundSamples.size(); + + System.out.println("检测到背景色: RGB(" + avgR + ", " + avgG + ", " + avgB + ")"); + + // 计算背景色的标准差 + int variance = 0; + for (int[] sample : backgroundSamples) { + int distance = (int) Math.sqrt( + Math.pow(sample[0] - avgR, 2) + + Math.pow(sample[1] - avgG, 2) + + Math.pow(sample[2] - avgB, 2) + ); + variance += distance; + } + int threshold = Math.max(50, variance / backgroundSamples.size() + 30); + + System.out.println("背景检测阈值: " + threshold); + + // 判断每个像素是否为背景 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - avgR, 2) + + Math.pow(g - avgG, 2) + + Math.pow(b - avgB, 2) + ); + + isBackground[x][y] = distance <= threshold; + } + } + + return isBackground; + } + + /** + * 基于边缘的主体检测 + */ + private static boolean[][] detectSubjectByEdges(BufferedImage image) { + System.out.println("执行边缘主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 使用Sobel算子检测边缘 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + isSubject[x][y] = magnitude > 50; // 提高边缘阈值 + } + } + + return isSubject; + } + + /** + * 基于中心区域的主体检测 + */ + private static boolean[][] detectSubjectByCenter(BufferedImage image) { + System.out.println("执行中心区域主体检测..."); + + int width = image.getWidth(); + int height = image.getHeight(); + boolean[][] isSubject = new boolean[width][height]; + + // 分析中心区域的颜色分布 + int centerX = width / 2; + int centerY = height / 2; + int radius = Math.min(width, height) / 4; + + // 收集中心区域的颜色样本 + java.util.List centerSamples = new java.util.ArrayList<>(); + for (int x = centerX - radius; x < centerX + radius; x++) { + for (int y = centerY - radius; y < centerY + radius; y++) { + if (x >= 0 && x < width && y >= 0 && y < height) { + int rgb = image.getRGB(x, y); + centerSamples.add(new int[]{(rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF}); + } + } + } + + // 计算中心区域的平均颜色 + int avgR = 0, avgG = 0, avgB = 0; + for (int[] sample : centerSamples) { + avgR += sample[0]; + avgG += sample[1]; + avgB += sample[2]; + } + avgR /= centerSamples.size(); + avgG /= centerSamples.size(); + avgB /= centerSamples.size(); + + // 判断每个像素是否与中心区域颜色相似 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + int distance = (int) Math.sqrt( + Math.pow(r - avgR, 2) + + Math.pow(g - avgG, 2) + + Math.pow(b - avgB, 2) + ); + + isSubject[x][y] = distance <= 80; // 提高颜色相似度阈值 + } + } + + return isSubject; + } + + /** + * 精确抠图(旧方法) + * 根据主体掩码提取主体 + */ + @SuppressWarnings("unused") + private static BufferedImage extractSubject(BufferedImage image, BufferedImage mask) { + System.out.println("执行精确抠图..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int originalRgb = image.getRGB(x, y); + int maskRgb = mask.getRGB(x, y); + + // 检查掩码是否为白色(主体) + boolean isSubject = (maskRgb & 0x00FFFFFF) == 0x00FFFFFF; + + if (isSubject) { + // 保持原色,完全不透明 + result.setRGB(x, y, originalRgb); + } else { + // 设为透明 + result.setRGB(x, y, 0x00000000); + } + } + } + + System.out.println("精确抠图完成"); + return result; + } + + /** + * 边缘优化(旧方法) + * 优化抠图边缘的质量 + */ + @SuppressWarnings("unused") + private static BufferedImage optimizeEdges(BufferedImage image) { + System.out.println("执行边缘优化..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + // 边缘羽化处理 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (image.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha > 0) { + // 检查周围像素的透明度 + int transparentNeighbors = 0; + int totalNeighbors = 0; + + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + totalNeighbors++; + + int neighborAlpha = (image.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha == 0) { + transparentNeighbors++; + } + } + } + + // 如果周围有透明像素,创建羽化效果 + if (transparentNeighbors > 0) { + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + + // 根据透明邻居数量调整透明度,使用更平滑的过渡 + float featherFactor = 1.0f - (float) transparentNeighbors / totalNeighbors; + featherFactor = (float) Math.pow(featherFactor, 0.7); // 使用幂函数使过渡更平滑 + int newAlpha = Math.round(255 * featherFactor); + + int newRgb = (newAlpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + } + } + + System.out.println("边缘优化完成"); + return result; + } + + /** + * 最终透明化处理(旧方法) + */ + @SuppressWarnings("unused") + private static BufferedImage applyFinalTransparency(BufferedImage image) { + System.out.println("执行最终透明化处理..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 应用高斯模糊进行最终平滑 + float[][] kernel = { + {0.077f, 0.123f, 0.077f}, + {0.123f, 0.200f, 0.123f}, + {0.077f, 0.123f, 0.077f} + }; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + float rSum = 0, gSum = 0, bSum = 0, aSum = 0; + + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + int rgb = image.getRGB(x + dx, y + dy); + float weight = kernel[dx + 1][dy + 1]; + + rSum += ((rgb >> 16) & 0xFF) * weight; + gSum += ((rgb >> 8) & 0xFF) * weight; + bSum += (rgb & 0xFF) * weight; + aSum += ((rgb >> 24) & 0xFF) * weight; + } + } + + int r = Math.max(0, Math.min(255, Math.round(rSum))); + int g = Math.max(0, Math.min(255, Math.round(gSum))); + int b = Math.max(0, Math.min(255, Math.round(bSum))); + int a = Math.max(0, Math.min(255, Math.round(aSum))); + + int newRgb = (a << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + + System.out.println("最终透明化处理完成"); + return result; + } + + /** + * 边缘检测和主体分离(旧方法) + * 使用Sobel算子进行边缘检测 + */ + @SuppressWarnings("unused") + private static BufferedImage detectEdgesAndSeparate(BufferedImage image) { + System.out.println("执行边缘检测和主体分离..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Sobel算子 + int[][] sobelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; + int[][] sobelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + // 计算Sobel梯度 + int gx = 0, gy = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + int rgb = image.getRGB(x + i, y + j); + int gray = (int)(0.299 * ((rgb >> 16) & 0xFF) + + 0.587 * ((rgb >> 8) & 0xFF) + + 0.114 * (rgb & 0xFF)); + gx += gray * sobelX[i + 1][j + 1]; + gy += gray * sobelY[i + 1][j + 1]; + } + } + + int magnitude = (int) Math.sqrt(gx * gx + gy * gy); + + // 根据边缘强度决定透明度 + int alpha = 255; + if (magnitude > 50) { // 强边缘,保持不透明 + alpha = 255; + } else if (magnitude > 20) { // 中等边缘,部分透明 + alpha = 128 + (magnitude - 20) * 4; + } else { // 弱边缘或背景,设为透明 + alpha = 0; + } + + // 保持原色但应用新的透明度 + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + + int newRgb = (alpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + + System.out.println("边缘检测完成"); + return result; + } + + + /** + * 高级抗锯齿处理(旧方法) + * 使用高斯模糊和边缘平滑 + */ + @SuppressWarnings("unused") + private static BufferedImage applyAdvancedAntiAliasing(BufferedImage image) { + System.out.println("应用高级抗锯齿处理..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 高斯模糊核 + float[][] kernel = { + {0.077f, 0.123f, 0.077f}, + {0.123f, 0.200f, 0.123f}, + {0.077f, 0.123f, 0.077f} + }; + + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + float rSum = 0, gSum = 0, bSum = 0, aSum = 0; + + // 应用高斯模糊 + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + int rgb = image.getRGB(x + dx, y + dy); + float weight = kernel[dx + 1][dy + 1]; + + rSum += ((rgb >> 16) & 0xFF) * weight; + gSum += ((rgb >> 8) & 0xFF) * weight; + bSum += (rgb & 0xFF) * weight; + aSum += ((rgb >> 24) & 0xFF) * weight; + } + } + + int r = Math.round(rSum); + int g = Math.round(gSum); + int b = Math.round(bSum); + int a = Math.round(aSum); + + // 确保值在有效范围内 + r = Math.max(0, Math.min(255, r)); + g = Math.max(0, Math.min(255, g)); + b = Math.max(0, Math.min(255, b)); + a = Math.max(0, Math.min(255, a)); + + int newRgb = (a << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + + System.out.println("抗锯齿处理完成"); + return result; + } + + /** + * 最终优化处理(旧方法) + * 清理残留和优化边缘 + */ + @SuppressWarnings("unused") + private static BufferedImage finalOptimization(BufferedImage image) { + System.out.println("执行最终优化处理..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = result.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + // 清理孤立的透明像素 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (image.getRGB(x, y) >> 24) & 0xFF; + + if (currentAlpha == 0) { + // 检查周围是否有不透明像素 + int opaqueNeighbors = 0; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + int neighborAlpha = (image.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha > 128) { + opaqueNeighbors++; + } + } + } + + // 如果周围有很多不透明像素,创建渐变 + if (opaqueNeighbors >= 4) { + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + int newAlpha = 64; // 半透明 + + int newRgb = (newAlpha << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + } + } + } + } + + System.out.println("最终优化完成"); + return result; + } + + /** + * 高级白色背景移除算法(备用方法) + * 结合多种检测方法 + */ + @SuppressWarnings("unused") + private static BufferedImage removeWhiteBackgroundAdvanced(BufferedImage image) { + System.out.println("使用高级白色背景移除算法..."); + + // 创建新的透明图像 + BufferedImage transparentImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB + ); + + // 获取图像尺寸 + int width = image.getWidth(); + int height = image.getHeight(); + + // 背景检测参数 + int whiteThreshold = 240; // 白色阈值,低于此值认为是白色 + int tolerance = 15; // 容差值 + + System.out.println("使用参数: 白色阈值=" + whiteThreshold + ", 容差=" + tolerance); + + // 统计处理信息 + int totalPixels = width * height; + int transparentPixels = 0; + int keptPixels = 0; + + // 处理每个像素 + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + + // 提取RGB值 + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int a = (rgb >> 24) & 0xFF; + + // 检查是否为背景色(多种检测方法) + boolean isBackground = isBackgroundColor(r, g, b, whiteThreshold, tolerance); + + if (isBackground) { + // 设置为透明 + transparentImage.setRGB(x, y, 0x00000000); // 完全透明 + transparentPixels++; + } else { + // 保持原像素,但确保Alpha通道正确 + int newRgb = (a << 24) | (r << 16) | (g << 8) | b; + transparentImage.setRGB(x, y, newRgb); + keptPixels++; + } + } + } + + System.out.println("高级背景移除完成: 总像素=" + totalPixels + + ", 透明像素=" + transparentPixels + + ", 保留像素=" + keptPixels); + + return transparentImage; + } + + /** + * 检测是否为背景色 + * 使用多种方法提高检测准确性 + */ + private static boolean isBackgroundColor(int r, int g, int b, int threshold, int tolerance) { + // 方法1: 检查是否为纯白色或接近白色 + if (r >= threshold && g >= threshold && b >= threshold) { + return true; + } + + // 方法2: 检查RGB值是否接近(灰色系) + int maxDiff = Math.max(Math.abs(r - g), Math.max(Math.abs(r - b), Math.abs(g - b))); + if (maxDiff <= tolerance && r >= threshold - tolerance) { + return true; + } + + // 方法3: 检查亮度是否足够高 + double brightness = 0.299 * r + 0.587 * g + 0.114 * b; + if (brightness >= threshold) { + return true; + } + + // 方法4: 检查HSV中的V值(明度) + float[] hsv = rgbToHsv(r, g, b); + if (hsv[2] >= threshold / 255.0f) { // V值归一化 + return true; + } + + return false; + } + + /** + * RGB转HSV + */ + private static float[] rgbToHsv(int r, int g, int b) { + float[] hsv = new float[3]; + float rf = r / 255.0f; + float gf = g / 255.0f; + float bf = b / 255.0f; + + float max = Math.max(rf, Math.max(gf, bf)); + float min = Math.min(rf, Math.min(gf, bf)); + float delta = max - min; + + // V (Value/Brightness) + hsv[2] = max; + + // S (Saturation) + hsv[1] = max == 0 ? 0 : delta / max; + + // H (Hue) + if (delta == 0) { + hsv[0] = 0; + } else if (max == rf) { + hsv[0] = 60 * ((gf - bf) / delta); + } else if (max == gf) { + hsv[0] = 60 * (2 + (bf - rf) / delta); + } else { + hsv[0] = 60 * (4 + (rf - gf) / delta); + } + + if (hsv[0] < 0) hsv[0] += 360; + + return hsv; + } + + /** + * 应用边缘平滑处理(备用方法) + * 减少锯齿效果,使边缘更自然 + */ + @SuppressWarnings("unused") + private static BufferedImage applyEdgeSmoothing(BufferedImage image) { + System.out.println("应用边缘平滑处理..."); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage smoothedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // 复制原图 + Graphics2D g2d = smoothedImage.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + // 边缘平滑处理 + for (int x = 1; x < width - 1; x++) { + for (int y = 1; y < height - 1; y++) { + int currentAlpha = (image.getRGB(x, y) >> 24) & 0xFF; + + // 如果当前像素是透明的,检查周围像素 + if (currentAlpha == 0) { + int opaqueNeighbors = 0; + int totalAlpha = 0; + + // 检查8个邻居像素 + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + + int neighborAlpha = (image.getRGB(x + dx, y + dy) >> 24) & 0xFF; + if (neighborAlpha > 0) { + opaqueNeighbors++; + totalAlpha += neighborAlpha; + } + } + } + + // 如果周围有不透明像素,创建渐变效果 + if (opaqueNeighbors > 0) { + int avgAlpha = totalAlpha / opaqueNeighbors; + int newAlpha = Math.min(avgAlpha / 3, 128); // 限制透明度 + + if (newAlpha > 0) { + int originalRgb = image.getRGB(x, y); + int r = (originalRgb >> 16) & 0xFF; + int g = (originalRgb >> 8) & 0xFF; + int b = originalRgb & 0xFF; + + int newRgb = (newAlpha << 24) | (r << 16) | (g << 8) | b; + smoothedImage.setRGB(x, y, newRgb); + } + } + } + } + } + + System.out.println("边缘平滑处理完成"); + return smoothedImage; + } + + + /** + * 基于检测到的背景色进行背景移除(备用方法) + */ + @SuppressWarnings("unused") + private static BufferedImage removeBackgroundByColor(BufferedImage image, int[] backgroundColor, int tolerance) { + System.out.println("基于检测背景色进行移除,容差: " + tolerance); + + int width = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + int bgR = backgroundColor[0]; + int bgG = backgroundColor[1]; + int bgB = backgroundColor[2]; + + int transparentPixels = 0; + int keptPixels = 0; + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int rgb = image.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + int a = (rgb >> 24) & 0xFF; + + // 计算与背景色的距离 + int distance = (int) Math.sqrt( + Math.pow(r - bgR, 2) + + Math.pow(g - bgG, 2) + + Math.pow(b - bgB, 2) + ); + + if (distance <= tolerance) { + // 设置为透明 + result.setRGB(x, y, 0x00000000); + transparentPixels++; + } else { + // 保持原像素 + int newRgb = (a << 24) | (r << 16) | (g << 8) | b; + result.setRGB(x, y, newRgb); + keptPixels++; + } + } + } + + System.out.println("背景移除结果: 透明像素=" + transparentPixels + ", 保留像素=" + keptPixels); + return result; + } + + private static byte[] convertToByteArray(BufferedImage image) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "png", baos); + return baos.toByteArray(); + } + + /** + * 上传文件到MinIO + * + * @param imageBytes 图片字节数组 + * @return 上传后的文件名 + * @throws Exception 上传异常 + */ + private static String uploadToMinIO(byte[] imageBytes) throws Exception { + try { + // 创建MinIO客户端 + MinioClient minioClient = MinioClient.builder() + .endpoint(MINIO_ENDPOINT) + .credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY) + .build(); + + // 生成唯一文件名 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String uuid = UUID.randomUUID().toString().substring(0, 8); + String fileName = "transparent_" + timestamp + "_" + uuid + ".png"; + + // 确保存储桶存在 + boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET_NAME).build()); + if (!bucketExists) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET_NAME).build()); + System.out.println("创建存储桶: " + BUCKET_NAME); + } + + // 上传文件 + minioClient.putObject( + PutObjectArgs.builder() + .bucket(BUCKET_NAME) + .object(fileName) + .stream(new ByteArrayInputStream(imageBytes), imageBytes.length, -1) + .contentType("image/png") + .build() + ); + + System.out.println("文件上传成功: " + BUCKET_NAME + "/" + fileName); + return fileName; + + } catch (Exception e) { + System.err.println("上传到MinIO失败: " + e.getMessage()); + throw new Exception("上传到MinIO失败: " + e.getMessage(), e); + } + } + + /** + * 生成临时访问链接 + * + * @param fileName 文件名 + * @return 临时访问链接 + */ + private static String generateTempUrl(String fileName) { + try { + // 创建MinIO客户端 + MinioClient minioClient = MinioClient.builder() + .endpoint(MINIO_ENDPOINT) + .credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY) + .build(); + + // 生成7天有效期的预签名URL + String presignedUrl = minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(BUCKET_NAME) + .object(fileName) + .expiry(7, TimeUnit.DAYS) // 7天有效期 + .build() + ); + + System.out.println("生成临时链接成功,有效期: 7天"); + return presignedUrl; + + } catch (Exception e) { + System.err.println("生成临时链接失败: " + e.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/rj/controller/AliyunBackgroundGenerationTest.java b/src/test/java/com/rj/controller/AliyunBackgroundGenerationTest.java index 77fc521..de0f6a8 100644 --- a/src/test/java/com/rj/controller/AliyunBackgroundGenerationTest.java +++ b/src/test/java/com/rj/controller/AliyunBackgroundGenerationTest.java @@ -17,6 +17,7 @@ import java.io.*; import java.net.URL; import com.rj.service.MinIOService; +import com.rj.utils.ImageConversionUtil; import java.util.*; import java.util.List; @@ -41,6 +42,9 @@ public class AliyunBackgroundGenerationTest { @Autowired private MinIOService minIOService; + @Autowired + private ImageConversionUtil imageConversionUtil; + private final ObjectMapper objectMapper = new ObjectMapper(); /** @@ -304,14 +308,14 @@ public class AliyunBackgroundGenerationTest { // 1. 分析阿里云图片特征(参考) String aliyunImageUrl = "https://vision-poster.oss-cn-shanghai.aliyuncs.com/lllcho.lc/data/test_data/images/main_images/new_main_img/a.png"; - analyzeImageCharacteristics(aliyunImageUrl, "阿里云参考图片"); + //analyzeImageCharacteristics(aliyunImageUrl, "阿里云参考图片"); // 2. 分析您的原始图片特征 String yourImageUrl = "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0"; - analyzeImageCharacteristics(yourImageUrl, "您的原始图片"); - - // 3. 转换图片为符合阿里云要求的格式(使用高级转换方法) - byte[] convertedImageBytes = convertImageToAliyunFormatAdvanced(yourImageUrl); + String yourImageUrl2 = "http://101.35.52.237:19005/car/1760663602296_153e5c5aed024210909d904a19c9410d.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251017%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251017T011323Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=ab5908cfb26fde10b4445d67285cbebb061936687443fae59ed3edb7bf6cfaf4"; // analyzeImageCharacteristics(yourImageUrl, "您的原始图片"); + String yourImageUrl3 = "http://101.35.52.237:19005/car/1760664450807_c409152beb0344feb227438ff2c20e73.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251017%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251017T012731Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=36ee81f8c51826b16af508dc3a90c5af8f951e76575b7a13ff4570c6a92078b5"; + // 3. 转换图片为符合阿里云要求的格式(使用工具类) + byte[] convertedImageBytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(yourImageUrl3); if (convertedImageBytes == null) { log.error("图片转换失败,无法继续"); return; @@ -1695,9 +1699,9 @@ public class AliyunBackgroundGenerationTest { try { log.info("开始测试使用转换后的图片进行背景生成"); - // 1. 转换图片为字节数组(使用高级转换方法) + // 1. 转换图片为字节数组(使用工具类) String originalImageUrl = "http://101.35.52.237:19005/car/1760605862400_e3a319ba13184fdd9f4a08cdc3e3b30e.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20251016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251016T091103Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a2b4ae679552b6cfebcfeee228b84497a6eb458dc061dbf244f533fed41356b0"; - byte[] convertedImageBytes = convertImageToAliyunFormatAdvanced(originalImageUrl); + byte[] convertedImageBytes = imageConversionUtil.convertImageToAliyunFormatAdvanced(originalImageUrl); if (convertedImageBytes == null) { log.error("图片转换失败,无法继续测试"); diff --git a/src/test/java/com/rj/service/FaceDetectImageCountTest.java b/src/test/java/com/rj/service/FaceDetectImageCountTest.java index a0446d5..f3e4175 100644 --- a/src/test/java/com/rj/service/FaceDetectImageCountTest.java +++ b/src/test/java/com/rj/service/FaceDetectImageCountTest.java @@ -229,6 +229,8 @@ public class FaceDetectImageCountTest { + + diff --git a/src/test/java/com/rj/service/TtsRequestLogShortUrlTest.java b/src/test/java/com/rj/service/TtsRequestLogShortUrlTest.java index 9a0ca08..f9484e2 100644 --- a/src/test/java/com/rj/service/TtsRequestLogShortUrlTest.java +++ b/src/test/java/com/rj/service/TtsRequestLogShortUrlTest.java @@ -170,6 +170,8 @@ public class TtsRequestLogShortUrlTest { + + diff --git a/src/test/java/com/rj/service/VideoSynthesisTempUrlTest.java b/src/test/java/com/rj/service/VideoSynthesisTempUrlTest.java index 407339f..6b2afad 100644 --- a/src/test/java/com/rj/service/VideoSynthesisTempUrlTest.java +++ b/src/test/java/com/rj/service/VideoSynthesisTempUrlTest.java @@ -148,6 +148,8 @@ public class VideoSynthesisTempUrlTest { + + diff --git a/src/test/java/com/rj/service/VideoSynthesisVideoNameTest.java b/src/test/java/com/rj/service/VideoSynthesisVideoNameTest.java index fc8db65..debacce 100644 --- a/src/test/java/com/rj/service/VideoSynthesisVideoNameTest.java +++ b/src/test/java/com/rj/service/VideoSynthesisVideoNameTest.java @@ -123,6 +123,8 @@ public class VideoSynthesisVideoNameTest { + +