Difference between revisions of "Example Watershed with Seeds"

From BoofCV
Jump to navigationJump to search
m
m
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
[[file:Example_Watershed_with_Seeds.jpg|center|450px|Found watershed around particles drawn in red.]]
[[file:Example_Watershed_with_Seeds.jpg|center|450px|Found watershed around particles drawn in red.]]


Watershed is one of the earlier and still more popular image segmentation routines. While very fast, one major problem it suffers from is that it generate an excessive number of regions. In certain applications you can overcome this problem by providing initial seeds to watershed.
Watershed is one of the earlier and still more popular image segmentation routines. While very fast, one major problem it suffers from is that it generate an excessive number of regions. In certain applications you can overcome this problem by providing initial seeds to watershed.


Example Code:
Example Code:
* [https://github.com/lessthanoptimal/BoofCV/blob/v0.23/examples/src/boofcv/examples/segmentation/ExampleWatershedWithSeeds.java ExampleWatershedWithSeeds.java]
* [https://github.com/lessthanoptimal/BoofCV/blob/v0.40/examples/src/main/java/boofcv/examples/segmentation/ExampleWatershedWithSeeds.java ExampleWatershedWithSeeds.java]


Concepts:
Concepts:
Line 22: Line 22:
/**
/**
  * Watershed image segmentation will often produce an excessive number of regions since each local minimum is the
  * Watershed image segmentation will often produce an excessive number of regions since each local minimum is the
  * seed which creates a new region. To get around this problem you can provide the seeds manually. The down side
  * seed which creates a new region. To get around this problem you can provide the seeds manually. The down side
  * to providing manual seeds is that it is no longer a general purpose algorithm and requires knowledge of the image
  * to providing manual seeds is that it is no longer a general purpose algorithm and requires knowledge of the image
  * structure to provide the seeds. This example demonstrates how to do this.
  * structure to provide the seeds. This example demonstrates how to do this.
  *
  *
  * @author Peter Abeles
  * @author Peter Abeles
  */
  */
public class ExampleWatershedWithSeeds {
public class ExampleWatershedWithSeeds {
public static void main(String[] args) {
public static void main( String[] args ) {
 
BufferedImage image = UtilImageIO.loadImageNotNull(UtilIO.pathExample("particles01.jpg"));
BufferedImage image = UtilImageIO.loadImage(UtilIO.pathExample("particles01.jpg"));
GrayU8 input = ConvertBufferedImage.convertFromSingle(image, null, GrayU8.class);
GrayU8 input = ConvertBufferedImage.convertFromSingle(image, null, GrayU8.class);


// declare working data
// declare working data
GrayU8 binary = new GrayU8(input.width,input.height);
var binary = new GrayU8(input.width, input.height);
GrayS32 label = new GrayS32(input.width,input.height);
var label = new GrayS32(input.width, input.height);


// Try using the mean pixel value to create a binary image then erode it to separate the particles from
// Try using the mean pixel value to create a binary image then erode it to separate the particles from
// each other
// each other
double mean = ImageStatistics.mean(input);
double mean = ImageStatistics.mean(input);
ThresholdImageOps.threshold(input, binary, (int) mean, true);
ThresholdImageOps.threshold(input, binary, (int)mean, true);
GrayU8 filtered = BinaryImageOps.erode8(binary, 2, null);
GrayU8 filtered = BinaryImageOps.erode8(binary, 2, null);
int numRegions = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, label).size() + 1;
int numRegions = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, label).size() + 1;
// +1 to regions because contour only counts blobs and not the background
// +1 to regions because contour only counts blobs and not the background


// The labeled image can be used as is. A precondition for seeded watershed is that all seeds have an
// The labeled image can be used as is. A precondition for seeded watershed is that all seeds have an
// ID > 0. Luckily, a value of 0 was used for background pixels in the contour algorithm.
// ID > 0. Luckily, a value of 0 was used for background pixels in the contour algorithm.
WatershedVincentSoille1991 watershed = FactorySegmentationAlg.watershed(ConnectRule.FOUR);
WatershedVincentSoille1991 watershed = FactorySegmentationAlg.watershed(ConnectRule.FOUR);


watershed.process(input,label);
watershed.process(input, label);


GrayS32 output = watershed.getOutput();
GrayS32 output = watershed.getOutput();


BufferedImage outLabeled = VisualizeBinaryData.renderLabeledBG(label, numRegions, null);
BufferedImage outLabeled = VisualizeBinaryData.renderLabeledBG(label, numRegions, null);
VisualizeRegions.watersheds(output,image,1);
VisualizeRegions.watersheds(output, image, 1);


// Removing the watersheds and update the region count
// Removing the watersheds and update the region count
Line 61: Line 60:
watershed.removeWatersheds();
watershed.removeWatersheds();
numRegions -= 1;
numRegions -= 1;
BufferedImage outRegions = VisualizeRegions.regions(output,numRegions,null);
BufferedImage outRegions = VisualizeRegions.regions(output, numRegions, null);


ListDisplayPanel gui = new ListDisplayPanel();
var gui = new ListDisplayPanel();
gui.addImage(image, "Watersheds");
gui.addImage(image, "Watersheds");
gui.addImage(outRegions, "Regions");
gui.addImage(outRegions, "Regions");

Latest revision as of 15:33, 17 January 2022

Found watershed around particles drawn in red.

Watershed is one of the earlier and still more popular image segmentation routines. While very fast, one major problem it suffers from is that it generate an excessive number of regions. In certain applications you can overcome this problem by providing initial seeds to watershed.

Example Code:

Concepts:

  • Image Segmentation
  • Super Pixels

Related Examples:

Relevant Applets:

Example Code

/**
 * Watershed image segmentation will often produce an excessive number of regions since each local minimum is the
 * seed which creates a new region. To get around this problem you can provide the seeds manually. The down side
 * to providing manual seeds is that it is no longer a general purpose algorithm and requires knowledge of the image
 * structure to provide the seeds. This example demonstrates how to do this.
 *
 * @author Peter Abeles
 */
public class ExampleWatershedWithSeeds {
	public static void main( String[] args ) {
		BufferedImage image = UtilImageIO.loadImageNotNull(UtilIO.pathExample("particles01.jpg"));
		GrayU8 input = ConvertBufferedImage.convertFromSingle(image, null, GrayU8.class);

		// declare working data
		var binary = new GrayU8(input.width, input.height);
		var label = new GrayS32(input.width, input.height);

		// Try using the mean pixel value to create a binary image then erode it to separate the particles from
		// each other
		double mean = ImageStatistics.mean(input);
		ThresholdImageOps.threshold(input, binary, (int)mean, true);
		GrayU8 filtered = BinaryImageOps.erode8(binary, 2, null);
		int numRegions = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, label).size() + 1;
		// +1 to regions because contour only counts blobs and not the background

		// The labeled image can be used as is. A precondition for seeded watershed is that all seeds have an
		// ID > 0. Luckily, a value of 0 was used for background pixels in the contour algorithm.
		WatershedVincentSoille1991 watershed = FactorySegmentationAlg.watershed(ConnectRule.FOUR);

		watershed.process(input, label);

		GrayS32 output = watershed.getOutput();

		BufferedImage outLabeled = VisualizeBinaryData.renderLabeledBG(label, numRegions, null);
		VisualizeRegions.watersheds(output, image, 1);

		// Removing the watersheds and update the region count
		// NOTE: watershed.getTotalRegions() does not return correct results if seeds are used!
		watershed.removeWatersheds();
		numRegions -= 1;
		BufferedImage outRegions = VisualizeRegions.regions(output, numRegions, null);

		var gui = new ListDisplayPanel();
		gui.addImage(image, "Watersheds");
		gui.addImage(outRegions, "Regions");
		gui.addImage(outLabeled, "Seeds");
		ShowImages.showWindow(gui, "Watershed", true);

		// Additional processing would be needed for this example to be really useful.
		// The watersheds can be used to characterize the background while the seed binary image the particles
		// From this the particles could be more accurately classified by assigning each pixel one of the two
		// just mentioned groups based distance
	}
}