/** * */ package fr.unistra.pelican.algorithms.detection; import java.util.Arrays; import fr.unistra.pelican.Algorithm; import fr.unistra.pelican.AlgorithmException; import fr.unistra.pelican.DoubleImage; import fr.unistra.pelican.Image; import fr.unistra.pelican.algorithms.arithmetic.Blending; import fr.unistra.pelican.algorithms.edge.Sobel; import fr.unistra.pelican.algorithms.io.ImageLoader; import fr.unistra.pelican.algorithms.morphology.connected.ExtractMinimaOrMaxima; import fr.unistra.pelican.algorithms.morphology.connected.ExtractMinimaOrMaxima.Operation; import fr.unistra.pelican.algorithms.morphology.gray.GrayMedian; import fr.unistra.pelican.algorithms.visualisation.MViewer; import fr.unistra.pelican.util.IMath; import fr.unistra.pelican.util.Line; import fr.unistra.pelican.util.Tools; import fr.unistra.pelican.util.connectivityTrees.connectivity.TrivialConnectivity; import fr.unistra.pelican.util.morphology.FlatStructuringElement2D; /** * <p>Performs a Hough transform of the XY space for line detection. Result is the accumulation buffer in (r-theta) space. * <br>Result x dim is theta dim taking value in [0;pi] * <br>Result y dim is r dim taking value in [0; sqrt(xdim^2+ydim^2)] * <p>Values of input image is supposed to be all positives. * <p>deltaTheta specifies quantization step of theta, default value is 0.01. * <br>deltaR specifies quantization step of r; default value is 1.0 * <p>deltaTheta can be easily retrieved from result dimension but deltaR must be stored in buffer properties under key R_SCALE * * <p>static method getLineFromBuffer is here to convert a point coordinate in r-theta space into a line in xy space * * @author Benjamin Perret * */ public class HoughTransform extends Algorithm { /** * Key to store the deltaR parameter in accumulation buffer properties. * This value is necessary to obtain right results with function getLineFromBuffer */ public static final String R_SCALE="HOUGH_TRANSFORM_R_SCALE"; /** * Input image */ public Image image; /** * resolution for theta quantization */ public double deltaTheta=0.01; /** * resolution of the r quantization */ public double deltaR=1.0; /** * Result : accumulation buffer. */ public DoubleImage accumulator ; /** * Are we working in a cylinder space (y dimension is wrapped) */ public boolean cylinderSpace=false; /** * To avoid disprecancies in calculus for nearly vertical lines, an angle limit is defined. * Under this limit line is assumed to be vertical and wrapping is not considered. * The angle is the angle between a vertical line and the considered line. * */ public double angleLimitForWrapping=0.3; /** * */ public HoughTransform() { super(); super.inputs="image"; super.options="deltaR,deltaTheta,cylinderSpace,angleLimitForWrapping"; super.outputs="accumulator"; } /* (non-Javadoc) * @see fr.unistra.pelican.Algorithm#launch() */ @Override public void launch() throws AlgorithmException { if(image.bdim>1 || image.tdim>1 || image.zdim>1) { System.err.println("Hough Transform warning : I will only process XY dims, ZTB are ignored. Input image is " +image); } int ydim = (int)Math.ceil(Math.sqrt(image.xdim*image.xdim+image.ydim*image.ydim)/deltaR); int xdim = (int)Math.ceil(Math.PI/deltaTheta); accumulator=new DoubleImage(xdim,2*ydim,1,1,1); accumulator.properties.put(R_SCALE, deltaR); double cos [] =new double[xdim]; double sin [] =new double[xdim]; int c=0; for(double theta=-Tools.piD2; theta <= Tools.piD2;theta+=deltaTheta) { cos[c]=Math.cos(theta); sin[c]=Math.sin(theta); c++; } for(int y=0;y<image.ydim;y++) { for(int x=0;x<image.xdim;x++) { double v=image.getPixelXYDouble(x, y); if(v>0) { c=0; for(double theta=-Tools.piD2; theta <= Tools.piD2;theta+=deltaTheta) { double r=(((double)y)*cos[c]+((double)x)*sin[c]); if(cylinderSpace) { if(Math.abs(theta-Tools.piD2)>angleLimitForWrapping && Math.abs(theta+Tools.piD2)>angleLimitForWrapping) { // y=ax+b double b=r/cos[c]; // space wrapping double b2=Tools.modulo(b, image.ydim); // compute wrapping if(b2!=b) // wrapping occured? { double k=b2/b; // simple geometry double r2=r*k; b=b2; r=r2; } } int ri=(int)((r/deltaR+ydim +0.5)); accumulator.setPixelXYDouble(c,ri , v+accumulator.getPixelXYDouble(c, ri)); } else { int ri=(int)((r/deltaR+ydim +0.5)); accumulator.setPixelXYDouble(c,ri , v+accumulator.getPixelXYDouble(c, ri)); } c++; } } } } } /** * Get a line from a point in the theta-r space of the accumulation buffer * @param accumulationBuffer accumulationBuffer, result of HoughTransform * @param rCoord rCoord(y) of the point in the theta-r space * @param thetaCoord thetaCoord(x) of the point in the theta-r space * @return corresponding line in xy space */ public static Line getLineFromBuffer(DoubleImage accumulationBuffer, int thetaCoord, int rCoord) { double deltaR=1.0; Object o=accumulationBuffer.properties.get(R_SCALE); if(o != null && o instanceof Double) { deltaR=(Double)o; } double theta=(Math.PI/(double)(accumulationBuffer.xdim))*(double)thetaCoord-Tools.piD2; double r=(rCoord-accumulationBuffer.ydim/2)*deltaR; //System.out.println("theta " +theta + " dist " +r ); Line l; if(theta==0.0) { System.out.println("ligne verticale"); l=new Line((int)r,0,(int)r,(int)((accumulationBuffer.ydim/2)*deltaR)); }else{ double a=-Math.sin(theta)/Math.cos(theta); double b=r/Math.cos(theta); int x1=0; int y1=(int)(b+0.5); int x2=(int)((accumulationBuffer.ydim)*deltaR); if(y1>x2) { y1=x2; x1=(int)((y1-b)/a); }else if(y1<-x2) { y1=-x2; x1=(int)((y1-b)/a); } int y2=(int)(a*x2+b); if(y2>x2) { y2=x2; x2=(int)((y2-b)/a); }else if(y2<-x2) { y2=-x2; x2=(int)((y2-b)/a); } l=new Line(x1,y1,x2,y2); } return l; } public static DoubleImage exec(Image image) { return(DoubleImage) new HoughTransform().process(image); } public static DoubleImage exec(Image image,double deltaR, double deltaTheta) { return(DoubleImage) new HoughTransform().process(image,deltaR,deltaTheta); } public static DoubleImage exec(Image image,double deltaR, double deltaTheta,boolean cylinderSpace) { return(DoubleImage) new HoughTransform().process(image,deltaR,deltaTheta,cylinderSpace); } public static DoubleImage exec(Image image, double deltaR) { return(DoubleImage) new HoughTransform().process(image,deltaR); } }