Example Fit Polygon

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

/** * 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 */publicclassExampleFitPolygon{// Used to bias it towards more or fewer sides. larger number = fewer sidesstaticdoublecornerPenalty=0.25;// The fewest number of pixels a side can havestaticintminSide=10;staticListDisplayPanelgui=newListDisplayPanel();/** * Fits polygons to found contours around binary blobs. */publicstaticvoidfitBinaryImage(GrayF32input){GrayU8binary=newGrayU8(input.width,input.height);BufferedImagepolygon=newBufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);// the mean pixel value is often a reasonable threshold when creating a binary imagedoublemean=ImageStatistics.mean(input);// create a binary image by thresholdingThresholdImageOps.threshold(input,binary,(float)mean,true);// reduce noise with some filteringGrayU8filtered=BinaryImageOps.erode8(binary,1,null);filtered=BinaryImageOps.dilate8(filtered,1,null);// Find internal and external contour around each shapeList<Contour>contours=BinaryImageOps.contour(filtered,ConnectRule.EIGHT,null);// Fit a polygon to each shape and draw the resultsGraphics2Dg2=polygon.createGraphics();g2.setStroke(newBasicStroke(2));for(Contourc:contours){// Fit the polygon to the found external contour. Note loop = trueList<PointIndex_I32>vertexes=ShapeFittingOps.fitPolygon(c.external,true,minSide,cornerPenalty);g2.setColor(Color.RED);VisualizeShapes.drawPolygon(vertexes,true,g2);// handle internal contours nowg2.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. */publicstaticvoidfitCannyEdges(GrayF32input){BufferedImagedisplayImage=newBufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);// Finds edges inside the imageCannyEdge<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();Graphics2Dg2=displayImage.createGraphics();g2.setStroke(newBasicStroke(2));// used to select colors for each lineRandomrand=newRandom(234);for(EdgeContoure:contours){g2.setColor(newColor(rand.nextInt()));for(EdgeSegments:e.segments){// fit line segments to the point sequence. Note that loop is falseList<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. */publicstaticvoidfitCannyBinary(GrayF32input){BufferedImagedisplayImage=newBufferedImage(input.width,input.height,BufferedImage.TYPE_INT_RGB);GrayU8binary=newGrayU8(input.width,input.height);// Finds edges inside the imageCannyEdge<GrayF32,GrayF32>canny=FactoryEdgeDetectors.canny(2,false,true,GrayF32.class,GrayF32.class);canny.process(input,0.1f,0.3f,binary);// Only external contours are relevantList<Contour>contours=BinaryImageOps.contourExternal(binary,ConnectRule.EIGHT);Graphics2Dg2=displayImage.createGraphics();g2.setStroke(newBasicStroke(2));// used to select colors for each lineRandomrand=newRandom(234);for(Contourc:contours){List<PointIndex_I32>vertexes=ShapeFittingOps.fitPolygon(c.external,true,minSide,cornerPenalty);g2.setColor(newColor(rand.nextInt()));VisualizeShapes.drawPolygon(vertexes,true,g2);}gui.addImage(displayImage,"Canny Contour");}publicstaticvoidmain(Stringargs[]){// load and convert the image into a usable formatBufferedImageimage=UtilImageIO.loadImage(UtilIO.pathExample("shapes/shapes02.png"));GrayF32input=ConvertBufferedImage.convertFromSingle(image,null,GrayF32.class);fitCannyEdges(input);fitCannyBinary(input);fitBinaryImage(input);gui.addImage(image,"Original");ShowImages.showWindow(gui,"Polygon from Contour",true);}}