Difference between revisions of "Example Fit Polygon"

From BoofCV
Jump to navigationJump to search
(Created page with "<center> <gallery widths=280px heights=240px> file:Example_fit_polygon_input.png | Input image with curves and polygons file:Example_fit_polygon_result.png | Fitted polygons. ...")
 
m
 
(14 intermediate revisions by the same user not shown)
Line 6: Line 6:
</center>
</center>


Demonstration for how to fit a polygon to object contours and edges.  A polygon or line segment sequence can be fit to contours or edges.  The 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.
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:
Example Code:
* [https://github.com/lessthanoptimal/BoofCV/blob/v0.14/examples/src/boofcv/examples/ExampleFitEllipse.java ExampleFitEllipse.java]
* [https://github.com/lessthanoptimal/BoofCV/blob/v0.40/examples/src/main/java/boofcv/examples/features/ExampleFitPolygon.java ExampleFitPolygon.java]


Concepts:
Concepts:
Line 15: Line 15:
* Shape fitting
* Shape fitting


Relevant Applets:
Relevant Videos:
* [[Applet Contour| Contour Detector]]
* [https://youtu.be/TGg-xgTyaU8?t=195 Youtube Polyline v0.28]
* [[Applet Shape Fitting| Shape Fitting]]
 
Relevant Examples:
* [[Example_Binary_Image| Binary Image Processing]]
* [[Example_Fit_Ellipse| Ellipse Fitting]]


= Example Code =
= Example Code =
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 toleranceDist = 2;
static double cornerPenalty = 0.25;
static double toleranceAngle= Math.PI/10;
// The fewest number of pixels a side can have
static int minSide = 10;
 
static ListDisplayPanel gui = new ListDisplayPanel();


/**
/**
* Fits a polygons the found contours around binary blobs. This demonstrates how it can be used to handle
* Fits polygons to found contours around binary blobs.
* connected loops of points
*/
*/
public static void fitBinaryImage(ImageFloat32 input) {
public static void fitBinaryImage( GrayF32 input ) {


ImageUInt8 binary = new ImageUInt8(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 48: 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
ImageUInt8 filtered = BinaryImageOps.erode8(binary,null);
GrayU8 filtered = BinaryImageOps.erode8(binary, 1, null);
filtered = BinaryImageOps.dilate8(filtered, 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,8,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 61: Line 66:
g2.setStroke(new BasicStroke(2));
g2.setStroke(new BasicStroke(2));


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


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


// 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,toleranceDist,toleranceAngle,100);
vertexes = ShapeFittingOps.fitPolygon(internal, true, minSide, cornerPenalty);
VisualizeShapes.drawPolygon(vertexes,true,g2);
VisualizeShapes.drawPolygon(vertexes, true, g2);
}
}
}
}


ShowImages.showWindow(polygon,"Binary Blob Contours");
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
* 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.
*/
*/
public static void fitCannyEdges( ImageFloat32 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<ImageFloat32,ImageFloat32> canny =
CannyEdge<GrayF32, GrayF32> canny =
FactoryEdgeDetectors.canny(2, true, true, ImageFloat32.class, ImageFloat32.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 100: 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);
toleranceDist,toleranceAngle,100);


VisualizeShapes.drawPolygon(vertexes, false, g2);
VisualizeShapes.drawPolygon(vertexes, false, g2);
Line 112: Line 117:
}
}


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


/**
/**
* 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( ImageFloat32 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);
ImageUInt8 binary = new ImageUInt8(input.width,input.height);
GrayU8 binary = new GrayU8(input.width, input.height);


// Finds edges inside the image
// Finds edges inside the image
CannyEdge<ImageFloat32,ImageFloat32> canny =
CannyEdge<GrayF32, GrayF32> canny =
FactoryEdgeDetectors.canny(2, false, true, ImageFloat32.class, ImageFloat32.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, 8, null);
// Only external contours are relevant
List<Contour> contours = BinaryImageOps.contourExternal(binary, ConnectRule.EIGHT);


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


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


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


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


 
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("../data/applet/shapes02.png");
BufferedImage image = UtilImageIO.loadImageNotNull(UtilIO.pathExample("shapes/shapes02.png"));
ImageFloat32 input = ConvertBufferedImage.convertFromSingle(image, null, ImageFloat32.class);
GrayF32 input = ConvertBufferedImage.convertFromSingle(image, null, GrayF32.class);
 
ShowImages.showWindow(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);
}
}
}
}
</syntaxhighlight>
</syntaxhighlight>

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);
	}
}