0

I've made a program that analyzes the first pixel of an image and then notes the values of it in a List, this is for checking if the image is black&white or in color. Now, does anyone know an efficient way of reading high-res images? Right now I'm using Bitmaps but they are highly inefficient. The images are around 18 megapixels each and I want to analyze around 400 photos. Code below:

Bitmap b;

foreach (FileInfo f in files) {
  // HUGE MEMORY LEAK
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();

  b = (Bitmap)Bitmap.FromFile(f.FullName);

  // reading pixel (0,0) from bitmap

When I run my program it says:

"An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll
Additional information: There is no available memory."

I've tried with System.GC.Collect() to clean up, as you can see, but the exception doesn't go away. If I analyse a folder that contains only a few photos, the program runs fine and gladly does it's job.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • Where is the list of files coming from? If you are pre-loading all the files in the directory before creating a Bitmap object, they are all in memory already. I would get a list of file names in the directory as a List, then iterate over each one, load a single file object and make sure it is nulled afterwards. Manually invoking GC is a very bad idea as it doesn't address the underlying cause of the memory usage. – Alex Aug 07 '15 at 11:50
  • 1
    Also, have a look at PixelFormat for determining colour depth. Looking at the first pixel to decide if the image is colour or not is not a reliable approach. If the first pixel is black or white, you have no idea if it is greyscale, monochrome or colour with a black/white background. https://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat.aspx. This SO post could be of use to you - http://stackoverflow.com/questions/2150504/how-can-i-check-the-color-depth-of-a-bitmap – Alex Aug 07 '15 at 11:52
  • Can you doublecheck if the problem really is the number of files and not that your many-files-scenario contains an even larger image? .NET has limits regarding the max size of objects. – Sebastian Negraszus Aug 07 '15 at 11:53
  • Do you remember to dispose? – Rasmus Damgaard Nielsen Aug 07 '15 at 12:17

2 Answers2

2

Using the first pixel of an image to check if it is colour or not is the wrong way to do this.

If you have an image with a black background (pixel value 0,0,0 in RGB), how do you know the image is black and white, and not colour with a black background?

Placing the bitmap in a Using is correct, as it will dispose properly.

The following will do the trick.

class Program
{
    static void Main(string[] args) {
        List<String> ImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };

        String rootDir = "C:\\Images";
        foreach (String fileName in Directory.EnumerateFiles(rootDir)) {
            if (ImageExtensions.Contains(Path.GetExtension(fileName).ToUpper())) {
                try {
                    //Image.FromFile will work just as well here.
                    using (Image i = Bitmap.FromFile(fileName)) {
                        if (i.PixelFormat == PixelFormat.Format16bppGrayScale) {
                            //Grey scale...
                        } else if (i.PixelFormat == PixelFormat.Format1bppIndexed) {
                            //1bit colour (possibly b/w, but could be other indexed colours)
                        }
                    }
                } catch (Exception e) {
                    Console.WriteLine("Error - " + e.Message);
                }
            }
        }
    }
}

The reference for PixelFormat is found here - https://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat%28v=vs.110%29.aspx

Objects in C# are limited to 2Gb, so I doubt that an individual image is causing the problem. I also would suggest that you should NEVER manually call the GC to solve a memory leak (though this is not technically a leak, just heavy memory usage).

Using statements are perfect for ensuring that an object is marked for disposal, and the GC is very good at cleaning up.

We perform intensive image processing in our software, and have never had issues with memory using the approach I have shown.

While simply reading the header to find image data is a perfectly correct solution, it does mean a lot of extra work to decode different file types, which is not necessary unless you are working with vast amounts of images in very small memory (although if that is your aim, straight C is a better way to do it rather than C#. Horses for courses and all that jazz!)

EDIT - I just ran this on a directory containing over 5000 high-res TIFFs with no memory issues. The slowest part of the process was the console output!

Alex
  • 1,643
  • 1
  • 14
  • 32
  • Thank you so much :) Well, my initial approach was to analyse 'all' pixels of the image and check if R == G == B, as it is then grayscale. That ended up being a 'very' heavy approach, so I decided to cut down to only 1000px, which was pretty slow too. I ended up with 2 or 1 pixels in the end lol. You seem very well educated in this. Have a nice day and thank you once again <3 – Jakob Lindskog Aug 07 '15 at 17:19
  • You're very welcome. My uni dissertation involved a lot of image analysis, and the software we develop now involves much of the same, so I've got a lot of hours under my belt trying to get image analysis algorithms to work properly. C# is actually very good for this. There's a lot of particularly useful functionality for image processing, especially when you compare it to what's available for Java! Working with images in C# is a picnic compared to trying to do any image processing using JAI! – Alex Aug 07 '15 at 17:51
  • Lol mate, I feel ya. I worked in Java before but C# is so much stronger. Go Microsoft! Btw, when debugging, my B&W-images are not seen as PixelFormat.Format16bppGrayScale but as PixelFormat.Format24bppRgb... sigh. The Image-class doesn't have the .getPixel(x,y) method that Bitmap has so I can't really use my old method either... :/ – Jakob Lindskog Aug 07 '15 at 21:25
  • Ah, in that case, they are B&W images saved as colour with a limited palette. There is a way to get all the pixels in a Bitmap, just change Image i to Bitmap i. Bitmap extends Image, and strictly speaking, any image format has a bit map which can be queried. Your previous method is sound, but must be applied to all pixels in the image, which will be somewhat slow. I'm not on my Windows machine at the moment but can have a look tomorrow. There might be a way to apply your method to all pixels a bit faster... – Alex Aug 08 '15 at 19:57
  • Incidentally, if you have an image which is true greyscale (Format16bppGrayScale), using the R = G = B approach would fail, so preferably, you need both methods. Check the image to see what the PixelFormat is, and if it is RGB, process it through your R = G = B algorithm – Alex Aug 08 '15 at 20:00
  • The debug says that i.PixelFormat == Format24bppRgb. So I guess I have to do my old method anyways. I'm thinking about checking different areas, as well as only checking if i.getPixel().GetSaturation() is 0. Thank you once again for caring about a little student like me :) – Jakob Lindskog Aug 09 '15 at 15:32
0

I guess, if you need only first pixel - not necessary to read all file. Maybe you should take just first pixel from bitmap byte array and work with it. You should find pixel array manually and take first.

How find pixel array? Well, it depends on file format. You should find specification for each usable format and use it. Here is example for BMP reader

Community
  • 1
  • 1
Artem Kulikov
  • 2,250
  • 19
  • 32
  • This would require a decoder for each possible image type. While this is a perfectly serviceable approach, it does mean a lot of extra work which is not desperately necessary for this task. – Alex Aug 07 '15 at 12:24