# Sobel Filter – Sobel-Feldman Edge Detection and Gradient Extraction

The Sobel filter (also called Sobel-Feldman operator) is an edge detection filter, which results in image emphasizing edges. Computer vision and image processing applications frequently use this filter to extract image gradients and edges. The Sobel operator is a separable filter that consists of two 3×3 convolution masks. Both masks have the same coefficients as they are rotated 90 degrees to each other. These masks are with such design that they provide maximum value at horizontal or vertical edge orientation. Occasional it is desirable to retrieve only one of the gradient orientation – horizontal or vertical. Sobel operator – 3×3 mask window

The image blow is an example result of Sobel operator and point out the enhanced objects outlines. Sobel operator result

## How Sobel Filter works?

The Sobel filter is represented by two 3×3 convolution kernels and is computational efficient for both CPU and for GPU parallel computing. Furthermore the Sobel kernels are separable, which is  additional option for optimization. Sobel-Feldman  Horizontal Gradient with separable convolution Sobel-Feldman Vertical Gradient with separable convolution

Each image pixel is processed by each kernel in order to produce the final gradient value using equation (2). For better performance it is acceptable to use sum of absolute directional values as in equation (3). Gradient magnitude calculation

After calculating the vertical and horizontal gradients, it is possible to retrieve the edge orientation, as described in (4): Gradient orientation calculation

Now as we have ways to calculate gradient magnitude and orientation it is possible to get thin and strong edges. This will help for further image analysis and objects recognition. You may also check edge thinning by non-maximum suppression for further steps.

## Sobel Filter source code

### 2D Canvas edge detection

First we define two helper functions, which we use for convolution operation. The conv3x we use to calculate Gx and the second one conv3y we use for Gy.


function conv3x(data, idx, w, p){
return (p*data[idx - w - 4] + p*data[idx - 4] + p*data[idx + w - 4]
-p*data[idx - w + 4] - p*data[idx + 4] - p*data[idx + 4 + 4]);
}

function conv3y(data, idx, w, p){
return (p*data[idx - w - 4] + p*data[idx - w] + p*data[idx - w + 4]
-(p*data[idx + w - 4] + p*data[idx + w] + p*data[idx + w + 4]));
}


Next we perform image iteration, so at each pixel we calculate Gx and Gy as described above.


for (j = 0, i = w + 4; i < l; i+=4, j++){
var dx = conv3x(data, i, w, mask);
var dy = conv3y(data, i, w, mask);
//var val = Math.sqrt(dx*dx + dy*dy);
var val = Math.abs(dx) + Math.abs(dy);
buff[j] = val;
}


Note that for Sobel operator the values in our mask are: [1, 2, 1].

### Edge Detection by WebGL Fragment Shader

The source code below represents a way to calculate gradient magnitude and orientation using Graphics Processing Unit (GPU) by WebGL/OpenGL API. The u_kernel is an input parameter holding the Sobel coefficients. Using the above description we can calculate dx and dy by math (1). As a next step the GLSL shader calculates the gradient orientation theta as in (4). Note that we should scale the orientation angle from range [0.0, 2.0*PI] to [0.0, 1.0]. Finally the gradient magnitude and gradient orientation are set to the first two components of gl_FragColor vector. We keep gradients orientation for further processing by Non Maximum Suppression, which produce thin edges.


precision mediump float;

#define KERNEL_SIZE 3
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[KERNEL_SIZE];
#define M_PI 3.1415926535897932384626433832795
#define GET_PIXEL(_x, _y) (texture2D(u_image, textCoord + onePixel*vec2(_x, _y)))

void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec2 textCoord = gl_FragCoord.xy / u_textureSize;
float dx = (length(GET_PIXEL(-1, -1)*u_kernel +
GET_PIXEL(-1,  0)*u_kernel +
GET_PIXEL(-1, +1)*u_kernel) -
length(GET_PIXEL(+1, -1)*u_kernel +
GET_PIXEL(+1,  0)*u_kernel +
GET_PIXEL(+1, +1)*u_kernel));
float dy = (length(GET_PIXEL(-1, -1)*u_kernel +
GET_PIXEL(0, -1)*u_kernel +
GET_PIXEL(+1, -1)*u_kernel) -
length(GET_PIXEL(-1, +1)*u_kernel +
GET_PIXEL(0, +1)*u_kernel +
GET_PIXEL(+1, +1)*u_kernel));

///gl_FragColor = vec4(vec3(length(vec2(dx, dy))), 1.0);
float theta = (atan(dy, dx) + M_PI) / (2.0*M_PI);
gl_FragColor = vec4(length(vec2(dx, dy)), theta, 0.0, 1.0);
}


Sigma: 3