多图合成

This commit is contained in:
spllzh
2025-10-17 21:27:27 +08:00
parent cfcd04fe7e
commit 8e640e03c8
26 changed files with 4644 additions and 97 deletions

View File

@@ -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};
}
}