Java - 将图像转换为黑白 - 在亮色情况下失败

8

我试图将一张图片转换为纯黑白(不是灰度)。

我使用了以下代码:

BufferedImage blackAndWhiteImage = new BufferedImage(
        dWidth.intValue(),
        dHeight.intValue(),
        BufferedImage.TYPE_BYTE_BINARY);
Graphics2D graphics = blackAndWhiteImage.createGraphics();
graphics.drawImage(colourImage, 0, 0, null);

return blackAndWhiteImage;

一切都很好,直到我决定尝试更亮的颜色,比如Google Logo:

googleLogo

它输出了这个:

goolgeLogoBroken1

然后我尝试先通过使用以下方法进行灰度处理:

BufferedImage blackAndWhiteImage2 = new BufferedImage(
        dWidth.intValue(),
        dHeight.intValue(),
        BufferedImage.TYPE_USHORT_GRAY);

看起来它保存了蓝色,但没有最亮的颜色(在这种情况下是黄色),并且您可能会发现它的质量下降了:

goolgeLogoBroken1

非常感谢您的建议;我相信我正在寻找的是将每种颜色转换为黑色,除了白色(这将是背景颜色),当应用TYPE_BYTE_BINARY时,已经完成此操作,删除了alpha通道。

编辑: 也许我没有解释得很清楚:

  • 最终图片必须有白色背景 **1
  • 每种其他颜色都必须转换为黑色

**1- 有些情况下,图片实际上是黑底白字...这很恼人(whiteOnBlackExample),因为它会使这个过程变得非常复杂,所以我将在以后留下这个问题,现在的重点是转换“普通”图像。

我所做的是,首先去掉 alpha 通道(如果存在)-> 因此将 alpha 通道转换为白色;然后将每种其他颜色转换为黑色。


问题在于你只使用了黑色和白色,而没有灰色,因此亮色被显示为白色。 - HReynaud
1
请参考http://stackoverflow.com/questions/23980554/how-to-delete-the-white-pixels-in-an-image-in-java,你可以通过将非白色像素替换为黑色来实现相同的效果。 - StanislavL
@StanislavL 遍历和操作像素似乎比内置方法慢。请参见https://dev59.com/0ZLea4cB1Zd3GeqP7dUN。 - user1803551
@user1803551 我实际上正在使用JavaFX Image,然后来回转换为AWT Image。没有限制。 - 4673_j
2
你绝对需要一个带有可调阈值的算法。一般来说,算法应该如下:pixel(x, y) < threshold ? white : black。然后只需选择适当的阈值即可。由于原始像素是RGB格式,您还可能需要将此值转换为可与阈值进行比较的值。有几种方法可以实现这一点: 亮度 方法:(max(R, G, B) + min(R, G, B)) / 2平均值 方法:(R + G + B) / 3亮度 方法:0.21 * R + 0.72 * G + 0.07 * B - Yevhen Danchenko
显示剩余4条评论
2个回答

5
如果您使用JavaFX,您可以使用亮度为-1(最小值)的ColorAdjust效果,将所有(非白色)颜色变为黑色:
public class Main extends Application {

    Image image = new Image("https://istack.dev59.com/UPmqE.webp");

    @Override
    public void start(Stage primaryStage) {
        ImageView colorView = new ImageView(image);
        ImageView bhView = new ImageView(image);

        ColorAdjust colorAdjust = new ColorAdjust();
        colorAdjust.setBrightness(-1);
        bhView.setEffect(colorAdjust);

        primaryStage.setScene(new Scene(new VBox(colorView, bhView)));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

enter image description here

这些效果已经优化,所以它们可能比您手动应用它们要快。 编辑 由于您的要求是:
  1. 任何不透明的像素都应转换为白色,
  2. 任何不是白色的像素都应转换为黑色,
预设计的效果似乎不适合您 - 它们太具体了。您可以逐个像素进行操作。
WritableImage writableImage = new WritableImage(image.getPixelReader(), (int) image.getWidth(), (int) image.getHeight());
PixelWriter pixelWriter = writableImage.getPixelWriter();
PixelReader pixelReader = writableImage.getPixelReader();
for (int i = 0; i < writableImage.getHeight(); i++) {
    for (int j = 0; j < writableImage.getWidth(); j++) {
        Color c = pixelReader.getColor(j, i);
        if (c.getOpacity() < 1) {
            pixelWriter.setColor(j, i, Color.WHITE);
        }
        if (c.getRed() > 0 || c.getGreen() > 0 || c.getBlue() > 0) {
            pixelWriter.setColor(j, i, Color.BLACK);
        }
    }
}
ImageView imageView = new ImageView(writableImage);

enter image description here

注意,应用规则的顺序很重要。如果您先应用1再应用2,则透明的非白色像素会变为白色;但是如果您先应用2再应用1,则最终颜色将变为黑色。这是因为预定义的WHITEBLACK颜色是不透明的。您可以手动设置红、绿和蓝色值,而不改变alpha值。这完全取决于您的确切要求。
请记住,由于某些文件格式的有损压缩,您可能根本找不到真正的白色,而是一个接近真正白色的值,您的眼睛无法分辨出差异。

我之前尝试过这种方法,但只有部分有效;请尝试使用此链接获取图像,看看会发生什么:http://i.imgur.com/FxNnW2M.jpg - 4673_j
@4673_j 这张图片的问题在于它没有很多真正的白色,但许多看似白色的像素接近白色,例如 0.07450980693101883, 0.07450980693101883, 0.07450980693101883(RGB)。因此这些像素也被设置为黑色。请记住,不同的图像格式具有不同的颜色光谱,有些是有损的。这归结于你所称之为“白色”实际上并不是真正的白色。 - user1803551
@4673_j 还有一个问题是关于你想如何处理透明度。 - user1803551
你是正确的,我的错误。我假设它是零,所以一切都变黑了。透明度/alpha必须转换为白色。 - 4673_j
没错,那应该可以解决问题了。我现在正试图迭代每个像素,但我对图像处理并不是很有经验。 - 4673_j
显示剩余5条评论

3

以下是我评论中的示例。首先打开输入图像,然后创建一个新的输出图像。

BufferedImage myColorImage = ImageIO.read(fileInput);
BufferedImage myBWImage = new BufferedImage(myColorImage.getWidth(), myColorImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY);

然后遍历所有像素并将RGB值与阈值进行比较:

for (int x = 0; x < myColorImage.getWidth(); x++)
    for (int y = 0; y < myColorImage.getHeight(); y++)
        if (rgbToGray(myColorImage.getRGB(x, y), MODE.AVERAGE) > threshold)
            myBWImage.setRGB(x, y, 0);
        else
            myBWImage.setRGB(x, y, 0xffffff); 

这里是rgbToGray方法的实现,用于与阈值进行比较:

private static int rgbToGray(int rgb, MODE mode) {
    // split rgb integer into R, G and B components
    int r = (rgb >> 16) & 0xff;
    int g = (rgb >> 8) & 0xff;
    int b = rgb & 0xff;
    int gray;
    // Select mode
    switch (mode) {
        case LIGHTNESS:
            gray = Math.round((Math.max(r, Math.max(g, b)) + Math.min(r, Math.min(g, b))) / 2);
            break;
        case LUMINOSITY:
            gray = Math.round(0.21f * r + 0.72f * g + 0.07f * b);
            break;
        case AVERAGE:
        default:
            gray = Math.round((r + g + b) / 3);
            break;
    }
    return gray;
}

一个实用的枚举:

private enum MODE {
    LIGHTNESS, AVERAGE, LUMINOSITY
}

我得到了以下结果:

Black and White converted

注意:对于您的谷歌图像,即使阈值为1也是合适的,对于其他图像,您应该从范围[0..255]中选择另一个值。对于照片而言,最适合的值可能在100-150左右。MODE也会影响最终结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接