Difference between revisions of "Example Fit Polygon"

From BoofCV
Jump to navigationJump to search
m
m
 
(6 intermediate revisions by the same user not shown)
Line 9: Line 9:


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


Concepts:
Concepts:
* Object contours/edges
* Object contours/edges
* Shape fitting
* Shape fitting
Relevant Videos:
* [https://youtu.be/TGg-xgTyaU8?t=195 Youtube Polyline v0.28]


Relevant Examples:
Relevant Examples:
Line 24: Line 27:
/**
/**
  * Demonstration of how to convert a point sequence describing an objects outline/contour into a sequence of line
  * Demonstration of how to convert a point sequence describing an objects outline/contour into a sequence of line
  * segments. Useful when analysing shapes such as squares and triangles or when trying to simply the low level
  * segments. Useful when analysing shapes such as squares and triangles or when trying to simply the low level
  * pixel output.
  * pixel output.
  *
  *
Line 31: Line 34:
public class ExampleFitPolygon {
public class ExampleFitPolygon {


// Polynomial fitting tolerances
// Used to bias it towards more or fewer sides. larger number = fewer sides
static double splitFraction = 0.05;
static double cornerPenalty = 0.25;
static double minimumSideFraction = 0.1;
// The fewest number of pixels a side can have
static int minSide = 10;


static ListDisplayPanel gui = new ListDisplayPanel();
static ListDisplayPanel gui = new ListDisplayPanel();
Line 40: Line 44:
* Fits polygons to found contours around binary blobs.
* Fits polygons to found contours around binary blobs.
*/
*/
public static void fitBinaryImage(GrayF32 input) {
public static void fitBinaryImage( GrayF32 input ) {


GrayU8 binary = new GrayU8(input.width,input.height);
GrayU8 binary = new GrayU8(input.width, input.height);
BufferedImage polygon = new BufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);
BufferedImage polygon = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);


// the mean pixel value is often a reasonable threshold when creating a binary image
// the mean pixel value is often a reasonable threshold when creating a binary image
Line 49: Line 53:


// create a binary image by thresholding
// create a binary image by thresholding
ThresholdImageOps.threshold(input, binary, (float) mean, true);
ThresholdImageOps.threshold(input, binary, (float)mean, true);


// reduce noise with some filtering
// reduce noise with some filtering
Line 55: Line 59:
filtered = BinaryImageOps.dilate8(filtered, 1, null);
filtered = BinaryImageOps.dilate8(filtered, 1, null);


// Find the contour around the shapes
// Find internal and external contour around each shape
List<Contour> contours = BinaryImageOps.contour(filtered, ConnectRule.EIGHT,null);
List<Contour> contours = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, null);


// Fit a polygon to each shape and draw the results
// Fit a polygon to each shape and draw the results
Line 62: Line 66:
g2.setStroke(new BasicStroke(2));
g2.setStroke(new BasicStroke(2));


for( Contour c : contours ) {
for (Contour c : contours) {
// Fit the polygon to the found external contour. Note loop = true
// Fit the polygon to the found external contour. Note loop = true
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external,true,
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external, true, minSide, cornerPenalty);
splitFraction, minimumSideFraction,100);


g2.setColor(Color.RED);
g2.setColor(Color.RED);
VisualizeShapes.drawPolygon(vertexes,true,g2);
VisualizeShapes.drawPolygon(vertexes, true, g2);


// handle internal contours now
// handle internal contours now
g2.setColor(Color.BLUE);
g2.setColor(Color.BLUE);
for( List<Point2D_I32> internal : c.internal ) {
for (List<Point2D_I32> internal : c.internal) {
vertexes = ShapeFittingOps.fitPolygon(internal,true, splitFraction, minimumSideFraction,100);
vertexes = ShapeFittingOps.fitPolygon(internal, true, minSide, cornerPenalty);
VisualizeShapes.drawPolygon(vertexes,true,g2);
VisualizeShapes.drawPolygon(vertexes, true, g2);
}
}
}
}
Line 82: Line 85:


/**
/**
* Fits a sequence of line-segments into a sequence of points found using the Canny edge detector. In this case
* Fits a sequence of line-segments into a sequence of points found using the Canny edge detector. In this case
* the points are not connected in a loop. The canny detector produces a more complex tree and the fitted
* the points are not connected in a loop. The canny detector produces a more complex tree and the fitted
* points can be a bit noisy compared to the others.
* points can be a bit noisy compared to the others.
Line 88: Line 91:
public static void fitCannyEdges( GrayF32 input ) {
public static void fitCannyEdges( GrayF32 input ) {


BufferedImage displayImage = new BufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);
BufferedImage displayImage = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);


// Finds edges inside the image
// Finds edges inside the image
CannyEdge<GrayF32,GrayF32> canny =
CannyEdge<GrayF32, GrayF32> canny =
FactoryEdgeDetectors.canny(2, true, true, GrayF32.class, GrayF32.class);
FactoryEdgeDetectors.canny(2, true, true, GrayF32.class, GrayF32.class);


canny.process(input,0.1f,0.3f,null);
canny.process(input, 0.1f, 0.3f, null);
List<EdgeContour> contours = canny.getContours();
List<EdgeContour> contours = canny.getContours();


Line 103: Line 106:
Random rand = new Random(234);
Random rand = new Random(234);


for( EdgeContour e : contours ) {
for (EdgeContour e : contours) {
g2.setColor(new Color(rand.nextInt()));
g2.setColor(new Color(rand.nextInt()));


for(EdgeSegment s : e.segments ) {
for (EdgeSegment s : e.segments) {
// fit line segments to the point sequence. Note that loop is false
// fit line segments to the point sequence. Note that loop is false
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(s.points,false,
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(s.points, false, minSide, cornerPenalty);
splitFraction, minimumSideFraction,100);


VisualizeShapes.drawPolygon(vertexes, false, g2);
VisualizeShapes.drawPolygon(vertexes, false, g2);
Line 119: Line 121:


/**
/**
* Detects contours inside the binary image generated by canny. Only the external contour is relevant. Often
* Detects contours inside the binary image generated by canny. Only the external contour is relevant. Often
* easier to deal with than working with Canny edges directly.
* easier to deal with than working with Canny edges directly.
*/
*/
public static void fitCannyBinary( GrayF32 input ) {
public static void fitCannyBinary( GrayF32 input ) {


BufferedImage displayImage = new BufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);
BufferedImage displayImage = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);
GrayU8 binary = new GrayU8(input.width,input.height);
GrayU8 binary = new GrayU8(input.width, input.height);


// Finds edges inside the image
// Finds edges inside the image
CannyEdge<GrayF32,GrayF32> canny =
CannyEdge<GrayF32, GrayF32> canny =
FactoryEdgeDetectors.canny(2, false, true, GrayF32.class, GrayF32.class);
FactoryEdgeDetectors.canny(2, false, true, GrayF32.class, GrayF32.class);


canny.process(input,0.1f,0.3f,binary);
canny.process(input, 0.1f, 0.3f, binary);


List<Contour> contours = BinaryImageOps.contour(binary, ConnectRule.EIGHT, null);
// Only external contours are relevant
List<Contour> contours = BinaryImageOps.contourExternal(binary, ConnectRule.EIGHT);


Graphics2D g2 = displayImage.createGraphics();
Graphics2D g2 = displayImage.createGraphics();
Line 141: Line 144:
Random rand = new Random(234);
Random rand = new Random(234);


for( Contour c : contours ) {
for (Contour c : contours) {
// Only the external contours are relevant.
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external, true, minSide, cornerPenalty);
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external,true,
splitFraction, minimumSideFraction,100);


g2.setColor(new Color(rand.nextInt()));
g2.setColor(new Color(rand.nextInt()));
VisualizeShapes.drawPolygon(vertexes,true,g2);
VisualizeShapes.drawPolygon(vertexes, true, g2);
}
}


Line 153: Line 154:
}
}


public static void main( String args[] ) {
public static void main( String[] args ) {
// load and convert the image into a usable format
// load and convert the image into a usable format
BufferedImage image = UtilImageIO.loadImage(UtilIO.pathExample("shapes/shapes02.png"));
BufferedImage image = UtilImageIO.loadImageNotNull(UtilIO.pathExample("shapes/shapes02.png"));
GrayF32 input = ConvertBufferedImage.convertFromSingle(image, null, GrayF32.class);
GrayF32 input = ConvertBufferedImage.convertFromSingle(image, null, GrayF32.class);
gui.addImage(image,"Original");


fitCannyEdges(input);
fitCannyEdges(input);
fitCannyBinary(input);
fitCannyBinary(input);
fitBinaryImage(input);
fitBinaryImage(input);
gui.addImage(image, "Original");


ShowImages.showWindow(gui, "Polygon from Contour", true);
ShowImages.showWindow(gui, "Polygon from Contour", true);

Latest revision as of 13:02, 17 January 2022

Demonstration for how to fit a polygon to object contours and edges. The input contours can be found from binary blobs and the edge sequence from Canny edge detector. This is often a useful preprocessing step before applying a higher level image processing algorithm.

Example Code:

Concepts:

  • Object contours/edges
  • Shape fitting

Relevant Videos:

Relevant Examples:

Example Code

/**
 * Demonstration of how to convert a point sequence describing an objects outline/contour into a sequence of line
 * segments. Useful when analysing shapes such as squares and triangles or when trying to simply the low level
 * pixel output.
 *
 * @author Peter Abeles
 */
public class ExampleFitPolygon {

	// Used to bias it towards more or fewer sides. larger number = fewer sides
	static double cornerPenalty = 0.25;
	// The fewest number of pixels a side can have
	static int minSide = 10;

	static ListDisplayPanel gui = new ListDisplayPanel();

	/**
	 * Fits polygons to found contours around binary blobs.
	 */
	public static void fitBinaryImage( GrayF32 input ) {

		GrayU8 binary = new GrayU8(input.width, input.height);
		BufferedImage polygon = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);

		// the mean pixel value is often a reasonable threshold when creating a binary image
		double mean = ImageStatistics.mean(input);

		// create a binary image by thresholding
		ThresholdImageOps.threshold(input, binary, (float)mean, true);

		// reduce noise with some filtering
		GrayU8 filtered = BinaryImageOps.erode8(binary, 1, null);
		filtered = BinaryImageOps.dilate8(filtered, 1, null);

		// Find internal and external contour around each shape
		List<Contour> contours = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, null);

		// Fit a polygon to each shape and draw the results
		Graphics2D g2 = polygon.createGraphics();
		g2.setStroke(new BasicStroke(2));

		for (Contour c : contours) {
			// Fit the polygon to the found external contour. Note loop = true
			List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external, true, minSide, cornerPenalty);

			g2.setColor(Color.RED);
			VisualizeShapes.drawPolygon(vertexes, true, g2);

			// handle internal contours now
			g2.setColor(Color.BLUE);
			for (List<Point2D_I32> internal : c.internal) {
				vertexes = ShapeFittingOps.fitPolygon(internal, true, minSide, cornerPenalty);
				VisualizeShapes.drawPolygon(vertexes, true, g2);
			}
		}

		gui.addImage(polygon, "Binary Blob Contours");
	}

	/**
	 * Fits a sequence of line-segments into a sequence of points found using the Canny edge detector. In this case
	 * the points are not connected in a loop. The canny detector produces a more complex tree and the fitted
	 * points can be a bit noisy compared to the others.
	 */
	public static void fitCannyEdges( GrayF32 input ) {

		BufferedImage displayImage = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);

		// Finds edges inside the image
		CannyEdge<GrayF32, GrayF32> canny =
				FactoryEdgeDetectors.canny(2, true, true, GrayF32.class, GrayF32.class);

		canny.process(input, 0.1f, 0.3f, null);
		List<EdgeContour> contours = canny.getContours();

		Graphics2D g2 = displayImage.createGraphics();
		g2.setStroke(new BasicStroke(2));

		// used to select colors for each line
		Random rand = new Random(234);

		for (EdgeContour e : contours) {
			g2.setColor(new Color(rand.nextInt()));

			for (EdgeSegment s : e.segments) {
				// fit line segments to the point sequence. Note that loop is false
				List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(s.points, false, minSide, cornerPenalty);

				VisualizeShapes.drawPolygon(vertexes, false, g2);
			}
		}

		gui.addImage(displayImage, "Canny Trace");
	}

	/**
	 * Detects contours inside the binary image generated by canny. Only the external contour is relevant. Often
	 * easier to deal with than working with Canny edges directly.
	 */
	public static void fitCannyBinary( GrayF32 input ) {

		BufferedImage displayImage = new BufferedImage(input.width, input.height, BufferedImage.TYPE_INT_RGB);
		GrayU8 binary = new GrayU8(input.width, input.height);

		// Finds edges inside the image
		CannyEdge<GrayF32, GrayF32> canny =
				FactoryEdgeDetectors.canny(2, false, true, GrayF32.class, GrayF32.class);

		canny.process(input, 0.1f, 0.3f, binary);

		// Only external contours are relevant
		List<Contour> contours = BinaryImageOps.contourExternal(binary, ConnectRule.EIGHT);

		Graphics2D g2 = displayImage.createGraphics();
		g2.setStroke(new BasicStroke(2));

		// used to select colors for each line
		Random rand = new Random(234);

		for (Contour c : contours) {
			List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external, true, minSide, cornerPenalty);

			g2.setColor(new Color(rand.nextInt()));
			VisualizeShapes.drawPolygon(vertexes, true, g2);
		}

		gui.addImage(displayImage, "Canny Contour");
	}

	public static void main( String[] args ) {
		// load and convert the image into a usable format
		BufferedImage image = UtilImageIO.loadImageNotNull(UtilIO.pathExample("shapes/shapes02.png"));
		GrayF32 input = ConvertBufferedImage.convertFromSingle(image, null, GrayF32.class);

		fitCannyEdges(input);
		fitCannyBinary(input);
		fitBinaryImage(input);
		gui.addImage(image, "Original");

		ShowImages.showWindow(gui, "Polygon from Contour", true);
	}
}