0

Hello I have a scanner application, I return the scanned image into a Bitmap and then I showed into a Picture Box. I have a requirement which includes to automatically crop the image and delete the blank/null whitespaces. For example, this is the image that I show to the user on the picture box.

enter image description here

As you can see the scanned image is an small card and the image is a full letter papper with a lot of whitespaces, what I expect is to crop the image automatically or with a button to only show to the user the red bordered part.

Searching for a solution I saw a similar question and I tried with a code from this answer but doesn't seems to work as I expect.

What is wrong with that code? Is there any other way that makes what I want to do?

This is what I tried:

public Bitmap CropImage(Bitmap bitmap)
{
    int w = bitmap.Width;
    int h = bitmap.Height;

    Func<int, bool> IsAllWhiteRow = row =>
    {
        for (int i = 0; i < w; i++)
        {
            if (bitmap.GetPixel(i, row).R != 255)
            {
                return false;
            }
        }
        return true;
    };

    Func<int, bool> IsAllWhiteColumn = col =>
    {
        for (int i = 0; i < h; i++)
        {
            if (bitmap.GetPixel(col, i).R != 255)
            {
                return false;
            }
        }
        return true;
    };

    int leftMost = 0;
    for (int col = 0; col < w; col++)
    {
        if (IsAllWhiteColumn(col)) leftMost = col + 1;
        else break;
    }

    int rightMost = w - 1;
    for (int col = rightMost; col > 0; col--)
    {
        if (IsAllWhiteColumn(col)) rightMost = col - 1;
        else break;
    }

    int topMost = 0;
    for (int row = 0; row < h; row++)
    {
        if (IsAllWhiteRow(row)) topMost = row + 1;
        else break;
    }

    int bottomMost = h - 1;
    for (int row = bottomMost; row > 0; row--)
    {
        if (IsAllWhiteRow(row)) bottomMost = row - 1;
        else break;
    }

    if (rightMost == 0 && bottomMost == 0 && leftMost == w && topMost == h)
    {
        return bitmap;
    }

    int croppedWidth = rightMost - leftMost + 1;
    int croppedHeight = bottomMost - topMost + 1;

    try
    {
        Bitmap target = new Bitmap(croppedWidth, croppedHeight);
        using (Graphics g = Graphics.FromImage(target))
        {
            g.DrawImage(bitmap,
                    new RectangleF(0, 0, croppedWidth, croppedHeight),
                    new RectangleF(leftMost, topMost, croppedWidth, croppedHeight),
                    GraphicsUnit.Pixel);
        }
        return target;
    }
    catch (Exception ex)
    {
        throw new Exception(string.Format("Values are top={0} bottom={1} left={2} right={3}", topMost, bottomMost, leftMost, rightMost), ex);
    }
}
User1899289003
  • 850
  • 2
  • 21
  • 40
  • Is the image shown here completely representative of your input? If so, I can spot a black border line, which would mean there is hardly any row that is completely white (well, or Red, since you're only checking if R = 255). Maybe you need to check if > 95% of the row is white? – Sean Skelly Jun 24 '20 at 21:32
  • @SeanSkelly Hello, the above image is the real size that is returned from the scanner, however the black and red lines were added by me [this is the original image](https://imgur.com/a/RM0uekD) – User1899289003 Jun 24 '20 at 21:41
  • Thanks. Interestingly, the gray 'smudge' is still there. It implies to me that your image is less than perfect coming from the scanner. Have you stepped through the rows to ensure they are all truly White, with R values of 255? I wonder if the scanner is simply returning pixel values that are very close to White, with values of 254, for example. – Sean Skelly Jun 24 '20 at 21:46
  • @SeanSkelly I see which gray line are you talking about, however this line is not a real problem for me so, is there a way to make this code works even if I don't get a perfect result, for example with a result 90-95% similar to what I expect? I'm going to try what you say and check if something change – User1899289003 Jun 24 '20 at 21:52
  • Yes, you should probably modify your functions for 'is row or column white' to check that 90-95% of pixels are within 90-95% of 255, for example. It seems your pixel data is not so 'pure' that you can check that _all_ pixels are 255. I bet most of them are not even 255, but something close to it. – Sean Skelly Jun 24 '20 at 21:55
  • @SeanSkelly One last thing, could you give an example of how my function has to be to check 90-95% of 255 please – User1899289003 Jun 24 '20 at 22:01

1 Answers1

0

If your image is not truly 'pure white' in the white areas, where R=G=B=255, I suggest modifying your functions for IsAllWhiteRow to something more like:

int thresholdValue = 250;
double percentAllowedBelowThreshold = 0.95;

Func<int, bool> IsAllWhiteRow = row =>
{
    int numberPixelsBelowThreshold = 0;
    for (int i = 0; i < w; i++)
    {
        if (bitmap.GetPixel(i, row).R < thresholdValue)
        {
            numberPixelsBelowThreshold++;
        }
    }
    return (numberPixelsBelowThreshold / w) > percentAllowedBelowThreshold;
};

And then do something similar for columns. You may need to change your threshold values, depending on your image inputs. For example, if the real part of your image has a lot of white in it, you may need a threshold of .98 or higher! Plus this code is not optimized, etc.

You'll also need to step through your image to see if the value I picked for 250 is reasonable; I haven't looked at the actual RGB values in the 'white' areas of the bitmap to see if it is or not.

Sean Skelly
  • 1,229
  • 7
  • 13