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.
Filter by Category
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.
Extraction of connected components in image processing is a morphological process, where we isolate object to a separate image.
Hole filling in image processing is a morphological operation that fills in shapes of black pixels surrounded by white pixels.
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.
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 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.
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;
}
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.