图像处理循环中的内存异常(需要比 GC.collect 更好的解决方案)

Memory exception in image processing loop (Need better solution than GC.collect)

我正在制作一个小应用程序,它以 windows 形式显示实时网络摄像头,并且还存储带水印的图像以按指定的时间间隔驱动(最终目标是创建延时视频)。

我正在使用 AForge 库进行图像和视频处理。

我遇到了问题,似乎存在内存泄漏,尽管我尝试确保在每个发生图像处理的位置都使用 "using" 语句。

下面是发生图像处理的代码(NewFrame 事件)

    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;
        }
    }       

现在,当 运行 运行程序时,我看到内存使用量上下波动,通常在下降前达到 900MB 左右。但是偶尔会涨到2GB。有时,我什至会在这一行遇到内存不足的异常:

Graphics graphics = Graphics.FromImage(frame)

因此,在花了一个小时左右的时间尝试重塑代码并查找内存泄漏后,我终于尝试了代码中注释掉的 GC.Collect 行(羞耻)。在那之后,我的内存使用量保持不变,低于 60MB。而且我可以 运行 这个程序 24 小时没有任何问题。

所以我读了一些关于 GC.Collect 的内容,它有多糟糕,例如,在程序中经常执行它可能需要大量的处理能力。但是当我比较我的程序使用的 CPU 功率时,无论我注释掉该行还是保留它,它都没有真正改变。但是如果我在新帧事件结束时收集,内存问题就消失了。

我想为我的问题找到一个不涉及 GC.collect 函数的解决方案,因为我知道这是糟糕的编程习惯,我应该找到潜在的问题根源。

先谢谢大家了!

我不擅长 win 表单,但我认为这一行:

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

将保留以前的图像未处理,这会泄漏内存(Bitmap 保留的非托管内存)。由于 Bitmap 具有终结器 - 它会在将来的某个时间(或当您调用 GC.Collect 时)被 GC 回收,但正如您已经了解的那样 - 在这种情况下依赖 GC 并不是一个好习惯。所以尝试这样做:

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

Larse 的合理评论:在图像仍被分配给 PictureBox.Image 时最好不要处理图像,因为谁知道呢,也许 PictureBox 控件在您分配时会对旧图像做任何事情一个新的。那么替代方案是:

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

这对我们很有效, 在此之前,我们尝试过使用语句和各种方法,但一种解决方案可能在一台机器上有效,但在另一台机器上无效,但通过引用当前在 picturebox 中设置的图像并在 afterwords 中处理它效果很好,不完全确定为什么 picture.image 在设置新图像时不会自动处理(令人沮丧!)但是嘿,至少存在这个简单的解决方法