How To Make Opening And Closing – Morphology With C#

Opening and closing in image processing are morphological operations which are basically sequences of erosion and dilation operations.


Andraz Krzisnik
How To Make Opening And Closing –...

Opening and closing are two morphological operations, each consisting of erosion and dilation processes in specific sequence. Morphological processes are nonlinear operations, because we’re dealing with set operations.

Therefore, we process image data, by putting it into sets. There is no limit, what kind of images we could work with with these techniques. However, for simplicity sake, we’ll demonstrate them on binary images where we separate pixels into two sets.

We’ll separate pixels into foreground or white pixel set and background or black pixel set. As for how erosion and dilation works, I suggest you check out my other posts where I talked about each more in depth.

This tutorial and every other morphological processing tutorial will use the principles you can learn from posts about erosion and dilation.

Opening and closing differences

Opening is the process where we first apply erosion and after that we follow up with dilation. Where as with closing we apply dilation first and erosion second.

Resulting images if we applied it on the same image would be quite different to one another. Opening smoothes the contour of objects in the image, breaks lines that are thinner than the structuring element we use and it also eliminates thin protrusions.

Closing smoothes sections of contour but in comparison to opening, it fuses breaks that are narrower than the structuring element. It also eliminates small holes and fills gaps in the contour of the objects.

Code

I’ve written functions that apply erosion and dilation and showed them on my other posts, but I’ll put them here as well. As you’ll be able to see, these are the simplest versions I could possibly make them.

For more complicated functions, I’d suggest you added the functionality to perform reflection for dilation. And another thing we could add was the option to use different shapes of structuring element and values held within.

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

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

         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);

         int o = (se_dim - 1) / 2;
         for (int i = o; i < w - o; i++)
         {
             for (int j = o; j < h - o; j++)
             {
                 int position = i * 3 + j * image_data.Stride;
                 for (int k = -o; k <= o; k++)
                 {
                     for (int l = -o; l <= o; l++)
                     {
                         int se_pos = position + k * 3 + l * image_data.Stride;
                         for (int c = 0; c < 3; c++)
                         {
                             result[se_pos + c] = Math.Max(result[se_pos + c], buffer[position]);
                         }
                     }
                 }
             }
         }

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

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

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

         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);

         int o = (se_dim - 1) / 2;
         for (int i = o; i < w - o; i++)
         {
             for (int j = o; j < h - o; j++)
             {
                 int position = i * 3 + j * image_data.Stride;
                 byte val = 255;
                 for (int x = -o; x <= o; x++)
                 {
                     for (int y = -o; y <= o; y++)
                     {
                         int kposition = position + x * 3 + y * image_data.Stride;
                         val = Math.Min(val, buffer[kposition]);
                     }
                 }

                 for (int c = 0; c < 3; c++)
                 {
                     result[position + c] = val;
                 }
             }
         }

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

         return res_img;
     }

Conclusion

I hope this tutorial was helpful in understanding opening and closing in image processing.

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

Related Articles

Thresholding

How To Make A Basic Global Thresholding Algorithm – C#

This tutorial demonstrates how to get optimal threshold value for basic global thresholding operation for segmentation in image processing.

Posted on by Andraz Krzisnik
Edge Detection

How To Make Marr Hildreth Edge Detection Algorithm In C#

Marr hildreth edge detection process is one of the earliest sophisticated edge detection based segmentation operations in image processing.

Posted on by Andraz Krzisnik