4

I am making a small application that shows a live webcam feed in a windows form, and also stores watermarked images to drive at a specified interval (Creating a timelapse video is the end goal).

I am using the AForge library for image and video processing.

I have problems were there seems to be a memory leak, even though i try to make sure to use "using" statements at every location where image processing occurs.

Below is the code were the image processing takes place (The NewFrame event)

    private void Video_NewFrame(object sender, NewFrameEventArgs eventArgs)
    {
        try
        {
            if (ImageProcessing) // If the previous frame is not done processing, let this one go
                return;
            else
                ImageProcessing = true;

            using (Bitmap frame = (Bitmap)eventArgs.Frame)
            {
                // Update the GUI picturebox to show live webcam feed
                Invoke((Action)(() =>
                {
                    webcam_PictureBox.Image = (Bitmap)frame.Clone();
                }));

                // During tests, store images to drive at a certain interval
                if (ImageStoreTimer.Elapsed.TotalSeconds > ImageStoreTime)
                {
                    DateTime dt = DateTime.Now;

                    using (Graphics graphics = Graphics.FromImage(frame))
                    {
                        PointF firstLocation = new PointF(frame.Width / 2, frame.Height / 72);
                        PointF secondLocation = new PointF(frame.Width / 2, frame.Height / 15);

                        StringFormat drawFormat = new StringFormat();
                        drawFormat.Alignment = StringAlignment.Center;

                        using (Font arialFont = new Font("Arial", 15))
                        {
                            graphics.DrawString(dt.ToString(), arialFont, Brushes.Red, firstLocation, drawFormat);
                            graphics.DrawString(Pressure.ToString("F0") + " mbar", arialFont, Brushes.Red, secondLocation, drawFormat);
                        }
                    }

                    // Place images in a folder with the same name as the test
                    string filePath = Application.StartupPath + "\\" + TestName + "\\";
                    // Name images by number 1....N
                    string fileName = (Directory.GetFiles(filePath).Length + 1).ToString() + ".jpeg";

                    frame.Save(filePath + fileName, ImageFormat.Jpeg);
                    ImageStoreTimer.Restart();
                }
            }
            //GC.Collect(); <----- I dont want this
        }
        catch
        {
            if (ProgramClosing == true){}
                // Empty catch for exceptions caused by the program being closed incorrectly
            else
                throw;
        }
        finally
        {
            ImageProcessing = false;
        }
    }       

Now, when running the program, i see memory usage going up and down, usually it gets to about 900MB before dropping. But occasionally it will rise to 2GB. Occasionally, i even get an out of memory exception at this line:

Graphics graphics = Graphics.FromImage(frame)

So after spending an hour or so to trying to reshape the code and looking for my memory leak, i at last tried the GC.Collect line that is commented out in the code (Shame). After that, my memory usage stays constant, at less than 60MB. And i can run the program for 24 hours without any problems.

So i read a bit about GC.Collect, and how bad it is, for example that it could take a lot of processing power to do it to often in a program. But when i compare the CPU power used by my program, it does not really change regardless if i comment the line out or leave it. But the memory problem is gone if i collect at the end of the new frame event.

I would like to find a solution to my problem that does not involve the GC.collect function, as i know it is bad programming practice and i should instead find the underlying problem source.

Thank you all in advance!

A.Force
  • 73
  • 6
  • yes GC is bad. Its always better to reuse an object in stead of creating new ones that has helped me alot with memory problems and is better for performance to – GuidoG Apr 27 '18 at 09:02
  • have you tried commenting out the "store-image" block? Only to make sure its not the source of the problem – casiosmu Apr 27 '18 at 09:06
  • 1
    If you are working with a lot of garbage then calling garbage collection when you know about the nature about what might be in gen 1 and 2 can be useful and in some cases needed. Yes GC can have a performance hit, so you need to understand it and also not try to plug up bad design with the wrong tool. In general 98 out of 100 questions that refer to GC.collect are usually the result of memory leaks. though just saying these calls should be avoided at all costs and are bad oversimplifies things just a little too much – TheGeneral Apr 27 '18 at 09:21

2 Answers2

6

I'm not good with win forms but I think that this line:

webcam_PictureBox.Image = (Bitmap)frame.Clone();

Will leave previous image undisposed, which leaks memory (unmanaged memory hold by Bitmap). Since Bitmap has finalizer - it will be reclaimed by GC at some future time (or when you call GC.Collect), but as you already understand - it's not a good practice to rely on GC in such case. So try to do it like this instead:

if (webcam_PictureBox.Image != null)
    webcam_PictureBox.Image.Dispose();
webcam_PictureBox.Image = (Bitmap)frame.Clone();

Reasonable comment by Larse: it might be better to not dispose image while it's still being assigned to PictureBox.Image, because who knows, maybe PictureBox control does anything with old image when you are assigning a new one. So alternative is then:

var oldImage = webcam_PictureBox.Image;
webcam_PictureBox.Image = (Bitmap)frame.Clone();
if (oldImage != null)
    oldImage.Dispose();
Evk
  • 98,527
  • 8
  • 141
  • 191
  • I also set to null in these cases not just disposing, is that overkill ? – GuidoG Apr 27 '18 at 09:05
  • 1
    Nitpicking, but it would probably be better to yank the image out of the picturebox before disposing it. I can't see anything in referencesource that accesses the image before setting the new one but it's not a good idea (in my opinion) to dispose of an object you've already shared with another object. So I would do something like `Image oldImage = webcam_PictureBox.Image; webcam_PictureBox.Image = (Bitmap)frame.Clone(); oldImage?.Dispose();` – Lasse V. Karlsen Apr 27 '18 at 09:06
  • @LasseVågsætherKarlsen yes that's a reasonable thing to do indeed. – Evk Apr 27 '18 at 09:08
  • @GuidoG but you are setting it to another image in the next line, so setting to null does nothing useful. – Evk Apr 27 '18 at 09:09
  • True, but in your new code the oldImage is not set to another image right after the dispose. So would it be to much to set oldImage to null here ? Just trying to learn something here – GuidoG Apr 27 '18 at 09:13
  • @GuidoG garbage collector (without debugger attached) will have no problem to collect `oldImage` immediately after last usage (so, after line `oldImage.Dispose()`). I don't mean it will collect right after last usage, but it _can_, and it does not need to, for example, wait until enclosing method will finish. For that reason, setting local variable to null doesn't help anything, since it's done with the assumption it will help GC to collect it earlier, but that's not so. – Evk Apr 27 '18 at 09:16
  • 1
    @GuidoG - assuming Release code rather than Debug, the GC and JIT don't need much help. If `oldImage` isn't read from again after the `Dispose`, that variable doesn't keep anything alive during GC. – Damien_The_Unbeliever Apr 27 '18 at 09:16
  • 1
    It seems that this was the problem! I edited the code according to Evk's updated answer, and now my memory usage is constant at 60 mb, without the garbage collection! Thank you all. – A.Force Apr 27 '18 at 10:25
0

This worked well for us, https://stackoverflow.com/a/70914235/10876657 before that, we tried using statements and all sorts but one solution might work on one machine, but not on the other, but by referencing the image currently set in picturebox and disposing of it afterwords has worked well, not entirely sure why picture.image is not disposed of automatically when a new image is set (frustrating!) but hey, at least this simple workaround exists