Java image analysis - counting vertical lines - java

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

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 draw a rectangle containing an object in Android (Java, OpenCV)

Basically, I want to achieve this, and so far, I've written the following Java code...
// Display the camera frame
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
// The object's width and height are set to 0
objectWidth = objectHeight = 0;
// frame is captured as a coloured image
frame = inputFrame.rgba();
/** Since the Canny algorithm only works on greyscale images and the captured image is
* coloured, we transform the captured cam image into a greyscale one
*/
Imgproc.cvtColor(frame, grey, Imgproc.COLOR_RGB2GRAY);
// Calculating borders of image using the Canny algorithm
Imgproc.Canny(grey, canny, 180, 210);
/** To avoid background noise (given by the camera) that makes the system too sensitive
* small variations, the image is blurred to a small extent. Blurring is one of the
* required steps before any image transformation because this eliminates small details
* that are of no use. Blur is a low-pass filter.
*/
Imgproc.GaussianBlur(canny, canny, new Size(5, 5), 5);
// Calculate the contours
Imgproc.findContours(canny, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
/** The contours come in different sequences
* 1 sequence for each connected component.
* Taking the assumption only 1 object is in view, if we have more than 1 connected
* component, this'll be considered part of the details of the object.
*
* For this, we put all contours together in a single sequence
* If there is at least 1 contour, I can continue processing
*/
for (MatOfPoint mat : contours) {
// Retrieve and store all contours in one giant map
mat.copyTo(allContours);
}
MatOfPoint2f allCon = new MatOfPoint2f(allContours.toArray());
// Calculating the minimal rectangle to contain the contours
RotatedRect box = Imgproc.minAreaRect(allCon);
// Getting the vertices of the rectangle
Point[] vertices = initialiseWithDefaultPointInstances(4);
box.points(vertices);
// Now the vertices are in possession, temporal smoothing can be performed.
for (int i = 0; i < 4; i++) {
// Smooth coordinate x of the vertex
vertices[i].x = alpha * lastVertices[i].x + (1.0 - alpha) * vertices[i].x;
// Smooth coordinate y of the vertex
vertices[i].y = alpha * lastVertices[i].y + (1.0 - alpha) * vertices[i].y;
// Assign the present smoothed values as lastVertices for the next smooth
lastVertices[i] = vertices[i];
}
/** With the vertices, the object size is calculated.
* The object size is calculated through pythagoras theorm. In addition, it gives
* the distance between 2 points in a bi-dimensional space.
*
* For a rectangle, considering any vertex V, its two sizes (width and height) can
* be calculated by calculating the distance of V from the previous vertex and
* calculating the distance of V from the next vertex. This is the reason why I
* calculate the distance between vertici[0]/vertici[3] and vertici[0]/vertici[1]
*/
objectWidth = (int) (conversionFactor * Math.sqrt((vertices[0].x - vertices[3].x) * (vertices[0].x - vertices[3].x) + (vertices[0].y - vertices[3].y) * (vertices[0].y - vertices[3].y)));
objectHeight = (int) (conversionFactor * Math.sqrt((vertices[0].x - vertices[1].x) * (vertices[0].x - vertices[1].x) + (vertices[0].y - vertices[1].y) * (vertices[0].y - vertices[1].y)));
/** Draw the rectangle containing the contours. The line method draws a line from 1
* point to the next, and accepts only integer coordinates; for this reason, 2
* temporary Points have been created and why I used Math.round method.
*/
Point pt1 = new Point();
Point pt2 = new Point();
for (int i = 0; i < 4; i++) {
pt1.x = Math.round(vertices[i].x);
pt1.y = Math.round(vertices[i].y);
pt2.x = Math.round(vertices[(i + 1) % 4].x);
pt2.y = Math.round(vertices[(i + 1) % 4].y);
Imgproc.line(frame, pt1, pt2, red, 3);
}
//If the width and height are non-zero, then print the object size on-screen
if (objectWidth != 0 && objectHeight != 0) {
String text;
text = String.format("%d x %d", objectWidth, objectHeight);
widthValue.setText(text);
}
// This function must return
return frame;
}
// Initialising an array of points
public static Point[] initialiseWithDefaultPointInstances(int length) {
Point[] array = new Point[length];
for (int i = 0; i < length; i++) {
array[i] = new Point();
}
return array;
}
What I want to achieve is drawing a rectangle on-screen that contains the object's contours (edges). If anyone knows the answer to my question, please feel free to comment below, as I have been stuck on this for a couple of hours
Here's the code referenced in the comment How to draw a rectangle containing an object in Android (Java, OpenCV)
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
// The object's width and height are set to 0
List<Integer> objectWidth = new ArrayList<>();
List<Integer> objectHeight = new ArrayList<>();
// frame is captured as a coloured image
Mat frame = inputFrame.rgba();
Mat gray = new Mat();
Mat canny = new Mat();
List<MatOfPoint> contours = new ArrayList<>();
/** Since the Canny algorithm only works on greyscale images and the captured image is
* coloured, we transform the captured cam image into a greyscale one
*/
Imgproc.cvtColor(frame, gray, Imgproc.COLOR_RGB2GRAY);
// Calculating borders of image using the Canny algorithm
Imgproc.Canny(gray, canny, 180, 210);
/** To avoid background noise (given by the camera) that makes the system too sensitive
* small variations, the image is blurred to a small extent. Blurring is one of the
* required steps before any image transformation because this eliminates small details
* that are of no use. Blur is a low-pass filter.
*/
Imgproc.GaussianBlur(canny, canny, new Size(5, 5), 5);
// Calculate the contours
Imgproc.findContours(canny, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
/** The contours come in different sequences
* 1 sequence for each connected component.
* Taking the assumption only 1 object is in view, if we have more than 1 connected
* component, this'll be considered part of the details of the object.
*
* For this, we put all contours together in a single sequence
* If there is at least 1 contour, I can continue processing
*/
if(contours.size() > 0){
// Calculating the minimal rectangle to contain the contours
List<RotatedRect> boxes = new ArrayList<>();
for(MatOfPoint contour : contours){
RotatedRect box = Imgproc.minAreaRect(new MatOfPoint2f(contour.toArray()));
boxes.add(box);
}
// Getting the vertices of the rectangle
List<Point[]> vertices = initialiseWithDefaultPointInstances(boxes.size(), 4);
for(int i=0; i<boxes.size(); i++){
boxes.get(i).points(vertices.get(i));
}
/*
double alpha = 0.5;
// Now the vertices are in possession, temporal smoothing can be performed.
for(int i = 0; i<vertices.size(); i++){
for (int j = 0; j < 4; j++) {
// Smooth coordinate x of the vertex
vertices.get(i)[j].x = alpha * lastVertices.get(i)[j].x + (1.0 - alpha) * vertices.get(i)[j].x;
// Smooth coordinate y of the vertex
vertices.get(i)[j].y = alpha * lastVertices.get(i)[j].y + (1.0 - alpha) * vertices.get(i)[j].y;
// Assign the present smoothed values as lastVertices for the next smooth
lastVertices.get(i)[j] = vertices.get(i)[j];
}
}*/
/** With the vertices, the object size is calculated.
* The object size is calculated through pythagoras theorm. In addition, it gives
* the distance between 2 points in a bi-dimensional space.
*
* For a rectangle, considering any vertex V, its two sizes (width and height) can
* be calculated by calculating the distance of V from the previous vertex and
* calculating the distance of V from the next vertex. This is the reason why I
* calculate the distance between vertici[0]/vertici[3] and vertici[0]/vertici[1]
*/
double conversionFactor = 1.0;
for(Point[] points : vertices){
int width = (int) (conversionFactor * Math.sqrt((points[0].x - points[3].x) * (points[0].x - points[3].x) + (points[0].y - points[3].y) * (points[0].y - points[3].y)));
int height = (int) (conversionFactor * Math.sqrt((points[0].x - points[1].x) * (points[0].x - points[1].x) + (points[0].y - points[1].y) * (points[0].y - points[1].y)));
objectWidth.add(width);
objectHeight.add(height);
}
/** Draw the rectangle containing the contours. The line method draws a line from 1
* point to the next, and accepts only integer coordinates; for this reason, 2
* temporary Points have been created and why I used Math.round method.
*/
Scalar red = new Scalar(255, 0, 0, 255);
for (int i=0; i<vertices.size(); i++){
Point pt1 = new Point();
Point pt2 = new Point();
for (int j = 0; j < 4; j++) {
pt1.x = Math.round(vertices.get(i)[j].x);
pt1.y = Math.round(vertices.get(i)[j].y);
pt2.x = Math.round(vertices.get(i)[(j + 1) % 4].x);
pt2.y = Math.round(vertices.get(i)[(j + 1) % 4].y);
Imgproc.line(frame, pt1, pt2, red, 3);
}
if (objectWidth.get(i) != 0 && objectHeight.get(i) != 0){
Imgproc.putText(frame, "width: " + objectWidth + ", height: " + objectHeight, new Point(Math.round(vertices.get(i)[1].x), Math.round(vertices.get(i)[1].y)), 1, 1, red);
}
}
}
// This function must return
return frame;
}
// Initialising an array of points
public static List<Point[]> initialiseWithDefaultPointInstances(int n_Contours, int n_Points) {
List<Point[]> pointsList = new ArrayList<>();
for(int i=0; i<n_Contours; i++){
Point[] array = new Point[n_Points];
for (int j = 0; j < n_Points; j++) {
array[j] = new Point();
}
pointsList.add(array);
}
return pointsList;
}

Implementing a License plate detection algorithm

To improve my knowledge of imaging and get some experience working with the topics, I decided to create a license plate recognition algorithm on the Android platform.
The first step is detection, for which I decided to implement a recent paper titled "A Robust and Efficient Approach to License Plate Detection". The paper presents their idea very well and uses quite simple techniques to achieve detection. Besides some details lacking in the paper, I implemented the bilinear downsampling, converting to gray scale, and the edging + adaptive thresholding as described in Section 3A, 3B.1, and 3B.2.
Unfortunately, I am not getting the output this paper presents in e.g. figure 3 and 6.
The image I use for testing is as follows:
The gray scale (and downsampled) version looks fine (see the bottom of this post for the actual implementation), I used a well-known combination of the RGB components to produce it (paper does not mention how, so I took a guess).
Next is the initial edge detection using the Sobel filter outlined. This produces an image similar to the ones presented in figure 6 of the paper.
And finally, the remove the "weak edges" they apply adaptive thresholding using a 20x20 window. Here is where things go wrong.
As you can see, it does not function properly, even though I am using their stated parameter values. Additionally I have tried:
Changing the beta parameter.
Use a 2d int array instead of Bitmap objects to simplify creating the integral image.
Try a higher Gamma parameter so the initial edge detection allows more "edges".
Change the window to e.g. 10x10.
Yet none of the changes made an improvement; it keeps producing images as the one above. My question is: what am I doing different than what is outlined in the paper? and how can I get the desired output?
Code
The (cleaned) code I use:
public int[][] toGrayscale(Bitmap bmpOriginal) {
int width = bmpOriginal.getWidth();
int height = bmpOriginal.getHeight();
// color information
int A, R, G, B;
int pixel;
int[][] greys = new int[width][height];
// scan through all pixels
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
// get pixel color
pixel = bmpOriginal.getPixel(x, y);
R = Color.red(pixel);
G = Color.green(pixel);
B = Color.blue(pixel);
int gray = (int) (0.2989 * R + 0.5870 * G + 0.1140 * B);
greys[x][y] = gray;
}
}
return greys;
}
The code for edge detection:
private int[][] detectEges(int[][] detectionBitmap) {
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] edges = new int[width][height];
// Loop over all pixels in the bitmap
int c1 = 0;
int c2 = 0;
for (int y = 0; y < height; y++) {
for (int x = 2; x < width -2; x++) {
// Calculate d0 for each pixel
int p0 = detectionBitmap[x][y];
int p1 = detectionBitmap[x-1][y];
int p2 = detectionBitmap[x+1][y];
int p3 = detectionBitmap[x-2][y];
int p4 = detectionBitmap[x+2][y];
int d0 = Math.abs(p1 + p2 - 2*p0) + Math.abs(p3 + p4 - 2*p0);
if(d0 >= Gamma) {
c1++;
edges[x][y] = Gamma;
} else {
c2++;
edges[x][y] = d0;
}
}
}
return edges;
}
The code for adaptive thresholding. The SAT implementation is taken from here:
private int[][] AdaptiveThreshold(int[][] detectionBitmap) {
// Create the integral image
processSummedAreaTable(detectionBitmap);
int width = detectionBitmap.length;
int height = detectionBitmap[0].length;
int[][] binaryImage = new int[width][height];
int white = 0;
int black = 0;
int h_w = 20; // The window size
int half = h_w/2;
// Loop over all pixels in the bitmap
for (int y = half; y < height - half; y++) {
for (int x = half; x < width - half; x++) {
// Calculate d0 for each pixel
int sum = 0;
for(int k = -half; k < half - 1; k++) {
for (int j = -half; j < half - 1; j++) {
sum += detectionBitmap[x + k][y + j];
}
}
if(detectionBitmap[x][y] >= (sum / (h_w * h_w)) * Beta) {
binaryImage[x][y] = 255;
white++;
} else {
binaryImage[x][y] = 0;
black++;
}
}
}
return binaryImage;
}
/**
* Process given matrix into its summed area table (in-place)
* O(MN) time, O(1) space
* #param matrix source matrix
*/
private void processSummedAreaTable(int[][] matrix) {
int rowSize = matrix.length;
int colSize = matrix[0].length;
for (int i=0; i<rowSize; i++) {
for (int j=0; j<colSize; j++) {
matrix[i][j] = getVal(i, j, matrix);
}
}
}
/**
* Helper method for processSummedAreaTable
* #param row current row number
* #param col current column number
* #param matrix source matrix
* #return sub-matrix sum
*/
private int getVal (int row, int col, int[][] matrix) {
int leftSum; // sub matrix sum of left matrix
int topSum; // sub matrix sum of top matrix
int topLeftSum; // sub matrix sum of top left matrix
int curr = matrix[row][col]; // current cell value
/* top left value is itself */
if (row == 0 && col == 0) {
return curr;
}
/* top row */
else if (row == 0) {
leftSum = matrix[row][col - 1];
return curr + leftSum;
}
/* left-most column */
if (col == 0) {
topSum = matrix[row - 1][col];
return curr + topSum;
}
else {
leftSum = matrix[row][col - 1];
topSum = matrix[row - 1][col];
topLeftSum = matrix[row - 1][col - 1]; // overlap between leftSum and topSum
return curr + leftSum + topSum - topLeftSum;
}
}
Marvin provides an approach to find text regions. Perhaps it can be a start point for you:
Find Text Regions in Images:
http://marvinproject.sourceforge.net/en/examples/findTextRegions.html
This approach was also used in this question:
How do I separates text region from image in java
Using your image I got this output:
Source Code:
package textRegions;
import static marvin.MarvinPluginCollection.findTextRegions;
import java.awt.Color;
import java.util.List;
import marvin.image.MarvinImage;
import marvin.image.MarvinSegment;
import marvin.io.MarvinImageIO;
public class FindVehiclePlate {
public FindVehiclePlate() {
MarvinImage image = MarvinImageIO.loadImage("./res/vehicle.jpg");
image = findText(image, 30, 20, 100, 170);
MarvinImageIO.saveImage(image, "./res/vehicle_out.png");
}
public MarvinImage findText(MarvinImage image, int maxWhiteSpace, int maxFontLineWidth, int minTextWidth, int grayScaleThreshold){
List<MarvinSegment> segments = findTextRegions(image, maxWhiteSpace, maxFontLineWidth, minTextWidth, grayScaleThreshold);
for(MarvinSegment s:segments){
if(s.height >= 10){
s.y1-=20;
s.y2+=20;
image.drawRect(s.x1, s.y1, s.x2-s.x1, s.y2-s.y1, Color.red);
image.drawRect(s.x1+1, s.y1+1, (s.x2-s.x1)-2, (s.y2-s.y1)-2, Color.red);
image.drawRect(s.x1+2, s.y1+2, (s.x2-s.x1)-4, (s.y2-s.y1)-4, Color.red);
}
}
return image;
}
public static void main(String[] args) {
new FindVehiclePlate();
}
}

Making an Image Concave in Java

I had a quick question, and wondered if anyone had any ideas or libraries I could use for this. I am making a java game, and need to make 2d images concave. The problem is, 1: I don't know how to make an image concave. 2: I need the concave effect to be somewhat of a post process, think Oculus Rift. Everything is normal, but the camera of the player distorts the normal 2d images to look 3d. I am a Sophmore, so I don't know very complex math to accomplish this.
Thanks,
-Blue
If you're not using any 3D libraries or anything like that, just implement it as a simple 2D distortion. It doesn't have to be 100% mathematically correct as long as it looks OK. You can create a couple of arrays to store the distorted texture co-ordinates for your bitmap, which means you can pre-calculate the distortion once (which will be slow but only happens once) and then render multiple times using the pre-calculated values (which will be faster).
Here's a simple function using a power formula to generate a distortion field. There's nothing 3D about it, it just sucks in the center of the image to give a concave look:
int distortionU[][];
int distortionV[][];
public void computeDistortion(int width, int height)
{
// this will be really slow but you only have to call it once:
int halfWidth = width / 2;
int halfHeight = height / 2;
// work out the distance from the center in the corners:
double maxDistance = Math.sqrt((double)((halfWidth * halfWidth) + (halfHeight * halfHeight)));
// allocate arrays to store the distorted co-ordinates:
distortionU = new int[width][height];
distortionV = new int[width][height];
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// work out the distortion at this pixel:
// find distance from the center:
int xDiff = x - halfWidth;
int yDiff = y - halfHeight;
double distance = Math.sqrt((double)((xDiff * xDiff) + (yDiff * yDiff)));
// distort the distance using a power function
double invDistance = 1.0 - (distance / maxDistance);
double distortedDistance = (1.0 - Math.pow(invDistance, 1.7)) * maxDistance;
distortedDistance *= 0.7; // zoom in a little bit to avoid gaps at the edges
// work out how much to multiply xDiff and yDiff by:
double distortionFactor = distortedDistance / distance;
xDiff = (int)((double)xDiff * distortionFactor);
yDiff = (int)((double)yDiff * distortionFactor);
// save the distorted co-ordinates
distortionU[x][y] = halfWidth + xDiff;
distortionV[x][y] = halfHeight + yDiff;
// clamp
if(distortionU[x][y] < 0)
distortionU[x][y] = 0;
if(distortionU[x][y] >= width)
distortionU[x][y] = width - 1;
if(distortionV[x][y] < 0)
distortionV[x][y] = 0;
if(distortionV[x][y] >= height)
distortionV[x][y] = height - 1;
}
}
}
Call it once passing the size of the bitmap that you want to distort. You can play around with the values or use a totally different formula to get the effect you want. Using an exponent less than one for the pow() function should give the image a convex look.
Then when you render your bitmap, or copy it to another bitmap, use the values in distortionU and distortionV to distort your bitmap, e.g.:
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// int pixelColor = bitmap.getPixel(x, y); // gets undistorted value
int pixelColor = bitmap.getPixel(distortionU[x][y], distortionV[x][y]); // gets distorted value
canvas.drawPixel(x + offsetX, y + offsetY, pixelColor);
}
}
I don't know what your actual function for drawing a pixel to the canvas is called, the above is just pseudo-code.

Image interpolation - nearest neighbor (Processing)

I've been having trouble with an image interpolation method in Processing. This is the code I've come up with and I'm aware that it will throw an out of bounds exception since the outer loop goes further than the original image but how can I fix that?
PImage nearestneighbor (PImage o, float sf)
{
PImage out = createImage((int)(sf*o.width),(int)(sf*o.height),RGB);
o.loadPixels();
out.loadPixels();
for (int i = 0; i < sf*o.height; i++)
{
for (int j = 0; j < sf*o.width; j++)
{
int y = round((o.width*i)/sf);
int x = round(j / sf);
out.pixels[(int)((sf*o.width*i)+j)] = o.pixels[(y+x)];
}
}
out.updatePixels();
return out;
}
My idea was to divide both components that represent the point in the scaled image by the scale factor and round it in order to obtain the nearest neighbor.
For getting rid of the IndexOutOfBoundsException try caching the result of (int)(sf*o.width) and (int)(sf*o.height).
Additionally you might want to make sure that x and y don't leave the bounds, e.g. by using Math.min(...) and Math.max(...).
Finally, it should be int y = round((i / sf) * o.width; since you want to get the pixel in the original scale and then muliply with the original width. Example: Assume a 100x100 image and a scaling factor of 1.2. The scaled height would be 120 and thus the highest value for i would be 119. Now, round((119 * 100) / 1.2) yields round(9916.66) = 9917. On the other hand round(119 / 1.2) * 100 yields round(99.16) * 100 = 9900 - you have a 17 pixel difference here.
Btw, the variable name y might be misleading here, since its not the y coordinate but the index of the pixel at the coordinates (0,y), i.e. the first pixel at height y.
Thus your code might look like this:
int scaledWidth = (int)(sf*o.width);
int scaledHeight = (int)(sf*o.height);
PImage out = createImage(scaledWidth, scaledHeight, RGB);
o.loadPixels();
out.loadPixels();
for (int i = 0; i < scaledHeight; i++) {
for (int j = 0; j < scaledWidth; j++) {
int y = Math.min( round(i / sf), o.height ) * o.width;
int x = Math.min( round(j / sf), o.width );
out.pixels[(int)((scaledWidth * i) + j)] = o.pixels[(y + x)];
}
}

Categories

Resources