How To Make Opening By Reconstruction Work With C#

Opening by reconstruction is a morphological operation in image processing for removing small objects and recovering shape accurately after.


Andraz Krzisnik
How To Make Opening By Reconstruction Work...

Opening by reconstruction is a morphological operation for reconstructing image data accurately. In other words, it’s an upgrade in comparison to the basic opening algorithm

When we’re using this operation, we’re dealing with binary images. In case you’re not familiar, what kind of images these are, they’re basically black and white. In morphological terms, we have foreground and background pixels.

Morphological operations are non-linear, which means that their result depends on the shapes and general position of foreground pixels. Furthermore, we divide image into sets and here we’re going to divide them into two, one for each color.

What’s the difference between basic opening and opening by reconstruction?

I’m glad you asked. When we’re applying basic opening, first apply erosion to the image and then dilation to the resulting image. While the erosion part is the same for both of these processes, they differ at the dilation part.

We use geodesic dilation, which means we limit growth of basic dilation with a mask. This mask, in our case, is the input image before erosion.

Purpose of opening is to remove small objects with erosion and recover the shape with dilation. By using the mask we can recover former shape of the object that remain, perfectly.

Closing by reconstruction and opening by reconstruction

Closing by reconstruction is the other side of the coin. Similarly, the first part, which is dilation, is the same as with the basic version. In this case, they differ at erosion, because we use geodesic erosion which takes a mask to limit the shrinkage.

While the opening is useful for images where background is black and foreground white, closing is better suited for the opposite scenario.

Code

I’ve written a few functions, with which we can put together both processes I’ve described above.

public static byte[] Erode(this byte[] buffer, int w, int h)
     {
         byte[] result = new byte[buffer.Length];

         for (int x = 1; x < w - 1; x++)
         {
             for (int y = 1; y < h - 1; y++)
             {
                 int position = x + y * w;
                 byte val = 15;
                 for (int i = -1; i < 2; i++)
                 {
                     for (int j = -1; j < 2; j++)
                     {
                         int se_pos = position + i + j * w;
                         val = Math.Min(val, buffer[se_pos]);
                     }
                 }
                 result[position] = val;
             }
         }
         return result;
     }

     public static byte[] Erode(this byte[] buffer, byte[] mask, int w, int h)
     {
         byte[] result = buffer;
         for (int x = 1; x < w - 1; x++)
         {
             for (int y = 1; y < h - 1; y++)
             {
                 int position = x + y * w;
                 byte val = 15;
                 for (int i = -1; i < 2; i++)
                 {
                     for (int j = -1; j < 2; j++)
                     {
                         int se_pos = position + i + j * w;
                         val = Math.Min(val, buffer[se_pos]);
                     }
                 }
                 if (mask[position] == 0)
                 {
                     result[position] = val;
                 }
             }
         }
         return result;
     }

     public static byte[] Dilate(this byte[] buffer, int w, int h)
     {
         byte[] result = new byte[buffer.Length];
         for (int x = 1; x < w - 1; x++)
         {
             for (int y = 1; y < h - 1; y++)
             {
                 int position = x + y * w;
                 byte val = 0;
                 for (int i = -1; i < 2; i++)
                 {
                     for (int j = -1; j < 2; j++)
                     {
                         int se_pos = position + i + j * w;
                         val = Math.Max(val, buffer[se_pos]);
                     }
                 }
                 result[position] = val;
             }
         }
         return result;
     }

     public static byte[] Dilate(this byte[] buffer, byte[] mask, int w, int h)
     {
         byte[] result = buffer;
         for (int x = 1; x < w - 1; x++)
         {
             for (int y = 1; y < h - 1; y++)
             {
                 int position = x + y * w;
                 byte val = 0;
                 for (int i = -1; i < 2; i++)
                 {
                     for (int j = -1; j < 2; j++)
                     {
                         int se_pos = position + i + j * w;
                         val = Math.Max(val, buffer[se_pos]);
                     }
                 }
                 if (mask[position] > 0)
                 {
                     result[position] = val;
                 }
             }
         }
         return result;
     }

     public static Bitmap OpeningByReconstruction(this Bitmap image)
     {
         int w = image.Width;
         int h = image.Height;
         BitmapData image_data = image.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.ReadOnly,
             PixelFormat.Format8bppIndexed);

         int bytes = image_data.Stride * image_data.Height;
         byte[] buffer = new byte[bytes];
         byte[] result = new byte[bytes];

         Marshal.Copy(image_data.Scan0, buffer, 0, bytes);
         image.UnlockBits(image_data);

         result = buffer.Erode(w, h);
         result = result.Dilate(buffer, w, h);

         Bitmap res_img = new Bitmap(w, h);
         BitmapData res_data = res_img.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.WriteOnly,
             PixelFormat.Format8bppIndexed);
         Marshal.Copy(result, 0, res_data.Scan0, bytes);
         res_img.UnlockBits(res_data);

         return res_img;
     }

     public static Bitmap ClosingByReconstruction(this Bitmap image)
     {
         int w = image.Width;
         int h = image.Height;

         BitmapData image_data = image.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.ReadOnly,
             PixelFormat.Format8bppIndexed);

         int bytes = image_data.Stride * image_data.Height;
         byte[] buffer = new byte[bytes];
         byte[] result = new byte[bytes];

         Marshal.Copy(image_data.Scan0, buffer, 0, bytes);
         image.UnlockBits(image_data);
         result = buffer.Dilate(w, h);
         result = result.Erode(buffer, w, h);
         Bitmap res_img = new Bitmap(w, h);
         BitmapData res_data = res_img.LockBits(
             new Rectangle(0, 0, w, h),
             ImageLockMode.WriteOnly,
             PixelFormat.Format8bppIndexed);
         Marshal.Copy(result, 0, res_data.Scan0, bytes);
         res_img.UnlockBits(res_data);
         return res_img;
     }

Conclusion

I hope this tutorial was helpful in giving you a better understanding of opening by reconstruction and its dual.

You can also download the demo project and try it out yourself.

Related Articles

C# Image Processing

How To Use Fourier Transform On Images – C# Guide

Fourier transform is an equation that turns normal pixel values into complex numbers. But to know what these complex numbers mean, we should give a little more context to them by...

Posted on by Andraz Krzisnik
Morphological Processes

How To Make Thickening In Image Processing Work In C#

Thickening is a morphological operation in image processing, which adds foreground or white pixels to objects in order to thicken them.

Posted on by Andraz Krzisnik