boofcv.alg.feature.detect.line.HoughTransformGradient 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-2020, 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.feature.detect.line;
import boofcv.abst.feature.detect.extract.NonMaxSuppression;
import boofcv.alg.InputSanityCheck;
import boofcv.alg.feature.detect.peak.MeanShiftPeak;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.alg.weights.WeightPixelGaussian_F32;
import boofcv.core.image.FactoryGImageGray;
import boofcv.core.image.GImageGray;
import boofcv.struct.QueueCorner;
import boofcv.struct.border.BorderType;
import boofcv.struct.image.GrayF32;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageGray;
import georegression.struct.line.LineParametric2D_F32;
import georegression.struct.point.Point2D_F32;
import georegression.struct.point.Point2D_I16;
import org.ddogleg.struct.FastQueue;
import org.ddogleg.struct.GrowQueue_F32;
import java.util.ArrayList;
import java.util.List;
/**
*
* Base class for Hough transforms which use a pixel coordinate and the gradient to describe a line.
*
*
*
* [1] Section 9.3 of E.R. Davies, "Machine Vision Theory Algorithms Practicalities," 3rd Ed. 2005
*
*
* @author Peter Abeles
*/
public class HoughTransformGradient> {
// extracts line from the transform
NonMaxSuppression extractor;
// stores returned lines
FastQueue linesAll = new FastQueue<>(10, LineParametric2D_F32::new);
// Lines after similar ones have been merged together
List linesMerged = new ArrayList<>();
// contains a set of counts for detected lines in each pixel
// floating point image used because that's what FeatureExtractor's take as input
GrayF32 transform = new GrayF32(1,1);
// found lines in transform space
final QueueCorner foundLines = new QueueCorner(10);
// list of points in the transform with non-zero values
final QueueCorner candidates = new QueueCorner(10);
// line intensities for later pruning
GrowQueue_F32 foundIntensity = new GrowQueue_F32(10);
// Refine lines using mean shift. If radius <= 0 it won't be used
MeanShiftPeak refine = new MeanShiftPeak<>(10,0.001f,
new WeightPixelGaussian_F32(),true,GrayF32.class, BorderType.ZERO);
HoughTransformParameters parameters;
// used to make the input image type generic
GImageGray _derivX,_derivY;
// post processing pruning
ImageLinePruneMerge post = new ImageLinePruneMerge();
// tuning parameters for merging
double mergeAngle = Math.PI*0.05;
double mergeDistance = 10;
int maxLines = 0; // zero means no restrictions
/**
* Specifies parameters of transform.
*
* @param extractor Extracts local maxima from transform space. A set of candidates is provided, but can be ignored.
*/
public HoughTransformGradient(NonMaxSuppression extractor,
HoughTransformParameters parameters,
Class derivType ) {
this.extractor = extractor;
this.parameters = parameters;
refine.setImage(transform);
refine.setRadius(3);
_derivX = FactoryGImageGray.create(derivType);
_derivY = FactoryGImageGray.create(derivType);
}
/**
* Computes the Hough transform using the image gradient and a binary image which flags pixels as being edges or not.
*
* @param derivX (Input) Image derivative along x-axis.
* @param derivY (Input) Image derivative along y-axis.
* @param binary (Input) Non-zero pixels are considered to be line pixels.
*/
public > void transform(D derivX , D derivY , GrayU8 binary )
{
InputSanityCheck.checkSameShape(derivX,derivY,binary);
parameters.initialize(derivX.width,derivX.height,transform);
ImageMiscOps.fill(transform,0);
candidates.reset();
_derivX.wrap(derivX);
_derivY.wrap(derivY);
transform(binary);
extractLines();
if( maxLines <= 0 ) {
linesMerged.clear();
linesMerged.addAll(linesAll.toList());
} else {
mergeLines(binary.width,binary.height);
}
}
/**
* Searches for local maximas and converts into lines.
*/
protected void extractLines() {
linesAll.reset();
foundLines.reset();
foundIntensity.reset();
extractor.process(transform,null, candidates,null, foundLines);
for( int i = 0; i < foundLines.size(); i++ ) {
Point2D_I16 p = foundLines.get(i);
if( parameters.isTransformValid(p.x,p.y) )
{
LineParametric2D_F32 l = linesAll.grow();
l.p.set(p.x,p.y);
refine.search(p.x,p.y);
// check for divergence
if( l.p.distance(refine.getPeakX(),refine.getPeakY()) < refine.getRadius()*2 ) {
l.p.set(refine.getPeakX(),refine.getPeakY());
}
parameters.transformToLine(l.p.x,l.p.y,l);
foundIntensity.push(transform.get(p.x,p.y));
}
}
}
protected void mergeLines( int width , int height ) {
post.reset();
for( int i = 0; i < linesAll.size(); i++ ) {
post.add(linesAll.get(i),foundIntensity.get(i));
}
// NOTE: angular accuracy is a function of range from sub image center. This pruning
// function uses a constant value for range accuracy. A custom algorithm should really
// be used here.
post.pruneSimilar((float) mergeAngle, (float)mergeDistance, width, height);
post.pruneNBest(maxLines);
post.createList(linesMerged);
}
/**
* Takes the detected point along the line and its gradient and converts it into transform space.
* @param x point in image.
* @param y point in image.
* @param derivX gradient of point.
* @param derivY gradient of point.
*/
final protected void parameterize( final QueueCorner candidates, final int x , final int y , float derivX , float derivY )
{
Point2D_F32 parameter = new Point2D_F32();
parameters.parameterize(x,y,derivX,derivY,parameter);
// finds the foot a line normal equation and put the point into image coordinate
int x0 = (int)parameter.x;
int y0 = (int)parameter.y;
// weights for bilinear interpolate type weightings
float wx = parameter.x-x0;
float wy = parameter.y-y0;
// make a soft decision and spread counts across neighbors
addParameters(candidates,x0,y0, (1f-wx)*(1f-wy));
addParameters(candidates,x0+1,y0, (wx)*(1f-wy));
addParameters(candidates,x0,y0+1, (1f-wx)*(wy));
addParameters(candidates,x0+1,y0+1, (wx)*(wy));
}
final protected void addParameters( QueueCorner candidates, int x , int y , float amount ) {
if( transform.isInBounds(x,y)) {
int index = transform.startIndex+y*transform.stride+x;
// keep track of candidate pixels so that a sparse search can be done
// to detect lines
if( transform.data[index] == 0 )
candidates.append(x,y);
transform.data[index] += amount;
}
}
/**
* Returns the Hough transform image.
*
* @return Transform image.
*/
public GrayF32 getTransform() {
return transform;
}
public FastQueue getLinesAll() {
return linesAll;
}
/**
* Returns the intensity/edge count for each returned line. Useful when doing
* post processing pruning.
*
* @return Array containing line intensities.
*/
public float[] getFoundIntensity() {
return foundIntensity.data;
}
void transform(GrayU8 binary )
{
// apply the transform to the entire image
for( int y = 0; y < binary.height; y++ ) {
int start = binary.startIndex + y*binary.stride;
int end = start + binary.width;
for( int index = start; index < end; index++ ) {
if( binary.data[index] != 0 ) {
int x = index-start;
parameterize(candidates,x,y,_derivX.unsafe_getF(x,y),_derivY.unsafe_getF(x,y));
}
}
}
}
public void setRefineRadius( int radius ) {
refine.setRadius(radius);
}
public int getRefineRadius() {
return refine.getRadius();
}
public double getMergeAngle() {
return mergeAngle;
}
public void setMergeAngle(double mergeAngle) {
this.mergeAngle = mergeAngle;
}
public double getMergeDistance() {
return mergeDistance;
}
public void setMergeDistance(double mergeDistance) {
this.mergeDistance = mergeDistance;
}
public int getMaxLines() {
return maxLines;
}
public void setMaxLines(int maxLines) {
this.maxLines = maxLines;
}
/**
* Lines after merging/pruning has occurred
*/
public List getLinesMerged() {
return linesMerged;
}
public NonMaxSuppression getExtractor() {
return extractor;
}
public MeanShiftPeak getRefine() {
return refine;
}
public HoughTransformParameters getParameters() {
return parameters;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy