How To Make Pruning In Image Processing Work With C#
Pruning in image processing is a morphological operation for removing spurs. It serves mainly as a post processing technique for cleaning up.
Filter by Category
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.
Boundary extraction in image processing is one of the basic morphological algorithms with which we extract only the outline of shown objects.
Opening and closing in image processing are morphological operations which are basically sequences of erosion and dilation operations.
Pruning is a combination of morphological operations in image processing, which we commonly use for processing handwriting. However, there are a few modifications we need to make to processes we will include.
In case you’re not familiar with morphological processes we’ve worked with so far, let’s mention what’s essential. First of all, we will deal with binary images, which means that they have either black or white pixels. Or if we phrase it in morphological terms, these images have background and foreground pixels.
Secondly, we divide images into sets, on which we will later apply morphological operations. Furthermore, we use structuring elements with each operation.
We can view structuring elements as kernels in convolution. However, we don’t compute image data with structuring element, we compare wether certain position has a foreground or background pixel.
Firstly, we’re going to apply a few steps of thinning with modified structuring elements. These structuring elements are meant for detecting endpoints.
We will have 8 different structuring elements, that will be able to detect an endpoint of the handwritten letters in any direction.
Thinning part of this process will remove any spurs or parasitic components left behind by skeletonization. Therefore this process is essential complement to these kind of processes.
However, we do remove good part of the letters as well, so we will also need to apply dilation. Furthermore, we’re going to limit the growth by dilating only those pixels that were foreground in the input image. This way, we’ll get those good pixels back.
I’ve written a few functions, which all together apply the entire process of pruning. We use this image processing operation for automated recognition of handwriting.
public static byte[] Thin(this byte[] buffer, int[][,] se, int w, int h)
{
byte[] result = buffer;
for (int i = 0; i < se.Length; i++)
{
for (int x = 1; x < w - 1; x++)
{
for (int y = 1; y < h - 1; y++)
{
int position = x + y * w;
List<bool> change = new List<bool>();
int match = 8;
for (int kx = -1; kx < 2; kx++)
{
for (int ky = -1; ky < 2; ky++)
{
int se_pos = position + kx + ky * w;
if (se_pos == position)
{
continue;
}
if (se[i][kx + 1, ky + 1] == -1)
{
match--;
}
if ((se[i][kx + 1, ky + 1] == 1 && buffer[se_pos] > 0) || (se[i][kx + 1, ky + 1] == 0 && buffer[se_pos] == 0))
{
change.Add(true);
}
}
}
if (buffer[position] > 0 && change.Count == match)
{
result[position] = 0;
}
}
}
}
return result;
}
public static byte[] Dilate(this byte[] buffer, byte[] limiter, 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 kx = -1; kx < 2; kx++)
{
for (int ky = -1; ky < 2; ky++)
{
int se_pos = position + kx + ky * w;
val = Math.Max(val, buffer[se_pos]);
}
}
if (limiter[position] > 0)
{
result[position] = val;
}
}
}
return result;
}
public static Bitmap Prune(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;
int[][,] se =
{
Structuring_Element.left,
Structuring_Element.topleft,
Structuring_Element.top,
Structuring_Element.topright,
Structuring_Element.right,
Structuring_Element.bottomright,
Structuring_Element.bottom,
Structuring_Element.bottomleft
};
for (int i = 0; i < 3; i++)
{
result = result.Thin(se, w, h);
}
for (int i = 0; i < 3; i++)
{
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;
}
This post should serve as an example, how we can combine various morphological operations together. They can tackle problems we face in practice.
I hope this guide was helpful in understanding pruning in image processing. You can also download the demo project and try it out yourself.