Skip to content

Commit

Permalink
Merge pull request #170 from gerasidev/main
Browse files Browse the repository at this point in the history
Otsu's Thresholding Augmentation with example
  • Loading branch information
Samyssmile authored Mar 28, 2024
2 parents 347439a + 2f87a1d commit 26c32c4
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package de.example.augmentation;

import de.edux.augmentation.effects.OtsuThresholdingAugmentation;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class OtsuThresholdingAugmentationExample {

private static final String IMAGE_PATH = "images" + File.separator + "cyborg.png";

public static void main(String[] args) throws IOException {
BufferedImage originalImage = loadTestImage(IMAGE_PATH);
OtsuThresholdingAugmentation otsuThreshold = new OtsuThresholdingAugmentation();
BufferedImage otsuImage = otsuThreshold.apply(originalImage);
display(originalImage, "Original Image");
display(otsuImage, "After Otsu Augmentation");
}

public static BufferedImage loadTestImage(String path) throws IOException {
var resourcePath = path;
var imageStream =
SingleImageAugmentationExample.class.getClassLoader().getResourceAsStream(resourcePath);
if (imageStream == null) {
throw new IOException("Cannot find resource: " + resourcePath);
}
return ImageIO.read(imageStream);
}

public static void display(BufferedImage img, String title) {
JFrame frame = new JFrame(title);
JLabel label = new JLabel();
frame.setSize(img.getWidth(), img.getHeight());
label.setIcon(new ImageIcon(img));
frame.getContentPane().add(label, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package de.edux.augmentation.effects;

import de.edux.augmentation.core.AbstractAugmentation;

import java.awt.Graphics;
import java.awt.image.BufferedImage;

/**
* Applies Otsu's thresholding method to the given image.
* Converts the image to grayscale, calculates the histogram, finds the optimal threshold,
* and applies the threshold to create a binary image.
*/
public class OtsuThresholdingAugmentation extends AbstractAugmentation {
private static final Integer HISTOGRAM_SIZE = 256;
private static final int RED_INTENSITY_SHIFT = 16;

/**
* Applies Otsu's thresholding method to the given image.
* @param image the original image to be thresholded
* @return a binary image where pixels with intensity greater than the threshold are white,
* and others are black
*/
@Override
public BufferedImage apply(BufferedImage image) {
BufferedImage grayImage = toGrayScale((image));
int[] histogram = calculateHistogram(grayImage);
int threshold = findOtsuThreshold(histogram, grayImage.getWidth() * grayImage.getHeight());
BufferedImage thresholdedImage = new BufferedImage(grayImage.getWidth(), grayImage.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
for (int i = 0; i < grayImage.getWidth(); i++) {
for (int j = 0; j < grayImage.getHeight(); j++) {
int pixel = grayImage.getRGB(i, j);
int intensity = (pixel >> RED_INTENSITY_SHIFT) & 0xff;
thresholdedImage.setRGB(i, j, intensity > threshold ? 0xFFFFFF : 0x000000);
}
}
return thresholdedImage;
}

private BufferedImage toGrayScale(BufferedImage img) {
System.out.println(" Converting to GrayScale.");
BufferedImage grayImage = new BufferedImage(
img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics g = grayImage.getGraphics();
g.drawImage(img, 0, 0, null);
g.dispose();
return grayImage;
}

private int[] calculateHistogram(BufferedImage image) {
int[] histogram = new int[HISTOGRAM_SIZE];
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
int pixel = image.getRGB(i, j);
int intensity = (pixel >> 16) & 0xff;
histogram[intensity]++;
}
}
return histogram;
}

private int findOtsuThreshold(int[] histogram, int totalPixels) {
float totalPixelIntensity = 0;
for (int intensity = 0; intensity < HISTOGRAM_SIZE; intensity++) {
totalPixelIntensity += intensity * histogram[intensity];
}
float backgroundPixelIntensity = 0;
int backgroundWeight = 0;
int foregroundWeight = 0;
float maxVariance = 0;
int optimalThreshold = 0;
for (int threshold = 0; threshold < HISTOGRAM_SIZE; threshold++) {
backgroundWeight += histogram[threshold];

if (backgroundWeight == 0) continue;
foregroundWeight = totalPixels - backgroundWeight;
if (foregroundWeight == 0) break;

backgroundPixelIntensity += (float) (threshold * histogram[threshold]);

float meanBackgroundIntensity = backgroundPixelIntensity / backgroundWeight;
float meanForegroundIntensity = (totalPixelIntensity - backgroundPixelIntensity) / foregroundWeight;
float betweenClassVariance = calculateBetweenClassVariance(backgroundWeight, foregroundWeight, meanBackgroundIntensity, meanForegroundIntensity);

if (betweenClassVariance > maxVariance) {
maxVariance = betweenClassVariance;
optimalThreshold = threshold;
}
}
return optimalThreshold;
}

private float calculateBetweenClassVariance(int backgroundWeight, int foregroundWeight, float meanBackgroundIntensity, float meanForegroundIntensity) {
return (float) ((float) backgroundWeight * (float) foregroundWeight * Math.pow(meanBackgroundIntensity - meanForegroundIntensity, 2));
}

}








Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package de.edux.augmentation.effects;

import de.edux.augmentation.core.AugmentationBuilder;
import de.edux.augmentation.core.AugmentationSequence;
import org.junit.jupiter.api.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

import static de.edux.augmentation.AugmentationTestUtils.loadTestImage;
import static de.edux.augmentation.AugmentationTestUtils.openImageInPreview;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class OtsuThresholdingAugmentationTest {

@Test
void apply() throws IOException, InterruptedException {
var image = loadTestImage("augmentation" + File.separator + "shaman-shadows.png");
int originalWidth = image.getWidth();
int originalHeight = image.getHeight();

AugmentationSequence augmentationSequence =
new AugmentationBuilder().addAugmentation(new OtsuThresholdingAugmentation()).build();
BufferedImage augmentedImage = augmentationSequence.applyTo(image);

int[] originalPixels =
image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
int[] augmentedPixels =
augmentedImage.getRGB(
0,
0,
augmentedImage.getWidth(),
augmentedImage.getHeight(),
null,
0,
augmentedImage.getWidth());

assertNotNull(augmentedImage, "Augmented image should not be null.");

assertEquals(
originalWidth,
augmentedImage.getWidth(),
"Augmented image width should match the specified width.");
assertEquals(
originalHeight,
augmentedImage.getHeight(),
"Augmented image height should match the specified height.");
assertFalse(
Arrays.equals(originalPixels, augmentedPixels),
"The augmented image should differ from the original.");

Path outputPath = Paths.get("augmented.png");
ImageIO.write(augmentedImage, "png", outputPath.toFile());

assertTrue(Files.exists(outputPath), "Output image file should exist.");
assertTrue(Files.size(outputPath) > 0, "Output image file should not be empty.");

openImageInPreview(image);
openImageInPreview(augmentedImage);
}


}

0 comments on commit 26c32c4

Please sign in to comment.