Image processing help (background removal)

Asked

Viewed 1,432 times

3

I need to create a code to perform the frame processing of a video. In case the program will scan the pixels of the image and analyze the colors of the pixels and if you find colors close to the color initially defined, you should replace it, for example, with a green key Chroma (replace the background).

I would like tips on this: which library to use, how can I perform the color check be close to the color I will define (I was told to use the RGB band, but I was not told very specifically what to do).

If you have some example code it would also be very useful.

EDIT:

What I don’t really know is how to treat the image.

My goal is to read all the pixels of it and check their color. In case, I want to check if the pixels are similar to an orange shade that I will choose, to be more specific. If the program finds a certain pixel that is similar to this orange (lighter, darker, etc...), it should replace it with a green Chroma key style.

I don’t know how to do this pixel comparison. I was recommended to use the RGB band of each pixel to determine its similarity to orange (through a "threshold" of similarity). However I do not know if this would be the best way to compare the pixels and nor how to compare the RGB values to see if the pixel found is similar to orange (is within the threshold if similarity).

  • Hello. This other question has a similar problem (although in C#, but you can get an idea of the Limiarization algorithm and, who knows, it helps you): http://answall.com/questions/105693/contar-quantos-pixels-de-uma-cor-tem-a-imagem Anyway, Please provide more details of your particular problem, otherwise your question becomes too wide because any suggestion is equally valid.

  • 1

    In fact, it seems that you have difficulties that go beyond the problem of image segmentation. It seems that you do not know, for example, how to access the pixels of an image in Java. If this is the case, I would suggest you edit this question to make it more specific in this aspect, and go opening new questions to get specific help and thus build your learning more gradually. A single broad question tends to be closed as such. :)

  • 1

    Thanks for the tips Luiz, I added something more specific to the question.

  • Well, I’m on vacation so I don’t have a development environment (and at the same time, hehehe) to post an answer. But you can get an idea of how to access/change the pixel values in an image using Java of this code: http://introcs.cs.princeton.edu/java/31datatype/Threshold.java.html

  • 1

    And don’t forget: if you provide in the question an example image of the video frames you need to process, it would make it much easier for someone to give you more direct answers.

1 answer

6

As I’ve already commented, this other question has a similar problem (but in C#). The trivial solution is to use the liming process: you sweep all the pixels of the image and swap by the desired color (green) only those pixels that are below or above a chosen threshold.

I made an example program similar to what is in my reply of that other question (that is, I basically converted that C# code to Java):

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

@SuppressWarnings("serial")
public class Limiar extends JFrame implements ActionListener {

    private JLabel m_oImage;

    private JButton m_oLoadButton;
    private JButton m_oDetectButton;

    private JPanel m_oButtonsPanel;

    final JFileChooser m_oFileChooser = new JFileChooser();

    public Limiar() {
        super("Exemplo de Limiarização");

        m_oImage = new JLabel();
        m_oImage.setHorizontalAlignment(JLabel.CENTER);
        m_oImage.setVerticalAlignment(JLabel.CENTER);
        add(m_oImage, BorderLayout.CENTER);

        m_oButtonsPanel = new JPanel();
        m_oButtonsPanel.setLayout(new BoxLayout(m_oButtonsPanel, BoxLayout.Y_AXIS));
        m_oButtonsPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
        add(m_oButtonsPanel, BorderLayout.EAST);

        m_oLoadButton = new JButton("Carregar Imagem...");
        m_oLoadButton.addActionListener(this);
        m_oLoadButton.setActionCommand("Load");
        m_oLoadButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        m_oButtonsPanel.add(m_oLoadButton);

        m_oButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));

        m_oDetectButton = new JButton("Substituir fundo");
        m_oDetectButton.addActionListener(this);
        m_oDetectButton.setActionCommand("Detect");
        m_oDetectButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        m_oButtonsPanel.add(m_oDetectButton);
    }

    @Override
    public void actionPerformed(ActionEvent oEvent) {
        if (oEvent.getActionCommand().equals("Load")) {

            int iRet = m_oFileChooser.showOpenDialog(this);

            if (iRet == JFileChooser.APPROVE_OPTION) {
                File oFile = m_oFileChooser.getSelectedFile();

                BufferedImage oImg;
                ImageIcon oIcon;
                try {
                    oImg = ImageIO.read(new File(oFile.getPath()));
                    oIcon = new ImageIcon(oImg);
                    m_oImage.setIcon(oIcon);
                } catch (IOException e) {
                    JOptionPane.showMessageDialog(null, "Não foi possível abrir a imagem selecionada");
                }

            }
        }
        else if (oEvent.getActionCommand().equals("Detect")) {

            final double THRESHOLD = 127.5;

            ImageIcon oIcon = (ImageIcon) m_oImage.getIcon();
            BufferedImage oImg = new BufferedImage(oIcon.getIconWidth(), oIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);

            Graphics2D oGr = oImg.createGraphics();
            oIcon.paintIcon(null, oGr, 0, 0);
            oGr.dispose();

            double dBrightness;
            Color oColor;

            for (int x = 0; x < oImg.getWidth(); x++) {
                for (int y = 0; y < oImg.getHeight(); y++) {
                    oColor = new Color(oImg.getRGB(x, y));

                    dBrightness = 0.21 * oColor.getRed() + 0.72 * oColor.getGreen()  + 0.07 * oColor.getBlue();
                    if (dBrightness > THRESHOLD) {
                        oImg.setRGB(x, y, Color.GREEN.getRGB());
                    }
                }
            }

            m_oImage.setIcon(new ImageIcon(oImg));
        }
    }   

    public static void main(String[] args) {
        Limiar t = new Limiar();

        t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        t.setSize(600,480);
        t.setLocationRelativeTo(null);
        t.setVisible(true);
    }   
}

The result (using the same tattoo image as the question cited) is as follows (the background is switched to green, and the rest is kept unchanged):

Screen after loading tattoo image:

inserir a descrição da imagem aqui

Screen after processing background replacement:

inserir a descrição da imagem aqui

Important details

  1. In the original C# code there was already a method (getBrightness) to return the brightness (i.e., convert the color in 3 bands to grayscale). I don’t know if there is a method for this in Java (at least I didn’t find anything in the class Color). So I converted myself (with a note that is explained in this my other answer).

  2. The values of a pixel (either in RGB or grayscale) are normalized in C# as a float amid 0.0 and 1.0. In the case of Java, these are values between 0.0 and 255.0. So the initial threshold chosen in that original code was 0.5 and here is 127.5.

  3. The case of the other question was simpler because the images were more standardized with a lighter background than the rest (they always had a white background). You don’t offer details about the video you’re processing (not even an example! Tsc, Tsc, Tsc), but I imagine the background won’t have a very simple pattern. You will certainly need to change the threshold value to do some tests. And if the background is darker than the rest of the image, you will also need to change the brightness comparison operator > for < (to detect and exchange only what is darkest - remember that the closer to the 0, darker the pixel is). For example, the processing of this famous meme below:

inserir a descrição da imagem aqui

It will work like this with the current code:

inserir a descrição da imagem aqui

And so, if you just change the operator to <:

inserir a descrição da imagem aqui

Since in this image the background is darker than the cat, the exchange works better. However, note how the algorithm fails to include the cat’s tie and glasses as part of the "background". In cases like these, you could make more far-fetched comparisons, such as whether the pixel is in a luminosity range, or whether it is included in a large connected region (i.e., whether it is more likely to be a background or not).

  1. Alternatively to the limiarization method, it may be possible to make statistical segmentations with analysis of the probabilities of the color histogram (something similar to what I describe in this my other answer) or with the training of a decision tree. But, this will depend on your problem domain (and just to quote once again, you unfortunately haven’t provided any information about it, despite the numerous requests).

  2. Remember that doing segmentation processing for each frame of a video will be computationally costly and can slow down rendering output. Maybe you need to work to improve performance or rethink the problem.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.