【问题标题】:Memory exception in image processing loop (Need better solution than GC.collect)图像处理循环中的内存异常(需要比 GC.collect 更好的解决方案)
【发布时间】:2018-04-27 08:53:39
【问题描述】:

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

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

我有问题似乎存在内存泄漏,即使我尝试确保在每个进行图像处理的位置都使用“使用”语句。

以下是图像处理发生的代码(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 函数的问题的解决方案,因为我知道这是不好的编程习惯,我应该找到潜在的问题根源。

提前谢谢大家!

【问题讨论】:

  • 是的,GC 不好。重用一个对象总是比创建新对象更好,这对我解决内存问题有很大帮助,并且对性能更好
  • 你试过注释掉“store-image”块吗?只是为了确保它不是问题的根源
  • 如果您正在处理大量垃圾,那么当您了解第 1 代和第 2 代中可能存在的内容的性质时调用垃圾收集可能很有​​用,并且在某些情况下是必要的。是的,GC 可能会影响性能,因此您需要了解它,并且不要试图用错误的工具来填补糟糕的设计。一般来说,100 个涉及 GC.collect 的问题中有 98 个通常是内存泄漏的结果。虽然只是说应该不惜一切代价避免这些电话,而且是不好的,但过于简单化了一点

标签: c# .net winforms garbage-collection aforge


【解决方案1】:

我不擅长赢取表格,但我认为这条线:

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

将保留以前的图像未处理,这会泄漏内存(位图持有的非托管内存)。由于 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();

【讨论】:

  • 在这些情况下,我也设置为 null 而不仅仅是处置,是不是矫枉过正?
  • 吹毛求疵,但最好在处理之前将图像从图片框中拉出。在设置新图像之前,我在参考源中看不到任何可以访问图像的东西,但是(在我看来)处置已经与另一个对象共享的对象并不是一个好主意。所以我会做类似Image oldImage = webcam_PictureBox.Image; webcam_PictureBox.Image = (Bitmap)frame.Clone(); oldImage?.Dispose();
  • @LasseVågsætherKarlsen 是的,这确实是一件合理的事情。
  • @GuidoG - 假设发布代码而不是调试,GC 和 JIT 不需要太多帮助。如果在 Dispose 之后没有再次读取 oldImage,则该变量不会在 GC 期间保持任何活动。
  • 看来是这个问题!我根据 Evk 的更新答案编辑了代码,现在我的内存使用量恒定在 60 mb,没有垃圾收集!谢谢大家。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-22
    • 2011-05-05
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 2014-11-22
    • 2021-07-13
    • 1970-01-01
    相关资源
    最近更新 更多