1

Im implementing my own function to binarize fingerprint images. In my method i try to work with LockBits for the first time.

Could you explain me, why i get a lot of artefacts on my images? Eample:

enter image description here

On the left picture i binarize image by Get/SetPixel and it is working pretty ok, but why i cant get same good result on the right image (a lot of red dots)? Did i forget or dont know about something?

private Bitmap Binarization(Bitmap tempBmp)
    {

        int threshold = otsuValue(tempBmp); //calculating threshold with Otsu method

        unsafe
        {
            BitmapData bmpData = tempBmp.LockBits(new System.Drawing.Rectangle(0, 0, tempBmp.Width, tempBmp.Height), ImageLockMode.ReadWrite, tempBmp.PixelFormat);
            byte* ptr = (byte*)bmpData.Scan0;


            int height = tempBmp.Height;
            int width = bmpData.Width * 4;
            Parallel.For(0, height, y =>
            {
                byte* offset = ptr + (y * bmpData.Stride); //set row
                for (int x = 0; x < width; x = x + 4)
                {
                    //changing pixel value 
                    offset[x] = offset[x] > threshold ? Byte.MaxValue : Byte.MinValue;
                    offset[x+1] = offset[x+1] > threshold ? Byte.MaxValue : Byte.MinValue;
                    offset[x+2] = offset[x+2] > threshold ? Byte.MaxValue : Byte.MinValue;
                    offset[x+3] = offset[x+3] > threshold ? Byte.MaxValue : Byte.MinValue;

                }
            });

            tempBmp.UnlockBits(bmpData);
        }

        return tempBmp;
    }

The same history, when i want to cut a little of bytes from image, but problem looks a little more complex.

enter image description here

Why it dont even get into good "if" statemant?

private Bitmap Binarization(Bitmap tempBmp)
    {

        int threshold = otsuValue(tempBmp);

        unsafe
        {
            BitmapData bmpData = tempBmp.LockBits(new System.Drawing.Rectangle(0, 0, tempBmp.Width, tempBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); 
            //Format8bpp, not pixel format from image
            byte* ptr = (byte*)bmpData.Scan0;


            int height = tempBmp.Height;
            int width = bmpData.Width; //i cut "* 4" here because of one channel image
            Parallel.For(0, height, y =>
            {
                byte* offset = ptr + (y * bmpData.Stride); //set row
                for (int x = 0; x < width; x++)
                {
                    //changing pixel values
                    offset[x] = offset[x] > threshold ? Byte.MaxValue : Byte.MinValue;

                }
            });

            tempBmp.UnlockBits(bmpData);
        }

        return tempBmp;
    }

Thanks for any advices for improve my functions.

  • 1
    _offset[x+3] = offset[x+3] > threshold ? Byte.MaxValue : Byte.MinValue;_ - You should not set the alpha value to anything but 255! Try: _offset[x+3] = Byte.MaxValue;_ – TaW Aug 14 '18 at 07:47
  • I tried a lot of things, even without `offset+3` i get that red dots and i dont know why. Maybe i should use `Marshall.Copy`? –  Aug 14 '18 at 07:55
  • In colour image, i get even more artefatcs (`Get/SetPixel` works great for binarization) in lockbits with parallel for –  Aug 14 '18 at 07:57
  • 1
    _I tried a lot of things, even without offset+3_ Huh?? Do you understand what `offset+3` is? (It is the alpha channel ie the transparency and always should be 255!!!) – TaW Aug 14 '18 at 09:15
  • Btw: You did not post any `if` statement. – TaW Aug 14 '18 at 09:21
  • Yup, i know its transparency one and in get/set pixel its pretty clear, but like you see above, i dont really get that logic behind lockbits because with or without fourth channel i still get same bad results. if statemant, i named thing with `? positive : negative `, i dont really know how to name it in english but i thought it is pretty clear to understand what its doing :D –  Aug 14 '18 at 09:52
  • 2
    Ok, it is called a ternary operator. Well, you do need to set the alpha channel to 255. There is no _with or without_ ! As to the algorithm you use: It is not really suited to guarantee a b/w result. You are testing the RGB channels separately, so if R is > threshold it will be turned on. In fact the real question is why not a lot more artifacts come up. Must have to do with the source image. Solution: Add all three channels and comapred to threshold * 3; then set all to the same black or white value! – TaW Aug 14 '18 at 09:59

2 Answers2

1

1) The algorithm you use does not guarantee a b/w result.

You are testing the RGB channels separately, so if R > threshold but G or B < threshold it will be turned on but G and/or B not etc..

In fact the real question is why not a lot more artifacts come up. Must have to do with the source image.

Solution: Add all three channels and compare to threshold * 3; then set all to the same black or white value!

2) There is another issue: The code you copied from somewhere blindly assumes the image has 32 bpp. But whiile the image you posted is apng it still only has 24 bpp. Therefore your code will do all sorts of funny things..

Updated and corrected example:

        ..
        int pixWidth = tempBmp.PixelFormat == IMG.PixelFormat.Format24bppRgb ? 3 : 
                       tempBmp.PixelFormat == IMG.PixelFormat.Format32bppArgb ? 4 : 4;


        byte* ptr = (byte*)bmpData.Scan0;

        threshold3 = threshold * 3;
        int height = tempBmp.Height;
        int width = bmpData.Width * pixWidth;
        Parallel.For(0, height, y =>
        {
            byte* offset = ptr + (y * bmpData.Stride); //set row
            for (int x = 0; x < width; x = x + pixWidth)
            {
                //changing pixel value 
                int v = (offset[x] + offset[x + 1] + offset[x + 2]) > threshold3 ? 
                        Byte.MaxValue : Byte.MinValue;;
                offset[x] = (byte)v ;
                offset[x+1] = (byte)v;
                offset[x+2] = (byte)v;
                if (pixWidth == 4) offset[x+3] = 255;

            }
        });
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thanks for your advice, but copied your solution to my problem, i get even more artifaacts on the image. I know logic behind it, because it looks pretty the same as build in method to manipulate pixels (color, setpixel etc) but somehow i dont know how to use it properly. In my opinion it should work, but it wont work correctly. I coded your example, the output now looks like: [IMAGE](https://imgur.com/a/e6qH7Xe) I will try do the same with marshall copy. Maybe `Threading.Tasks` makes that problem? Marshall copy solo is pretty fast too –  Aug 14 '18 at 11:04
  • 1
    Whoops, you are right. That's what you get from posting untested code. Will update shortly.. – TaW Aug 14 '18 at 11:26
  • 1
    The other issues was that your image actually is 24bpp. Have a look at the update! – TaW Aug 14 '18 at 11:39
  • I just didnt know, that .pngs images are 24bpp, now it looks pretty clear and works perfectly. But for convert image to 8bpp i need diffrent method? Like i see, its not that easy lik ein my example (`tempBmp.LockBits(new System.Drawing.Rectangle(0, 0, tempBmp.Width, tempBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed`) –  Aug 14 '18 at 12:27
  • 1
    _pngs images are 24bpp_ They can be 24 or 32 bpp. - 8bpp: Yes they need to get a palette in addition to the channel bits. – TaW Aug 14 '18 at 13:02
0

For this problem, I have my solution which looks like this:

public static bool Offset(Bitmap b_mapa, int threshold)
        {
            int threshold3 = threshold * 3;
            int red, green, blue;
            BitmapData bmData = b_mapa.LockBits(new Rectangle(0, 0, b_mapa.Width, b_mapa.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            int stride = bmData.Stride;
            System.IntPtr Scan0 = bmData.Scan0;
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                int nOffset = stride - b_mapa.Width * 3;
                for (int y = 0; y < b_mapa.Height; ++y)
                {
                    for (int x = 0; x < b_mapa.Width; ++x)
                    {
                        blue = p[0];
                        green = p[1];
                        red = p[2];
                        int v = ((int)(blue + green + red) > threshold3 ? (int)255 : (int)0);
                        p[0] = p[1] = p[2] = (byte)v;
                        p += 3;
                    }
                    p += nOffset;
                }
            }
            b_mapa.UnlockBits(bmData);
            return true;
        }

This function uses a shared resource called b_mapa,and our function should return a bool value depending result of processing. I dont use parallel functionality here, because, when we use LockBits, we work directly with memory, without using embeded functions which can be 1000 faster and no point parallelize it. If the filter are convolutional then the parallelization may be used for a little bit faster code. Buy the way, when I load images, I convert them into Bitmap, its simplier to do everything later and dont think about how much bits I have for color representation.

As you see, in bitmap array values goes BGR not RGB, just to let you know, because many make that mistake and messed up with colors.