// Catalano Imaging Library
// The Catalano Framework
//
// Copyright © Diego Catalano, 2012-2016
// diego.catalano at live.com
//
// Copyright © Adrian F Clark
// alien at essex.ac.uk
//
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

package Catalano.Imaging.Tools;

import Catalano.Core.IntPoint;
import Catalano.Imaging.FastBitmap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Hough line transfomation.
 * @author Diego Catalano
 */
public class HoughLineTransformation {
    
    // The size of the neighbourhood in which to search for other local maxima 
    int radius = 4;
 
    // How many discrete values of theta shall we check? 
    final int maxTheta = 180;
 
    // Using maxTheta, work out the step 
    double thetaStep = Math.PI / maxTheta;
    
    int stepsPerDegree = 1;
    
    int minIntensity = 0;
    
    int maxIntensity = Integer.MAX_VALUE;
    
    boolean sort = true;
 
    // the width and height of the image 
    protected int width, height;
 
    // the hough array 
    protected int[][] houghArray;
 
    // the coordinates of the centre of the image 
    protected float centerX, centerY;
 
    // the height of the hough array 
    protected int houghHeight;
 
    // double the hough height (allows for negative numbers) 
    protected int doubleHeight;
 
    // the number of points that have been added 
    protected int numPoints;
 
    // cache of values of sin and cos for different theta values. Has a significant performance improvement. 
    private double[] sinCache;
    private double[] cosCache;

    /**
     * Initialize a new instance of the HoughLineTransformation class.
     */
    public HoughLineTransformation() {}
    
    /**
     * Initialize a new instance of the HoughLineTransformation class.
     * @param minIntensity Minimum intensity.
     */
    public HoughLineTransformation(int minIntensity) {
        this.minIntensity = Math.max(1,minIntensity);
    }
    
    /**
     * Initialize a new instance of the HoughLineTransformation class.
     * @param minIntensity Minimum intensity.
     * @param maxIntensity Minimum intensity.
     */
    public HoughLineTransformation(int minIntensity, int maxIntensity) {
        this.minIntensity = Math.max(1,minIntensity);
        this.maxIntensity = Math.max(1,maxIntensity);
    }
    
    /**
     * Initialize a new instance of the HoughLineTransformation class.
     * @param minIntensity Minimum intensity.
     * @param maxIntensity Minimum intensity.
     * @param sort Sort Hough lines per intensity.
     */
    public HoughLineTransformation(int minIntensity, int maxIntensity, boolean sort) {
        this.minIntensity = Math.max(1,minIntensity);
        this.maxIntensity = Math.max(1,maxIntensity);
        this.sort = sort;
    }

    /**
     * Get Radius.
     * @return Radius.
     */
    public int getRadius() {
        return radius;
    }

    /**
     * Set Radius.
     * @param radius Radius.
     */
    public void setRadius(int radius) {
        this.radius = radius;
    }

    /**
     * Get Intensity.
     * @return Intensity.
     */
    public int getIntensity() {
        return minIntensity;
    }

    /**
     * Set Intensity.
     * @param intensity Intensity.
     */
    public void setIntensity(int intensity) {
        this.minIntensity = Math.max(1,intensity);
    }

    public int getStepsPerDegree() {
        return stepsPerDegree;
    }

    public void setStepsPerDegree(int stepsPerDegree) {
        this.stepsPerDegree = stepsPerDegree;
        
        stepsPerDegree = Math.max( 1, Math.min( 10, stepsPerDegree ) );
        houghHeight = 180 * stepsPerDegree;
        thetaStep = Math.PI / houghHeight;

        // precalculate Sine and Cosine values
        sinCache = new double[houghHeight];
        cosCache = new double[houghHeight];

        for ( int i = 0; i < houghHeight; i++ )
        {
            sinCache[i] = Math.sin( i * thetaStep );
            cosCache[i] = Math.cos( i * thetaStep );
        }
    }
    
    private void init(){
        // Calculate the maximum height the hough array needs to have 
        houghHeight = (int) (Math.sqrt(2) * Math.max(height, width)) / 2; 
 
        // Double the height of the hough array to cope with negative r values 
        doubleHeight = 2 * houghHeight;
 
        // Create the hough array 
        houghArray = new int[maxTheta][doubleHeight];
 
        // Find edge points and vote in array 
        centerX = width / 2;
        centerY = height / 2;
 
        // Count how many points there are 
        numPoints = 0;
 
        // cache the values of sin and cos for faster processing 
        sinCache = new double[maxTheta];
        cosCache = sinCache.clone();
        for (int t = 0; t < maxTheta; t++) {
            double realTheta = t * thetaStep;
            sinCache[t] = Math.sin(realTheta);
            cosCache[t] = Math.cos(realTheta);
        }
    }
    
    /**
     * Process Image.
     * @param fastBitmap Image to be processed.
     */
    public void ProcessImage(FastBitmap fastBitmap) { 
        
        if (fastBitmap.isGrayscale()){
            this.width = fastBitmap.getWidth();
            this.height = fastBitmap.getHeight();

            init();
            // Now find edge points and update the hough array 
            for (int x = 0; x < height; x++) {
                for (int y = 0; y < width; y++) {
                    // Find non-black pixels
                    if (fastBitmap.getGray(x, y) == 255) {
                        addPoint(x, y);
                    }
                }
            }
        }
        else{
            try {
                throw new IllegalArgumentException("HoughLineTransformation only works with grayscale images.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public void addEdgePoints(ArrayList edgePoints){
        for (IntPoint point : edgePoints) {
            addPoint(point.x, point.y);
        }
    }
    
    private void addPoint(int x, int y) {
 
        // Go through each value of theta
        for (int t = 0; t < maxTheta; t++) {
 
            //Work out the r values for each theta step
            int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
 
            // this copes with negative values of r
            r += houghHeight;
 
            if (r < 0 || r >= doubleHeight) continue;
 
            // Increment the hough array
            houghArray[t][r]++;
 
        }
 
        numPoints++;
    }
    
    public List getLines() {
 
        // Initialise the vector of lines that we'll return 
        List lines = new ArrayList(); 
 
        // Only proceed if the hough array is not empty 
        if (numPoints == 0) return lines;
        
        // Used for set relative intensity.
        double max = getMaximumValue();
        
        // Search for local peaks above threshold to draw 
        for (int t = 0; t < maxTheta; t++) { 
            loop: 
            for (int r = radius; r < doubleHeight - radius; r++) { 
 
                // Only consider points above threshold 
                if (houghArray[t][r] > minIntensity && houghArray[t][r] < maxIntensity) { 
 
                    int peak = houghArray[t][r]; 
 
                    // Check that this peak is indeed the local maxima 
                    for (int dx = -radius; dx <= radius; dx++) {
                        for (int dy = -radius; dy <= radius; dy++) {
                            int dt = t + dx;
                            int dr = r + dy;
                            if (dt < 0) dt = dt + maxTheta;
                            else if (dt >= maxTheta) dt = dt - maxTheta;
                            if (houghArray[dt][dr] > peak) {
                                // found a bigger point nearby, skip 
                                continue loop;
                            }
                        }
                    }
 
                    // calculate the true value of theta 
                    double theta = t * thetaStep;
 
                    // add the line to the vector 
                    lines.add(new HoughLine(theta, r, peak, (double)peak / max));
 
                }
            }
        }
        if (sort) Collections.sort(lines);
        return lines;
    }
    
    /**
     * Maximum value.
     * @return Maximum value.
     */
    private int getMaximumValue() {
        int max = 0;
        for (int t = 0; t < maxTheta; t++) {
            for (int r = 0; r < doubleHeight; r++) {
                if (houghArray[t][r] > max) {
                    max = houghArray[t][r];
                }
            }
        }
        return max;
    }
    
    public FastBitmap getHoughArrayImage() {
        int max = getMaximumValue();
        FastBitmap fastBitmap = new FastBitmap(maxTheta, doubleHeight);
        for (int t = 0; t < maxTheta; t++) {
            for (int r = 0; r < doubleHeight; r++) {
                double value = 255 * ((double) houghArray[t][r]) / max;
                int v = 255 - (int) value;
                fastBitmap.setRGB(r, t, v, v, v);
            }
        }
        return fastBitmap;
    }
}
 

Ads help maintain this website.