Difference between revisions of "Example Fit Polygon"
From BoofCV
Jump to navigationJump to searchm |
m |
||
(3 intermediate revisions by the same user not shown) | |||
Line 9: | Line 9: | ||
Example Code: | Example Code: | ||
* [https://github.com/lessthanoptimal/BoofCV/blob/v0. | * [https://github.com/lessthanoptimal/BoofCV/blob/v0.40/examples/src/main/java/boofcv/examples/features/ExampleFitPolygon.java ExampleFitPolygon.java] | ||
Concepts: | Concepts: | ||
Line 27: | 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. | * segments. Useful when analysing shapes such as squares and triangles or when trying to simply the low level | ||
* pixel output. | * pixel output. | ||
* | * | ||
Line 44: | 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 53: | 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 59: | Line 59: | ||
filtered = BinaryImageOps.dilate8(filtered, 1, null); | filtered = BinaryImageOps.dilate8(filtered, 1, null); | ||
// Find | // 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 66: | 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. | // Fit the polygon to the found external contour. Note loop = true | ||
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external,true, minSide,cornerPenalty); | 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 | // 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, minSide,cornerPenalty); | vertexes = ShapeFittingOps.fitPolygon(internal, true, minSide, cornerPenalty); | ||
VisualizeShapes.drawPolygon(vertexes,true,g2); | VisualizeShapes.drawPolygon(vertexes, true, g2); | ||
} | } | ||
} | } | ||
Line 85: | Line 85: | ||
/** | /** | ||
* Fits a sequence of line-segments into a sequence of points found using the Canny edge detector. | * 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 91: | 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 106: | 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. | // fit line segments to the point sequence. Note that loop is false | ||
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(s.points,false, minSide,cornerPenalty); | List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(s.points, false, minSide, cornerPenalty); | ||
VisualizeShapes.drawPolygon(vertexes, false, g2); | VisualizeShapes.drawPolygon(vertexes, false, g2); | ||
Line 121: | Line 121: | ||
/** | /** | ||
* Detects contours inside the binary image generated by canny. | * 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. | // Only external contours are relevant | ||
List<Contour> contours = BinaryImageOps.contourExternal(binary, ConnectRule.EIGHT); | |||
Graphics2D g2 = displayImage.createGraphics(); | Graphics2D g2 = displayImage.createGraphics(); | ||
Line 143: | 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, minSide, cornerPenalty); | |||
List<PointIndex_I32> vertexes = ShapeFittingOps.fitPolygon(c.external,true, minSide,cornerPenalty); | |||
g2.setColor(new Color(rand.nextInt())); | g2.setColor(new Color(rand.nextInt())); | ||
VisualizeShapes.drawPolygon(vertexes,true,g2); | VisualizeShapes.drawPolygon(vertexes, true, g2); | ||
} | } | ||
Line 154: | Line 154: | ||
} | } | ||
public static void main( String | 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. | 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); | ||
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);
}
}