2D Vertical/Horizontal collision detection - java

I'm writing a 2D platformer in Java, and I've run into a problem. I have two objects: Both have bounding boxes, and four coordinates to represent the corners of the boxes.
My issue is that I am trying to find a way to simulate collision, but I just can't seem to do it. I've tried searching all over the place, but most sites just demonstrate OpenGL tactics.
Let us represent the bounding box coordinates like so:
TL: Top-left
TR: Top-right
BL: Bottom-left
BR: Bottom-right
Here is how I originally proposed testing collision:
if(TL1 > TL2 && TL1 < TR2) //X-axis
//Collision happened, TL1 corner is inside of second object
else if(BL1 < TL2 && BL1 > BL2) //Y-axis
//Collision happened, BL1 corner is inside of second object
This is a very crude way of showing it, but basically I'm checking to see if a corner intersects the other object. The problem is, it doesn't account for both axii. That is to say, an x-collision will occur even if one object is above the other.
If I check for collision on both axii, then there is no way of telling whether it was a horizontal or vertical collision. Or maybe there is and I haven't figured it out.
Can anyone point me in the right direction?

this is from java.awt.Rectangle.
you should be able to modify it to suit the coords you have.
/**
* Determines whether or not this <code>Rectangle</code> and the specified
* <code>Rectangle</code> intersect. Two rectangles intersect if
* their intersection is nonempty.
*
* #param r the specified <code>Rectangle</code>
* #return <code>true</code> if the specified <code>Rectangle</code>
* and this <code>Rectangle</code> intersect;
* <code>false</code> otherwise.
*/
public boolean intersects(Rectangle r) {
int tw = this.width;
int th = this.height;
int rw = r.width;
int rh = r.height;
if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
return false;
}
int tx = this.x;
int ty = this.y;
int rx = r.x;
int ry = r.y;
rw += rx;
rh += ry;
tw += tx;
th += ty;
// overflow || intersect
return ((rw < rx || rw > tx) &&
(rh < ry || rh > ty) &&
(tw < tx || tw > rx) &&
(th < ty || th > ry));
}

Related

Cannot work out how to compare 2 dimensional arrays of different sizes

So I am building a project in order to build a tetris style game, and I want to be able to test whether the shape will be able to be added to a 5 x 5 grid. The shape is modelled by a 2D array, where a 1 is considered to be a single block of the shape (the shapes are made of a few blocks). The shapes are modelled with a 3 x 3 grid. The thing I must do is check the grid for whether the shape will be able to fit on top of it. Take for example placing a line shape at the top square of the grid, the line will go out of bounds and should not work, or another example is that the grid may already have a shape on it and so the line should not be able to be put on top of it.
This is the code that I've got thus far, and it is not working, I'm just really having a tough time conceptualising what to do. Thank you in advance.
Please note that cols is the number of columns in the grid (5) and rows is the same (5). Game piece is the shape, and the co-ordinates is where the user has clicked on the 5x5 grid.
Also: The anchor point of the shape is 1,1 of the 3x3 grid (so the anchor point is right in the middle of the grid). And get(int x, int y) method is getting the value stored in the 5x5 grid.
Sorry if this was not made clear in the beginning but I am trying to basically see whether the shape stored in the 3x3 grid (made up of blocks) can be placed on top of the 5x5 grid. The 3x3 grid that contains the block has a centre anchor point, so it would be 1,1 (since arrays start with 0). If the 5x5 grid has other blocks that are at the same co-ordinate of the new shape being added, then I want it to return false or if the shape becomes out of bounds when being placed on the 5x5 grid, but if it can be added successfully then it will return true.
public boolean canPlayPiece (GamePiece piece, int x, int y) {
logger.info("canPlayPiece - Block clicked coordinates: " + x + "," + y);
// Piece co-ordinates are 3 x 3, each element that is 1 means there is a block there
int[][] pieceCoordinates = piece.getBlocks();
// For loop to iterate through the grid
// first looping through x values
for (int i = x - 1; i < cols; i++) {
System.out.println("i= " + i);
// nested for loop to find the y values stored inside the x
for (int j = y - 1; j < rows; j++) {
System.out.println("j: " + j);
if (pieceCoordinates[x][y] == 1 && get(i,j) != 0) {
logger.info("canPlayPiece: FALSE");
return false;
}
}
}
logger.info("canPlayPiece: TRUE");
return true;
}
Ok i made the following for you:
public boolean canPlayPiece(GamePiece piece, int x, int y) {
int[][] pc = piece.getBlocks();
final int w = 3, h = 3, e = w - 1;
final int offX = -1, offY = -1; // The offset of the left top corner from 'x' and 'y'
int i, si, ei, ax, ay, rx, ry;
for (ei = w * h - 1; ei >= 0 && pc[ei / w][ei % w] == 0; ei--);
for (si = 0; si <= ei && pc[si / w][si % w] == 0; si++);
for (i = si + 1, ax = si % w; ax > 0 && i <= ei; i++) if (pc[i / w][rx = i % w] != 0) { si += Math.min(rx - ax, 0); ax = rx; }
for (i = ei - 1, ax = ei % w; ax < e && i >= si; i--) if (pc[i / w][rx = i % w] != 0) { ei += Math.max(rx - ax, 0); ax = rx; }
if (si > ei) return true; // There is no block in the piece's grid
int sx = si % w, sy = si / w, ex = ei % w, ey = ei / w; // The bounds of the shape inside of pc
int asx = x + offX + sx, asy = y + offY + sy, aex = asx + ex - sx, aey = asy + ey - sy;
if ((asx | asy | aex | aey | cols - 1 - aex | rows - 1 - aey) < 0) return false; // Would be out of bounds
for (rx = sx, ax = asx; rx <= ex; rx++, ax++) {
for (ry = sy, ay = asy; ry <= ey; ry++, ay++) {
// if (grid[ay][ax] != 0 && pc[ry][rx] != 0) return false; // Block overlaps another block
if (get(ax, ay) != 0 && pc[ry][rx] != 0) return false; // Block overlaps another block
}
}
return true;
}
First it figues out the bounds of the shape inside of 'pc' grid (the grid returned by 'piece.getBlocks()')
If there is no shape inside of 'pc' it will return true, since an empty shape can be placed anywhere (change the return value to false, if you want to return false in that case)
If the inner shape would go out of bounds, when being inserted, it will return false
In the end it will walk through both the grid (using your 'get(x: int, y: int) function) and 'pc' to check whether the shape in 'pc' overlaps with any preexisting blocks inside the grid. And if it doesn't it returns true.
I really hope that this works for you. I tested it out and it worked at least for me.

How to determine if two points do not have any obstructions between them

I am currently trying to put together an algorithm where I can know if there is an obstruction between two defined points in a plane.
Here is an example image.
We can see with the image that point 1, 2, 3, & 6 are all accessible from the origin point. Points 4 and 5 are not. You pass through the polygon.
The code I am using is the following. pStartPoint and pEndPoint is the line from the origin to the point in question. The function checks all edges to see if the line passes through the edge.
public double GetSlopeOfLine(Point a, Point b){
double x = b.y - a.y;
double y = b.x - a.x;
return (x / y);
}
public double GetOffsetOfLine(double x, double y, double slope){
return (y - (slope * x));
}
public boolean IsPointAccessable(Point pStartPoint, Point pEndPoint){
//Define the equation of the line for these points. Once we have slope and offset the equation is
//y = slope * x + offset;
double slopeOfLine = GetSlopeOfLine(pStartPoint, pEndPoint);
double offSet = GetOffsetOfLine(pStartPoint.x, pStartPoint.y, slopeOfLine);
//Collision detection for each side of each obstacle. Once we get the point of collision, does it lie on the
//line in between the two points? If so, collision, and I can't reach that point yet.
for (Iterator<Obstacles> ObstacleIt = AdjustedObstaclesList.iterator(); ObstacleIt.hasNext();) {
Obstacles pObstacle = ObstacleIt.next();
int NumberOfEdges = pObstacle.getPoints().size();
for(int i=0; i<NumberOfEdges; i++){
//Get Edge[i];
int index = i;
Point pFirstPoint = (Point)pObstacle.getPoints().get(index);
if(i >= NumberOfEdges - 1)
index = 0;
else
index = i+1;
Point pNextPoint = (Point)pObstacle.getPoints().get(index);
double slopeOfEdge = GetSlopeOfLine(pFirstPoint, pNextPoint);
double offsetEdge = GetOffsetOfLine(pNextPoint.x, pNextPoint.y, slopeOfEdge);
int x = Math.round((float) ((-offSet + offsetEdge) / (slopeOfLine - slopeOfEdge)));
int y = Math.round((float) ((slopeOfLine * x) + offSet));
//If it lies on either point I could be looking at two adjacent points. I can still reach that point.
if(x > pStartPoint.x && x < pEndPoint.x && y > pStartPoint.y && y < pEndPoint.y &&
x > pFirstPoint.x && x < pNextPoint.x && y > pFirstPoint.y && y < pNextPoint.y){
return false;
}
}
}
return true;
}
If the line passes through and the point where the lines cross is found between pStartPoint and pEndPoint I am assuming that pEndPoint cannot be reached.
This function is not working and I am wondering if it has something to do with the fact that the origin is not at the bottom left but at the top left and that (width, height) of my window is located in the bottom right. Therefore the coordinate plane is messed up.
My mind must be mush because I cannot think how to adjust for this and if that is truly my mistake as I cannot seem to fix the error. I thought adjusting the slope and offset by multiplying each by -1 might have been the solution but that doesn't seem to work.
Is my solution the right one? Does my code seem correct in checking for an intersect point? Is there a better solution to see if a point is accessible.
There is also going to be the next step after this where once I determine what points are accessible if I am now on one of the points of the polygon. For example, from point 1 what points are accessible without crossing into the polygon?
First, I would like to say that using slopes for this kind of task is do-able, but also difficult due to the fact that they are very volatile in the sense that they can go from negative infinity to infinity with a very small change in the point. Here's a slightly different algorithm, which relies on angles rather than slopes. Another advantage of using this is that the coordinate systems don't really matter here. It goes like this (I reused as much of your existing code as I could):
public boolean IsPointAccessable(Point pStartPoint, Point pEndPoint) {
//Collision detection for each side of each obstacle. Once we get the point of collision, does it lie on the
//line in between the two points? If so, collision, and I can't reach that point yet.
for (Iterator<Obstacles> ObstacleIt = AdjustedObstaclesList.iterator(); ObstacleIt.hasNext();) {
Obstacles pObstacle = ObstacleIt.next();
int NumberOfEdges = pObstacle.getPoints().size();
for(int i=0; i<NumberOfEdges; i++){
//Get Edge[i];
int index = i;
Point pFirstPoint = (Point)pObstacle.getPoints().get(index);
if(i >= NumberOfEdges - 1)
index = 0;
else
index = i+1;
Point pNextPoint = (Point)pObstacle.getPoints().get(index);
// Here is where we get a bunch of angles that encode in them important info on
// the problem we are trying to solve.
double angleWithStart = getAngle(pNextPoint, pFirstPoint, pStartPoint);
double angleWithEnd = getAngle(pNextPoint, pFirstPoint, pEndPoint);
double angleWithFirst = getAngle(pStartPoint, pEndPoint, pFirstPoint);
double angleWithNext = getAngle(pStartPoint, pEndPoint, pNextPoint);
// We have accumulated all the necessary angles, now we must decide what they mean.
// If the 'start' and 'end' angles are different signs, then the first and next points
// between them. However, for a point to be inaccessible, it also must be the case that
// the 'first' and 'next' angles are opposite sides, as then the start and end points
// Are between them so a blocking occurs. We check for that here using a creative approach
// This is a creative way of checking if two numbers are different signs.
if (angleWithStart * angleWithEnd <= 0 && angleWithFirst * angleWithNext <= 0) {
return false;
}
}
}
return true;
}
Now, all that is left to do is find a method that calculates the signed angle formed by three points. A quick google search yielded this method (from this SO question):
private double getAngle(Point previous, Point center, Point next) {
return Math.toDegrees(Math.atan2(center.x - next.x, center.y - next.y)-
Math.atan2(previous.x- center.x,previous.y- center.y));
}
Now, this method should work in theory (I am testing to be sure and will edit my answer if I find any issues with signs of angles or something like that). I hope you get the idea and that my comments explain the code well enough, but please leave a comment/question if you want me to elaborate further. If you don't understand the algorithm itself, I recommend getting a piece of paper out and following the algorithm to see what exactly is going on. Hope this helps!
EDIT: To hopefully aid in better understanding the solution using angles, I drew a picture with the four base cases of how the start, end, first, and next could be oriented, and have attached it to this question. Sorry for the sloppiness, I drew it rather quickly, but this should in theory make the idea clearer.
If you have a low segment count (for instance, your example only shows 12 segments for three shapes, two shapes of which we know we can ignore (because of bounding box checks), then I would recommend simply performing line/line intersection checking.
Point s = your selected point;
ArrayList<Point> points = polygon.getPoints();
ArrayList<Edge> edges = polygon.getEdges();
for(Point p: points) {
Line l = new Line(s, p);
for(Edge e: edges) {
Point i = e.intersects(l);
if (i != null) {
System.out.println("collision", i.toString());
}
}
}
With an intersects method that is pretty straight forward:
Point intersects(Line l) {
// boring variable aliassing:
double x1 = this.p1.x,
y1 = this.p1.y,
x2 = this.p2.x,
y2 = this.p2.y,
x3 = l.p1.x,
y2 = l.p1.y,
x3 = l.p2.x,
y2 = l.p2.y,
// actual line intersection algebra:
nx = (x1 * y2 - y1 * x2) * (x3 - x4) -
(x1 - x2) * (x3 * y4 - y3 * x4),
ny = (x1 * y2 - y1 * x2) * (y3 - y4) -
(y1 - y2) * (x3 * y4 - y3 * x4),
d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d == 0) return null;
return new Point(nx/d, ny/d);
}

Raycasting inaccurately calculating intersections

I am trying to write a raycasting algorithm described in this article. I use a coordinate system where y increases upward, x increases to the left. The variable names in the following code snippet are taken from the article, I will describe them so that the code is easier to read:
rayDirX and rayDirY are coordinates of the vector pointing from the player's position on the map in the direction where the ray is being cast, alpha is the angle between this ray and the origin.
Px, Py are precise coordinates of the player's position on the map
Ax, Ay are the precise coordinates of the first horizontal intersection, later other horizontal intersections.
Bx, By are the precise coordinates of the first vertical intersection, later other vertical intersections.
horXa, horYa, vertXa, vertYb are the step increments, which are constant after finding the first intersection. I suspect that the problem with the algorithm is that these values are not calculated correctly.
mapX, mapY are the coordinates of the lower left corner of an intersection on a map. This is used to check if there is a wall on this position in the array map[][].
Horizontal and vertical intersections are checked alternately. Ax, Ay, or Bx, By should hold the precise position of the intersection.
// Calculate the initial intersections
// Horizontal intersections
// If the ray is facing up
if (rayDirY > 0)
{
horYa = 1;
Ay = mapY+1;
}
// If the ray is facing down
else
{
horYa = -1;
Ay = mapY;
}
horXa = Math.abs(horYa)/tanAlpha;
Ax = Px + (Ay-Py)/tanAlpha;
// Vertical intersections
// If the ray is facing right
if (rayDirX > 0)
{
vertXa = 1;
Bx = mapX+1;
}
// If the ray is facing left
else
{
vertXa = -1;
Bx = mapX;
}
vertYa = Math.abs(vertXa)*tanAlpha;
By = Py + (Px-Bx)*tanAlpha;
//Loop to find where the ray hits a wall
//Number of texture to display
int texNum;
boolean horizontal = Math.abs(Ax * Math.cos(alpha)) < Math.abs(Bx*Math.cos(alpha));
//This loop runs for each ray with the setup above
while(true) {
// Check horizontal intersection
if (horizontal)
{
mapX = (int)Math.floor(Math.abs(Ax));
mapY = (int)Math.floor(Math.abs(Ay));
if(mapX > mapWidth-1) mapX = mapWidth-1;
if(mapY > mapHeight-1) mapY = mapHeight-1;
if(mapX < 0) mapX = 0;
if(mapY < 0) mapY = 0;
texNum = map[mapX][mapY];
if(texNum > 0) break;
Ax += horXa;
Ay += horYa;
horizontal = false;
}
else {
mapX = (int)Math.floor(Math.abs(Bx));
mapY = (int)Math.floor(Math.abs(By));
if(mapX > mapWidth-1) mapX = mapWidth-1;
if(mapY > mapHeight-1) mapY = mapHeight-1;
if(mapX < 0) mapX = 0;
if(mapY < 0) mapY = 0;
texNum = map[mapX][mapY];
if (texNum > 0) break;
Bx += vertXa;
By += vertYa;
horizontal = true;
}
}
Using this algorithm, the image is not rendered properly, see the screenshot. I suspect that it is because of horYa, Ay, vertXa, Bx not being calculated correctly. UPDATE: After some debugging efforts, it seems that sometimes we choose to compute horizontal intersection instead of vertical and vice versa... Really strange!
Can you please spot an error in the algorithm? Thank you for any input!

Algorithm that determines a relationship between a square and rectangle

I need to find an algorithm which determines a relationship between a square and rectangle. It must be able to determine if:
The square is completely inside the rectangle
The square is partially inside (overlaps) the rectangle
Square's corner only touches a rectangle's corner
Square's edge is on the rectangle's edge
And here are the inputs (given values) that will help us to extract a mathematical formula for each case:
x coordinate of the center of the square = squareX
y coordinate of the center of the square = squareY
width of the square = squareW
x coordinate of the center of the rectangle = recX
y coordinate of the center of the rectangle = recY
width of the rectangle = recW
length of the rectangle = recL
P.S: Rectangle's sizes are always bigger than the square's width.
I will write the code in Java once we can extract an algorithm using mathematical operations.
Edit:
For the case of corners in touch, here is the code I wrote, and it works (Math.abs means the absolute value):
((Math.abs(Math.abs(recX-squareX)-(recW+squareW)/2))<=0.001) && ((Math.abs(Math.abs(recY-squareY)-(recL+squareW)/2))<=0.001)
updated for doubles
double dx = Math.abs(rectX - squareX);
double dy = Math.abs(rectY - squarey);
double dw2 = (rectW + squareW) / 2;
double dh2 = (rectL + squareW) / 2;
if (Double.compare(dx, dw2) == 0 && Double.compare(dy, dh2) == 0)
return CORNER_TOUCH;
else if (Double.compare(dx, dw2) > 0 || Double.compare(dy, dh2) > 0)
return OUTSIDE;
else if (Double.compare(dx, dw2) == 0 || Double.compare(dy, dh2) == 0)
return EDGE_TOUCH;
else if (Double.compare(dx, rectW - dw2) <= 0 &&
Double.compare(dy, rectL - dh2) <= 0)
return INSIDE;
else
return OVERLAPS;
squareX1 = squareX - squareW/2
squareY1 = squareY - squareW/2
squareX2 = squareX + squareW/2
squareY2 = squareY + squareW/2
recX1 = recX - recW/2
recY1 = recY - recL/2
recX2 = recX + recW/2
recY2 = recY + recL/2
inside = squareX1 > recX1 && squareX2 < recX2 && squareY1 > recY1 && squareY2 < recY2
overlaps = squareX1 < recX2 && squareX2 > recX1 && squareY1 < recY2 && squareY2 > recY1
the last two one should be trivial

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