Java图像分析-计算垂直线数

8

我需要在Java中进行图像分析算法方面的一些帮助。我基本上有这样的图片: alt text

所以,你可能猜到了,我需要数一下这些线。

你认为什么方法最好呢?

谢谢, Smaug


1
这个可能会有帮助,顺便+1:http://en.wikipedia.org/wiki/Hough_transform - jmj
4个回答

2
一个简单的分割算法可以帮助您解决问题。以下是算法的工作原理:
  • 从左到右扫描像素,并记录第一个黑色(无论您的线条颜色是什么)像素的位置。
  • 除非您找不到黑色像素,否则继续此过程。同样记录此位置。
  • 我们只对Y位置感兴趣。现在使用这个Y位置水平地分割图像。
  • 现在我们将做同样的过程,但这次我们将在刚刚创建的段中从上到下(一列一列)扫描。
  • 这次我们对X位置感兴趣。
  • 因此,最终我们得到每行的范围,或者可以说是每行的边界框。
  • 这些边界框的总数就是行数。
您可以根据需要对算法进行许多优化。

+1 很好的解释。这也用于OCR中的字符分割。 - Faisal Feroz
有没有一种Java API可以实现这个功能? - Sid

1
package ac.essex.ooechs.imaging.commons.edge.hough; 

import java.awt.image.BufferedImage; 
import java.awt.*; 
import java.util.Vector; 
import java.io.File; 

/** 
 * <p/> 
 * Java Implementation of the Hough Transform.<br /> 
 * Used for finding straight lines in an image.<br /> 
 * by Olly Oechsle 
 * </p> 
 * <p/> 
 * Note: This class is based on original code from:<br /> 
 * <a href="http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm">http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm</a> 
 * </p> 
 * <p/> 
 * If you represent a line as:<br /> 
 * x cos(theta) + y sin (theta) = r 
 * </p> 
 * <p/> 
 * ... and you know values of x and y, you can calculate all the values of r by going through 
 * all the possible values of theta. If you plot the values of r on a graph for every value of 
 * theta you get a sinusoidal curve. This is the Hough transformation. 
 * </p> 
 * <p/> 
 * The hough tranform works by looking at a number of such x,y coordinates, which are usually 
 * found by some kind of edge detection. Each of these coordinates is transformed into 
 * an r, theta curve. This curve is discretised so we actually only look at a certain discrete 
 * number of theta values. "Accumulator" cells in a hough array along this curve are incremented 
 * for X and Y coordinate. 
 * </p> 
 * <p/> 
 * The accumulator space is plotted rectangularly with theta on one axis and r on the other. 
 * Each point in the array represents an (r, theta) value which can be used to represent a line 
 * using the formula above. 
 * </p> 
 * <p/> 
 * Once all the points have been added should be full of curves. The algorithm then searches for 
 * local peaks in the array. The higher the peak the more values of x and y crossed along that curve, 
 * so high peaks give good indications of a line. 
 * </p> 
 * 
 * @author Olly Oechsle, University of Essex 
 */ 

public class HoughTransform extends Thread { 

    public static void main(String[] args) throws Exception { 
        String filename = "/home/ooechs/Desktop/vase.png"; 

        // load the file using Java's imageIO library 
        BufferedImage image = javax.imageio.ImageIO.read(new File(filename)); 

        // create a hough transform object with the right dimensions 
        HoughTransform h = new HoughTransform(image.getWidth(), image.getHeight()); 

        // add the points from the image (or call the addPoint method separately if your points are not in an image 
        h.addPoints(image); 

        // get the lines out 
        Vector<HoughLine> lines = h.getLines(30); 

        // draw the lines back onto the image 
        for (int j = 0; j < lines.size(); j++) { 
            HoughLine line = lines.elementAt(j); 
            line.draw(image, Color.RED.getRGB()); 
        } 
    } 

    // The size of the neighbourhood in which to search for other local maxima 
    final int neighbourhoodSize = 4; 

    // How many discrete values of theta shall we check? 
    final int maxTheta = 180; 

    // Using maxTheta, work out the step 
    final double thetaStep = Math.PI / maxTheta; 

    // 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; 

    /** 
     * Initialises the hough transform. The dimensions of the input image are needed 
     * in order to initialise the hough array. 
     * 
     * @param width  The width of the input image 
     * @param height The height of the input image 
     */ 
    public HoughTransform(int width, int height) { 

        this.width = width; 
        this.height = height; 

        initialise(); 

    } 

    /** 
     * Initialises the hough array. Called by the constructor so you don't need to call it 
     * yourself, however you can use it to reset the transform if you want to plug in another 
     * image (although that image must have the same width and height) 
     */ 
    public void initialise() { 

        // 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); 
        } 
    } 

    /** 
     * Adds points from an image. The image is assumed to be greyscale black and white, so all pixels that are 
     * not black are counted as edges. The image should have the same dimensions as the one passed to the constructor. 
     */ 
    public void addPoints(BufferedImage image) { 

        // Now find edge points and update the hough array 
        for (int x = 0; x < image.getWidth(); x++) { 
            for (int y = 0; y < image.getHeight(); y++) { 
                // Find non-black pixels 
                if ((image.getRGB(x, y) & 0x000000ff) != 0) { 
                    addPoint(x, y); 
                } 
            } 
        } 
    } 

    /** 
     * Adds a single point to the hough transform. You can use this method directly 
     * if your data isn't represented as a buffered image. 
     */ 
    public 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++; 
    } 

    /** 
     * Once points have been added in some way this method extracts the lines and returns them as a Vector 
     * of HoughLine objects, which can be used to draw on the 
     * 
     * @param percentageThreshold The percentage threshold above which lines are determined from the hough array 
     */ 
    public Vector<HoughLine> getLines(int threshold) { 

        // Initialise the vector of lines that we'll return 
        Vector<HoughLine> lines = new Vector<HoughLine>(20); 

        // Only proceed if the hough array is not empty 
        if (numPoints == 0) return lines; 

        // Search for local peaks above threshold to draw 
        for (int t = 0; t < maxTheta; t++) { 
            loop: 
            for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) { 

                // Only consider points above threshold 
                if (houghArray[t][r] > threshold) { 

                    int peak = houghArray[t][r]; 

                    // Check that this peak is indeed the local maxima 
                    for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) { 
                        for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; 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)); 

                } 
            } 
        } 

        return lines; 
    } 

    /** 
     * Gets the highest value in the hough array 
     */ 
    public int getHighestValue() { 
        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; 
    } 

    /** 
     * Gets the hough array as an image, in case you want to have a look at it. 
     */ 
    public BufferedImage getHoughArrayImage() { 
        int max = getHighestValue(); 
        BufferedImage image = new BufferedImage(maxTheta, doubleHeight, BufferedImage.TYPE_INT_ARGB); 
        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; 
                int c = new Color(v, v, v).getRGB(); 
                image.setRGB(t, r, c); 
            } 
        } 
        return image; 
    } 

}

源代码:http://vase.essex.ac.uk/software/HoughTransform/HoughTransform.java.html


1
我已经使用Marvin Framework实现了一个简单的解决方案(必须改进),它可以查找垂直线的起始点和结束点,并打印出找到的线的总数。
方法:
  1. 使用给定的阈值对图像进行二值化。
  2. 对于每个像素,如果它是黑色(实心),则尝试找到一条垂直线。
  3. 保存起点和终点的x、y坐标。
  4. 线的长度是否达到最小长度?这是一条可接受的线!
  5. 以红色打印起点,以绿色打印终点。
输出图像如下:

enter image description here

程序的输出:
Vertical line fount at: (74,9,70,33)
Vertical line fount at: (113,9,109,31)
Vertical line fount at: (80,10,76,32)
Vertical line fount at: (137,11,133,33)
Vertical line fount at: (163,11,159,33)
Vertical line fount at: (184,11,180,33)
Vertical line fount at: (203,11,199,33)
Vertical line fount at: (228,11,224,33)
Vertical line fount at: (248,11,244,33)
Vertical line fount at: (52,12,50,33)
Vertical line fount at: (145,13,141,35)
Vertical line fount at: (173,13,169,35)
Vertical line fount at: (211,13,207,35)
Vertical line fount at: (94,14,90,36)
Vertical line fount at: (238,14,236,35)
Vertical line fount at: (130,16,128,37)
Vertical line fount at: (195,16,193,37)
Vertical lines total: 17

Finally, the source code:

import java.awt.Color;
import java.awt.Point;
import marvin.image.MarvinImage;
import marvin.io.MarvinImageIO;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinPluginLoader;

public class VerticalLineCounter {

    private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");

    public VerticalLineCounter(){
        // Binarize
        MarvinImage image = MarvinImageIO.loadImage("./res/lines.jpg");
        MarvinImage binImage = image.clone();
        threshold.setAttribute("threshold", 127);
        threshold.process(image, binImage);

        // Find lines and save an output image
        MarvinImage imageOut = findVerticalLines(binImage, image);
        MarvinImageIO.saveImage(imageOut, "./res/lines_out.png");
    }

    private MarvinImage findVerticalLines(MarvinImage binImage, MarvinImage originalImage){
        MarvinImage imageOut = originalImage.clone();
        boolean[][] processedPixels = new boolean[binImage.getWidth()][binImage.getHeight()];
        int color;
        Point endPoint;
        int totalLines=0;
        for(int y=0; y<binImage.getHeight(); y++){
            for(int x=0; x<binImage.getWidth(); x++){
                if(!processedPixels[x][y]){
                    color = binImage.getIntColor(x, y);

                    // Black?
                    if(color == 0xFF000000){
                        endPoint = getEndOfLine(x,y,binImage,processedPixels);

                        // Line lenght threshold
                        if(endPoint.x - x > 5 || endPoint.y - y > 5){
                            imageOut.fillRect(x-2, y-2, 5, 5, Color.red);
                            imageOut.fillRect(endPoint.x-2, endPoint.y-2, 5, 5, Color.green);
                            totalLines++;
                            System.out.println("Vertical line fount at: ("+x+","+y+","+endPoint.x+","+endPoint.y+")");
                        }
                    }
                }
                processedPixels[x][y] = true;
            }
        }
        System.out.println("Vertical lines total: "+totalLines);
        return imageOut;
    }

    private Point getEndOfLine(int x, int y, MarvinImage image, boolean[][] processedPixels){
        int xC=x;
        int cY=y;
        while(true){
            processedPixels[xC][cY] = true;
            processedPixels[xC-1][cY] = true;
            processedPixels[xC-2][cY] = true;
            processedPixels[xC-3][cY] = true;
            processedPixels[xC+1][cY] = true;
            processedPixels[xC+2][cY] = true;
            processedPixels[xC+3][cY] = true;

            if(getSafeIntColor(xC,cY,image)  < 0xFF000000){
                // nothing
            }
            else if(getSafeIntColor(xC-1,cY,image) == 0xFF000000){
                xC = xC-2;
            }
            else if(getSafeIntColor(xC-2,cY,image) == 0xFF000000){
                xC = xC-3;
            }
            else if(getSafeIntColor(xC+1,cY,image)  == 0xFF000000){ 
                xC = xC+2;
            }
            else if(getSafeIntColor(xC+2,cY,image)  == 0xFF000000){ 
                xC = xC+3;
            }
            else{
                return new Point(xC, cY);
            }
            cY++;
        }
    }
    private int getSafeIntColor(int x, int y, MarvinImage image){
        if(x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()){
            return image.getIntColor(x, y);
        }
        return -1;
    }
    public static void main(String args[]){
        new VerticalLineCounter();
        System.exit(0);
    }
}

0

这取决于它们有多像。

  1. 以保留线条并将背景变为纯白的方式将图像转换为1位(黑白)
  2. 可能进行简单的清理,如去除任何小的黑色组件。

然后,

  1. 找到一个黑色像素
  2. 使用泛洪算法查找其范围
  3. 查看形状是否符合成为一条线的标准(如果是,则lineCount++)
  4. 删除它
  5. 重复此过程,直到没有黑色像素为止

很多事情都取决于你如何完成第3步,以下是一些想法:

  1. 仅在此部分上使用Hough检查您是否有一条线,并且它是垂直的(ish)
  2. (在#1之后)将其旋转到垂直位置并检查其宽度/高度比

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接