boofcv.alg.shapes.polyline.SplitMergeLineFitLoop 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 georegression.geometry.UtilPoint2D_I32;
import georegression.metric.Distance2D_F64;
import georegression.struct.point.Point2D_I32;
import org.ddogleg.struct.GrowQueue_I32;
import java.util.List;
/**
* Implementation of {@link SplitMergeLineFit} for looped lists of points. The beginning and end of the list are
* assumed to be connected. An additional check is done in the start to find two points which are far apart.
*
* @author Peter Abeles
*/
// TODO only check lines that changed for splitting
public class SplitMergeLineFitLoop extends SplitMergeLineFit {
// number of points in the contour
protected int N;
/**
* @see SplitMergeLineFit#SplitMergeLineFit(double,double, int)
*/
public SplitMergeLineFitLoop(double splitFraction,
double minimumSplitFraction,
int maxIterations)
{
super(splitFraction,minimumSplitFraction, maxIterations);
}
@Override
public boolean process( List contour ) {
this.contour = contour;
this.N = contour.size();
this.minimumSideLengthPixel = (int)Math.ceil(N* minimumSideLengthFraction);
// ------------- find initial line segments
splits.reset();
// can't fit a line to a single point
if( N <= 1 ) {
return false;
}
// Go around the contour looking for two points on opposite ends which are far apart
int startIndex = selectFarthest(contour);
int middleIndex = (startIndex+N/2)%N;
// split each half recursively
splits.add(startIndex);
splitPixels(startIndex, N / 2);
splits.add(middleIndex);
splitPixels(middleIndex, N - (N / 2));
// ------------ Refine the initial segments by splitting and merging each segment
if( splits.size <= 2 )
return false; // can't merge a single line
// System.out.println("Enter loop");
for( int i = 0; i < maxIterations; i++ ) {
boolean merged = mergeSegments();
if( splits.size() <= 0)
return false;
if( !merged && !splitSegments() )
break;
if( splits.size() <= 2 || splits.size() >= abortSplits )
return false;
}
return true;
}
/**
* Recursively splits pixels between indexStart to indexStart+length. A split happens if there is a pixel
* more than the desired distance away from the two end points. Results are placed into 'splits'
*/
protected void splitPixels(int indexStart, int length) {
// too short to split
if( length < minimumSideLengthPixel)
return;
// end points of the line
int indexEnd = (indexStart+length)%N;
int splitOffset = selectSplitOffset(indexStart,length);
if( splitOffset >= 0 ) {
// System.out.println(" splitting ");
splitPixels(indexStart, splitOffset);
int indexSplit = (indexStart+splitOffset)%N;
splits.add(indexSplit);
splitPixels(indexSplit, circularDistance(indexSplit, indexEnd));
}
}
/**
* Computes the distance between pairs of points which are separated by 1/2 the contour list. The index of the
* first pixel in the pair with the greatest distance is returned
* @return Index of the first pixel which should be used to split the list. The other end is ret + N/2
*/
protected int selectFarthest( List contour ) {
int bestIndex = -1;
int bestDistance = 0;
int N = contour.size();
int half = N/2;
for( int i = 0; i < half; i++ ) {
int end = (i+half)%N;
Point2D_I32 a = contour.get(i);
Point2D_I32 b = contour.get(end);
int dist = UtilPoint2D_I32.distanceSq(a.x,a.y,b.x,b.y);
if( bestDistance < dist ) {
bestIndex = i;
bestDistance = dist;
}
}
// if( bestIndex == -1 )
// System.out.println();
return bestIndex;
}
/**
* Merges lines together if the common corner is close to a common line
* @return true the list being changed
*/
protected boolean mergeSegments() {
// See if merging will cause a degenerate case
if( splits.size() <= 3 )
return false;
boolean change = false;
work.reset();
for( int i = 0; i < splits.size; i++ ) {
int start = splits.data[i];
int end = splits.data[(i+2)%splits.size];
if( selectSplitOffset(start,circularDistance(start,end)) < 0 ) {
// merge the two lines by not adding it
change = true;
} else {
work.add(splits.data[(i + 1)%splits.size]);
}
}
// swap the two lists
GrowQueue_I32 tmp = work;
work = splits;
splits = tmp;
return change;
}
/**
* Splits a line in two if there is a point that is too far away
* @return true for change
*/
protected boolean splitSegments() {
boolean change = false;
work.reset();
for( int i = 0; i < splits.size-1; i++ ) {
change |= checkSplit(change, i,i+1);
}
change |= checkSplit(change, splits.size - 1, 0);
// swap the two lists
GrowQueue_I32 tmp = work;
work = splits;
splits = tmp;
return change;
}
private boolean checkSplit(boolean change, int i0 , int i1) {
int start = splits.data[i0];
int end = splits.data[i1];
int length = circularDistance(start, end);
int bestOffset = selectSplitOffset(start,length);
if( bestOffset >= 0 ) {
change = true;
work.add(start);
work.add((start+bestOffset)%N);
} else {
work.add(start);
}
return change;
}
/**
* Finds the point between indexStart and the end point which is the greater distance from the line
* (set up prior to calling). Returns the index of the element with a distances greater than tolerance, otherwise -1
*/
protected int selectSplitOffset( int indexStart , int length ) {
int bestOffset = -1;
int indexEnd = (indexStart + length) % N;
Point2D_I32 startPt = contour.get(indexStart);
Point2D_I32 endPt = contour.get(indexEnd);
line.p.set(startPt.x,startPt.y);
line.slope.set(endPt.x-startPt.x,endPt.y-startPt.y);
double bestDistanceSq = splitThresholdSq(contour.get(indexStart), contour.get(indexEnd));
// adjusting using 'minimumSideLengthPixel' to ensure it doesn't create a new line which is too short
int minLength = Math.max(1,minimumSideLengthPixel);// 1 is the minimum so that you don't split on the same corner
length -= minLength;
for( int i = minLength; i <= length; i++ ) {
Point2D_I32 b = contour.get((indexStart+i)%N);
point2D.set(b.x,b.y);
double dist = Distance2D_F64.distanceSq(line, point2D);
if( dist >= bestDistanceSq ) {
bestDistanceSq = dist;
bestOffset = i;
}
}
return bestOffset;
}
/**
* Distance the two points are apart in clockwise direction
*/
protected int circularDistance( int start , int end ) {
if( end >= start )
return end-start;
else
return N-start+end;
}
}