Как преобразовать изображение в черно-белое с помощью Java

Я конвертирую изображение в черно-белое в imagemagick с помощью такой команды:

convert myimg.png -monochrome  out3.png

Мне интересно, можно ли добиться того же результата в Java? Без использования Im4Java или JMagick?


person birdy    schedule 25.01.2013    source источник
comment
@AndrewThompson, этот ответ касается преобразования в оттенки серого.   -  person mmgp    schedule 25.01.2013
comment
поскольку вы ничего не упомянули, я думаю, мне следует попытаться прояснить, что здесь нужно. Вы ищете алгоритм сглаживания, я не знаю, какую конкретную реализацию использует ImageMagick, но классический Флойд-Стейнберг - хорошее первое предположение.   -  person mmgp    schedule 25.01.2013
comment
почему этот вопрос имеет -1?   -  person birdy    schedule 25.01.2013
comment
возможно, потому что вы не приложили усилий, пытаясь понять, что производит convert -monochrome, и дали совершенно другой ответ, чем тот, который вы задали. С тем же успехом вы могли спросить: как преобразовать значение в 0, если оно ниже 127, и как преобразовать значение в 255, если оно больше или равно 127? тупой вопрос.   -  person mmgp    schedule 25.01.2013


Ответы (4)


Я думаю, это зависит от того, что вы подразумеваете под «монохромным»/«черно-белым»…

введите здесь описание изображения

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

            }
        }


    }
}
person MadProgrammer    schedule 25.01.2013
comment
Из трех изображений в вашем ответе мне нужно крайнее правое - person birdy; 25.01.2013
comment
Просто любопытно, можно ли установить пороговый уровень (яркости) для преобразования в 1-битный монохромный? - person David R Tribble; 25.01.2013
comment
@birdy Тогда тебе нужно blackWhite BufferedImage - person MadProgrammer; 25.01.2013
comment
@Loadmaster Насколько мне известно, не используя методы, описанные выше. Вы могли бы написать BufferedImageOP, который сделал это, хотя - person MadProgrammer; 25.01.2013
comment
RescaleOp перед преобразованием добьется цели. Смотрите мой ответ. - person Andrew Thompson; 25.01.2013
comment
Интересно: вызов ColorConvertOp с цветовым пространством CS_Gray дает лучший результат, чем вызов с цветовым пространством целевого изображения. - person Tilman Hausherr; 27.01.2021

Попробуйте этот грубый пример. Сначала мы осветляем или затемняем изображение, используя 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);
    }
}
person Andrew Thompson    schedule 25.01.2013
comment
Большое спасибо. С отсканированными черно-белыми текстовыми страницами я обнаружил, что уровень 0,55f хорошо работает, чтобы сохранить много деталей на миниатюре исходного изображения. - person Sarel Botha; 04.04.2017

Эффект, которого вы достигаете, достигается не за счет бинаризации с заранее определенным порогом, а за счет техники, называемой дизерингом. Многие методы сглаживания работают за счет распространения ошибки (интенсивность в текущем изображении - двоичный вывод в данной точке) и, таким образом, корректировки следующих выходов. Это делается для создания визуального эффекта, при котором результирующее изображение может показаться не черно-белым, если не присматриваться к нему слишком внимательно.

Один из таких простых и хорошо известных методов принадлежит Флойду-Стейнбергу, псевдокод для него:

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

Учитывая прямую реализацию данного псевдокода, следующее изображение справа является двоичной версией изображения слева. На самом деле на изображении справа только черный и белый цвета, это тривиальный вопрос для тех, кто знает об этом методе, но для тех, кто не знает, это может показаться невозможным. Создаваемые узоры создают впечатление наличия нескольких оттенков серого, в зависимости от того, насколько далеко вы смотрите на изображение.

введите здесь описание изображениявведите здесь описание изображения

person mmgp    schedule 25.01.2013

-Попробуйте ниже простой код,

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

}
person Swati patil    schedule 07.01.2015