I have blured an image. But it is not smooth.
I heard that if I use the gaussian blur technique then I can remove the box effect.
But I don't know how to implement it with my code (I did some random technique but it messes with the color). Can you suggest me how to do gaussian blur with my code?
public class BlurImageDemo {
Color c[];
BlurImageDemo() throws IOException, InterruptedException {
File f = new File("D:\\x.jpg");
BufferedImage im = ImageIO.read(f);
BufferedImage bi = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
int i = 0;
int max = 400, radius = 10;
int a1 = 0, r1 = 0, g1 = 0, b1 = 0;
c = new Color[max];
int x = 1, y = 1, x1, y1, ex = 5, d = 0;
for (x = radius; x < im.getHeight() - radius; x++) {
for (y = radius; y < im.getWidth() - radius; y++) {
//20x20 matrix
for (x1 = x - radius; x1 < x + radius; x1++) {
for (y1 = y - radius; y1 < y + radius; y1++) {
c[i++] = new Color(im.getRGB(y1, x1));
//System.out.println(i);
}
}
i = 0;
for (d = 0; d < max; d++) {
a1 = a1 + c[d].getAlpha();
}
a1 = a1 / (max);
for (d = 0; d < max; d++) {
r1 = r1 + c[d].getRed();
}
r1 = r1 / (max);
for (d = 0; d < max; d++) {
g1 = g1 + c[d].getGreen();
}
g1 = g1 / (max);
for (d = 0; d < max; d++) {
b1 = b1 + c[d].getBlue();
}
b1 = b1 / (max);
int sum1 = (a1 << 24) + (r1 << 16) + (g1 << 8) + b1;
bi.setRGB(y, x, (int) (sum1));
}
}
ImageIO.write(bi, "jpg", new File("D:\\x1.jpg"));
}
public static void main(String[] args) throws IOException, InterruptedException {
new BlurImageDemo();
}
}
One really nice property of Gaussian blur is that it is separable, meaning that it can be expressed as the composition of a purely horizontal and a purely vertical blur. The advantage of doing that is that, per pixel, it them takes 2N multiplications (N is the size of the kernel), whereas the 2D non-separated version takes N2 multiplications. For N=7 (as you have), that's already a decent difference. It also has a small downside, the intermediate result is either rounded (losing some precision) or big (3 floats per pixel instead of 1 int), usually a little rounding is not a problem though.
An other thing, more of an implementation detail, is that the division by the total weight of the kernel can be put into the kernel itself, saving a whole bunch of (pretty slow) divisions.
Also your kernel doesn't actually look like a Gaussian, it's too "pointy". That's up to you, but the Gaussian kernel is the only circularly symmetric one that is also separable (if you only look at real-valued kernels) and generally has nice properties, so I would recommend only deviating from it if there is a good reason for it.
Anyway I'll write some example code now, not tested:
BufferedImage transposedHBlur(BufferedImage im) {
int height = im.getHeight();
int width = im.getWidth();
// result is transposed, so the width/height are swapped
BufferedImage temp = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
float[] k = new float[7] { 0.00598, 0.060626, 0.241843, 0.383103, 0.241843, 0.060626, 0.00598 };
// horizontal blur, transpose result
for (int y = 0; y < height; y++) {
for (int x = 3; x < width - 3; x++) {
float r = 0, g = 0, b = 0;
for (int i = 0; i < 7; i++) {
int pixel = im.getRGB(x + i - 3, y);
b += (pixel & 0xFF) * k[i];
g += ((pixel >> 8) & 0xFF) * k[i];
r += ((pixel >> 16) & 0xFF) * k[i];
}
int p = (int)b + ((int)g << 8) + ((int)r << 16);
// transpose result!
temp.setRGB(y, x, p);
}
}
return temp;
}
Since it also transposes, you can simply call it twice and the second time will effectively be a vertical blur that also restores the orientation:
temp = transposedHBlur(input);
result = transposedHBlur(temp);
This is the best article I ever found:
Java Image Processing
I got the answer. here it is.
public class BlurImageDemo {
Color c[];
BlurImageDemo() throws IOException, InterruptedException {
File f = new File("D:\\p.jpg");
BufferedImage im = ImageIO.read(f);
BufferedImage bi = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
int i = 0;
int max = 49, radius = 3;
int a1 = 0, r1 = 0, g1 = 0, b1 = 0,t=0;
c = new Color[max];
float xx[] = {1,1,1,1,1,1,1,
1,3,3,3,3,3,1,
1,3,4,4,4,3,1,
1,3,4,15,4,3,1,
1,3,4,4,4,3,1,
1,3,3,3,3,3,1,
1,1,1,1,1,1,1,
};
float h=0;
for(t=0;t<xx.length;t++){
h+=xx[t];
}
System.out.println(h);
int x = 1, y = 1, x1, y1, ex = 5, d = 0, ll = 0;
for (x = radius;x < im.getHeight()- radius; x++) {
for (y = radius; y < im.getWidth() - radius; y++) {
for (x1 = x - radius; x1 <= x + radius; x1++) {
for (y1 = y - radius; y1 <= y + radius; y1++) {
c[i] = new Color(im.getRGB(y1, x1));
//System.out.println(i);
//ll+=xx[i];
//System.out.println(ll);
i++;
}
}
i = 0;
ll = 0;
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getAlpha();
a1 = (int) (a1 + o);
}
a1 = (int) (a1 / h);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getRed();
r1 = (int) (r1 + o);
}
r1 = (int) (r1 / h);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getGreen();
g1 = (int) (g1 + o);
}
g1 = (int) (g1 / h);
//System.out.println(g1);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getBlue();
//System.out.println(o);
b1 = (int) (b1 + o);
}
b1 = (int) (b1 / h);
int sum1 = (r1 << 16) + (g1 << 8) + b1;
bi.setRGB(y, x, sum1);
r1 = g1 = b1 = 0;
//System.out.println(new Color(sum1));
}
}
ImageIO.write(bi,
"jpg", new File("D:\\p2.jpg"));
}
public static void main(String[] args) throws IOException, InterruptedException {
new BlurImageDemo();
}
}
Related
I want to make a sort of a colorwheel where you enter a float value to get a certain color
Here is the code i currently have:
private Color Wheel(float WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
return new Color((255 - WheelPos * 3)/255, 0,(WheelPos * 3)/255,1);
}
if(WheelPos < 170) {
WheelPos -= 85;
return new Color(0, (WheelPos * 3)/255, (255 - WheelPos * 3)/255,1);
}
WheelPos -= 170;
return new Color((WheelPos * 3)/255, (255 - WheelPos * 3)/255, 0,1);
}
the colors between the r,g,b colors look not colorfull at all
How do i make these colors brighter?
If you want everything to be bright, you can use your input value as the hue, and then calculate color from hue, saturation, and value (saturation and value both being at maximum).
Math taken from Android's Color class and simplified for full saturation/value:
//note, hue is on 0-360 scale.
public static Color hueToSaturatedColor(float hue) {
float r,g,b;
float Hprime = hue/60;
float X = 1 - Math.abs(Hprime%2 - 1);
if (Hprime<1){
r = 1;
g = X;
b = 0;
} else if (Hprime<2){
r = X;
g = 1;
b = 0;
} else if (Hprime<3){
r = 0;
g = 1;
b = X;
} else if (Hprime<4){
r = 0;
g = X;
b = 1;
} else if (Hprime<5){
r = X;
g = 0;
b = 1;
} else if (Hprime<6){
r = 1;
g = 0;
b = X;
} else {
r = 0;
g = 0;
b = 0;
}
return new Color(r, g, b, 1);
}
So I have the image of a map. And I have an image of an overlay which I want to place over the map such that you can see the map primarily but also the overlay. However on a pixel by pixel basis, I do not know how to evaluate the color of the resultant picture for it to be a mix of both the map and the overlay. For explanation purposes I made the following image:
You can see The carrots mainly, but you can also see Donald Trump over top of the carrots. So given the RGB values (or HSV, whichever one is more useful) of each individual pixel of the whole image, how would I combine them in such a way that I can see both?
The way I ended up solving this was by turning the RGB value of each pixel into an HSV color through the formula on http://www.rapidtables.com/convert/color/rgb-to-hsv.htm. Then I combined the Hue, Saturation, and Value values for each of the colors with whatever ratio I wanted and turned that HSV value back into RGB. I found that if I want to see one object more clearly than the other, you should preserve that object's Value value more. So since in my project I want to see the map more than the overlay, I matched the final Value value with that of the original map image.
Here's my code for the HSV color.
public static class HSVColor {
double[] HSV = new double[3];
int[] RGB = new int[3];
int RGBV;
//Constructor using the formula from http://www.rapidtables.com/convert/color/rgb-to-hsv.htm
public HSVColor(int R, int G, int B) {
RGB[0] = R;
RGB[1] = G;
RGB[2] = B;
RGBV = (256 * 256) * R + 256 * G + B;
double r = (double) R / 255.0;
double g = (double) G / 255.0;
double b = (double) B / 255.0;
double min = Math.min(r, Math.min(g, b));
double max = Math.max(r, Math.max(g, b));
double delta = max - min;
if (r == max) {
HSV[0] = 60 * (((g - b) / delta) % 6);
} else if (g == max) {
HSV[0] = 60 * (((b - r) / delta) + 2);
} else if (b == max) {
HSV[0] = 60 * (((r - g) / delta) + 4);
} else {
HSV[0] = 0;
}
if (max == 0) {
HSV[1] = 0;
} else {
HSV[1] = delta / max;
}
HSV[2] = max;
}
//Constructor using the formula from http://www.rapidtables.com/convert/color/hsv-to-rgb.htm
public HSVColor(double H, double S, double V) {
HSV[0] = H;
HSV[1] = S;
HSV[2] = V;
double C = V * S;
double X = C * (1 - Math.abs(((H / 60) % 2) - 1));
double m = V - C;
double r;
double g;
double b;
if (H < 60) {
r = C;
g = X;
b = 0;
} else if (H < 120) {
r = X;
g = C;
b = 0;
} else if (H < 180) {
r = 0;
g = C;
b = X;
} else if (H < 240) {
r = 0;
g = X;
b = C;
} else if (H < 300) {
r = X;
g = 0;
b = C;
} else {
r = C;
g = 0;
b = X;
}
RGB[0] = (int) ((r + m) * 255);
RGB[1] = (int) ((g + m) * 255);
RGB[2] = (int) ((b + m) * 255);
RGBV = (256 * 256) * RGB[0] + 256 * RGB[1] + RGB[2];
}
}
I want to fill triangles with gouraud shading
I calculated normals for each vertex and used the following code but it dosn't work correctly
I interpolated colors against y using these formulas
Ir = Ir2 - (Ir2 - Ir1)* (v2.y -y)/dy;
Ig = Ig2 - (Ig2 - Ig1)* (v2.y -y)/dy;
Ib = Ib2 - (Ib2 - Ib1)* (v2.y -y)/dy;
and against x direction using
rr = r2- (r2-r1)*(Xs2-j)/dxs;
in method drawCurrentTriangle(Graphics2D g) how can i calculate color from interpolated vertex normal.(there is no lights in the scene (only triangle color))
public boolean convert(Triangle triangle) {
ensureCapacity();
clearCurrentScan();
triangle.getVlist()[0].r = triangle.normals[0].x;
triangle.getVlist()[0].g = triangle.normals[0].y;
triangle.getVlist()[0].b = triangle.normals[0].z;
triangle.getVlist()[1].r = triangle.normals[1].x;
triangle.getVlist()[1].g = triangle.normals[1].y;
triangle.getVlist()[1].b = triangle.normals[1].z;
triangle.getVlist()[2].r = triangle.normals[2].x;
triangle.getVlist()[2].g = triangle.normals[2].y;
triangle.getVlist()[2].b = triangle.normals[2].z;
for (int i = 0; i < 3; i++) {
Vector3d v1 = triangle.getVlist()[i];
Vector3d v2;
if (i == 2) {
v2 = triangle.getVlist()[0];
} else {
v2 = triangle.getVlist()[i + 1];
}
// ensure v1.y < v2.y
if (v1.y > v2.y) {
Vector3d temp = v1;
v1 = v2;
v2 = temp;
}
double dy = v2.y - v1.y;
Ir1 = v1.r;
Ig1 = v1.g;
Ib1 = v1.b;
Ir2 = v2.r;
Ig2 = v2.g;
Ib2 = v2.b;
// ignore horizontal lines
if (dy == 0) {
continue;
}
int startY = Math.max(FastMath.ceil(v1.y), minY);
int endY = Math.min(FastMath.ceil(v2.y) - 1, maxY);
top = Math.min(top, startY);
bottom = Math.max(bottom, endY);
double dx = v2.x - v1.x;
double Ir;
double Ig;
double Ib;
double Ic;
double Ia;
double Yc;
// special case: vertical line
if (dx == 0) {
int x = FastMath.ceil(v1.x);
// ensure x within view bounds
x = Math.min(maxX + 1, Math.max(x, minX));
for (int y = startY; y <= endY; y++) {
Ir = Ir2 - (Ir2 - Ir1)* (v2.y -y)/dy;
Ig = Ig2 - (Ig2 - Ig1)* (v2.y -y)/dy;
Ib = Ib2 - (Ib2 - Ib1)* (v2.y -y)/dy;
scans[y].setBoundary(x, Ir, Ig, Ib);
}
} else {
// scan-convert this edge (line equation)
double gradient = dx / dy;
// (slower version)
for (int y = startY; y <= endY; y++) {
int x = FastMath.ceil(v1.x + (y - v1.y) * gradient);
// ensure x within view bounds
x = Math.min(maxX + 1, Math.max(x, minX));
Ir = Ir2 - (Ir2 - Ir1)* (v2.y -y)/dy;
Ig = Ig2 - (Ig2 - Ig1)* (v2.y -y)/dy;
Ib = Ib2 - (Ib2 - Ib1)* (v2.y -y)/dy;
scans[y].setBoundary(x, Ir, Ig, Ib);
}
// check if visible (any valid scans)
for (int i = top; i <= bottom; i++) {
if (scans[i].isValid()) {
return true;
}
}
return false;
}
}
}
protected void drawCurrentTriangle(Graphics2D g) {
int y = scanConverter.getTopBoundary();
double Xs1 = 0;
double Xs2 = 0;
double dxs = 0;
double r1 = 0;
double g1 = 0;
double b1 = 0;
double r2 = 0;
double g2 = 0;
double b2 = 0;
double rr = 0;
double gg = 0;
double bb = 0;
while (y <= scanConverter.getBottomBoundary()) {
GouraudTriangleScanConverter.Scan scan = scanConverter.getScan(y);
if (scan.isValid()) {
r1 = scan.rL;
g1 = scan.gL;
b1 = scan.bL;
r2 = scan.rR;
g2 = scan.gR;
b2 = scan.bR;
Xs1 = scan.left;
Xs2 = scan.right;
dxs = Xs2-Xs1;
for (int j = scan.left; j < scan.right; j++) {
rr = r2- (r2-r1)*(Xs2-j)/dxs;
gg = g2- (g2-g1)*(Xs2-j)/dxs;
bb = b2- (b2-b1)*(Xs2-j)/dxs;
if(rr > 255) rr = 255;
if(gg > 255) gg = 255;
if(bb > 255) bb = 255;
g.setColor(new Color((int)rr, (int)gg, (int)bb));
g.drawLine(j,y,j,y);
}
//g.drawLine(scan.right,y,scan.right,y);
}
y++;
}
}
public static class Scan {
public int left;
public int right;
public double rL = -1;
public double gL = -1;
public double bL = -1;
public double rR = -1;
public double gR = -1;
public double bR = -1;
/**
* Sets the left and right boundary for this scan if
* the x value is outside the current boundary.
*/
public void setBoundary(int x, double r, double g, double b) {
if (x > max)
max = x;
if (x < left) {
left = x;
rL = r;
gL = g;
bL = b;
}
if (x - 1 > right) {
right = x - 1;
rR = r;
gR = g;
bR = b;
}
}
/**
* Determines if this scan is valid (if left <= right).
*/
public boolean isValid() {
return (left <= right);
}
}
how can i apply colors to mesh.when i in
fix colors(no lighting)
I have been trying to implement a box blur algorithm in android.
The code seems to be fine but when trying to apply it, some areas in the blurred image have big yellow and white smudges all over the blurred photo.
Can anyone help me find out what i'm doing wrong?
Thanks:
Here is what i have:
public static Bitmap boxBlur(Bitmap bmp, int range) {
assert (range & 1) == 0 : "Range must be odd.";
Bitmap blurred = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(),
Config.ARGB_8888);
Canvas c = new Canvas(blurred);
int w = bmp.getWidth();
int h = bmp.getHeight();
int[] pixels = new int[bmp.getWidth() * bmp.getHeight()];
bmp.getPixels(pixels, 0, w, 0, 0, w, h);
boxBlurHorizontal(pixels, w, h, range / 2);
boxBlurVertical(pixels, w, h, range / 2);
c.drawBitmap(pixels, 0, w, 0.0F, 0.0F, w, h, true, null);
return blurred;
}
private static void boxBlurHorizontal(int[] pixels, int w, int h,
int halfRange) {
int index = 0;
int[] newColors = new int[w];
for (int y = 0; y < h; y++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
for (int x = -halfRange; x < w; x++) {
int oldPixel = x - halfRange - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixel];
if (color != 0) {
r -= Color.red(color);
g -= Color.green(color);
b -= Color.blue(color);
}
hits--;
}
int newPixel = x + halfRange;
if (newPixel < w) {
int color = pixels[index + newPixel];
if (color != 0) {
r += Color.red(color);
g += Color.green(color);
b += Color.blue(color);
}
hits++;
}
if (x >= 0) {
newColors[x] = Color.argb(0xFF, (byte) (r / hits),
(byte) (g / hits), (byte) (b / hits));
}
}
for (int x = 0; x < w; x++) {
pixels[index + x] = newColors[x];
}
index += w;
}
}
private static void boxBlurVertical(int[] pixels, int w, int h,
int halfRange) {
int[] newColors = new int[h];
int oldPixelOffset = -(halfRange + 1) * w;
int newPixelOffset = (halfRange) * w;
for (int x = 0; x < w; x++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
int index = -halfRange * w + x;
for (int y = -halfRange; y < h; y++) {
int oldPixel = y - halfRange - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixelOffset];
if (color != 0) {
r -= Color.red(color);
g -= Color.green(color);
b -= Color.blue(color);
}
hits--;
}
int newPixel = y + halfRange;
if (newPixel < h) {
int color = pixels[index + newPixelOffset];
if (color != 0) {
r += Color.red(color);
g += Color.green(color);
b += Color.blue(color);
}
hits++;
}
if (y >= 0) {
newColors[y] = Color.argb(0xFF, (byte) (r / hits),
(byte) (g / hits), (byte) (b / hits));
}
index += w;
}
for (int y = 0; y < h; y++) {
pixels[y * w + x] = newColors[y];
}
}
}
Found the problem!
The line:
newColors[x] = Color.argb(0xFF, (byte) (r / hits), (byte) (g / hits), (byte) (b / hits));
I converted the averages to bytes, where they should have been ints.
Changed it to:
newColors[x] = Color.argb(0xFF, (int) (r / hits), (int) (g / hits), (int) (b / hits));
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.