Java图像分析 – 计算垂直线

我需要一点Java的图像分析算法的帮助。 我基本上有这样的图像: 替代文字

所以,正如你可能猜到的那样,我需要计算一下。

你认为哪种方法最好?

谢谢,Smaug

一个简单的分割算法可以帮助你。 下面是该算法的工作原理:

  • 从左到右扫描像素并记录第一个黑色(无论您的线条的颜色是什么)像素的位置。
  • 除非您在找不到黑色像素时找到一个完整的扫描,否则请继续此过程。 记录这个位置。
  • 我们只对这里的Y职位感兴趣。 现在使用此Y位置水平分割图像。
  • 现在我们将执行相同的过程,但这次我们将在刚创建的段中从上到下扫描(一次一列)。
  • 这次我们对X职位感兴趣。
  • 所以最后我们得到每个行范围,或者你可以为每一行说一个边界框。
  • 这些边界框的总数是行数。

您可以根据需要在算法中进行许多优化。

package ac.essex.ooechs.imaging.commons.edge.hough; import java.awt.image.BufferedImage; import java.awt.*; import java.util.Vector; import java.io.File; /** * 

* Java Implementation of the Hough Transform.
* Used for finding straight lines in an image.
* by Olly Oechsle * *

* Note: This class is based on original code from:
* http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm * *

* If you represent a line as:
* x cos(theta) + y sin (theta) = r * *

* ... 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. * *

* 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. * *

* 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. * *

* 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. * * * @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 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 rr += 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 getLines(int threshold) { // Initialise the vector of lines that we'll return Vector lines = new Vector(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

我使用Marvin Framework实现了一个简单的解决方案(必须进行改进),找到垂直线的起点和终点并打印找到的总行数。

做法:

  1. 使用给定阈值对图像进行二值化。
  2. 对于每个像素,如果是黑色(实心),请尝试查找垂直线
  3. 保存起点和终点的x,y
  4. 这条线的最小长度是多少? 这是一个可以接受的线!
  5. 将起点打印为红色,将终点打印为绿色。

输出图像如下所示:

在此处输入图像描述

程序输出:

 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 

最后,源代码:

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

这取决于它们的样子。

  1. 将图像保留为1位(黑色和白色),以保留线条并使背景变为纯白色
  2. 也许做像斑点去除一样的简单清理(删除任何小的黑色组件)。

然后,

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

很大程度上取决于你做得多好#3,一些想法

  1. 在本节中使用Hough检查你有一行,它是垂直的(ish)
  2. (在#1之后)将其旋转到垂直方向并检查其宽高比