can anyone tell me what i'm doing wrong? i'm trying to get the circles to bounce off each other but they don't seem to be working.i keep making changes to fix the issue but that only makes more issues, whilst the main issue isn't resolved. have i used the wrong math's algorithm to check for collisions? or is it right and i have just made an error i cant seem to find? any help would be appreciated.
public float[][] CreateDots() {
if (first == true) {
for (int i = 0; i < dotNumber; i++) {
do{
dotX = r.nextInt(300);
dotY = r.nextInt(300);
dotWidth = r.nextFloat() * 50;
dotRadius = dotWidth / 2;
dotMass = r.nextFloat() / 10;
dotCentreX = dotX + dotRadius;
dotCentreY = dotY + dotRadius;
dotVelocityX = r.nextFloat();
dotVelocityY = r.nextFloat();
dots[i][0] = dotX;
dots[i][1] = dotY;
dots[i][2] = dotVelocityX;
dots[i][3] = dotVelocityY;
dots[i][4] = dotRadius;
dots[i][5] = dotCentreX;
dots[i][6] = dotCentreY;
dots[i][7] = dotMass;
dots[i][8] = dotWidth;
}while(collision == true);
}
first = false;
} else {
for (int i = 0; i < dotNumber; i++) {
dots[i][0] = dots[i][0] + dots[i][2];
dots[i][1] = dots[i][1] + dots[i][3];
if (dots[i][0] + dots[i][8] >= wallX) {
dots[i][2] = -dots[i][2];
}
if (dots[i][1] + dots[i][8] >= wallY) {
dots[i][3] = -dots[i][3];
}
if (dots[i][0] < 0) {
dots[i][2] = -dots[i][2];
}
if (dots[i][1] < 0) {
dots[i][3] = -dots[i][3];
}
}
}
repaint();
return dots;
}
public void bounce() {
collisionDot = false;
for (int i = 0; i < dotNumber; i++) {
for (int a = i + 1; a < dotNumber; a++) {
// difference between the x and y velocity of two dots
float xVelDiff = dots[i][2] - dots[a][2];
float yVelDiff = dots[i][3] - dots[a][3];
//difference between the centre x and y of two dots
float xDist = dots[i][5] - dots[a][5];
float yDist = dots[i][6] - dots[a][6];
System.out.println(xVelDiff + " * " + xDist + " + " + yVelDiff + " * " + yDist + " = "+ (xVelDiff * xDist + yVelDiff * yDist));
//not quite sure yet
if (xVelDiff * xDist + yVelDiff * yDist <= 0) {
angleCollision = (float) -Math.atan2(dots[a][0] - dots[i][0], dots[a][1] - dots[i][1]);
float mass = (dots[i][7] + dots[a][7]);
float mass2 = (dots[i][7] - dots[a][7]);
// x and y velocity and angle of collision for the two dots
float[] u1 = rotate(dots[i][2], dots[i][3], (float) angleCollision);
float[] u2 = rotate(dots[a][2], dots[a][3], (float) angleCollision);
//Velocity of dot 1
float[] v1 = new float[2];
v1[0] = u1[0] * mass2 / mass + u2[0] * 2 * dots[a][7] / (mass);
v1[1] = u1[1];
// velocity of dot 2
float[] v2 = new float[2];
v2[0] = u2[0] * mass2 / mass + u1[0] * 2 * dots[a][7] / (mass);
v2[1] = u2[1];
// final velocity of two colliding dots is:
float[] vFinal1 = rotate(v1[0], v1[1], (float) -angleCollision);;
float[] vFinal2 = rotate(v2[0], v2[1], (float) -angleCollision);;
if (a != i && !(dots[a][0] == 0 && dots[a][1] == 0)) {
// if the x and y distance between the two dots centres is less than their radii combined then the dots have collided
boolean thisCollision = Math.pow(xDist, 2) + Math.pow(yDist, 2) <= Math.pow((dots[a][4] + dots[i][4]), 2);
//if the dots collided, create new final velocity's from the angle of collision and the x and y velocitys at collision
if (thisCollision) {
collisionDot = true;
dots[i][2] = vFinal1[0];
dots[i][3] = vFinal1[1];
dots[a][2] = vFinal2[0];
dots[a][3] = vFinal2[1];
return;
}
}
}
}
}
}
public float[] rotate(float velocityX, float velocityY, float angle) {
float x1 = (float) (velocityX * Math.cos(angle) - velocityY * Math.sin(angle));
float y1 = (float) (velocityX * Math.sin(angle) - velocityY * Math.cos(angle));
float vel[] = new float[2];
vel[0] = x1;
vel[1] = y1;
return vel;
}
Im trying to implement Heightmaps to my game, so now i have made several methods, but they are so slow and also, the height returns numbers like 1.463563298e14!
How can i get a value between 0&1 from the pixel on the heightmap?
These are my methods:
public void setupHeights() {
int pixWidth = heightfile.getWidth();
int pixHeight = heightfile.getHeight();
int w = pixWidth;
int h = pixHeight;
this.heights = new float[w][h];
boolean countWidth = true;
for (int z = 0; z < pixHeight; z++) {
depth++;
for (int x = 0; x < pixWidth; x++) {
if (countWidth)
width++;
int height;
if (whiteHigh) {
height = 256 - (-1 * heightfile.getRGB(x, z));
} else {
height = -1 * heightfile.getRGB(x, z);
}
heights[x][z] = height;
numPoints++;
}
countWidth = false;
}
System.out.println("Heights have been set up!");
}
#Override
public void update() {
if (!this.hasWorldObj())
return;
if (heightfile!=null) {
for (Entity e : entities) {
if (e != null && !(e.posX < pos.getX() || e.posZ < pos.getZ() || e.posX > pos.getX() + 1
|| e.posZ > pos.getZ() + 1)) {
{
if (heightfile != null) {
double localX = Math.abs(pos.getX() - e.posX);
double localZ = Math.abs(pos.getZ() - e.posZ);
int x = (int) (localX * width);
int y = (int) (localZ * depth);
e.posY = pos.getY() + getHeight(x, y);
}
}
} else
e = null;
}
}
}
public float getHeight(float xf, float zf) {
int x = worldCoordToIndex(xf);
int z = worldCoordToIndex(zf);
System.out.println("getting height for: " + x + ", " + z);
if (x < 0)
x = 0;
if (z < 0)
z = 0;
if (z >= heights.length) {
System.out.println("WARN getHeight z index out of bounds: " + z);
z = heights.length - 1;
}
if (x >= heights[z].length) {
System.out.println("WARN getHeight x index out of bounds: " + x);
x = heights[z].length - 1;
}
System.out.println("Returned height: " + heights[z][x] * heightScale);
return heights[z][x] * heightScale;
}
private int worldCoordToIndex(float f) {
return (int) Math.floor(f / widthScale);
}
i am currently attempting to make a fractal terrain generator using the diamonds and squares algorithm. I have the algorithm completed (I think) but I don't know how to use JFrames very well.
How would I get this to display the tiles?
package fractal.terrain;
import java.util.Random;
public class Main {
public int mapSize = 257;
public static void main(String[] args) {
Main m = new Main();
double tileData[][] = new double[m.mapSize][m.mapSize];
Random r = new Random();
final double seed = r.nextInt(10000 - 500 + 1);
System.out.println("The seed is " + seed);
tileData[0][0] = tileData[0][m.mapSize - 1] = tileData[m.mapSize - 1][0]
= tileData[m.mapSize - 1][m.mapSize - 1] = seed;
double h = 10000.0;
for (int sideLength = m.mapSize - 1; sideLength >= 2; sideLength /= 2) {
int halfSide = sideLength / 2;
for (int x = 0; x < m.mapSize - 1; x += sideLength) {
for (int y = 0; y < m.mapSize - 1; y += sideLength) {
double avg = tileData[x][y]
+ tileData[x + sideLength][y]
+ tileData[x][y + sideLength]
+ tileData[x + sideLength][y + sideLength];
avg /= 4.0;
tileData[x + halfSide][y + halfSide] = avg + (r.nextDouble() * 2 * h) - h;
}
}
for (int x = 0; x < m.mapSize - 1; x += halfSide) {
for (int y = (x + halfSide) % sideLength; y < m.mapSize - 1; y += sideLength) {
double avg = tileData[(x - halfSide + m.mapSize) % m.mapSize][y]
+ tileData[(x + halfSide) % m.mapSize][y]
+ tileData[x][(y + halfSide) % m.mapSize]
+ tileData[x][(y - halfSide + m.mapSize) % m.mapSize];
avg /= 4.0;
avg = avg + (r.nextDouble() * 2 * h) - h;
tileData[x][y] = avg;
if (x == 0) {
tileData[m.mapSize - 1][y] = avg;
}
if (y == 0) {
tileData[x][m.mapSize - 1] = avg;
}
}
}
}
}
}
override the draw method on a JPanel and cycle through the array drawing different colored Rectangles for each value.
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (int x = 0; x < m.mapSize; x++) {
for (int y = 0; y < m.mapSize; y++) {
//check each tile and assign in a color based on its value
if(tileData[x][y] == aCertainNumber){
g.setColor();
}
g.fillRect(x*10, y*10, 10, 10);
}
}
this should display each tile 10 pixels by 10 pixels
you could also output to an image with
BufferedImage map = new BufferedImage(mapSize, mapSize,BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < mapSize; x++) {
for (int y = 0; y < mapSize; y++) {
if(mapData[x][y]==aCertainNumber)
map.setRGB(x, y, theColorYouWant.getRGB());
}
}
Ok i know this is sort of a non specific question but i am making a verlet integration physics engine for a game similar to, for example angry birds. I am writing a practice engine just to get the jist of it (credits for simpler c++ version go to Benedikt Bitterli) and no matter what i do i cant figure out how to implement friction. I posted the main collision and caluculation methods below if someone could at least tell me where or in which method i should add something and the name of the techneque or somthing.
private void updateVerlet() {
float tempX;
float tempY;
for (int b = 0; b < bodies.size(); b++) {
for (int i = 0; i < bodies.get(b).vertices.size(); i++) {
Vertex v = bodies.get(b).vertices.get(i);
tempX = v.x;
tempY = v.y;
v.x += v.x - v.ox + v.accx * timestep * timestep;
v.y += v.y - v.oy + v.accy * timestep * timestep;
v.ox = tempX;
v.oy = tempY;
}
}
}
private void updateEdges() {
for (int b = 0; b < bodies.size(); b++) {
for (int i = 0; i < bodies.get(b).edges.size(); i++) {
Edge e = bodies.get(b).edges.get(i);
float distX = e.v2.x - e.v1.x;
float distY = e.v2.y - e.v1.y;
float dist = (float)Math.hypot(distX, distY);
float diff = dist - e.length;
float len = 1f / (float)Math.hypot(distX, distY);// Normalize with (float)Math.hypot(distX, distY); again????
distX *= len;
distY *= len;
e.v1.x += distX * diff * 0.5;
e.v1.y += distY * diff * 0.5;
e.v2.x -= distX * diff * 0.5;
e.v2.y -= distY * diff * 0.5;
}
}
}
private void iterateCollisions() {
for (int iteration = 0; iteration < iterations; iteration++) {
// Temporary solution to prevent bodies from falling out of the screen
for (int b = 0; b < bodies.size(); b++) {
for (int i = 0; i < bodies.get(b).vertices.size(); i++) {
bodies.get(b).vertices.get(i).x = Math.max(Math.min(bodies.get(b).vertices.get(i).x, (float)screenWidth), 0.0f);
bodies.get(b).vertices.get(i).y = Math.max(Math.min(bodies.get(b).vertices.get(i).y, (float)screenHeight), 0.0f);
}
}
updateEdges();
for (int b = 0; b < bodies.size(); b++) {
bodies.get(b).calculateCenter();
}
for (int b1 = 0; b1 < bodies.size(); b1++) {
for (int b2 = 0; b2 < bodies.size(); b2++) {
if (bodies.get(b1) != bodies.get(b2)) {
if (bodiesOverlap(bodies.get(b1), bodies.get(b2))) {
if (detectCollision(bodies.get(b1), bodies.get(b2))) {
processCollision();
}
}
}
}
}
}
}
private boolean bodiesOverlap(PhysicsBody b1, PhysicsBody b2) {
return
(b1.minX <= b2.maxX) &&
(b1.minY <= b2.maxY) &&
(b1.maxX >= b2.minX) &&
(b2.maxY >= b1.minY);
}
private boolean detectCollision(PhysicsBody b1, PhysicsBody b2) {
float minDistance = 10000.0f;
Edge e;
for (int i = 0; i < b1.edges.size() + b2.edges.size(); i++) {
if (i < b1.edges.size()) {
e = b1.edges.get(i);
} else {
e= b2.edges.get(i - b1.edges.size());
}
if (!e.boundary)
continue;
axis.x = e.v1.y - e.v2.y;
axis.y = e.v2.x - e.v1.x;
float len = 1f / (float)Math.hypot(axis.x, axis.y);
axis.x *= len;
axis.y *= len;
MinMax dataA = b1.projectToAxis(axis);
MinMax dataB = b2.projectToAxis(axis);
float distance = intervalDistance(dataA, dataB);
if (distance > 0f)
return false;
else if (Math.abs(distance) < minDistance) {
minDistance = Math.abs(distance);
CollisionInfo.normalX = axis.x;
CollisionInfo.normalY = axis.y;
CollisionInfo.e = e;
}
}
CollisionInfo.depth = minDistance;
if (CollisionInfo.e.parent != b2) {
PhysicsBody temp = b2;
b2 = b1;
b1 = temp;
}
float diffX = b1.centerX - b2.centerX;
float diffY = b1.centerY - b2.centerY;
float mult = CollisionInfo.normalX * diffX + CollisionInfo.normalY * diffY;
if (mult < 0) {
CollisionInfo.normalX = 0 - CollisionInfo.normalX;
CollisionInfo.normalY = 0 - CollisionInfo.normalY;
}
minDistance = 10000.0f;
for (int i = 0; i < b1.vertices.size(); i++) {
diffX = b1.vertices.get(i).x - b2.centerX;
diffY = b1.vertices.get(i).y - b2.centerY;
float distance = CollisionInfo.normalX * diffX + CollisionInfo.normalX * diffY;
if (distance < minDistance) {
minDistance = distance;
CollisionInfo.v = b1.vertices.get(i);
}
}
return true;
}
private void processCollision() {
Vertex v1 = CollisionInfo.e.v1;
Vertex v2 = CollisionInfo.e.v2;
float collisionVectorX = CollisionInfo.normalX * CollisionInfo.depth;
float collisionVectorY = CollisionInfo.normalY * CollisionInfo.depth;
float t;
if (Math.abs(v1.x - v2.x) > Math.abs(v1.y - v2.y)) {
t = (CollisionInfo.v.x - collisionVectorX - v1.x) / (v2.x - v1.x);
}
else {
t = (CollisionInfo.v.y - collisionVectorY - v1.y) / (v2.y - v1.y);
}
float lambda = 1.0f / (t * t + (1 - t) * (1 - t));
float edgeMass = t * v2.parent.mass + (1f - t) * v1.parent.mass;
float invCollisionMass = 1.0f / (edgeMass + CollisionInfo.v.parent.mass);
float ratio1 = CollisionInfo.v.parent.mass * invCollisionMass;
float ratio2 = edgeMass*invCollisionMass;
v1.x -= collisionVectorX * ((1 - t) * ratio1 * lambda);
v1.y -= collisionVectorY * (( 1 - t) * ratio1 * lambda);
v2.x -= collisionVectorX * (t * ratio1 * lambda);
v2.y -= collisionVectorY * (t * ratio1 * lambda);
CollisionInfo.v.x += collisionVectorX * ratio2;
CollisionInfo.v.y += collisionVectorY * ratio2;
}
try this code, friction on the bottom world boundary
for every particle, limit horizontal movement.
if(Particle.Y >= world_height) { Particle.OldX = Particle.OldX - (Particle.OldX - Particle.X)/2; }
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.