I'm using Winforms. In my form I have a picturebox that displays a black and white image. I also have a button that if you click it, the button will remove spots / dots on the images. When the image dimension is not big it removes the spots quickly. If the image is large it takes a while. Also sometimes this function removes some of the words from the image it thinks its a spot. How can I improve the performance of this function and more accurately remove the spots or dots basically despeckle the image?
Update Upon research I found this library that seemed promising for this question:
http://www.aforgenet.com/framework/docs/html/cdf93487-0659-e371-fed9-3b216efb6954.htm
Link for spotted image: http://www.filedropper.com/testing-image3
Image Example
Note image in link has a bigger version of this:
Image Information
The thing to note here is that it is a black and white image - Bit Depth 1
My Code
private int[] mask = new int[9];
private void remove_spot_btn_Click(object sender, EventArgs e)
{
Bitmap img = new Bitmap(pictureBox1.Image);
Color c;
for (int ii = 0; ii < img.Width; ii++)
{
for (int jj = 0; jj < img.Height; jj++)
{
if (ii - 1 >= 0 && jj - 1 >= 0)
{
c = img.GetPixel(ii - 1, jj - 1);
mask[0] = Convert.ToInt16(c.R);
}
else
{
mask[0] = 0;
}
if (jj - 1 >= 0 && ii + 1 < img.Width)
{
c = img.GetPixel(ii + 1, jj - 1);
mask[1] = Convert.ToInt16(c.R);
}
else
mask[1] = 0;
if (jj - 1 >= 0)
{
c = img.GetPixel(ii, jj - 1);
mask[2] = Convert.ToInt16(c.R);
}
else
mask[2] = 0;
if (ii + 1 < img.Width)
{
c = img.GetPixel(ii + 1, jj);
mask[3] = Convert.ToInt16(c.R);
}
else
mask[3] = 0;
if (ii - 1 >= 0)
{
c = img.GetPixel(ii - 1, jj);
mask[4] = Convert.ToInt16(c.R);
}
else
mask[4] = 0;
if (ii - 1 >= 0 && jj + 1 < img.Height)
{
c = img.GetPixel(ii - 1, jj + 1);
mask[5] = Convert.ToInt16(c.R);
}
else
mask[5] = 0;
if (jj + 1 < img.Height)
{
c = img.GetPixel(ii, jj + 1);
mask[6] = Convert.ToInt16(c.R);
}
else
mask[6] = 0;
if (ii + 1 < img.Width && jj + 1 < img.Height)
{
c = img.GetPixel(ii + 1, jj + 1);
mask[7] = Convert.ToInt16(c.R);
}
else
mask[7] = 0;
c = img.GetPixel(ii, jj);
mask[8] = Convert.ToInt16(c.R);
Array.Sort(mask);
int mid = mask[4];
img.SetPixel(ii, jj, Color.FromArgb(mid, mid, mid));
}
}
pictureBox1.Image = img;
MessageBox.Show("Complete");
}
Despeckle means filter that smoothes areas in which noise is noticeable while leaving complex areas untouched.
Right-click in the image and select Filters → Enhance → Despeckle . You can play around with the settings, but you'll likely want to keep the radius pretty small. If you raise it too high you'll destroy details like the catchlights in the eye. The “Recursive” option seems to cause a more aggressive filter effect.
As also mentioned in comments, to change pixels in a Bitmap
with better performance rather than SetPixel
, you can use Bitmap.LockBits
method to access bitmap data.
To make your code faster with minimal changes, You can create a class which encapsulates fast access to bitmap data using LockBits
and create a GetPixel
and a SetPixel
method for the class.
For example, I used a class which is written by Vano Maisuradze with small changes (I removed unnecessary try/catch blocks from code). The class is using LockBits
method and provides fast version of GetPixel
and SetPixel
methods.
Then your code should be changed to:
var bmp = new Bitmap(pictureBox1.Image);
var img = new LockBitmap(bmp);
img.LockBits();
Color c;
//...
//...
//...
img.UnlockBits();
pictureBox1.Image = bmp;
MessageBox.Show("Complete");
Here is the implementation of the class:
public class LockBitmap
{
Bitmap source = null;
IntPtr Iptr = IntPtr.Zero;
BitmapData bitmapData = null;
public byte[] Pixels { get; set; }
public int Depth { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public LockBitmap(Bitmap source)
{
this.source = source;
}
/// <summary>
/// Lock bitmap data
/// </summary>
public void LockBits()
{
// Get width and height of bitmap
Width = source.Width;
Height = source.Height;
// get total locked pixels count
int PixelCount = Width * Height;
// Create rectangle to lock
Rectangle rect = new Rectangle(0, 0, Width, Height);
// get source bitmap pixel format size
Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
// Check if bpp (Bits Per Pixel) is 8, 24, or 32
if (Depth != 8 && Depth != 24 && Depth != 32)
{
throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
}
// Lock bitmap and return bitmap data
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
source.PixelFormat);
// create byte array to copy pixel values
int step = Depth / 8;
Pixels = new byte[PixelCount * step];
Iptr = bitmapData.Scan0;
// Copy data from pointer to array
Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
}
/// <summary>
/// Unlock bitmap data
/// </summary>
public void UnlockBits()
{
// Copy data from byte array to pointer
Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
// Unlock bitmap data
source.UnlockBits(bitmapData);
}
/// <summary>
/// Get the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty;
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (i > Pixels.Length - cCount)
throw new IndexOutOfRangeException();
if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
byte a = Pixels[i + 3]; // a
clr = Color.FromArgb(a, r, g, b);
}
if (Depth == 24) // For 24 bpp get Red, Green and Blue
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (Depth == 8)
// For 8 bpp get color value (Red, Green and Blue values are the same)
{
byte c = Pixels[i];
clr = Color.FromArgb(c, c, c);
}
return clr;
}
/// <summary>
/// Set the color of the specified pixel
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="color"></param>
public void SetPixel(int x, int y, Color color)
{
// Get color components count
int cCount = Depth / 8;
// Get start index of the specified pixel
int i = ((y * Width) + x) * cCount;
if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
Pixels[i + 3] = color.A;
}
if (Depth == 24) // For 24 bpp set Red, Green and Blue
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
}
if (Depth == 8)
// For 8 bpp set color value (Red, Green and Blue values are the same)
{
Pixels[i] = color.B;
}
}
}
As you found out, using AForge.NET is a good idea (you just have to add it as a nuget). I suggest you use its Median filter which is often used for denoising (see Median Filter in wikipedia).
AForge needs 24bpp RGB images, so you need to convert it first in your sample case, but here is an example of code that seems to work quite well on it:
// load the file as 24bpp RGB
using (var bmp = LoadForFiltering(@"C:\temp\Testing-Image3.tif"))
{
var filter = new Median();
// run the filter
filter.ApplyInPlace(bmp);
// save the file back (here, I used png as the output format)
bmp.Save(@"C:\temp\Testing-Image3.png");
}
private static Bitmap LoadForFiltering(string filePath)
{
var bmp = (Bitmap)Bitmap.FromFile(filePath);
if (bmp.PixelFormat == PixelFormat.Format24bppRgb)
return bmp;
try
{
// from AForge's sample code
if (bmp.PixelFormat == PixelFormat.Format16bppGrayScale || Bitmap.GetPixelFormatSize(bmp.PixelFormat) > 32)
throw new NotSupportedException("Unsupported image format");
return AForge.Imaging.Image.Clone(bmp, PixelFormat.Format24bppRgb);
}
finally
{
bmp.Dispose();
}
}
If you really need high performance, then you could go for NVidia CUDA/NPP (using directly the GPU) for example but this is more work, not directly supported from C# (and requires an NVidia card of course). Related question here: 2D CUDA median filter optimization and a white paper on CUDA here : Image Processing and Video Algorithms with CUDA
Your code is taking the median value of 9 nearby pixels and effectively just blurring. This is not a good noise-reduction algorithm-- it is more of a blur algorithm. Investigate which noise-reduction algorithm you need for your solution (depends on the kind of noise you have) and go from there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With