Canny Edge Detection using Processing - java

I am looking for a copy paste implementation of Canny Edge Detection in the processing language. I have zero idea about Image processing and very little clue about Processing, though I understand java pretty well.
Can some processing expert tell me if there is a way of implementing this http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java in processing?

I think if you treat processing in lights of Java then some of the problems could be solved very easily. What it means is that you can use Java classes as such.
For the demo I am using the implementation which you have shared.
>>Original Image
>>Changed Image
>>Code
import java.awt.image.BufferedImage;
import java.util.Arrays;
PImage orig;
PImage changed;
void setup() {
orig = loadImage("c:/temp/image.png");
size(250, 166);
CannyEdgeDetector detector = new CannyEdgeDetector();
detector.setLowThreshold(0.5f);
detector.setHighThreshold(1f);
detector.setSourceImage((java.awt.image.BufferedImage)orig.getImage());
detector.process();
BufferedImage edges = detector.getEdgesImage();
changed = new PImage(edges);
noLoop();
}
void draw()
{
//image(orig, 0,0, width, height);
image(changed, 0,0, width, height);
}
// The code below is taken from "http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java"
// I have stripped the comments for conciseness
public class CannyEdgeDetector {
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
public BufferedImage getSourceImage() {
return sourceImage;
}
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
public BufferedImage getEdgesImage() {
return edgesImage;
}
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
public float getLowThreshold() {
return lowThreshold;
}
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
public float getHighThreshold() {
return highThreshold;
}
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
public boolean isContrastNormalized() {
return contrastNormalized;
}
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
}

I've been spending some time with the Gibara Canny implementation and I'm inclined to agree with Settembrini's comment above; further to this one needs to change the implementation of the Gaussian Kernel generation.
The Gibara Canny uses:
(g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius)
The averaging across a pixel (+-0.5 pixels) in (g1 + g2 + g3) / 3f is great, but the correct variance calculation on the bottom half of the equation for single dimensions is:
(g1 + g2 + g3) / 3f / (Math.sqrt(2f * (float) Math.PI) * kernelRadius)
The standard deviation kernelRadius is sigma in the following equation:
Single direction gaussian
I'm assuming that Gibara is attempting to implement the two dimensional gaussian from the following equation: Two dimensional gaussian where the convolution is a direct product of each gaussian. Whilst this is probably possible and more concise, the following code will correctly convolve in two directions with the above variance calculation:
// First Convolution
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
int index = x + y;
float sumX = data[index] * kernel[0];
int xOffset = 1;
int yOffset = sourceImage.width;
for(; xOffset < k ;) {;
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += sourceImage.width;
xOffset++;
}
xConv[index] = sumX;
}
}
// Second Convolution
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
int index = x + y;
float sumY = xConv[index] * kernel[0];
int xOffset = 1;
int yOffset = sourceImage.width;
for(; xOffset < k ;) {;
sumY += xConv[xOffset] * (xConv[index - xOffset] + xConv[index + xOffset]);
yOffset += sourceImage.width;
xOffset++;
}
yConv[index] = sumY;
}
}
NB the yConv[] is now the bidirectional convolution, so the following gradient Sobel calculations are as follows:
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < k; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = k; x < sourceImage.width - k; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
float sum = 0.0f;
int index = x + y;
int yOffset = sourceImage.width;
for (int i = 1; i < k; i++) {
sum += diffKernel[i] * (yConv[index - yOffset] - yConv[index + yOffset]);
yOffset += sourceImage.width;
}
yGradient[index] = sum;
}
}
Gibara's very neat implementation of non-maximum suppression requires that these gradients be calculated seperately, however if you want to output an image with these gradients one can sum them using either Euclidean or Manhattan distances, the Euclidean would look like so:
// Calculate the Euclidean distance between x & y gradients prior to suppression
int [] gradients = new int [picsize];
for (int i = 0; i < xGradient.length; i++) {
gradients[i] = Math.sqrt(Math.sq(xGradient[i]) + Math.sq(yGradient[i]));
}
Hope this helps, is all in order and apologies for my code! Critique most welcome

In addition to Favonius' answer, you might want to try Greg's OpenCV Processing library which you can now easily install via Sketch > Import Library... > Add Library... and select OpenCV for Processing
After you install the library, you can have a play with the FindEdges example:
import gab.opencv.*;
OpenCV opencv;
PImage src, canny, scharr, sobel;
void setup() {
src = loadImage("test.jpg");
size(src.width, src.height);
opencv = new OpenCV(this, src);
opencv.findCannyEdges(20,75);
canny = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findScharrEdges(OpenCV.HORIZONTAL);
scharr = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findSobelEdges(1,0);
sobel = opencv.getSnapshot();
}
void draw() {
pushMatrix();
scale(0.5);
image(src, 0, 0);
image(canny, src.width, 0);
image(scharr, 0, src.height);
image(sobel, src.width, src.height);
popMatrix();
text("Source", 10, 25);
text("Canny", src.width/2 + 10, 25);
text("Scharr", 10, src.height/2 + 25);
text("Sobel", src.width/2 + 10, src.height/2 + 25);
}

Just as I side note. I studied the Gibara Canny implementation some time ago and found some flaws. E.g. he separates the Gauss-Filtering in 1d filters in x and y direction (which is ok and efficient as such), but then he doesn't apply two passes of those filters (one after another) but just applies SobelX to the x-first-pass-Gauss and SobelY to the y-first-pass-Gauss, which of course leads to low quality gradients. Thus be careful just by copy-past such code.

Related

Java can't show image [CANVAS]

I am currently making a game in Java and I am trying to draw an image on my screen, but nothing show up ( only a black screen but no errors ) :(
Here is the code to import the image:
public static Bitmap loadBitmap(String fileName) {
try {
BufferedImage img = ImageIO.read(Art.class.getResource(fileName));
int w = img.getWidth();
int h = img.getHeight();
Bitmap result = new Bitmap(w, h);
img.getRGB(0, 0, w, h, result.pixels, 0, w);
for (int I = 0; I < result.pixels.length; i++) {
int in = result.pixels[i];
int col = (in & 0xf) >> 2;
if (in == 0xffff00ff) col = -1;
result.pixels[i] = col;
}
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
And the Bitmap class:
public void draw(Bitmap bitmap, int xOffs, int yOffs)
{
for(int y = 0; y < bitmap.height; y++)
{
int yPix = y + yOffs;
if(yPix < 0 || yPix >= height) continue;
for(int x = 0; x < bitmap.width; x++)
{
int xPix = x + xOffs;
if(xPix < 0 || xPix >= width) continue;
int alpha = bitmap.pixels[x + y * bitmap.width];
if(alpha > 0)
pixels[xPix + yPix * width] = bitmap.pixels[x + y * bitmap.width];
}
}
}
And to draw all of this :
public void render(Game game)
{
for(int y = 0; y < height; y++)
{
float yd = ((y + 0.5f) - height / 2.0f) / height;
if(yd < 0) yd *= -1;
float z = 10 / yd;
for(int x = 0; x < width; x++)
{
float xd = (x - width / 2.0f) / height;
xd *= z;
int xx = (int) (xd) & 7;
int yy = (int) (z + game.time * 0.1f) & 7;
pixels[x + y * width] = Art.floors.pixels[xx + yy * 64];
}
}
}
I have no errors! I don't really understand.. is this a bug caused by alpha or something? Ho and my image.png is 64x64 made in paint.net

elastic collision of balls

i am having a bug which i can't figure out.I tried many times,the collision detection and calculating new velocities seems fine ,but some balls seem to stuck with each other i don't why.Can you please help me out.
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.Random;
import javax.swing.JFrame;
public class ElasticCollision extends Canvas implements Runnable {
private static final int WIDTH = 300;
private static final int HEIGHT = WIDTH / 16 * 9;
private static final int SCALE = 3;
private static final String TITLE = "Elastic collision";
private boolean running = false;
private JFrame frame;
private Thread thread;
private Random random = new Random();
private Color color;
private int a, b, c;
private Ball[] ball;
private int x = 0, y = 0;
private int radius = 0;
private int speedX = 0, speedY = 0;
private int noOfBalls = 25;
private double newVelX1 = 0, newVelY1 = 0;
private double newVelX2 = 0, newVelY2 = 0;
private double angle1 = 0, angle2 = 0, angle3 = 0;
private int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
public ElasticCollision() {
Dimension size = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
setPreferredSize(size);
frame = new JFrame();
ball = new Ball[noOfBalls];
}
public void start() {
for (int i = 0; i < noOfBalls; i++) {
x = random.nextInt(getWidth());
y = random.nextInt(getHeight());
radius = 25 + random.nextInt(25);
speedX = 1 + random.nextInt(2);
speedY = 1 + random.nextInt(2);
ball[i] = new Ball(x, y, radius, speedX, speedY);
}
running = true;
thread = new Thread(this);
thread.start();
}
public void stop() {
running = false;
}
public void run() {
long lastTime = System.nanoTime();
double unprocessed = 0;
double nsPerTick = 1000000000.0 / 60;
int frames = 0;
int ticks = 0;
long lastTimer = System.currentTimeMillis();
while (running) {
long now = System.nanoTime();
unprocessed += (now - lastTime) / nsPerTick;
lastTime = now;
while (unprocessed >= 1) {
ticks++;
update();
unprocessed -= 1;
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < noOfBalls; i++) {
for (int j = i + 1; j < noOfBalls; j++) {
if (ball[i].inCollision != true
|| ball[j].inCollision != true)
checkCollision(ball[i], ball[j]);
}
}
frames++;
render();
if (System.currentTimeMillis() - lastTimer > 1000) {
lastTimer += 1000;
frame.setTitle(TITLE + " | " + ticks + " ticks, " + frames
+ " fps");
frames = 0;
ticks = 0;
}
}
stop();
}
public void update() {
for (int i = 0; i < noOfBalls; i++) {
ball[i].x += ball[i].speedX;
ball[i].y += ball[i].speedY;
if (ball[i].x >= getWidth() - ball[i].radius && ball[i].speedX > 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].x <= ball[i].radius && ball[i].speedX < 0)
ball[i].speedX = -ball[i].speedX;
if (ball[i].y >= getHeight() - ball[i].radius && ball[i].speedY > 0)
ball[i].speedY = -ball[i].speedY;
if (ball[i].y <= ball[i].radius && ball[i].speedY < 0)
ball[i].speedY = -ball[i].speedY;
}
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.yellow);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < noOfBalls; i++)
ball[i].paint(g);
g.dispose();
bs.show();
}
public void checkCollision(Ball ball1, Ball ball2) {
double distance;
if (ball1.x + ball1.radius + ball2.radius > ball2.x
&& ball1.x < ball1.x + ball1.radius + ball2.radius
&& ball1.y + ball1.radius + ball2.radius > ball2.y
&& ball1.y < ball2.y + ball1.radius + ball2.radius) {
distance = Math.sqrt(((ball1.x - ball2.x) * (ball1.x - ball2.x))
+ ((ball1.y - ball2.y) * (ball1.y - ball2.y)));
if ((int) distance < ball1.radius + ball2.radius) {
ball1.collision = true;
ball2.collision = true;
ball1.inCollision = true;
ball2.inCollision = true;
ball1.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball1.collisionY = ((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball1.radius;
ball2.collisionX = ((ball1.x * ball2.radius) + (ball2.x * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
ball2.collisionY =
((ball1.y * ball2.radius) + (ball2.y * ball1.radius))
/ (ball1.radius + ball2.radius) + ball2.radius;
/*
* x1 = (ball1.x - getWidth()) / 2; y1 = (ball2.y - getHeight())
* / 2; angle1 = Math.toDegrees(Math.atan2(y1, x1));
*
* x2 = (ball1.x - getWidth()) / 2; y2 = (ball2.y - getHeight())
* / 2; angle2 = Math.toDegrees(Math.atan2(y2, x2));
*/
double colision_angle = Math.toDegrees(Math.atan2(
(ball2.y - ball1.y), (ball2.x - ball1.x)));
double speed1 = Math.sqrt(ball1.speedX * ball1.speedX
+ ball1.speedY * ball1.speedY);
double speed2 = Math.sqrt(ball2.speedX * ball2.speedX
+ ball2.speedY * ball2.speedY);
double direction1 = Math.atan2(ball1.speedY, ball1.speedX);
double direction2 = Math.atan2(ball2.speedY, ball2.speedX);
double vx_1 = speed1 * Math.cos(direction1 - colision_angle);
double vy_1 = speed1 * Math.sin(direction1 - colision_angle);
double vx_2 = speed2 * Math.cos(direction2 - colision_angle);
double vy_2 = speed2 * Math.sin(direction2 - colision_angle);
double final_vx_1 = ((ball1.radius - ball2.radius) * vx_1 + (ball2.radius + ball2.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vx_2 = ((ball1.radius + ball1.radius) * vx_1 + (ball2.radius - ball1.radius)
* vx_2)
/ (ball1.radius + ball2.radius);
double final_vy_1 = vy_1;
double final_vy_2 = vy_2;
newVelX1 = (int) (Math.cos(colision_angle) * final_vx_1 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_1);
newVelY1 = (int) (Math.sin(colision_angle) * final_vx_1 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_1);
newVelX2 = (int) (Math.cos(colision_angle) * final_vx_2 + Math
.cos(colision_angle + Math.PI / 2) * final_vy_2);
newVelY2 = (int) (Math.sin(colision_angle) * final_vx_2 + Math
.sin(colision_angle + Math.PI / 2) * final_vy_2);
ball1.speedX = (int) newVelX1;
ball1.speedY = (int) newVelY1;
ball2.speedX = (int) newVelX2;
ball2.speedY = (int) newVelY2;
ball1.x = ball1.x + (int) newVelX1;
ball1.y = ball1.y + (int) newVelY1;
ball2.x = ball2.x + (int) newVelX2;
ball2.y = ball2.y + (int) newVelY2;
}
}
}
public static void main(String[] args) {
ElasticCollision balls = new ElasticCollision();
balls.frame.setResizable(false);
balls.frame.add(balls);
balls.frame.pack();
balls.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
balls.frame.setVisible(true);
balls.start();
}
}
class Ball {
protected int x = 0, y = 0;
protected int radius;
protected int speedX = 0, speedY = 0;
protected boolean collision = false;
protected int collisionX = 0, collisionY = 0;
protected boolean inCollision = false;
public Ball(int x, int y, int radius, int speedX, int speedY) {
this.x = x + radius;
this.y = y + radius;
this.radius = radius;
this.speedX = speedX;
this.speedY = speedY;
}
public void paint(Graphics g) {
if (!collision) {
g.setColor(Color.red);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
} else {
g.setColor(Color.green);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
g.setColor(Color.blue);
g.fillOval(collisionX - radius, collisionY - radius, 20, 20);
g.setColor(Color.black);
g.drawOval(collisionX - radius, collisionY - radius, 20, 20);
collision = false;
inCollision = false;
}
g.setColor(Color.black);
g.drawOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
My guess is that your logic doesn't let the balls move apart once the collision is detected. Only change the direction of movement once and not every instant the balls are close to one another.

How to increase continuance of edges detected by the canny filtrer

I have used a java canny detector from the public source. I wanted to detect edges of fibre in the image from microscope. But the result is kind of dissapointig. If you look at the result you can see that some edges are "doubled", we can see parrallel curves really close to each other in some places and also some false edges. I would like to improve the result of the algorithm. How can I change the parameters beside low/highthreshold to improve effect?
CODE IS RUNNING, just put the source.jpg in the project folder and run.
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
public class CannyEdgeDetector {
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
// accessors
/**
* The image that provides the luminance data used by this detector to
* generate edges.
*
* #return the source image, or null
*/
public BufferedImage getSourceImage() {
return sourceImage;
}
/**
* Specifies the image that will provide the luminance data in which edges
* will be detected. A source image must be set before the process method
* is called.
*
* #param image a source of luminance data
*/
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
/**
* Obtains an image containing the edges detected during the last call to
* the process method. The buffered image is an opaque image of type
* BufferedImage.TYPE_INT_ARGB in which edge pixels are white and all other
* pixels are black.
*
* #return an image containing the detected edges, or null if the process
* method has not yet been called.
*/
public BufferedImage getEdgesImage() {
return edgesImage;
}
/**
* Sets the edges image. Calling this method will not change the operation
* of the edge detector in any way. It is intended to provide a means by
* which the memory referenced by the detector object may be reduced.
*
* #param edgesImage expected (though not required) to be null
*/
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
/**
* The low threshold for hysteresis. The default value is 2.5.
*
* #return the low hysteresis threshold
*/
public float getLowThreshold() {
return lowThreshold;
}
/**
* Sets the low threshold for hysteresis. Suitable values for this parameter
* must be determined experimentally for each application. It is nonsensical
* (though not prohibited) for this value to exceed the high threshold value.
*
* #param threshold a low hysteresis threshold
*/
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
/**
* The high threshold for hysteresis. The default value is 7.5.
*
* #return the high hysteresis threshold
*/
public float getHighThreshold() {
return highThreshold;
}
/**
* Sets the high threshold for hysteresis. Suitable values for this
* parameter must be determined experimentally for each application. It is
* nonsensical (though not prohibited) for this value to be less than the
* low threshold value.
*
* #param threshold a high hysteresis threshold
*/
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* The default value is 16.
*
* #return the radius of the convolution operation in pixels
*/
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* This implementation will reduce the radius if the contribution of pixel
* values is deemed negligable, so this is actually a maximum radius.
*
* #param gaussianKernelWidth a radius for the convolution operation in
* pixels, at least 2.
*/
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
/**
* The radius of the Gaussian convolution kernel used to smooth the source
* image prior to gradient calculation. The default value is 16.
*
* #return the Gaussian kernel radius in pixels
*/
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
/**
* Sets the radius of the Gaussian convolution kernel used to smooth the
* source image prior to gradient calculation.
*
* #return a Gaussian kernel radius in pixels, must exceed 0.1f.
*/
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
/**
* Whether the luminance data extracted from the source image is normalized
* by linearizing its histogram prior to edge extraction. The default value
* is false.
*
* #return whether the contrast is normalized
*/
public boolean isContrastNormalized() {
return contrastNormalized;
}
/**
* Sets whether the contrast is normalized
* #param contrastNormalized true if the contrast should be normalized,
* false otherwise
*/
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
//NOTE: The elements of the method below (specifically the technique for
//non-maximal suppression and the technique for gradient computation)
//are derived from an implementation posted in the following forum (with the
//clear intent of others using the code):
// http://forum.java.sun.com/thread.jspa?threadID=546211&start=45&tstart=0
//My code effectively mimics the algorithm exhibited above.
//Since I don't know the providence of the code that was posted it is a
//possibility (though I think a very remote one) that this code violates
//someone's intellectual property rights. If this concerns you feel free to
//contact me for an alternative, though less efficient, implementation.
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
/*
* An explanation of what's happening here, for those who want
* to understand the source: This performs the "non-maximal
* supression" phase of the Canny edge detection in which we
* need to compare the gradient magnitude to that in the
* direction of the gradient; only if the value is a local
* maximum do we consider the point as an edge candidate.
*
* We need to break the comparison into a number of different
* cases depending on the gradient direction so that the
* appropriate values can be used. To avoid computing the
* gradient direction, we use two simple comparisons: first we
* check that the partial derivatives have the same sign (1)
* and then we check which is larger (2). As a consequence, we
* have reduced the problem to one of four identical cases that
* each test the central gradient magnitude against the values at
* two points with 'identical support'; what this means is that
* the geometry required to accurately interpolate the magnitude
* of gradient function at those points has an identical
* geometry (upto right-angled-rotation/reflection).
*
* When comparing the central gradient to the two interpolated
* values, we avoid performing any divisions by multiplying both
* sides of each inequality by the greater of the two partial
* derivatives. The common comparand is stored in a temporary
* variable (3) and reused in the mirror case (4).
*
*/
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
//NOTE: It is quite feasible to replace the implementation of this method
//with one which only loosely approximates the hypot function. I've tested
//simple approximations such as Math.abs(x) + Math.abs(y) and they work fine.
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
//NOTE: this implementation reuses the data array to store both
//luminance data from the image, and edge intensity from the processing.
//This is done for memory efficiency, other implementations may wish
//to separate these functions.
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
//NOTE: There is currently no mechanism for obtaining the edge data
//in any other format other than an INT_ARGB type BufferedImage.
//This may be easily remedied by providing alternative accessors.
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
public static void main(String[] args) {
//create the detector
CannyEdgeDetector detector = new CannyEdgeDetector();
//adjust its parameters as desired
// detector.setLowThreshold(2.5f);
detector.setHighThreshold(4f);
BufferedImage frame = null;
try {
frame = ImageIO.read(new File("source.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
//apply it to an image
detector.setSourceImage(frame);
detector.process();
BufferedImage edges = detector.getEdgesImage();
try {
ImageIO.write(edges, "png", new File("result.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Decreasing the sigma value should result in more edges and links. It also will result in more spurious edges, but the hysteresis process is there to try to alleviate that.
You may also want to look into the connected components to find important edges.

Java heightmap using lwjgl

I have coded a heightmap but it seems to lag the client. I just don't know how to increase the fps. I get about 3-6fps with the heightmap. Im using a quite large bmp for the heightmap, I think its 1024x1024. When i use a smaller on its fine, maybe im just not using the code effectively. Is there a better way to code this heightmap or did I just code it wrong. It is my first time I have worked on a heightmap. Thanks
public class HeightMap {
private final float xScale, yScale, zScale;
private float[][] heightMap;
private FloatBuffer vertices, normals, texCoords;
private IntBuffer indices;
private Vector3f[] verticesArray, normalsArray;
private int[] indicesArray;
private int width;
private int height;
public float getHeight(int x, int y) {
return heightMap[x][y] * yScale;
}
public HeightMap(String path, int resolution) {
heightMap = loadHeightmap("heightmap.bmp");
xScale = 1000f / resolution;
yScale = 8;
zScale = 1000f / resolution;
verticesArray = new Vector3f[width * height];
vertices = BufferUtils.createFloatBuffer(3 * width * height);
texCoords = BufferUtils.createFloatBuffer(2 * width * height);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int pos = height * x + y;
final Vector3f vertex = new Vector3f(xScale * x, yScale * heightMap[x][y], zScale * y);
verticesArray[pos] = vertex;
vertex.store(vertices);
texCoords.put(x / (float) width);
texCoords.put(y / (float) height);
}
}
vertices.flip();
texCoords.flip();
normalsArray = new Vector3f[height * width];
normals = BufferUtils.createFloatBuffer(3 * width * height);
final float xzScale = xScale;
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
final int nextX = x < width - 1 ? x + 1 : x;
final int prevX = x > 0 ? x - 1 : x;
float sx = heightMap[nextX][y] - heightMap[prevX][y];
if (x == 0 || x == width - 1) {
sx *= 2;
}
final int nextY = y < height - 1 ? y + 1 : y;
final int prevY = y > 0 ? y - 1 : y;
float sy = heightMap[x][nextY] - heightMap[x][prevY];
if (y == 0 || y == height - 1) {
sy *= 2;
}
final Vector3f normal = new Vector3f(-sx * yScale, 2 * xzScale, sy * yScale).normalise(null);
normalsArray[height * x + y] = normal;
normal.store(normals);
}
}
normals.flip();
indicesArray = new int[6 * (height - 1) * (width - 1)];
indices = BufferUtils.createIntBuffer(6 * (width - 1) * (height - 1));
for (int i = 0; i < width - 1; i++) {
for (int j = 0; j < height - 1; j++) {
int pos = (height - 1) * i + j;
indices.put(height * i + j);
indices.put(height * (i + 1) + j);
indices.put(height * (i + 1) + (j + 1));
indicesArray[6 * pos] = height * i + j;
indicesArray[6 * pos + 1] = height * (i + 1) + j;
indicesArray[6 * pos + 2] = height * (i + 1) + (j + 1);
indices.put(height * i + j);
indices.put(height * i + (j + 1));
indices.put(height * (i + 1) + (j + 1));
indicesArray[6 * pos + 3] = height * i + j;
indicesArray[6 * pos + 4] = height * i + (j + 1);
indicesArray[6 * pos + 5] = height * (i + 1) + (j + 1);
}
}
indices.flip();
}
private float[][] loadHeightmap(String fileName) {
try {
BufferedImage img = ImageIO.read(ResourceLoader.getResourceAsStream(fileName));
width = img.getWidth();
height = img.getHeight();
float[][] heightMap = new float[width][height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
heightMap[x][y] = 0xFF & img.getRGB(x, y);
}
}
return heightMap;
} catch (IOException e) {
System.out.println("Nincs meg a heightmap!");
return null;
}
}
public void render() {
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glNormalPointer(0, normals);
glVertexPointer(3, 0, vertices);
glTexCoordPointer(2, 0, texCoords);
glDrawElements(GL_TRIANGLE_STRIP, indices);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
}
Sorry to bring up an old topic, however i see a lot of people ask this:
Use a display list, instead of re-making the heightmap every time.
TheCodingUniverse has a good tutorial on how to do this.

canny edge detector in java

Hi I am working a on project that I need to implement an edge detector. I need to do it in VHDL however I am a little better at Java so I am looking at getting a working code in Java first then transfering it over. The code below I found but I can't get it working, I keep getting an error in the main on this line: detector.setSourceImage(frame); error says frame can not be resolved to a variable. I understand why I'm getting the error but not sure how to fix it because I don't know how to get the picture in. I am just looking for a quick fix to make this work so I can get started on the VHDL part. Thanks for any help you can give.
package CannyEdgeDetector;
public class CannyEdgeDetector {
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
// accessors
/**
* The image that provides the luminance data used by this detector to
* generate edges.
*
* #return the source image, or null
*/
public BufferedImage getSourceImage() {
return sourceImage;
}
/**
* Specifies the image that will provide the luminance data in which edges
* will be detected. A source image must be set before the process method
* is called.
*
* #param image a source of luminance data
*/
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
/**
* Obtains an image containing the edges detected during the last call to
* the process method. The buffered image is an opaque image of type
* BufferedImage.TYPE_INT_ARGB in which edge pixels are white and all other
* pixels are black.
*
* #return an image containing the detected edges, or null if the process
* method has not yet been called.
*/
public BufferedImage getEdgesImage() {
return edgesImage;
}
/**
* Sets the edges image. Calling this method will not change the operation
* of the edge detector in any way. It is intended to provide a means by
* which the memory referenced by the detector object may be reduced.
*
* #param edgesImage expected (though not required) to be null
*/
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
/**
* The low threshold for hysteresis. The default value is 2.5.
*
* #return the low hysteresis threshold
*/
public float getLowThreshold() {
return lowThreshold;
}
/**
* Sets the low threshold for hysteresis. Suitable values for this parameter
* must be determined experimentally for each application. It is nonsensical
* (though not prohibited) for this value to exceed the high threshold value.
*
* #param threshold a low hysteresis threshold
*/
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
/**
* The high threshold for hysteresis. The default value is 7.5.
*
* #return the high hysteresis threshold
*/
public float getHighThreshold() {
return highThreshold;
}
/**
* Sets the high threshold for hysteresis. Suitable values for this
* parameter must be determined experimentally for each application. It is
* nonsensical (though not prohibited) for this value to be less than the
* low threshold value.
*
* #param threshold a high hysteresis threshold
*/
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* The default value is 16.
*
* #return the radius of the convolution operation in pixels
*/
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* This implementation will reduce the radius if the contribution of pixel
* values is deemed negligable, so this is actually a maximum radius.
*
* #param gaussianKernelWidth a radius for the convolution operation in
* pixels, at least 2.
*/
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
/**
* The radius of the Gaussian convolution kernel used to smooth the source
* image prior to gradient calculation. The default value is 16.
*
* #return the Gaussian kernel radius in pixels
*/
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
/**
* Sets the radius of the Gaussian convolution kernel used to smooth the
* source image prior to gradient calculation.
*
* #return a Gaussian kernel radius in pixels, must exceed 0.1f.
*/
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
/**
* Whether the luminance data extracted from the source image is normalized
* by linearizing its histogram prior to edge extraction. The default value
* is false.
*
* #return whether the contrast is normalized
*/
public boolean isContrastNormalized() {
return contrastNormalized;
}
/**
* Sets whether the contrast is normalized
* #param contrastNormalized true if the contrast should be normalized,
* false otherwise
*/
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
//NOTE: The elements of the method below (specifically the technique for
//non-maximal suppression and the technique for gradient computation)
//are derived from an implementation posted in the following forum (with the
//clear intent of others using the code):
// http://forum.java.sun.com/thread.jspa?threadID=546211&start=45&tstart=0
//My code effectively mimics the algorithm exhibited above.
//Since I don't know the providence of the code that was posted it is a
//possibility (though I think a very remote one) that this code violates
//someone's intellectual property rights. If this concerns you feel free to
//contact me for an alternative, though less efficient, implementation.
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
/*
* An explanation of what's happening here, for those who want
* to understand the source: This performs the "non-maximal
* supression" phase of the Canny edge detection in which we
* need to compare the gradient magnitude to that in the
* direction of the gradient; only if the value is a local
* maximum do we consider the point as an edge candidate.
*
* We need to break the comparison into a number of different
* cases depending on the gradient direction so that the
* appropriate values can be used. To avoid computing the
* gradient direction, we use two simple comparisons: first we
* check that the partial derivatives have the same sign (1)
* and then we check which is larger (2). As a consequence, we
* have reduced the problem to one of four identical cases that
* each test the central gradient magnitude against the values at
* two points with 'identical support'; what this means is that
* the geometry required to accurately interpolate the magnitude
* of gradient function at those points has an identical
* geometry (upto right-angled-rotation/reflection).
*
* When comparing the central gradient to the two interpolated
* values, we avoid performing any divisions by multiplying both
* sides of each inequality by the greater of the two partial
* derivatives. The common comparand is stored in a temporary
* variable (3) and reused in the mirror case (4).
*
*/
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
//NOTE: It is quite feasible to replace the implementation of this method
//with one which only loosely approximates the hypot function. I've tested
//simple approximations such as Math.abs(x) + Math.abs(y) and they work fine.
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
//NOTE: this implementation reuses the data array to store both
//luminance data from the image, and edge intensity from the processing.
//This is done for memory efficiency, other implementations may wish
//to separate these functions.
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
//NOTE: There is currently no mechanism for obtaining the edge data
//in any other format other than an INT_ARGB type BufferedImage.
//This may be easily remedied by providing alternative accessors.
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
}
public static void main(String []Args){
//create the detector
CannyEdgeDetector detector = new CannyEdgeDetector();
//adjust its parameters as desired
detector.setLowThreshold(0.5f);
detector.setHighThreshold(1f);
//apply it to an image
detector.setSourceImage(frame);
detector.process();
BufferedImage edges = detector.getEdgesImage();
}
}
Why don't you just read a test image from a file? That way, you can verify that it's working properly before transferring.

Categories

Resources