Search code examples
c#opencvgarbage-collection

Is there a way to ensure gc when using opencvsharp in a .NET env?


I am using opencvsharp to do some image processing in a windows forms app and am having problems ensuring that the GC can collect at the appropriate times. This is causing an exception at the point when I run out of process memory. I sort of fixed this by following various recommendations (put all open cv iDisposables in using blocks, avoid unneeded allocations etc) but when I moved on to batch processing I still ran out of memory after a number of images.

Following some advice on an opencv forum, I put my code in a try..catch...finally sequence, expecting the GC.Collect() to sort out the problem.

        private void procDB_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK && Directory.Exists(_srcFolder))
            {

                _dstFolder = folderBrowserDialog1.SelectedPath;
                string[] files = Directory.GetFiles(_srcFolder);
                for (int i = 0; i < files.Length; i++) 
                {
                    try
                    {
                        if (Path.GetExtension(files[i]).ToLower() == ".jpg" ||
                            Path.GetExtension(files[i]).ToLower() == ".png" ||
                            Path.GetExtension(files[i]).ToLower() == ".gif" ||
                            Path.GetExtension(files[i]).ToLower() == ".bmp")
                        {
                            //process and save new files with 'converted ' tag in file name
                            using (Mat currentImage = ConformInputImage(Cv2.ImRead(files[i], ImreadModes.Color)))
                            {
                                using (_convMatBGR = ConvertImage(_srcMatBGR, currentImage))
                                {
                                    string outFileName = Path.Combine(_dstFolder, Path.GetFileNameWithoutExtension(files[i])) + "_conv" + Path.GetExtension(files[i]).ToLower();
                                    _convMatBGR.ImWrite(outFileName);
                                }
                            }
                        }
                    }
                    catch(Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                        i--;
                    }
                    finally { GC.Collect(); }
                }
            }
        }

However, it still hits the exception but now I can tell it to carry on, which it does quite happily for any number of images. This produces the memory usage shown here:

image of 4BG memory being used

Each step on the ramp is a new image being processed until it hits the exception, at which point it clears up all the garbage and when I dismiss the message box, it carries on. The garbage does seem to be collected but without freeing up any memory until the exception is reached - how do I fix it so it never hits the exception?n


Solution

  • In the vast majority of cases you should not need to call GC.Collect manually. It will run automatically before running out of memory. If you are running into out of memory exceptions it is likely that you have a memory leak. If the memory usage goes down after an exception it is likely that the exception caused some reference to be released, and that allows the GC to collect the memory.

    I would start by:

    1. Check if the result of Cv2.ImRead needs to be disposed. I.e. is ConformInputImage documented to dispose its input when the result object is disposed?
    2. Take a memory snapshot and check the object table. I would expect there to be only one or two Mat objects alive at any point. If there is any more than that you have found your leak.
    3. Take two memory snapshots and compare them, is there any type of object that increase in count between the two snapshots? That is a likely candidate for a leak.

    Note that you might need to be more vigilant with disposing objects when dealing with native libraries. Any managed wrapper should implement a finalizer to ensure the native objects are eventually freed, even if you forget to call dispose. But this is not always done, and even if it is done, the finalizer might not run immediately, and is not even guaranteed to run at all.