Java - 将图像转换为黑白 - 色彩鲜艳失败

Posted

技术标签:

【中文标题】Java - 将图像转换为黑白 - 色彩鲜艳失败【英文标题】:Java - Convert Image to black and white - fails with bright colors 【发布时间】:2017-10-10 15:15:23 【问题描述】:

我正在尝试将图像仅转换为黑白(不是灰度)。

我用过这个:

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 徽标:

结果是这样的:

然后我首先尝试使用:

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

它似乎保存了蓝色,但不是最亮的(在本例中为黄色),并且您可能会看到它的质量有所下降:

非常感谢任何建议;我相信我正在寻找的是将除白色以外的所有颜色转换为黑色(这将是背景颜色),这在应用 TYPE_BYTE_BINARY 删除 alpha 通道时已经完成。


编辑: 可能我解释的不是很清楚:

最终图像必须具有白色背景 **1 所有其他颜色都必须转换为黑色

**1 - 在某些情况下,图像实际上是黑底白字..这很烦人 (whiteOnBlackExample),因为它使这个过程变得非常复杂,我稍后会离开,因为现在的优先级是转换“正常”图像。

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

【问题讨论】:

问题是你只使用了黑色和白色,而不是灰色,所以明亮的颜色显示为白色。 见***.com/questions/23980554/… 你也可以用黑色替换非白色像素 @StanislavL 遍历和操作像素似乎比内置方法慢。见***.com/questions/34688182/… 您是仅限于 Swing/AWT 还是 JavaFX 是一种选择? 你肯定需要一个阈值可调的算法。一般来说,算法应该如下:pixel(x, y) < threshold ? white : black。然后只需选择合适的阈值。由于您的原始像素是 RGB,您可能还需要将此值转换为适用的值以与阈值进行比较。有几种方法可以做到这一点: Lightness 方法:(max(R, G, B) + min(R, G, B)) / 2; 平均方法:(R + G + B) / 3亮度0.21 * R + 0.72 * G + 0.07 * B 【参考方案1】:

如果您使用 JavaFX,您可以使用亮度为 -1(最小值)的 ColorAdjust 效果,这会使所有(非白色)颜色变为黑色:

public class Main extends Application 

    Image image = new Image("https://i.stack.imgur.com/UPmqE.png");

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

这些Effects 已经过优化,因此它们可能比手动应用它们更快。

编辑

既然你的要求是这样的

    任何不透明的像素都应转换为白色,并且 任何不是白色的像素都应转换为黑色,

据我所知,预先设计的效果并不适合您 - 它们太具体了。您可以逐个像素地进行操作:

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);

请注意,应用规则的顺序很重要。如果应用 1 然后应用 2,透明的非白色像素将变为白色,但如果应用 2 然后应用 1,它将最终变为黑色。这是因为预定义的 WHITEBLACK 颜色是不透明的。您可以手动设置红色、绿色和蓝色值,而不更改 alpha 值。这完全取决于您的具体要求。

请记住,由于某些文件格式的有损压缩,您可能根本找不到真正的白色,但是接近真正的白色的值,您的眼睛将无法区分。

【讨论】:

我以前尝试过这种方法,但它部分工作;试试这个图片链接,看看会发生什么:i.imgur.com/FxNnW2M.jpg @4673_j 该图像的问题在于它没有很多真正的白色,但许多看似白色的像素都接近白色,例如0.07450980693101883, 0.07450980693101883, 0.07450980693101883 (RGB)。所以这些像素也设置为黑色。请记住,不同的图像格式具有不同的色谱,有些是有损的。它归结为你会称之为 white 的东西,而它并不是真正的白色。 @4673_j 还有一个问题是你想如何处理透明度。 你是对的,我的错误,我认为它是零,这就是为什么一切都变黑了。透明度/alpha 必须转换为白色 @4673_j 所以任何不完全不透明(alpha = 1)的像素都应该是白色的,而任何不是完全白色的像素都应该是黑色的?有这些条件吗?【参考方案2】:

这是我评论中的示例。首先打开输入图像并创建一个新的输出图像。

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

我得到了以下结果:

注意:对于您的 google 图片,即使 threshold = 1 也适用,对于其他图片,您应该从 [0..255] 范围内选择其他值。对于照片,更合适的值很可能是 100-150。 MODE也会影响最终结果。

【讨论】:

以上是关于Java - 将图像转换为黑白 - 色彩鲜艳失败的主要内容,如果未能解决你的问题,请参考以下文章

笔记本电脑显示屏如何调为只显黑白色

打开Tiff文件为啥显示为黑白色

OpenCV-图像饱和度

如何通过覆盖现有图像将一堆图像转换为黑白图像?

CSS:如何将彩色图像转换为灰色或黑白图像[重复]

使用 JAVA 进行图像细化