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:
Screen after processing background replacement:
Important details
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).
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
.
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:
It will work like this with the current code:
And so, if you just change the operator to <
:
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).
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).
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.
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.
– Luiz Vieira
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. :)
– Luiz Vieira
Thanks for the tips Luiz, I added something more specific to the question.
– D0m41n
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
– Luiz Vieira
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.
– Luiz Vieira