C#: Sobel gradient magnitude on a diagonal edge

Combine horizontal and vertical Sobel responses into one edge-strength value.

C#
using System;

class Program {
  static void Main() {
    int[,] image = new int[5, 5];
    for (int r = 0; r < 5; r++) {
      for (int c = 0; c < 5; c++) {
        image[r, c] = c >= r ? 100 : 0;
      }
    }
    int[,] kx = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
    int[,] ky = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };

    for (int r = 1; r < 4; r++) {
      string row = "";
      for (int c = 1; c < 4; c++) {
        int gx = 0, gy = 0;
        for (int i = 0; i < 3; i++) {
          for (int j = 0; j < 3; j++) {
            int pixel = image[r - 1 + i, c - 1 + j];
            gx += pixel * kx[i, j];
            gy += pixel * ky[i, j];
          }
        }
        row += ((int)Math.Round(Math.Sqrt(gx * gx + gy * gy))).ToString().PadLeft(5);
      }
      Console.WriteLine(row);
    }
  }
}
Output
  424  141    0
  424  424  141
  141  424  424

Explanation

The Sobel operator uses two 3×3 kernels, one for the horizontal gradient Gx and one for the vertical gradient Gy. The strength of an edge at a pixel is the gradient magnitude √(Gx² + Gy²), and the angle of the edge normal is atan2(Gy, Gx).

The test image is dark below a diagonal line and bright above and on it. A horizontal or vertical edge gives a response in one kernel only. A diagonal edge produces a response in both, and the magnitude combines them. Flat areas, away from the edge, give zero.

Explore the dry run
Guided walkthroughReady
1using System;
2
3class Program {
4 static void Main() {
5 int[,] image = new int[5, 5];
6 for (int r = 0; r < 5; r++) {
7 for (int c = 0; c < 5; c++) {
8 image[r, c] = c >= r ? 100 : 0;
9 }
10 }
11 int[,] kx = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
12 int[,] ky = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
13
14 for (int r = 1; r < 4; r++) {
15 string row = "";
16 for (int c = 1; c < 4; c++) {
17 int gx = 0, gy = 0;
18 for (int i = 0; i < 3; i++) {
19 for (int j = 0; j < 3; j++) {
20 int pixel = image[r - 1 + i, c - 1 + j];
21 gx += pixel * kx[i, j];
22 gy += pixel * ky[i, j];
23 }
24 }
25 row += ((int)Math.Round(Math.Sqrt(gx * gx + gy * gy))).ToString().PadLeft(5);
26 }
27 Console.WriteLine(row);
28 }
29 }
30}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Pixels on or above the main diagonal are bright; those below are dark.a diagonal step edge
—
The vertical kernel is the horizontal kernel rotated: it subtracts the row above from the row below.a diagonal step edge
—
Both responses are accumulated for the same window.gx and gy for this window
—
Combine them into one magnitude per pixel.magnitude = √(gx² + gy²)
—
The value 424 (about 300·√2) appears on and next to the diagonal, while the window far above it is flat, so it gives 0.magnitude = √(gx² + gy²)
  424  141    0
  424  424  141
  141  424  424

Cost and common mistakes

O(R·C) for a fixed 3×3 kernel.

  • Adding |gx| + |gy| is a cheaper approximation of the magnitude that is often good enough.
  • Sobel responds to noise as well as edges, so smooth the image first and then choose a threshold.
  • The border pixels are skipped here because the kernel would extend outside the image.

Try it yourself

What is the magnitude where the window contains only bright pixels?

Reveal answer and reasoning

Zero. Gx and Gy both cancel when all nine pixels have the same value.

Further reading