/** * Fit a circle to three given points on the circumference. The method draws lines (chords) * between two pairs of the points, constructs their perpendicular bisectors, and finds the * intersection point of those two lines. This is the centre of the circle as radii passing * through the centre are perpendicular bisectors of chords in a circle. The radius is the * distance between the centre and any one of the initial three points. */ private QDCircle threePointCircleFit(List <QDPoint> pts) { QDLine l1 = new QDLine(pts[0], pts[1]); QDLine l2 = new QDLine(pts[1], pts[2]); QDInfiniteLine i1 = new QDInfiniteLine(l1.getMidpoint(), l1.getPerpAngleD()); QDInfiniteLine i2 = new QDInfiniteLine(l2.getMidpoint(), l2.getPerpAngleD()); QDPoint centre = i1.intersect(i2); float radius = QDUtils.QDUtils.getPtToPtDist(centre, pts[0]); return(new QDCircle(centre, radius)); }
public void getIntrinsicConstraints(QDInputPointSet ptSet) { if (ptSet.shapeType == QDShapeTypes.LINE) { QDLine line = (QDLine)ptSet.initialFit; float angle = line.angleD; // Check for vertical line and adjust by midpoint pivot if ((90.0f - line_snap_thresh < angle && angle < 90.0f + line_snap_thresh) || (-90.0f - line_snap_thresh < angle && angle < -90.0f + line_snap_thresh) && !line.vertical) { QDPoint newStart, newFinish; if (line.start.y > line.getMidpoint().y) { newStart = new QDPoint(line.midPoint.x, line.midPoint.y + (0.5f * line.length)); newFinish = new QDPoint(line.midPoint.x, line.midPoint.y - (0.5f * line.length)); } else { newStart = new QDPoint(line.midPoint.x, line.midPoint.y - (0.5f * line.length)); newFinish = new QDPoint(line.midPoint.x, line.midPoint.y + (0.5f * line.length)); } ptSet.fittedShape = new QDLine(newStart, newFinish); ptSet.constraints.Add(QDConstraintTypes.VERTICAL_LINE); } // Check for horizontal line and adjust by midpoint pivot else if ((-line_snap_thresh < angle && angle < line_snap_thresh) || (180.0f - line_snap_thresh < angle || angle < -180.0f + line_snap_thresh)) { float length = (float)Math.Sqrt(Math.Pow(line.start.x - line.finish.x, 2.0f) + Math.Pow(line.start.y - line.finish.y, 2.0f)); QDPoint midPt = new QDPoint((line.start.x + line.finish.x) * 0.5f, (line.start.y + line.finish.y) * 0.5f); QDPoint newStart = new QDPoint(); QDPoint newFinish = new QDPoint(); newStart.y = newFinish.y = midPt.y; if (line.start.x > midPt.x) { newStart.x = midPt.x + (0.5f * length); newFinish.x = midPt.x - (0.5f * length); } else { newStart.x = midPt.x - (0.5f * length); newFinish.x = midPt.x + (0.5f * length); } ptSet.fittedShape = new QDLine(newStart, newFinish); ptSet.constraints.Add(QDConstraintTypes.HORIZONTAL_LINE); } } }
// Add a point to the segment list and check for a corner in the segment // Returns true if a corner was detected, false otherwise public bool addPoint(QDPoint pt) { // Special case for the first point if (pts.Count == 0) { start = pt; pts.Add(pt); sampledMarkerPt = pt; return(false); } // Get the pt to pt gradient float angle = QDUtils.QDUtils.getPtToPtAngleD(pts.Last(), pt); // Store summary statistics if (angle > angle_max) { angle_max = angle; } if (angle < angle_min) { angle_min = angle; } angle_sum += angle; // Only stores the angles between points sufficiently far apart. This is the most // noise robust, and useful for getting the general form of the input shape if (QDUtils.QDUtils.getPtToPtDist(sampledMarkerPt, pt) > SUBSAMP_PT_DIST) { sampledPtAnglesD.Add(QDUtils.QDUtils.getPtToPtAngleD(sampledMarkerPt, pt)); sampledMarkerPt = pt; } // Add values to vectors anglesD.Add(angle); pts.Add(pt); // Step 1: track back to get testPt float TEST_RADIUS = 50.0f; float CNR_RADIUS = 10.0f; QDPoint testPt = null, cnrCandidatePt = null, currPt = pt; int midIdx = -1, testIdx = -1, cnrCandidateIdx = -1; midIdx = backtrackByDist(pts, pts.Count - 2, TEST_RADIUS / 2.0f); if (midIdx < 0) { return(false); } testIdx = backtrackByDist(pts, midIdx, TEST_RADIUS / 2.0f); if (testIdx < 0) { return(false); } testPt = pts[testIdx]; // Step 2: construct line from testPt to currPt QDInfiniteLine testLine = new QDInfiniteLine(testPt, currPt); // Step 3: Find perp dist from each pt between test and curr to the line float maxDist = 0.0f; int maxIdx = -1; for (int i = testIdx + 1; i < pts.Count - 1; i++) { float thisDist = testLine.getDistToPoint(pts[i]); if (thisDist > maxDist) { maxDist = thisDist; maxIdx = i; } } // Step 4: confirm cnrCandidatePt if (maxIdx < 0) { return(false); } cnrCandidatePt = pts[maxIdx]; int cnrIdx = maxIdx; if (!(QDUtils.QDUtils.getPtToPtDist(cnrCandidatePt, currPt) > CNR_RADIUS)) { return(false); } // Step 5: construct lines float oldAngleD = new QDLine(testPt, cnrCandidatePt).angleD; float newAngleD = new QDLine(cnrCandidatePt, currPt).angleD; // Step 6: test angle between lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(oldAngleD, newAngleD)) > CNR_ANGLE_THRESH) { // Step 7: test for continuous curvature int strTestIdx = backtrackByDist(pts, testIdx, CNR_RADIUS); if (strTestIdx < 0) { return(false); } int strTest2Idx = backtrackByDist(pts, strTestIdx, CNR_RADIUS); if (strTest2Idx < 0) { return(false); } float seg1 = new QDLine(pts[strTest2Idx], pts[strTestIdx]).angleD; float seg2 = new QDLine(pts[strTestIdx], pts[testIdx]).angleD; if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(seg1, seg2)) < 30.0f) { cornerTerminated = true; } else { cornerTerminated = false; } } if (cornerTerminated) { newSeg = new QDInputPointSet(CORNER_WINDOW, CNR_ANGLE_THRESH); newSeg.addPoint(cnrCandidatePt); // Add the corner point int lastIdx = pts.Count - 1; for (int i = 0; i < lastIdx - cnrIdx; i++) // For how many points there are after the corner { newSeg.addPoint(this.pts[cnrIdx + 1]); // Copy points after the corner into new seg this.pts.RemoveAt(cnrIdx + 1); // Delete points after corner from current set this.anglesD.Remove(cnrIdx); // There is one less angle than pts since first two pts make 1 angle } // Remove subsampled angles as well up to before the corner int deleteSubSamp = (int)Math.Ceiling(CNR_RADIUS / SUBSAMP_PT_DIST); for (int i = 0; i < deleteSubSamp; i++) { sampledPtAnglesD.Remove(sampledPtAnglesD.Count - deleteSubSamp + i); } return(true); } return(false); }
public List <QDShape> getExtrinsicConstraints(QDInputPointSet ptSet) { List <QDShape> modifiedShapes = new List <QDShape>(); if (ptSet.shapeType == QDShapeTypes.LINE) { QDLine line = (QDLine)ptSet.fittedShape; List <QDShapeDBPoint> nearStartPts = shapeDB.getShapesNearPoint(line.start, SEARCH_RADIUS); foreach (QDShapeDBPoint pt in nearStartPts) { if (pt.type == QDPointTypes.LINE_START || pt.type == QDPointTypes.LINE_FINISH) { QDLine nearLine = (QDLine)pt.shape; if (!modifiedShapes.Contains(nearLine)) { modifiedShapes.Add(nearLine); } QDInfiniteLine thisInf = new QDInfiniteLine(line.start, line.finish); QDInfiniteLine nearInf = new QDInfiniteLine(nearLine.start, nearLine.finish); // Check for nearly parallel lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(line.angleD, nearLine.angleD)) > 10f) { QDPoint intersectPt = thisInf.intersect(nearInf); if (QDUtils.QDUtils.getPtToPtDist(line.start, intersectPt) < SEARCH_RADIUS * 1.25) { line.start = intersectPt; if (pt.type == QDPointTypes.LINE_START) { nearLine.start = intersectPt; } else { nearLine.finish = intersectPt; } } } } } /** * GOTTA BE A BETTER WAY OF DOING THIS RATHER THAN DOING IT ONCE FOR START AND ONCE FOR FINISH */ List <QDShapeDBPoint> nearFinishPts = shapeDB.getShapesNearPoint(line.finish, SEARCH_RADIUS); foreach (QDShapeDBPoint pt in nearFinishPts) { if (pt.type == QDPointTypes.LINE_START || pt.type == QDPointTypes.LINE_FINISH) { QDLine nearLine = (QDLine)pt.shape; if (!modifiedShapes.Contains(nearLine)) { modifiedShapes.Add(nearLine); } QDInfiniteLine thisInf = new QDInfiniteLine(line.start, line.finish); QDInfiniteLine nearInf = new QDInfiniteLine(nearLine.start, nearLine.finish); // Check for nearly parallel lines if (Math.Abs(QDUtils.QDUtils.angleDiffMinorD(line.angleD, nearLine.angleD)) > 10f) { QDPoint intersectPt = thisInf.intersect(nearInf); if (QDUtils.QDUtils.getPtToPtDist(line.finish, intersectPt) < SEARCH_RADIUS * 1.25) { line.finish = intersectPt; if (pt.type == QDPointTypes.LINE_START) { nearLine.start = intersectPt; } else { nearLine.finish = intersectPt; } } } } } } return(modifiedShapes); }
private QDLine fitLine(QDInputPointSet pointSet) { int n = pointSet.pts.Count - 1; float xsum = 0.0f; float ysum = 0.0f; for (int i = 0; i < n; i++) { xsum += pointSet.pts[i].x; ysum += pointSet.pts[i].y; } float xbar = xsum / n; float ybar = ysum / n; float denom, numer, gradient, diffx; denom = numer = 0.0f; for (int i = 0; i < n; i++) { diffx = pointSet.pts[i].x - xbar; numer += diffx * (pointSet.pts[i].y - ybar); denom += diffx * diffx; } QDLine line = new QDLine(); if (denom < 1e-2f) { // Check for vertical case line.vertical = true; line.start.x = line.finish.x = xbar; // x coords at the average line.start.y = pointSet.pts[0].y; // y coords are limits of drawn line line.finish.y = pointSet.pts[n].y; // Check direction if (line.finish.y > line.start.y) { line.angleD = 90.0f; } else { line.angleD = -90.0f; } } else { // Otherwise choose start and finish values as limits of line gradient = numer / denom; line.angleD = ((float)Math.Atan2(numer, denom)) * 180.0f / ((float)Math.PI); line.intercept = ybar - (gradient * xbar); if (Math.Abs(line.angleD) < 45.0f) { line.start.x = pointSet.pts[0].x; line.start.y = line.start.x * gradient + line.intercept; line.finish.x = pointSet.pts[n].x; line.finish.y = pointSet.pts[n].y; } else { line.start.y = pointSet.pts[0].y; line.start.x = (line.start.y - line.intercept) / gradient; line.finish.y = pointSet.pts[n].y; line.finish.x = (line.finish.y - line.intercept) / gradient; } } return(new QDLine(line.start, line.finish)); }