OpenCvsharp_메모리 관리(메모리 해제)

00·2025년 1월 15일

메모리 관리

개요

영상 처리에서 메모리 관리는 매우 중요하다. 이미지와 비디오 데이터는 일반적으로 크기가 크기 때문에, 메모리를 효율적으로 사용하지 않으면 프로그램 성능이 저하되거나 메모리 부족 오류가 발생할 수 있다.

C#에서는 가비지 컬렉터(Garbage Collector)가 자동으로 메모리를 관리해 주지만, 영상 처리에서는 가비지 컬렉터만으로는 충분하지 않은 경우가 많다. 특히 OpenCvSharp와 같이 네이티브 라이브러리를 사용하는 경우, 가비지 컬렉터가 관리하지 않는 메모리를 직접 해제해야 한다.

메모리 해제를 명시적으로 하는 이유

  • 메모리 누수 방지: Mat 객체를 사용한 후 Dispose() 메서드를 호출하지 않으면 메모리 누수가 발생할 수 있다. 메모리 누수는 프로그램이 사용하는 메모리가 계속 증가하여 결국 메모리 부족 오류를 발생시킬 수 있다.
  • 성능 향상: 메모리를 해제하면 프로그램이 사용할 수 있는 메모리 공간이 늘어나므로, 프로그램의 성능을 향상시킬 수 있다.
  • 안정성 향상: 메모리 누수는 프로그램의 안정성을 저해할 수 있다. 메모리 해제를 통해 프로그램의 안정성을 높일 수 있다.

OpenCvSharp에서 메모리 해제 방법

OpenCVSharp에서 Mat 클래스는 이미지 데이터를 저장하는 데 사용된다. Mat 객체는 생성될 때 메모리를 할당하고, 더 이상 사용되지 않을 때 메모리해제해야 한다.

1.Dispose() 메서드 호출

Mat 객체의 메모리를 해제하는 가장 일반적인 방법은 Dispose() 메서드를 호출하는 것이다.

Mat image = new Mat("image.jpg");

// 이미지 처리 작업 수행

image.Dispose(); // 메모리 해제

Dispose() 메서드는 Mat 객체가 사용하는 메모리를 해제하고, 객체를 더 이상 사용할 수 없도록 만든다.

2.using

using 문을 사용하면 Dispose() 메서드를 명시적으로 호출하지 않아도, using 블록을 벗어날 때 자동으로 Dispose() 메서드가 호출된다.

using (Mat image = new Mat("image.jpg"))
{
    // 이미지 처리 작업 수행
} // using 블록을 벗어나면 image.Dispose()가 자동으로 호출된다.

using 문은 Dispose() 메서드를 호출하는 것을 잊어버리는 실수를 방지하고, 코드를 간결하게 만들어 준다.

3.GC.Collect() 메서드

GC.Collect() 메서드는 가비지 컬렉터를 강제로 실행하여 사용하지 않는 메모리를 회수한다. 하지만 GC.Collect() 메서드는 성능에 영향을 미칠 수 있으므로, 일반적으로는 사용하지 않는 것이 좋다.

추천하는 메모리 해제 방법

using 문을 사용하는 것을 추천한다. using 문은 Dispose() 메서드를 자동으로 호출해 주기 때문에, 메모리 해제를 잊어버리는 실수를 방지할 수 있다. 또한, using 문을 사용하면 코드가 더 간결해진다.


using 문으로 메모리 해제

OpenCvSharp에서 Mat 클래스를 사용할 때 using 문을 사용하면, Mat 객체가 사용하는 메모리를 자동으로 해제할 수 있다. 마치 식당에서 식사를 마치고 나면 자동으로 식기가 치워지는 것과 같다.

using 문은 IDisposable 인터페이스를 구현하는 객체에 대해 사용할 수 있으며, Mat 클래스도 IDisposable 인터페이스를 구현한다.

using 문을 사용하면 using 블록 내에서 Mat 객체를 사용하고, 블록을 벗어날 때 자동으로 Mat 객체의 Dispose() 메서드가 호출되어 메모리가 해제된다.

using 문 사용 예시

using OpenCvSharp;

class Example
{
    static void Main(string[] args)
    {
        using (Mat image = new Mat("image.jpg")) // using 블록 시작, Mat 객체 생성
        {
            // 이미지 처리 작업 수행
            Mat grayImage = new Mat();
            Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY); // image를 흑백으로 변환하여 grayImage에 저장

            Cv2.ImShow("Gray Image", grayImage); // grayImage를 화면에 표시
            Cv2.WaitKey(0); // 키 입력 대기
            Cv2.DestroyAllWindows(); // 모든 창 닫기

            grayImage.Dispose(); // grayImage 메모리 해제
        } // using 블록 종료, image.Dispose() 자동 호출
    }
}

코드 설명

  • using OpenCvSharp;: OpenCVSharp 라이브러리를 사용하기 위해 네임스페이스를 선언한다.
  • using (Mat image = new Mat("image.jpg")): using 블록을 시작하고, Mat 객체 image를 생성하여 "image.jpg" 파일을 로드한다.
  • Mat grayImage = new Mat();: Mat 객체 grayImage를 생성한다.
  • Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY);: image를 흑백으로 변환하여 grayImage에 저장한다.
  • Cv2.ImShow("Gray Image", grayImage);: grayImage를 화면에 표시한다.
  • Cv2.WaitKey(0);: 키 입력을 기다린다.
  • Cv2.DestroyAllWindows();: 모든 창을 닫는다.
  • grayImage.Dispose();: grayImage의 메모리를 해제한다.
  • }: using 블록을 종료한다. 이때 image.Dispose()가 자동으로 호출되어 image의 메모리가 해제된다.

using 문 사용 방법

  1. using 키워드 다음에 괄호 ()를 사용하여 블록을 만든다.
  2. 괄호 안에 IDisposable 인터페이스를 구현하는 객체를 선언하고 초기화한다. (Mat 객체는 IDisposable 인터페이스를 구현한다.)
  3. using 블록 내에서 객체를 사용한다.
  4. using 블록을 벗어나면 자동으로 객체의 Dispose() 메서드가 호출되어 리소스가 해제된다.

using 문을 사용하면 Mat 객체의 메모리 해제를 잊어버리는 실수를 방지하고, 코드를 간결하게 만들 수 있다.


실제 적용

기존 코드

private void UpdateFrame(object? sender, EventArgs e)
{
    capture.Read(frame); // 웹캠에서 프레임을 읽어와 frame 객체에 저장

    if (!frame.Empty()) // frame 객체가 비어 있지 않으면(프레임을 읽어오는 데 성공하면),
    {
        
        Mat filteredFrame = frame.Clone(); // 원본 프레임 복사
                                           // (필터를 적용하기 전에 원본 프레임을 복사하여
                                           // 필터가 원본 프레임에 영향을 주지 않도록 한다.)
                                           // (Mat 객체는 참조 형식이므로,
                                           // Mat 객체를 다른 변수에 할당할 때는 얕은 복사가 수행된다.
                                           // 즉, 두 변수가 같은 메모리 영역을 참조하게 된다.
                                           // 따라서 이미지 데이터를 복사하려면 Clone() 메서드를 사용해야 한다.)


        
        // isGrayscale, isBlur, isInvert 변수 값에 따라 해당 필터를 적용한다.
        if (isGrayscale) // 흑백 필터 적용
        {
            Cv2.CvtColor(filteredFrame, filteredFrame, ColorConversionCodes.BGR2GRAY);
        }


        if (isBlur) // 블러 필터 적용
        {
            Cv2.Blur(filteredFrame, filteredFrame, new OpenCvSharp.Size(5, 5));
        }


        if (isInvert) // 색상 반전 필터 적용
        {
            Cv2.BitwiseNot(filteredFrame, filteredFrame);
        }


        // 필터링된 이미지를 WPF 컨트롤에 표시
        imgDisplay.Source = WriteableBitmapConverter.ToWriteableBitmap(filteredFrame);


        filteredFrame.Dispose();
    }

개선 코드

using 문을 사용해서 메모리 해제 방법을 개선함.

        private void UpdateFrame(object? sender, EventArgs e)
        {
            capture.Read(frame);
            if (!frame.Empty())
            {
                // using 문을 사용하여 filteredFrame 객체를 감싸고,
                // using 블록을 벗어날 때 자동으로 Dispose() 메서드가 호출되도록 합니다.
                using (Mat filteredFrame = frame.Clone()) 
                {
                    if (isGrayscale)
                    {
                        Cv2.CvtColor(filteredFrame, filteredFrame, ColorConversionCodes.BGR2GRAY);
                    }

                    if (isBlur)
                    {
                        Cv2.Blur(filteredFrame, filteredFrame, new OpenCvSharp.Size(5, 5));
                    }

                    if (isInvert)
                    {
                        Cv2.BitwiseNot(filteredFrame, filteredFrame);
                    }

                    imgDisplay.Source = WriteableBitmapConverter.ToWriteableBitmap(filteredFrame);
                    // filteredFrame.Dispose()는 using 블록을 벗어날 때 자동으로 호출된다.
                }
            }
        }

UpdateFrame 메서드에서 Mat filteredFrame = frame.Clone(); 부분을 using (Mat filteredFrame = frame.Clone())으로 수정하여, filteredFrame 객체가 using 블록 내에서만 사용되도록 했다.

이렇게 하면 using 블록을 벗어날 때 filteredFrame.Dispose()가 자동으로 호출되어 메모리가 해제된다.


위 코드에서 frame 객체는 명시적으로 해제하지 않아도 되는가

frame 객체는 MainWindow 클래스의 멤버 변수로 선언되어 있으므로, MainWindow 창이 닫힐 때까지 유지된다. 따라서 UpdateFrame 메서드 내에서 frame 객체를 명시적으로 해제할 필요는 없다.

하지만 MainWindow 창이 닫힐 때 frame 객체가 사용하는 메모리를 해제하기 위해, MainWindowClosing 이벤트 핸들러에서 frame.Dispose()를 호출하는 것이 좋다.

private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    // 윈도우가 닫힐 때 리소스를 해제합니다.
    capture.Release();
    frame.Dispose(); // frame 객체 해제
    timer.Stop();
}

using 문을 사용하여 frame 객체를 해제하려면, UpdateFrame 메서드 내에서 frame 객체를 사용하는 부분을 using 블록으로 감싸야 한다.

하지만 frame 객체는 UpdateFrame 메서드가 호출될 때마다 재사용되므로, using 문을 사용하는 것은 적절하지 않다.

따라서 frame 객체는 MainWindow 창이 닫힐 때 Closing 이벤트 핸들러에서 Dispose() 메서드를 호출하여 해제하는 것이 좋다.


frame.Dispose();
video.Release();
Cv2.DestroyAllWindows();
  • Dispose()와 Release() 구문으로 프레임과 비디오에 대한 메모리를 해제한다.
  • 또한, 윈도우 창을 더 이상 사용하지 않으므로, 모든 윈도우 창을 제거(Cv2.DestroyAllWindows)한다.
  • 더 이상 사용되지 않는다면, 명시적으로 메모리를 해제해주는 것이 권장된다.

0개의 댓글