Detecting edges and corners using morphological filters

Detecting edges and corners using morphological filters

Morphological filters can also be used to detect specific features in an image. In this recipe, we will learn how to detect lines and corners in a gray-level image.

Getting started

In this recipe, the following image will be used:

How to do it...

Let's define a class named MorphoFeatures which will allow us to detect image features:

class MorphoFeatures {


     // threshold to produce binary image
     int threshold;
     // structuring elements used in corner detection
     cv::Mat cross;
     cv::Mat diamond;
     cv::Mat square;
     cv::Mat x;

Detecting lines is quite easy using the appropriate filter of the cv::morphologyEx function:

cv::Mat getEdges(const cv::Mat &image) {

   // Get the gradient image
   cv::Mat result;

   // Apply threshold to obtain a binary image

   return result;

The binary edge image is obtained through a simple private method of the class:

void applyThreshold(cv::Mat& result) {
   // Apply threshold on result
   if (threshold>0)
      cv::threshold(result, result, 
                    threshold, 255, cv::THRESH_BINARY);

Using this class in a main function, you then obtain the edge image as follows:

// Create the morphological features instance
MorphoFeatures morpho;

// Get the edges
cv::Mat edges;
edges= morpho.getEdges(image); 

The result is the following image:

The detection of corners using morphological corners is a bit more complex since it is not directly implemented in OpenCV. This is a good example of the use of non-square structuring elements. Indeed, it requires the definition of four different structuring elements shaped as a square, diamond, cross, and an X-shape. This is done in the constructor (all of these structuring elements having a fixed 5x5 dimension for simplicity):

MorphoFeatures() : threshold(-1), 
   // Creating the cross-shaped structuring element
   for (int i=0; i<5; i++) {
     <uchar>(2,i)= 1;<uchar>(i,2)= 1;                           
   // Creating the diamond-shaped structuring element<uchar>(0,0)= 0;<uchar>(0,1)= 0;<uchar>(1,0)= 0;<uchar>(4,4)= 0;<uchar>(3,4)= 0;<uchar>(4,3)= 0;<uchar>(4,0)= 0;<uchar>(4,1)= 0;<uchar>(3,0)= 0;<uchar>(0,4)= 0;<uchar>(0,3)= 0;<uchar>(1,4)= 0;
   // Creating the x-shaped structuring element
   for (int i=0; i<5; i++) {
    <uchar>(i,i)= 1;<uchar>(4-i,i)= 1;                           

In the detection of corner features, all of these structuring elements are applied in cascade to obtain the resulting corner map:

cv::Mat getCorners(const cv::Mat &image) {

   cv::Mat result;

   // Dilate with a cross   

   // Erode with a diamond

   cv::Mat result2;
   // Dilate with a X   

   // Erode with a square

   // Corners are obtained by differencing
   // the two closed images

   // Apply threshold to obtain a binary image

   return result;

In order to better visualize the result of the detection, the following method draws a circle on the image at each detected point on the binary map:

void drawOnImage(const cv::Mat& binary, 
                   cv::Mat& image) {
   cv::Mat_<uchar>::const_iterator it= 
   cv::Mat_<uchar>::const_iterator itend= 

   // for each pixel   
   for (int i=0; it!= itend; ++it,++i) {
      if (!*it)          

Corners are then detected on an image by using the following code:

// Get the corners
cv::Mat corners;
corners= morpho.getCorners(image);

// Display the corner on the image
cv::namedWindow("Corners on Image");
cv::imshow("Corners on Image",image);

The image of detected corners is then, as follows.

How it works...

A good way to help understand the effect of morphological operators on a gray-level image is to consider an image as a topological relief in which gray-levels correspond to elevation (or altitude). Under this perspective, bright regions correspond to mountains, while the darker areas form the valleys of the terrain. Also, since edges correspond to a rapid transition between darker and brighter pixels, these can be pictured as abrupt cliffs. If an erosion operator is applied on such a terrain, the net result will be to replace each pixel by the lowest value in a certain neighborhood, thus reducing its height. As a result, cliffs will be "eroded" as the valleys expand. Dilation has the exact opposite effect, that is, cliffs will gain terrain over the valleys. However, in both cases, the plateaux (that is, area of constant intensity) will remain relatively unchanged.

The above observations lead to a simple way of detecting the edges (or cliffs) of an image. This could be done by computing the difference between the dilated image and the eroded image. Since these two transformed images differ mostly at the edge locations, the image edges will be emphasized by the differentiation. This is exactly what the cv::morphologyEx function is doing when the cv::MORPH_GRADIENT argument is inputted. Obviously, the larger the structuring element is, the thicker the detected edges will be. This edge detection operator is also called the Beucher gradient (the next chapter will discuss the concept of image gradient in more detail). Note that similar results could also be obtained by simply subtracting the original image from the dilated one, or the eroded image from the original. The resulting edges would simply be thinner.

Corner detection is a bit more complex since it uses four different structuring elements. This operator is not implemented in OpenCV but we present it here to demonstrate how structuring elements of various shapes can be defined and combined. The idea is to close the image by dilating and eroding it with two different structuring elements. These elements are chosen such that they leave straight edges unchanged, but because of their respective effect, edges at corner points will be affected. Let's use the simple following image made of a single white square to better understand the effect of this asymmetrical closing operation:

The first square is the original image. When dilated with a cross-shaped structuring element, the square edges are expanded, except at the corner points where the cross shape does not hit the square. This is the result illustrated by the middle square. This dilated image is then eroded by a structuring element that, this time, has a diamond shape. This erosion brings back most edges at their original position, but pushes the corners even further since they were not dilated. The left square is then obtained, which, as it can be seen, has lost its sharp corners. The same procedure is repeated with an X-shaped and a square-shaped structuring element. These two elements are the rotated version of the previous ones and will consequently capture the corners at a 45-degree orientation. Finally, differencing the two results will extract the corner features.

See also

The article, Morphological gradients by J.-F. Rivest, P. Soille, S. Beucher, ISET's symposium on electronic imaging science and technology, SPIE, Feb. 1992, for more on morphological gradient.

The article A modified regulated morphological corner detector by F.Y. Shih, C.-F. Chuang, V. Gaddipati, Pattern Recognition Letters , volume 26, issue 7, May 2005, for more information on morphological corner detection.