Navigation

Related Articles

Back to Latest Articles

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 Otsu Thresholding Algorithm With C#

Otsu thresholding is a global thresholding method, with which we find optimal threshold intensity to ensure the maximum separability.

Posted on by Andraz Krzisnik
Queues

How To Use Queues In C# – Data Structures Made Easy

Queues are a limited access data structure, which works similarly to stacks. The difference between the two are their logistic principles.

Posted on by Andraz Krzisnik