Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Despeckle - Remove spots or dots from the image

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:

enter image description here

Image Information

The thing to note here is that it is a black and white image - Bit Depth 1

enter image description here

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");
    }
like image 552
taji01 Avatar asked Mar 02 '17 20:03

taji01


People also ask

What is Despeckle filter?

Despeckle means filter that smoothes areas in which noise is noticeable while leaving complex areas untouched.

How do I use Despeckle in gimp?

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.


3 Answers

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.


Note: The answer just tries to make your code faster by applying minimal changes. It doesn't apply any enhancement in your algorithm for better noise reduction.
Example

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;
        }
    }
}
like image 54
Reza Aghaei Avatar answered Sep 26 '22 14:09

Reza Aghaei


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

like image 20
Simon Mourier Avatar answered Sep 22 '22 14:09

Simon Mourier


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.

like image 35
Tim Avatar answered Sep 24 '22 14:09

Tim