Исключение памяти в цикле обработки изображения (Нужно лучшее решение, чем GC.collect) - PullRequest
0 голосов
/ 27 апреля 2018

Я создаю небольшое приложение, которое показывает прямую трансляцию веб-камеры в форме 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;
        }
    }       

Теперь, когда я запускаю программу, я вижу, как увеличивается и уменьшается использование памяти, обычно она падает до 900 МБ, прежде чем перестать работать. Но иногда это увеличится до 2 ГБ. Иногда я даже получаю исключение нехватки памяти в этой строке:

Graphics graphics = Graphics.FromImage(frame)

Итак, потратив час или около того на попытки изменить код и найти утечку памяти, я наконец-то попробовал строку GC.Collect, которая закомментирована в коде (Shame). После этого мое использование памяти остается постоянным, менее 60 МБ. И я могу без проблем запустить программу в течение 24 часов.

Итак, я прочитал немного о GC.Collect и о том, как это плохо, например, что может потребоваться много вычислительной мощности, чтобы сделать это часто в программе. Но когда я сравниваю мощность процессора, используемую моей программой, она на самом деле не меняется независимо от того, комментирую я строку или оставляю ее. Но проблема памяти исчезнет, ​​если я соберу в конце события нового кадра.

Я хотел бы найти решение моей проблемы, которое не включает функцию GC.collect, так как я знаю, что это плохая практика программирования, и вместо этого я должен найти источник проблемы.

Спасибо всем заранее!

1 Ответ

0 голосов
/ 27 апреля 2018

Я не очень хорошо разбираюсь в выигрышных формах, но я думаю, что эта строка:

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

Оставит предыдущее изображение нераспределенным, что приводит к утечке памяти (неуправляемая память удерживается Bitmap). Поскольку у Bitmap есть финализатор - он будет возвращен GC в будущем (или когда вы позвоните GC.Collect), но, как вы уже поняли, полагаться на GC в таком случае не рекомендуется. Поэтому попробуйте сделать это так:

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

Разумный комментарий Ларса: может быть, лучше не распоряжаться изображением, пока оно все еще присваивается PictureBox.Image, потому что, кто знает, может быть, PictureBox элемент управления делает что-то со старым изображением, когда вы назначаете новое. Таким образом, альтернатива тогда:

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