/** All the corners should be in increasing order from the first anchor. */ boolean sanityCheckCornerOrder(int numLines, GrowQueue_I32 corners) { int contourAnchor0 = corners.get(anchor0); int previous = 0; for (int i = 1; i < numLines; i++) { int contourIndex = corners.get(CircularIndex.addOffset(anchor0, i, corners.size())); int pixelsFromAnchor0 = CircularIndex.distanceP(contourAnchor0, contourIndex, contour.size()); if (pixelsFromAnchor0 < previous) { return false; } else { previous = pixelsFromAnchor0; } } return true; }
/** * Fits line segments along the contour with the first and last corner fixed at the original * corners. The output will be a new set of corner indexes. Since the corner list is circular, it * is assumed that anchor1 comes after anchor0. The same index can be specified for an anchor, it * will just go around the entire circle * * @param anchor0 corner index of the first end point * @param anchor1 corner index of the second end point. * @param corners Initial location of the corners * @param output Optimized location of the corners */ public boolean fitAnchored( int anchor0, int anchor1, GrowQueue_I32 corners, GrowQueue_I32 output) { this.anchor0 = anchor0; this.anchor1 = anchor1; int numLines = anchor0 == anchor1 ? corners.size() : CircularIndex.distanceP(anchor0, anchor1, corners.size); if (numLines < 2) { throw new RuntimeException("The one line is anchored and can't be optimized"); } lines.resize(numLines); if (verbose) System.out.println("ENTER FitLinesToContour"); // Check pre-condition // checkDuplicateCorner(corners); workCorners.setTo(corners); for (int iteration = 0; iteration < maxIterations; iteration++) { // fit the lines to the contour using only lines between each corner for each line if (!fitLinesUsingCorners(numLines, workCorners)) { return false; } // intersect each line and find the closest point on the contour as the new corner if (!linesIntoCorners(numLines, workCorners)) { return false; } // sanity check to see if corner order is still met if (!sanityCheckCornerOrder(numLines, workCorners)) { return false; // TODO detect and handle this condition better } // TODO check for convergence } if (verbose) System.out.println("EXIT FitLinesToContour. " + corners.size() + " " + workCorners.size()); output.setTo(workCorners); return true; }
/** * Given a sequence of points on the contour find the best fit line. * * @param contourIndex0 contour index of first point in the sequence * @param contourIndex1 contour index of last point (exclusive) in the sequence * @param line storage for the found line * @return true if successful or false if it failed */ boolean fitLine(int contourIndex0, int contourIndex1, LineGeneral2D_F64 line) { int numPixels = CircularIndex.distanceP(contourIndex0, contourIndex1, contour.size()); // if its too small if (numPixels < minimumLineLength) return false; Point2D_I32 c0 = contour.get(contourIndex0); Point2D_I32 c1 = contour.get(contourIndex1); double scale = c0.distance(c1); double centerX = (c1.x + c0.x) / 2.0; double centerY = (c1.y + c0.y) / 2.0; int numSamples = Math.min(20, numPixels); pointsFit.reset(); for (int i = 0; i < numSamples; i++) { int index = i * (numPixels - 1) / (numSamples - 1); Point2D_I32 c = contour.get(CircularIndex.addOffset(contourIndex0, index, contour.size())); Point2D_F64 p = pointsFit.grow(); p.x = (c.x - centerX) / scale; p.y = (c.y - centerY) / scale; } if (null == FitLine_F64.polar(pointsFit.toList(), linePolar)) { return false; } UtilLine2D_F64.convert(linePolar, line); // go from local coordinates into global line.C = scale * line.C - centerX * line.A - centerY * line.B; return true; }