如何使用Java将图像转换为黑白

9

我使用像这样的命令在imagemagick中将图像转换为黑白:

convert myimg.png -monochrome  out3.png

我想知道在Java中是否有可能实现相同的结果?而不使用Im4Java或JMagick?


@AndrewThompson 那个答案处理的是转换为灰阶。 - mmgp
2
既然您没有提到任何内容,我猜我应该尝试澄清这里需要什么。您需要一个抖动算法,我不知道ImageMagick使用哪种具体实现,但经典的Floyd-Steinberg是一个不错的第一选择。 - mmgp
为什么这个问题会有-1的评分? - birdy
也许是因为你没有尝试理解convert -monochrome的作用,而选择了与你所问的完全不同的答案。你最好直接问:“如果一个值小于127,如何将其转换为0?如果一个值大于或等于127,如何将其转换为255?”这就是你现在使用的方法,而且是一个相当愚蠢的问题。 - mmgp
4个回答

25

我想这取决于你对“单色/黑白”有什么理解......

输入图像描述

public class TestBlackAndWhite {

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

    public TestBlackAndWhite() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage master;
        private BufferedImage grayScale;
        private BufferedImage blackWhite;

        public TestPane() {
            try {
                master = ImageIO.read(new File("C:/Users/shane/Dropbox/pictures/439px-Join!_It's_your_duty!.jpg"));
                grayScale = ImageIO.read(new File("C:/Users/shane/Dropbox/pictures/439px-Join!_It's_your_duty!.jpg"));
                ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
                op.filter(grayScale, grayScale);

                blackWhite = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
                Graphics2D g2d = blackWhite.createGraphics();
                g2d.drawImage(master, 0, 0, this);
                g2d.dispose();

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = super.getPreferredSize();
            if (master != null) {
                size = new Dimension(master.getWidth() * 3, master.getHeight());
            }
            return size;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (master != null) {

                int x = (getWidth() - (master.getWidth() * 3)) / 2;
                int y = (getHeight() - master.getHeight()) / 2;

                g.drawImage(master, x, y, this);
                x += master.getWidth();
                g.drawImage(grayScale, x, y, this);
                x += master.getWidth();
                g.drawImage(blackWhite, x, y, this);

            }
        }


    }
}

在你的回答中的三张图片中,我需要最右边的那一张。 - birdy
1
只是好奇,你能设置一个阈值(亮度)水平来转换为1位单色吗? - David R Tribble
@birdy 那么你需要的是 blackWhite BufferedImage - MadProgrammer
据我所知,没有使用上述描述的方法。不过你可以编写一个BufferedImageOP来实现。 - MadProgrammer
1
在转换之前使用RescaleOp即可解决问题。请参考我的答案。 - Andrew Thompson
有趣的是:使用CS_Gray色彩空间调用ColorConvertOp比使用目标图像的色彩空间调用它产生更好的结果。 - Tilman Hausherr

10

尝试这个简单的例子。我们首先使用RescaleOp来增强或降低图像的亮度。

调整大小后变成黑白图像的图像

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

class ColorToBlackAndWhite {

    /**
     * Returns the supplied src image brightened by a float value from 0 to 10.
     * Float values below 1.0f actually darken the source image.
     */
    public static BufferedImage brighten(BufferedImage src, float level) {
        BufferedImage dst = new BufferedImage(
                src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
        float[] scales = {level, level, level};
        float[] offsets = new float[4];
        RescaleOp rop = new RescaleOp(scales, offsets, null);

        Graphics2D g = dst.createGraphics();
        g.drawImage(src, rop, 0, 0);
        g.dispose();

        return dst;
    }

    public static void main(String[] args) throws Exception {
        URL colorURL = new URL("http://i.stack.imgur.com/AuY9o.png");
        final BufferedImage colorImage = ImageIO.read(colorURL);

        float[] scales = {2f, 2f, 2f};
        float[] offsets = new float[4];
        RescaleOp rop = new RescaleOp(scales, offsets, null);

        final BufferedImage scaledImage = new BufferedImage(
                colorImage.getWidth(),
                colorImage.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = scaledImage.createGraphics();
        g.drawImage(colorImage, rop, 0, 0);

        final BufferedImage grayImage = new BufferedImage(
                colorImage.getWidth(),
                colorImage.getHeight(),
                BufferedImage.TYPE_BYTE_GRAY);
        g = grayImage.createGraphics();
        g.drawImage(colorImage, 0, 0, null);

        final BufferedImage blackAndWhiteImage = new BufferedImage(
                colorImage.getWidth(),
                colorImage.getHeight(),
                BufferedImage.TYPE_BYTE_BINARY);
        g = blackAndWhiteImage.createGraphics();
        g.drawImage(colorImage, 0, 0, null);

        g.dispose();

        Runnable r = new Runnable() {

            @Override
            public void run() {
                JPanel gui = new JPanel(new BorderLayout(2, 2));
                JPanel images = new JPanel(new GridLayout(0, 2, 2, 2));
                gui.add(images, BorderLayout.CENTER);

                final JLabel scaled = new JLabel(new ImageIcon(scaledImage));
                final JSlider brighten = new JSlider(0, 1000, 100);
                gui.add(brighten, BorderLayout.PAGE_START);
                ChangeListener cl = new ChangeListener() {

                    @Override
                    public void stateChanged(ChangeEvent e) {
                        int val = brighten.getValue();
                        float valFloat = val / 1000f;
                        BufferedImage bi = brighten(colorImage, valFloat);
                        BufferedImage bw = new BufferedImage(
                                colorImage.getWidth(),
                                colorImage.getHeight(),
                                BufferedImage.TYPE_BYTE_BINARY);
                        Graphics g = bw.createGraphics();
                        g.drawImage(bi, 0, 0, null);
                        g.dispose();

                        scaled.setIcon(new ImageIcon(bw));
                    }
                };
                brighten.addChangeListener(cl);

                images.add(new JLabel(new ImageIcon(colorImage)));
                images.add(scaled);
                images.add(new JLabel(new ImageIcon(grayImage)));
                images.add(new JLabel(new ImageIcon(blackAndWhiteImage)));

                JOptionPane.showMessageDialog(null, gui);
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

1
太好了,谢谢。通过扫描黑白文本页面,我发现0.55f的级别可以很好地保留原始图像缩略图中的许多细节。 - Sarel Botha

2
您所实现的效果并不是通过预定义阈值的二值化来实现的,而是通过一种称为抖动的技术来实现的。许多抖动方法通过传播误差(当前图像的强度-给定点处的二进制输出)来进行调整下一个输出。这样做是为了创建一种视觉效果,使得产生的图像看起来不是黑白的——如果您不仔细观察它的话。

其中一种简单且广为人知的方法称为Floyd-Steinberg,其伪代码如下:

for y := 1 to image height
    for x := 1 to image width
        v := im(y, x)
        if v < 128 then
            result(y, x) := 0
        else
            result(y, x) := 255
        error := v - result(y, x)
        propagate_error(im, y, x, error)

对于此方法,propagate_error可以如下给出(不考虑边界情况):

    im(y,   x+1) := im(y,   x+1) + (7/16) * error
    im(y+1, x+1) := im(y+1, x+1) + (1/16) * error
    im(y+1, x  ) := im(y+1, x  ) + (5/16) * error
    im(y+1, x-1) := im(y+1, x-1) + (3/16) * error

考虑到伪代码的直接实现,右侧图像是左侧图像的二进制版本。事实上,右侧图像只有黑色和白色两种颜色,对于了解此方法的人来说,这是微不足道的,但对于不了解的人来说,这可能看起来是不可能的。创建的图案给人以多种灰色调的印象,这取决于您观察图像的距离。

1

-尝试下面这个简单的代码:

 package com.bethecoder.tutorials.imageio;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class BlackAndWhiteTest {

  /**
   * @param args
   * @throws IOException 
   */
  public static void main(String[] args) throws IOException {

    File file = new File("C:/Temp/stpatricks_08.gif");
    BufferedImage orginalImage = ImageIO.read(file);

    BufferedImage blackAndWhiteImg = new BufferedImage(
        orginalImage.getWidth(), orginalImage.getHeight(),
        BufferedImage.TYPE_BYTE_BINARY);

    Graphics2D graphics = blackAndWhiteImg.createGraphics();
    graphics.drawImage(orginalImage, 0, 0, null);

    ImageIO.write(blackAndWhiteImg, "png", new File("c:/Temp/stpatricks_08_bw.png")); 
  }

}

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