boofcv.alg.shapes.polyline.MinimizeEnergyPrune Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of boofcv-feature Show documentation
Show all versions of boofcv-feature Show documentation
BoofCV is an open source Java library for real-time computer vision and robotics applications.
/*
* Copyright (c) 2011-2017, 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.metric.Distance2D_F64;
import georegression.struct.line.LineParametric2D_F64;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point2D_I32;
import org.ddogleg.struct.GrowQueue_I32;
import java.util.List;
/**
*
* Prunes corners from a pixel level accuracy contour by minizing a penalized energy function. The energy of a line
* segment is defined as Euclidean distance squared of each point from the line summed plus a penalty divided by the
* distance between two end points of the line. When a corner is removed the location of its neighbors are optimized
* again.
*
*
* Energy of line segment = [Sum( distance points from line squared) + penalty ] / (segment length)
*
*
* @author Peter Abeles
*/
public class MinimizeEnergyPrune {
// how much a corner adds to the energy calculation
double splitPenalty;
LineParametric2D_F64 line = new LineParametric2D_F64();
Point2D_F64 point = new Point2D_F64();
List contour;
double energySegment[] = new double[1];
GrowQueue_I32 bestCorners = new GrowQueue_I32();
GrowQueue_I32 workCorners1 = new GrowQueue_I32();
GrowQueue_I32 workCorners2 = new GrowQueue_I32();
public MinimizeEnergyPrune(double splitPenalty) {
this.splitPenalty = splitPenalty;
}
/**
* Given a contour and initial set of corners compute a new set of corner indexes
* @param contour List of points in the shape's contour
* @param input Initial set of corners
* @param output Pruned set of corners
* @return true if one or more corners were pruned, false if nothing changed
*/
public boolean prune(List contour, GrowQueue_I32 input, GrowQueue_I32 output) {
this.contour = contour;
output.setTo(input);
removeDuplicates(output);
// can't prune a corner and it will still be a polygon
if( output.size() <= 3 )
return false;
computeSegmentEnergy(output);
double total = 0;
for (int i = 0; i < output.size(); i++) {
total += energySegment[i];
}
FitLinesToContour fit = new FitLinesToContour();
fit.setContour(contour);
boolean modified = false;
while( output.size() > 3 ) {
double bestEnergy = total;
boolean betterFound = false;
bestCorners.reset();
for (int i = 0; i < output.size(); i++) {
// add all but the one which was removed
workCorners1.reset();
for (int j = 0; j < output.size(); j++) {
if( i != j ) {
workCorners1.add(output.get(j));
}
}
// just in case it created a duplicate
removeDuplicates(workCorners1);
if( workCorners1.size() > 3 ) {
// when looking at these anchors remember that they are relative to the new list without
// the removed corner and that the two adjacent corners need to be optimized
int anchor0 = CircularIndex.addOffset(i, -2, workCorners1.size());
int anchor1 = CircularIndex.addOffset(i, 1, workCorners1.size());
// optimize the two adjacent corners to the removed one
if (fit.fitAnchored(anchor0, anchor1, workCorners1, workCorners2)) {
// TODO this isn't taking advantage of previously computed line segment energy is it?
// maybe a small speed up can be had by doing that
double score = 0;
for (int j = 0, k = workCorners2.size() - 1; j < workCorners2.size(); k = j, j++) {
score += computeSegmentEnergy(workCorners2, k, j);
}
if (score < bestEnergy) {
betterFound = true;
bestEnergy = score;
bestCorners.reset();
bestCorners.addAll(workCorners2);
}
}
}
}
if ( betterFound ) {
modified = true;
total = bestEnergy;
output.setTo(bestCorners);
} else {
break;
}
}
return modified;
}
/**
* Look for two corners which point to the same point and removes one of them from the corner list
*/
void removeDuplicates( GrowQueue_I32 corners ) {
// remove duplicates
for (int i = 0; i < corners.size(); i++) {
Point2D_I32 a = contour.get(corners.get(i));
// start from the top so that removing a corner doesn't mess with the for loop
for (int j = corners.size()-1; j > i; j--) {
Point2D_I32 b = contour.get(corners.get(j));
if( a.x == b.x && a.y == b.y ) {
// this is still ok if j == 0 because it wrapped around. 'i' will now be > size
corners.remove(j);
}
}
}
}
/**
* Computes the energy of each segment individually
*/
void computeSegmentEnergy( GrowQueue_I32 corners ) {
if( energySegment.length < corners.size() ) {
energySegment = new double[ corners.size() ];
}
for (int i = 0,j=corners.size()-1; i < corners.size(); j=i,i++) {
energySegment[j] = computeSegmentEnergy(corners, j, i);
}
}
/**
* Returns the total energy after removing a corner
* @param removed index of the corner that is being removed
* @param corners list of corner indexes
*/
protected double energyRemoveCorner( int removed , GrowQueue_I32 corners ) {
double total = 0;
int cornerA = CircularIndex.addOffset(removed, -1 , corners.size());
int cornerB = CircularIndex.addOffset(removed, 1 , corners.size());
total += computeSegmentEnergy(corners, cornerA, cornerB);
if( cornerA > cornerB ) {
for (int i = cornerB; i < cornerA; i++)
total += energySegment[i];
} else {
for (int i = 0; i < cornerA; i++) {
total += energySegment[i];
}
for (int i = cornerB; i < corners.size(); i++) {
total += energySegment[i];
}
}
return total;
}
/**
* Computes the energy for a segment defined by the two corner indexes
*/
protected double computeSegmentEnergy(GrowQueue_I32 corners, int cornerA, int cornerB) {
int indexA = corners.get(cornerA);
int indexB = corners.get(cornerB);
if( indexA == indexB ) {
return 100000.0;
}
Point2D_I32 a = contour.get(indexA);
Point2D_I32 b = contour.get(indexB);
line.p.x = a.x;
line.p.y = a.y;
line.slope.set(b.x-a.x,b.y-a.y);
double total = 0;
int length = circularDistance(indexA,indexB);
for (int k = 1; k < length; k++) {
Point2D_I32 c = getContour(indexA + 1 + k);
point.set(c.x, c.y);
total += Distance2D_F64.distanceSq(line, point);
}
return (total+ splitPenalty)/a.distance2(b);
}
protected Point2D_I32 getContour(int index) {
return contour.get(index % contour.size());
}
/**
* Distance the two points are apart in clockwise direction
*/
protected int circularDistance( int start , int end ) {
return CircularIndex.distanceP(start,end,contour.size());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy