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

The process of extraction thin **stable edges** is preceded by gradient magnitude and orientation calculation. Frequently *edge parameters* are resolved using square masks with size *3×3*. The image is processed using these masks in a way that gradients strength and orientation are retrieved as described into Sobel tutorial. The result of this process will lead to **thick edges** with different magnitude as on the following image.

*Sobel operator result*

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

*Result of Non-maximum suppression operator*

## Edge thinning – Non maximum suppression

After the edge detection step is done it is time to process gradients and retrieve thin edges ideally one pixel width. One of the most widely used technique is the so called **Non-maximum suppression**. It can be described by the following steps:

* For each edge pixel choose its two neighbors based on gradient direction and use them for comparison

*Edge neighbors based on gradient direction*

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 gradient magnitude and orientation is calculated as described into the Sobel article. Then using the gradient parameters it comes turn to NMS fragment shader execution. Using **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);
}
```