How to Find Edges in Images

Edge detection is a common task in image processing. It is used to recognize the positions of structures, to examine motion sequences or, in general, simply to separate the foreground from the background. There are very advanced methods for this, but we want to focus on the fact that you can already achieve good results with simple options.

Edge Detection Using the filter() Function

As a first step, we need a suitable image in which the foreground and background can be easily separated. For this purpose, we borrow an image of a kingfisher from Wikipedia (URL in the code at the end of this article). We download this image with url and load it as a grayscale image into a prepared table. After we have extracted the dimensions using image().size.grid and thus the aspect ratio for the subsequent plots, we create a copy of the image in which we want to perform the edge detection.

To detect the edges, we use the filter() function, which we can pass any filter kernel to. The simplest way to perform edge detection with a filter kernel is to use a linear kernel {-1, 0, 1}, which we apply once for each row and column and add up the absolute values of the respective results. To get back to the usual data range of an image, we normalize and rescale the result afterwards. We obtain the result shown below.

Compressing the Data Range of the Edges to Increase Contrast

Although the edges are already recognizable, the contrast is quite weak in places. To improve this, we compress the data bandwidth of the resulting image by logarithmizing the data. Care must be taken not to logarithmize the background, as otherwise it will be amplified and interfere with the result. We therefore only use pixels from the image that have an intensity higher than 16/255. The result is normalized and rescaled again so that it takes up the entire data area of an image. This operation has significantly reduced the differences between light and hard edges; the edges of the kingfisher and the water droplets now stand out clearly in the image on the right. By the way: if you use colorscheme=moy for this plot, you get the cover image.

Extraction of the Polygon Coordinates

In order to actually be able to continue working with these detected edges, it is advantageous to know their coordinates. To do this, you can use the logtoidx() function directly, which returns the coordinates of every pixel that is not equal to zero. Care must be taken to ensure that the coordinates are determined line by line. A direct connection of these vertices in their order therefore does not return a continuous, enclosing polygon contour. Determining the correct sequence is non-trivial and amounts to the traveling salesman problem.

If you plot the result of the coordinate determination without connecting the points, you get the result on the right-hand side.

Approximation of the Kingfisher's Position

As a final step, we would like to use the extracted polygon vertices to approximately determine the position of the kingfisher in the image. To do this, we take advantage of the fact that the kingfisher has many more edge points than the surrounding water droplets.

We determine the center of gravity of the vertices by averaging the individual components of the vertices using poly().avg.cols. We also determine the standard deviation of the vertices around their center of gravity in a similar way to obtain the area that contains more than 68% of all vertices. In the plot, we then use 2-sigma to mark the area that contains more than 95% of all vertices, which already gives us a good approximation of the kingfisher's expansion in the image.

Ready to give it a shot? Here's the code: