1

So I have been trying to implement a Sobel filter in JavaScript using pixel manipulation.

When using it on this image:

input

The result I got looks like this:

output

As you can see it does not seem to detect horizontal edges. Where is the mistake in the code?

Here is my code:

function sobel() {
    let filter1 = [1, 0, -1, 2, 0, -2, 1, 0, -1];
    let filter2 = [1, 2, 1, 0, 0, 0, -1, -2, -1];

    let w = canvas.width;
    let h = canvas.height;

    toGreyScale();

    let pixel = context.getImageData(0, 0, w, h);

    let filteredImg = context.createImageData(w, h);

    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {

            // 
            let Gx = filter1[0] * pixel.data[(w * y + -1 + x - 1) * 4] +
                filter1[1] * pixel.data[(w * y + -1 + x) * 4] +
                filter1[2] * pixel.data[(w * y + -1 + x + 1) * 4] +

                filter1[3] * pixel.data[(w * y + x - 1) * 4] +
                filter1[4] * pixel.data[(w * y + x) * 4] +
                filter1[5] * pixel.data[(w * y + x + 1) * 4] +

                filter1[6] * pixel.data[(w * y + +1 + x - 1) * 4] +
                filter1[7] * pixel.data[(w * y + +1 + x) * 4] +
                filter1[8] * pixel.data[(w * y + +1 + x + 1) * 4];

            let Gy = filter2[0] * pixel.data[(w * y + -1 + x - 1) * 4] +
                filter2[1] * pixel.data[(w * y + -1 + x) * 4] +
                filter2[2] * pixel.data[(w * y + -1 + x + 1) * 4] +

                filter2[3] * pixel.data[(w * y + x - 1) * 4] +
                filter2[4] * pixel.data[(w * y + x) * 4] +
                filter2[5] * pixel.data[(w * y + x + 1) * 4] +

                filter2[6] * pixel.data[(w * y + +1 + x - 1) * 4] +
                filter2[7] * pixel.data[(w * y + +1 + x) * 4] +
                filter2[8] * pixel.data[(w * y + +1 + x + 1) * 4];


            let G = Math.sqrt(Gx * Gx + Gy * Gy); // compute total magnitude
            G = G/4; // normalize value;

            filteredImg.data[(w * y + x) * 4] = G;
            filteredImg.data[(w * y + x) * 4 + 1] = G;
            filteredImg.data[(w * y + x) * 4 + 2] = G;
            filteredImg.data[(w * y + x) * 4 + 3] = 255;
        }
    }
    context.putImageData(filteredImg, 0, 0, 0, 0, w, h);
}
Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
Enrico
  • 13
  • 4
  • It looks like you're showing the magnitude of Gx rather than `sqrt(Gx * Gx + Gy * Gy)`. I don't see an issue with the code you posted here, maybe you've been running different code? – Cris Luengo Jan 05 '23 at 15:46
  • @CrisLuengo I'm definitely running the code i posted. When only applying Gy it also fails to detect horizontal edges and shows vertical edges, which it shouldn't even detect. – Enrico Jan 05 '23 at 16:10
  • 1
    If you change the code and get the same output, you're likely running a different copy of the code. :/ – Cris Luengo Jan 05 '23 at 16:17
  • that pile of index arithmetic is all but sure to contain issues. abstract that. use a 2D array class, or whatever's equivalent in javascript. write it yourself, if you like. also write a function that calculates one filter application. **abstraction.** please also take the [tour] and review [mre]. debugging is important. it is required even. – Christoph Rackwitz Jan 05 '23 at 17:12
  • Using Stack Overflow as an interactive debugger is notoriously inefficient. Instead, find and learn to use whatever [debugger](/q/25385173/90527) your development suite provides. At that point, if you encounter a problem you can't solve you can then post a question about that specific issue with a [mcve]. See "[How much research effort is expected of Stack Overflow users?](//meta.stackoverflow.com/q/261592/90527)" Please look over the [help], especially the "[ask]" article. – outis Jan 05 '23 at 22:18

1 Answers1

1

When the code says, for example: (w * y + -1 + x - 1) * 4, you're not getting the pixel to the upper left, you're getting the current row at x-2. The expression should be:

// expression for pixel that's at (x-1, y-1) over-parenthesized for clarity
((w * (y-1)) + (x-1)) * 4

For clarity, put the index computation in one place, like:

const pixelDataAt = (x,y) => pixel.data[4 * (w*y + x)];

Then call this in the loop with the simple convolution neighborhood:

let Gx = filter1[0] * pixelDataAt(x-1, y-1) +
         filter1[0] * pixelDataAt(x  , y-1) + //...

/* where the convolution neighborhood for each filter is 

  (x-1, y-1), (x,   y-1), (x+1, y-1)
  (x-1, y  ),             (x+1, y  )
  (x-1, y+1), (x,   y+1), (x+1, y+1)

*/

This will make the code simpler to read and debug, and, if needed, simpler to optimize by caching redundant calculations.

danh
  • 62,181
  • 10
  • 95
  • 136