boofcv.alg.shapes.polyline.RefinePolyLineCorner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of feature Show documentation
Show all versions of feature Show documentation
BoofCV is an open source Java library for real-time computer vision and robotics applications.
/*
* Copyright (c) 2011-2015, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.alg.shapes.polyline;
import boofcv.misc.CircularIndex;
import georegression.geometry.UtilLine2D_F64;
import georegression.struct.line.LineGeneral2D_F64;
import georegression.struct.line.LineSegment2D_F64;
import georegression.struct.point.Point2D_I32;
import org.ddogleg.struct.GrowQueue_I32;
import java.util.List;
/**
*
* Optimizing corner placements to a pixel level when given a contour and integer list of approximate
* corner locations which define set of line segments. Corners are optimized by searching for another near by
* pixel in the provided contour which reduces the distance of the contour from the line segments. This
* is intended to optimize the output from {@link SplitMergeLineFit}. Can be configured to
* handle case where line segments form a loop or have disconnected end points.
*
*
*
* Each corner is individually optimized once per iteration. The process is repeated until the maximum number of
* iterations has been reached or there is no change in corner placements.
*
*
* @author Peter Abeles
*/
public class RefinePolyLineCorner {
// maximum number of iterations
private int maxIterations = 10;
// maximum number of samples along the line
private final int maxLineSamples = 20;
// the radius it will search around. computed when contour is passed in
protected int searchRadius;
// local storage
private LineSegment2D_F64 work = new LineSegment2D_F64();
LineGeneral2D_F64 line0 = new LineGeneral2D_F64();
LineGeneral2D_F64 line1 = new LineGeneral2D_F64();
// do the line segments form a loop or not?
boolean looping;
/**
* Constructor with configurable parameters
*
* @param looping true if it loops or false if not
* @param maxIterations Number of internal EM iterations
*/
public RefinePolyLineCorner(boolean looping, int maxIterations) {
this.looping = looping;
this.maxIterations = maxIterations;
}
/**
* Constructor using default parameters
*/
public RefinePolyLineCorner(boolean looping) {
this.looping = looping;
}
/**
* Fits a polygon to the contour given an initial set of candidate corners. If not looping the corners
* must include the end points still. Minimum of 3 points required. Otherwise there's no corner!.
*
* @param contour Contours around the shape
* @param corners (Input) initial set of corners. (output) refined set of corners
*/
public boolean fit( List contour , GrowQueue_I32 corners )
{
if( corners.size() < 3 ) {
return false;
}
searchRadius = Math.min(6,Math.max(contour.size()/12,3));
int startCorner,endCorner;
if( looping ) {
startCorner = 0;
endCorner = corners.size;
} else {
// the end point positions are fixed
startCorner = 1;
endCorner = corners.size-1;
}
boolean change = true;
for( int iteration = 0; iteration < maxIterations && change; iteration++ ) {
change = false;
for (int i = startCorner; i < endCorner; i++) {
int c0 = CircularIndex.minusPOffset(i, 1, corners.size());
int c2 = CircularIndex.plusPOffset(i, 1, corners.size());
int improved = optimize(contour, corners.get(c0), corners.get(i), corners.get(c2));
if( improved != corners.get(i)) {
corners.set(i,improved);
change = true;
}
}
}
return true;
}
/**
* Searches around the current c1 point for the best place to put the corner
*
* @return location of best corner in local search region
*/
protected int optimize( List contour , int c0,int c1,int c2 ) {
double bestDistance = computeCost(contour,c0,c1,c2,0);
int bestIndex = 0;
for( int i = -searchRadius; i <= searchRadius; i++ ) {
if( i == 0 ) {
// if it found a better point in the first half stop the search since that's probably the correct
// direction. Could be improved by remember past search direction
if( bestIndex != 0 )
break;
} else {
double found = computeCost(contour, c0, c1, c2, i);
if (found < bestDistance) {
bestDistance = found;
bestIndex = i;
}
}
}
return CircularIndex.addOffset(c1, bestIndex, contour.size());
}
/**
* Computes the distance between the two lines defined by corner points in the contour
* @param contour list of contour points
* @param c0 end point of line 0
* @param c1 start of line 0 and 1
* @param c2 end point of line 1
* @param offset added to c1 to make start of lines
* @return sum of distance of points along contour
*/
protected double computeCost(List contour, int c0, int c1, int c2,
int offset)
{
c1 = CircularIndex.addOffset(c1, offset, contour.size());
createLine(c0,c1,contour,line0);
createLine(c1,c2,contour,line1);
return distanceSum(line0,c0,c1,contour)+distanceSum(line1,c1,c2,contour);
}
/**
* Sum of Euclidean distance of contour points along the line
*/
protected double distanceSum( LineGeneral2D_F64 line , int c0 , int c1 , List contour ) {
double total = 0;
if( c0 < c1 ) {
int length = c1-c0+1;
int samples = Math.min(maxLineSamples,length);
for (int i = 0; i < samples; i++) {
int index = c0 + i*(length-1)/(samples-1);
total += distance(line,contour.get(index));
}
} else {
int lengthFirst = contour.size()-c0;
int lengthSecond = c1+1;
int length = lengthFirst+c1+1;
int samples = Math.min(maxLineSamples,length);
int samplesFirst = samples*lengthFirst/length;
int samplesSecond = samples*lengthSecond/length;
for (int i = 0; i < samplesFirst; i++) {
int index = c0 + i*lengthFirst/(samples-1);
total += distance(line,contour.get(index));
}
for (int i = 0; i < samplesSecond; i++) {
int index = i*lengthSecond/(samples-1);
total += distance(line,contour.get(index));
}
}
return total;
}
/**
* If A*A + B*B == 1 then a simplified distance formula can be used
*/
protected static double distance( LineGeneral2D_F64 line , Point2D_I32 p ) {
return Math.abs(line.A*p.x + line.B*p.y + line.C);
}
/**
* Given segment information create a line in general notation which has been normalized
*/
private void createLine( int index0 , int index1 , List contour , LineGeneral2D_F64 line )
{
if( index1 < 0 )
System.out.println("SHIT");
Point2D_I32 p0 = contour.get(index0);
Point2D_I32 p1 = contour.get(index1);
// System.out.println("createLine "+p0+" "+p1);
work.a.set(p0.x, p0.y);
work.b.set(p1.x, p1.y);
UtilLine2D_F64.convert(work,line);
// ensure A*A + B*B = 1
line.normalize();
}
}