多图合成
This commit is contained in:
@@ -171,6 +171,8 @@ public class PasswordUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -155,6 +155,8 @@ public class ServiceManager {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@ public class AliyunConfig {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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<String> processedForegroundEdges = new ArrayList<>();
|
||||
List<String> 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<String, Object> 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<String, Object> 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<String> 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<String> 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<String> 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<String> 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
|
||||
*/
|
||||
|
||||
@@ -393,6 +393,8 @@ public class MenuController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -363,6 +363,8 @@ public class RoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -369,6 +369,8 @@ public class UserRoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -135,6 +135,8 @@ public class DifyWorkflowResponseDto {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ public interface CustomerProfileAnalysisMapper extends BaseMapper<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ public interface ICustomerProfileAnalysisService extends IService<CustomerProfil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@ public class CustomerProfileAnalysisServiceImpl extends ServiceImpl<CustomerProf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IM
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1523
src/main/java/com/rj/utils/AliyunImageConversionUtil.java
Normal file
1523
src/main/java/com/rj/utils/AliyunImageConversionUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -115,6 +115,8 @@ spring:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -291,6 +291,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user