How To Make Morphological Smoothing Work With C#
Morphological smoothing is an image processing technique, which includes grayscale erosion and dilation, and grayscale opening and closing
Filter by Category
Morphological smoothing is an image processing technique, which includes grayscale erosion and dilation, and grayscale opening and closing
Automatic algorithm for filling holes is a sequence of morphological operations in reconstruction branch, which fills all holes in the image.
Opening by reconstruction is a morphological operation in image processing for removing small objects and recovering shape accurately after.
Geodesic dilation and erosion are fundamental morphological reconstruction algorithms which yield the same result if left to converge.
Pruning in image processing is a morphological operation for removing spurs. It serves mainly as a post processing technique for cleaning up.
Skeletonization is a morphological process in image processing, which extracts the center lines of all shapes, which look like their skeleton
Thickening is a morphological operation in image processing, which adds foreground or white pixels to objects in order to thicken them.
Thinning is a morphological operation in image processing, which eats away at the foregorund pixels and leaves only lines shaping objects.
Convex hull in image processing is a morphological operation, where we encapsulate a shape or and object in an image into a convex shape.
Hit or miss transform is a morphological process for shape detection. We need to use two different structuring elements to find the shapes.
Morphological smoothing is an image processing technique we can use for grayscale images. Additionally, it’s purpose is mainly to filter the image for removing noise and smoothing it.
It utilizes two fundamental grayscale morphological operations, which are grayscale erosion and dilation. Unlike operations we used to process binary images, these two work a little differently.
When we’re dealing with grayscale images, we don’t have just two sets of pixels like we had with binary images. However, in general it still follows the same rules when we’re applying it. What I mean by that is that we still compare structuring element values with image pixels they overlap.
The only difference here is that we have more intensities to work with. Therefore, when we’re applying grayscale erosion, we select the minimum intensity value in the neighborhood. On the other hand, when we’re working with dilation we select the maximum intensity.
We slide this structuring element over all the pixels in the input image and draw the resulting image by setting the value that overlaps in the center.
We’re going to use two different processes together, which are both a combination of erosion and dilation processes we described above. Furthermore, we call these processes grayscale opening and closing.
Opening is a composite operation, where we first apply erosion and follow up with dilation to that resulting image. Closing is the opposite, so we first apply dilation and then erosion.
We covered these two processes, when we were working with binary images. However, when we apply them to grayscale images, we don’t erase details, we suppress them. Additionally, with opening we supress bright details that are smaller than structuring element, while leaving dark ones unaffected.
Closing has the opposite effect.
public static byte[] Erode(this byte[] buffer, BitmapData image_data)
{
byte[] result = new byte[buffer.Length];
for (int x = 1; x < image_data.Width - 1; x++)
{
for (int y = 1; y < image_data.Height - 1; y++)
{
int position = x * 3 + y * image_data.Stride;
byte val = 255;
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
int se_pos = position + i * 3 + j * image_data.Stride;
val = Math.Min(val, buffer[se_pos]);
}
}
for (int c = 0; c < 3; c++)
{
result[position + c] = val;
}
}
}
return result;
}
public static byte[] Dilate(this byte[] buffer, BitmapData image_data)
{
byte[] result = new byte[buffer.Length];
for (int x = 1; x < image_data.Width - 1; x++)
{
for (int y = 1; y < image_data.Height - 1; y++)
{
int position = x * 3 + y * image_data.Stride;
byte val = 0;
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
int se_pos = position + i * 3 + j * image_data.Stride;
val = Math.Max(val, buffer[se_pos]);
}
}
for (int c = 0; c < 3; c++)
{
result[position + c] = val;
}
}
}
return result;
}
public static Bitmap MorphologicalSmoothing(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.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);
//opening
result = buffer.Erode(image_data);
result = result.Dilate(image_data);
//closing
result = result.Dilate(image_data);
result = result.Erode(image_data);
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;
}
I hope this tutorial was helpful in giving you a better understanding on how grayscale morphological smoothing works. It was also a good opportunity to touch on fundamental operations like erosion and dilation and their combinations.
You can also download the demo project and try it out yourself.