Counting objects in a binarized image, coordinates out of bounds - java

I am at wits end with this problem.
Please note I am a really novice coder (although I'm sure you will see by my code).
The basics:
I have an image that I would like to count the number of objects in. Objects in this instance are just joined up pixels (the image has been threshold-ed and undergone binarization and binary erosion to get to this stage, code not included).
My problem:
I am trying to write some code to count how many objects there are left in this image, and within that method I call another method which is meant to remove any objects that have already been included by searching for neighbouring pixels to which they are attached. However, my current implementation of this removal method is throwing up an error: "coordinates out of bounds". I'm asking for any help solving this issue.
Code for overall object counting:
/**
* countObjects in image
*
* #param binary image to count objects in
* #param original image to put labels on
*
* #return labelled original image for graphics overlay
*/
public static BufferedImage countObjects(BufferedImage image, BufferedImage original){
BufferedImage target = copyImage(image);
int rgbBand = 0;
boolean finished = false;
Graphics labelColour = original.getGraphics();
labelColour.setColor(Color.RED);
while(!finished){
finished = false;
for ( int i=0; i<= target.getRaster().getWidth() - 1; i++ ) {
for( int j=0; j< target.getRaster().getHeight() - 1; j++ ) {
int clrz = target.getRaster().getSample(i, j, rgbBand);
if (clrz == 1) {
System.out.println(clrz);
removeObject(i, j, target);
labelColour.drawString( ""+count, i, j);
finished=true;
}
}
}
}
return original;
Code for object removal:
/**
*
* #param x
* #param y
* #param newImage
*
*/
private static void removeObject( int x, int y, BufferedImage newImage ){
int rgbBand = 0;
int[] zero = new int[] { 0 };
newImage.getRaster().setPixel(x, y, zero);
for (int a = Math.max(0, x - 1); a <= Math.min(x + 1, newImage.getRaster().getWidth()); a++) {
for (int b = Math.max(0, y - 1); b <= Math.min(y + 1, newImage.getRaster().getHeight()); b++) {
int na = a;
int nb = b;
if (newImage.getRaster().getSample(na, nb, rgbBand) == 1) {
removeObject( nc, nd, newImage );
}
}
}
}
In the above removeObject method, I am trying to use a recursive technique to remove pixel coordinates from the image being counted once either they or neighbouring pixels have been labelled.
If any of this is unclear (and I know there are probably more than a few confusing parts of my code, please ask and I will explain further).
Thanks for any help.

I dont have enough reputation to comment hence writing my comment as answer .
Are u sure u dint messed up with the x and y coordinates ?I had faced a similar problem some time ago ,but I had messed up with height and width of the image.

Related

Problem with Scan-Line Polygon Filling algorithm in java

(please don't mark this question as not clear, I spent a lot of time posting it ;) )
Okay, I am trying to make a simple 2d java game engine as a learning project, and part of it is rendering a filled polygon as a feature.
I am creating this algorithm my self, and I really can't figure out what I am doing wrong.
My though process is something like so:
Loop through every line, get the number of points in that line, then get the X location of every point in that line,
Then loop through the line again this time checking if the x in the loop is inside one of the lines in the points array, if so, draw it.
Disclaimer: the Polygon class is another type of mesh, and its draw method returns an int array with lines drawn through each vertex.
Disclaimer 2: I've tried other people's solutions but none really helped me and none really explained it properly (which is not the point in a learning project).
The draw methods are called one per frame.
FilledPolygon:
#Override
public int[] draw() {
int[] pixels = new Polygon(verts).draw();
int[] filled = new int[width * height];
for (int y = 0; y < height; y++) {
int count = 0;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
count++;
}
}
int[] points = new int[count];
int current = 0;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
points[current] = x;
current++;
}
}
if (count >= 2) {
int num = count;
if (count % 2 != 0)
num--;
for (int i = 0; i < num; i += 2) {
for (int x = points[i]; x < points[i+1]; x++) {
filled[x + y * width] = 0xffffffff;
}
}
}
}
return filled;
}
The Polygon class simply uses Bresenham's line algorithm and has nothing to do with the problem.
The game class:
#Override
public void load() {
obj = new EngineObject();
obj.addComponent(new MeshRenderer(new FilledPolygon(new int[][] {
{0,0},
{60, 0},
{0, 60},
{80, 50}
})));
((MeshRenderer)(obj.getComponent(MeshRenderer.class))).color = CYAN;
obj.transform.position.Y = 100;
}
The expected result is to get this shape filled up.(it was created using the polygon mesh):
The actual result of using the FilledPolygon mesh:
You code seems to have several problems and I will not focus on that.
Your approach based on drawing the outline then filling the "inside" runs cannot work in the general case because the outlines join at the vertices and intersections, and the alternation outside-edge-inside-edge-outside is broken, in an unrecoverable way (you can't know which segment to fill by just looking at a row).
You'd better use a standard polygon filling algorithm. You will find many descriptions on the Web.
For a simple but somewhat inefficient solution, work as follows:
process all lines between the minimum and maximum ordinates; let Y be the current ordinate;
loop on the edges;
assign every vertex a positive or negative sign if y ≥ Y or y < Y (mind the asymmetry !);
whenever the endpoints of an edge have a different sign, compute the intersection between the edge and the line;
you will get an even number of intersections; sort them horizontally;
draw between every other point.
You can get a more efficient solution by keeping a trace of which edges cross the current line, in a so-called "active list". Check the algorithms known as "scanline fill".
Note that you imply that pixels[] has the same width*height size as filled[]. Based on the mangled output, I would say that they are just not the same.
Otherwise if you just want to fill a scanline (assuming everything is convex), that code is overcomplicated, simply look for the endpoints and loop between them:
public int[] draw() {
int[] pixels = new Polygon(verts).draw();
int[] filled = new int[width * height];
for (int y = 0; y < height; y++) {
int left = -1;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
left = x;
break;
}
}
if (left >= 0) {
int right = left;
for (int x = width - 1; x > left; x--) {
if (pixels[x + y * width] == 0xffffffff) {
right = x;
break;
}
}
for (int x = left; x <= right; x++) {
filled[x + y * width] = 0xffffffff;
}
}
}
return filled;
}
However this kind of approach relies on having the entire polygon in the view, which may not always be the case in real life.

How to swap pixels of an image randomly?

I've been working on a Java program that reads an image, subdivides it into a definable number of rectangular tiles, then swaps the pixels of each tile with those of another tile, and then puts them back together and renders the image.
An explanation of the idea: http://i.imgur.com/OPefpjf.png
I've been using the BufferedImage class, so my idea was to first read all width * height pixels from its data buffer and save them to an array.
Then, according to the tile height and width, copy the entire pixel information of each tile to small arrays, shuffle those, and then write back the data contained in these arrays to their position in the data buffer. It should then be enough to create a new BufferedImage with the original color and sample models as well as the updated data buffer.
However, I got ominous errors when creating a new WriteableRaster from the updated data buffer, and the number of pixels didn't match up (I had suddenly gotten 24 instead of originally 8, and so forth), so I figured there is something wrong with the way I address the pixel information.
( Reference pages for BufferedImage and WriteableRaster )
I used the following loop to iterate through the 1D data buffer:
// maximum iteration values
int numRows = height/tileHeight;
int numCols = width/tileWidth;
// cut picture into tiles
// for each column of the image matrix
// addressing columns (1D)
for ( int column = 0; column < numCols; column++ )
{
// for each row of the matrix
// addressing cells (2D)
for ( int row = 0; row < numRows; row++ )
{
byte[] pixels = new byte[(tileWidth+1) * (tileHeight+1)];
int celloffset = (column + (width * row)); // find cell base address
// for each row inside the cell
// adressing column inside a tile (3D)
for ( int colpixel = 0; colpixel < tileWidth; colpixel++ )
{
// for each column inside the tile -> each pixel of the cell
for ( int rowpixel = 0; rowpixel < tileHeight; rowpixel++ )
{
// address of pixel in original image buffer array allPixels[]
int origpos = celloffset + ((rowpixel * tileWidth) + colpixel);
// translated address of pixel in local pixels[] array of current tile
int transpos = colpixel + (rowpixel * tileWidth);
// source, start, dest, offset, length
pixels[transpos] = allPixels[origpos];
}
}
}
}
Is there something wrong with this code? Or is there perhaps a much easier way to do this that I haven't thought of yet?
The code below edits the image in place. So no need to create new objects, which should simplify. If you need to keep the original, just copy it entirely first. Also, no need to save to separate arrays.
Since you said "shuffle" I assume you want to swap the tiles randomly. I made a function for that, and if you just call it many times you will end up with tiles swapped randomly. If you want a pattern or some other rule of how they are swapped, just call the other function directly with your chosen tiles.
I haven't used BufferedImage before, but looking at the documentation,
http://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html
and this post,
Edit pixel values
It seems that an easy way is to use the methods getRGB and setRGB
int getRGB(int x, int y)
Returns an integer pixel in the default RGB color model
(TYPE_INT_ARGB) and default sRGB colorspace.
void setRGB(int x, int y, int rgb)
Sets a pixel in this BufferedImage to the specified RGB value.
I would try something like the following: (untested code)
Using random http://docs.oracle.com/javase/7/docs/api/java/util/Random.html
int numRows = height/tileHeight;
int numCols = width/tileWidth;
void swapTwoRandomTiles (BufferedImage b) {
//choose x and y coordinates randomly for the tiles
int xt1 = random.nextInt (numCols);
int yt1 = random.nextInt (numRows);
int xt2 = random.nextInt (numCols);
int yt2 = random.nextInt (numRows);
swapTiles (b,xt1,yt1,xt2,yt2);
}
void swapTiles(BufferedImage b, int xt1, int yt1, int xt2, int yt2) {
int tempPixel = 0;
for (int x=0; x<tileWidth; x++) {
for (int y=0; y<tileHeight; y++) {
//save the pixel value to temp
tempPixel = b.getRGB(x + xt1*tileWidth, y + yt1*tileHeight);
//write over the part of the image that we just saved, getting data from the other tile
b.setRGB ( x + xt1*tileWidth, y + yt1*tileHeight, b.getRGB ( x+xt2*tileWidth, y+yt2*tileHeight));
//write from temp back to the other tile
b.setRGB ( x + xt2*tileWidth, y + yt2*tileHeight, tempPixel);
}
}
}

Android: drawing an arraylist of Bitmaps results in a black background

it's my first time asking a question here so I'll try to stay on-topic. I'm trying to randomly generate a background by creating an appropriately-sized ArrayList of Bitmap objects, and drawing them in order. This implementation works fine loading a single Bitmap, by the way; it's just stumbling with a list.
Before I get to the code, I'd like to point out that Ideally I would make a single Bitmap by adding the individual pixels or tiles, and indeed have tried a few variations of that, but they all result in black screens; I'm starting to think it might be a problem with how I draw to the Canvas. Anyways, here's what I have:
First, I generate the random ArrayList, only using 3 colors right now. I'd make it return the list, but it's just a private method inside the thread referencing one of the thread's variables so it doesn't matter much.
private void genMap(Resources res)
{
// Load basic tiles.
Bitmap green = BitmapFactory.decodeResource(res, R.drawable.green);
Bitmap red = BitmapFactory.decodeResource(res, R.drawable.red);
Bitmap blue = BitmapFactory.decodeResource(res, R.drawable.blue);
// All tiles must be the same size.
int tile_width = green.getWidth();
int tile_height = green.getHeight();
int num_x = mCanvasWidth / tile_width;
int num_y = mCanvasHeight / tile_height;
for (int j = 0; j < num_y; j++)
{
for (int i = 0; i < num_x; i++)
{
double r = Math.random();
Bitmap tile;
if (r <= 1/3) {tile = green;}
else if (r <= 2/3) {tile = red;}
else {tile = blue;}
// Create a new Bitmap in order to avoid referencing the old value.
mBackgroundImages.add(Bitmap.createBitmap(tile));
}
}
}
So, that's how the random values are mapped to a pattern. The method is called in the thread's constructor, which is in turn called every time onCreate is called; for now, I'm just clearing the list and making a new random pattern each time:
...
Resources res = context.getResources();
mBackgroundImages = new ArrayList<Bitmap>();
genMap(res);
...
And finally, the draw method; it works fine loading a single Bitmap via BitmapFactory.decodeResources, but shows a black screen when doing this:
private void doDraw(Canvas canvas)
{
/* Draw the bg.
* Remember, Canvas objects accumulate.
* So drawn first = most in the background. */
if (canvas != null)
{
if (mBackgroundImages.size() > 0)
{
int tile_width = mBackgroundImages.get(0).getWidth();
int tile_height = mBackgroundImages.get(0).getHeight();
for (int y = 0; y < mCanvasHeight / tile_height; y++)
{
for(int x = 0; x < mCanvasWidth / tile_width; x++)
{
// Draw the Bitmap at the correct position in the list; Y * XWIDTH + X, at pos X * XWIDTH, Y * YWIDTH.
canvas.drawBitmap(mBackgroundImages.get((x + y * (mCanvasWidth/tile_width))), x * tile_width, y * tile_height, null);
}
}
}
}
}
Any help would be appreciated, thanks.
Have you double-checked the order of precedence of the following line?
((x + y * (mCanvasWidth/tile_width)))
Interesting question though: are you drawing solid colors? I would also see if this is actually making a Bitmap:
mBackgroundImages.add(Bitmap.createBitmap(tile));
You might actually be able to just keep references instead of creating a lot of bitmaps, but that can come later

Breaking bricks with chain reaction

I am developing a game in java just for fun. It is a ball brick breaking game of some sort.
Here is a level, when the ball hits one of the Orange bricks I would like to create a chain reaction to explode all other bricks that are NOT gray(unbreakable) and are within reach of the brick being exploded.
So it would clear out everything in this level without the gray bricks.
I am thinking I should ask the brick that is being exploded for other bricks to the LEFT, RIGHT, UP, and DOWN of that brick then start the same process with those cells.
//NOTE TO SELF: read up on Enums and List
When a explosive cell is hit with the ball it calls the explodeMyAdjecentCells();
//This is in the Cell class
public void explodeMyAdjecentCells() {
exploded = true;
ballGame.breakCell(x, y, imageURL[thickness - 1][0]);
cellBlocks.explodeCell(getX() - getWidth(),getY());
cellBlocks.explodeCell(getX() + getWidth(),getY());
cellBlocks.explodeCell(getX(),getY() - getHeight());
cellBlocks.explodeCell(getX(),getY() + getHeight());
remove();
ballGame.playSound("src\\ballgame\\Sound\\cellBrakes.wav", 100.0f, 0.0f, false, 0.0d);
}
//This is the CellHandler->(CellBlocks)
public void explodeCell(int _X, int _Y) {
for(int c = 0; c < cells.length; c++){
if(cells[c] != null && !cells[c].hasExploded()) {
if(cells[c].getX() == _X && cells[c].getY() == _Y) {
int type = cells[c].getThickness();
if(type != 7 && type != 6 && type != 2) {
cells[c].explodeMyAdjecentCells();
}
}
}
}
}
It successfully removes my all adjacent cells,
But in the explodeMyAdjecentCells() method, I have this line of code
ballGame.breakCell(x, y, imageURL[thickness - 1][0]);
//
This line tells the ParticleHandler to create 25 small images(particles) of the exploded cell.
Tough all my cells are removed the particleHandler do not create particles for all the removed cells.
The problem was solved youst now, its really stupid.
I had set particleHandler to create max 1500 particles. My god how did i not see that!
private int particleCellsMax = 1500;
private int particleCellsMax = 2500;
thx for all the help people, I will upload the source for creating the particles youst for fun if anyone needs it.
The source code for splitting image into parts was taken from:
Kalani's Tech Blog
//Particle Handler
public void breakCell(int _X, int _Y, String URL) {
File file = new File(URL);
try {
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis);
int rows = 5;
int colums = 5;
int parts = rows * colums;
int partWidth = image.getWidth() / colums;
int partHeight = image.getHeight() / rows;
int count = 0;
BufferedImage imgs[] = new BufferedImage[parts];
for(int x = 0; x < colums; x++) {
for(int y = 0; y < rows; y++) {
imgs[count] = new BufferedImage(partWidth, partHeight, image.getType());
Graphics2D g = imgs[count++].createGraphics();
g.drawImage(image, 0, 0, partWidth, partHeight, partWidth * y, partHeight * x, partWidth * y + partWidth, partHeight * x + partHeight, null);
g.dispose();
}
}
int numParts = imgs.length;
int c = 0;
for(int iy = 0; iy < rows; iy ++) {
for(int ix = 0; ix < colums; ix++) {
if(c < numParts) {
Image imagePart = Toolkit.getDefaultToolkit().createImage(imgs[c].getSource());
createCellPart(_X + ((image.getWidth() / colums) * ix), _Y + ((image.getHeight() / rows) * iy), c, imagePart);
c++;
} else {
break;
}
}
}
} catch(IOException io) {}
}
You could consider looking at this in a more OO way, and using 'tell don't ask'. So you would look at having a Brick class, which would know what its colour was, and its adjacent blocks. Then you would tell the first Block to explode, it would then know that if it was Orange (and maybe consider using Enums for this - not just numbers), then it would tell its adjacent Blocks to 'chain react' (or something like that), these blocks would then decide what to do (either explode in the case of an orange block - and call their adjacent blocks, or not in the case of a grey Block.
I know its quite different from what your doing currently, but will give you a better structured program hopefully.
I would imagine a method that would recursively get all touching cells of a similar color.
Then you can operate on that list (of all touching blocks) pretty easily and break all the ones are haven't been broken.
Also note that your getAdjentCell() method has side effects (it does the breaking) which isn't very intuitive based on the name.
// I agree with Matt that color (or type) should probably be an enum,
// or at least a class. int isn't very descriptive
public enum CellType { GRAY, RED, ORANGE }
public class Cell{
....
public final CellType type;
/**
* Recursively find all adjacent cells that have the same type as this one.
*/
public List<Cell> getTouchingSimilarCells() {
List<Cell> result = new ArrayList<Cell>();
result.add(this);
for (Cell c : getAdjecentCells()) {
if (c != null && c.type == this.type) {
result.addAll(c.getTouchingSimilarCells());
}
}
return result;
}
/**
* Get the 4 adjacent cells (above, below, left and right).<br/>
* NOTE: a cell may be null in the list if it does not exist.
*/
public List<Cell> getAdjecentCells() {
List<Cell> result = new ArrayList<Cell>();
result.add(cellBlock(this.getX() + 1, this.getY()));
result.add(cellBlock(this.getX() - 1, this.getY()));
result.add(cellBlock(this.getX(), this.getY() + 1));
result.add(cellBlock(this.getX(), this.getY() - 1));
return result;
}
}

Java image analysis - counting vertical lines

I need a little help on an image analysis algorithm in Java. I basically have images like this:
So, as you might guessed, I need to count the lines.
What approach do you think would be best?
Thanks,
Smaug
A simple segmentation algorithm can help you out. Heres how the algorithm works:
scan pixels from left to right and
record the position of the first
black (whatever the color of your
line is) pixel.
carry on this process
unless you find one whole scan when
you don't find the black pixel.
Record this position as well.
We are
just interested in the Y positions
here. Now using this Y position
segment the image horizontally.
Now
we are going to do the same process
but this time we are going to scan
from top to bottom (one column at a
time) in the segment we just created.
This time we are interested in X
positions.
So in the end we get every
lines extents or you can say a
bounding box for every line.
The
total count of these bounding boxes
is the number of lines.
You can do many optimizations in the algorithm according to your needs.
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 />
* http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm
* </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;
}
}
Source: http://vase.essex.ac.uk/software/HoughTransform/HoughTransform.java.html
I've implemented a simple solution (must be improved) using Marvin Framework that finds the vertical lines start and end points and prints the total number of lines found.
Approach:
Binarize the image using a given threshold.
For each pixel, if it is black (solid), try to find a vertical line
Save the x,y, of the start and end points
The line has a minimum lenght? It is an acceptable line!
Print the start point in red and the end point in green.
The output image is shown below:
The programs output:
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);
}
}
It depends on how much they look like that.
Bring the image to 1-bit (black and white) in a way that preserves the lines and brings the background to pure white
Perhaps do simple cleanup like speck removal (remove any small black components).
Then,
Find a black pixel
Use flood-fill algorithms to find its extent
See if the shape meets the criteria for being a line (lineCount++ if so)
remove it
Repeat this until there are no black pixels
A lot depends on how good you do #3, some ideas
Use Hough just on this section to check that you have one line, and that it is vertical(ish)
(after #1) rotate it to the vertical and check its width/height ratio

Categories

Resources