The gradient **Non-maximum suppression** also known as *edge thinning* is a process of extracting thin, one pixel wide object’s contours. This process follows gradient detection as a post processing step. The outlines thinning is using pre-calculated parameters such as **gradient magnitude** and **gradient orientation** retrieved at edge extraction step.

## Determine Gradients parameters

To extract **thin stable edges**, you first need to calculate gradient magnitude and orientation. Frequently to resolve edge parameters we apply square masks with size 3×3. The Sobel tutorial describes the exact process of using masks as gradient detector. A good contour operator provides accurate information about the orientation and size of the gradient. The result of this process will lead to **thick edges** with different magnitude as on the following image.

After execution of gradient thinning operator the resulting image will look like the one bellow.

## Edge thinning – Non maximum suppression

Once the edge detection step is completed, it’s time to process the gradients and extract the thin edges. Ideally, the contours should be one pixel wide. To achieve this we use the famous **Non-maximum suppression** algorithm. The steps below can describe it:

- Iterate through each image pixel
- For each edge pixel choose its two neighbors based on gradient direction and use them for comparison
- For each edge pixel choose its two neighbors based on gradient direction and use them for comparison

If the gradient magnitude is lower than one of the both neighbors suppress it by making it zero (black/background). Otherwise keep it unchanged and proceed with the next one

## Non-maximum suppression (NMS) source code

The Sobel article describes in detail how to calculate gradient magnitude and orientation. Then using the gradient parameters it comes turn to **degrees** operator the shader convert angle parameter stored into **u_image** from radians to degrees as **theta**. Each orientation is used to find the proper neighbors **ca** and **cb**. Finally compare each two neighbors with the central gradient magnitude **cc** and suppress if needed.

### WebGL Fragment Shader

```
precision mediump float;
#define KERNEL_SIZE 3
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
#define M_PI 3.1415926535897932384626433832795
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec2 textCoord = gl_FragCoord.xy / u_textureSize;
vec4 cc = texture2D(u_image, textCoord);
float theta = degrees(cc.y*M_PI*2.0);
int ax = 0, ay = 0;
if ((theta >= 337.5) || (theta < 22.5)) { ax = 1; ay = 0; }
else if ((theta >= 22.5) && (theta < 67.5)) { ax = 1; ay = 1; }
else if ((theta >= 67.5) && (theta < 112.5)) { ax = 0; ay = 1; }
else if ((theta >= 112.5) && (theta < 157.5)) { ax =-1; ay = 1; }
else if ((theta >= 157.5) && (theta < 202.5)) { ax =-1; ay = 0; }
else if ((theta >=202.5) && (theta < 247.5)) { ax =-1; ay =-1; }
else if ((theta >=247.5) && (theta < 292.5)) { ax = 0; ay =-1; }
else if ((theta >= 292.5) && (theta < 337.5)) { ax = 1; ay =-1; }
vec4 ca = texture2D(u_image, textCoord + onePixel*vec2(ax, ay));
vec4 cb = texture2D(u_image, textCoord + onePixel*vec2(-ax, -ay));
gl_FragColor = vec4((((cc.x <= ca.x) || (cc.x < cb.x)) ? vec3(0) : vec3(cc.x)), 1.0);
}
```