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 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.