OpenCvSharp를 사용하여 웹캠에서 영상을 캡처하고 화면에 표시하는 프로그램 오류 수정

00·2025년 1월 14일

배경

OpenCvSharp를 사용하여 웹캠에서 영상을 캡처하고 화면에 표시하는 간단한 프로그램을 만든다.

단계

1.웹캠을 열고,
2.프레임을 읽어와 화면에 표시하고,
3.'q' 키를 누르면 웹캠 뷰어를 종료한다.

XAML 코드

<Window x:Class="OpenCvSharpProjects.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:OpenCvSharpProjects"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Grid>
        <Image x:Name="imgCamera" Stretch="Uniform" />
    </Grid>
</Window>

소스 코드

using System.Windows;

using OpenCvSharp;
// OpenCV4의 데이터 형식이나 함수 및 메서드를 사용하기 위해 네임스페이스에 using OpenCvSharp;을 추가합니다.
// Mat 클래스 또한 using OpenCvSharp;에 포함되어 있습니다.

namespace OpenCvSharpProjects
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : System.Windows.Window
    {
        
        private VideoCapture video;
        private Mat frame;

        public MainWindow()
        {
            InitializeComponent();

            VideoCapture video = new VideoCapture(0);
            Mat frame = new Mat();

            Loaded += MainWindow_Loaded;
        }


        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //  프레임을 읽어오고 표시하는 작업을 반복
            while (Cv2.WaitKey(1) != 'q') // 1밀리초 동안 키 입력 대기
            {
                video.Read(frame);
                if (!frame.Empty())
                {
                    Cv2.ImShow("MainWindow", frame); // OpenCV 창에 이미지 표시
                }
            }


            frame.Dispose();
            video.Release();
            Cv2.DestroyAllWindows();
        }
    }
}

오류 발생

위와 같이 작성했을 때, 창에 아무것도 안뜨다가 혼자 꺼진다.

대처

WPF 창에 아무것도 안 뜨고 혼자 꺼지는 문제는 while 루프가 UI 스레드를 블록하고 있기 때문일 가능성이 높은 것으로 확인된다.

WPF 애플리케이션은 UI 스레드에서 실행되는데, while 루프가 UI 스레드를 점유하면 UI 업데이트가 이루어지지 않아 창이 표시되지 않고, 이벤트 처리도 제대로 되지 않아 윈도우가 닫히는 것으로 보인다.

해결 방벙

이 문제를 해결하려면 while 루프를 별도의 스레드에서 실행하거나, DispatcherTimer를 사용하여 UI 스레드를 블록하지 않도록 해야 한다.

DispatcherTimer를 사용하는 방법을 추천한다. DispatcherTimer는 WPF에서 UI 스레드에서 작업을 예약하는 데 사용되는 타이머이다.

단, DispatcherTimer는 OpenCvSharp.WpfExtensions가 참조돼야 한다.

다음과 같이 코드를 수정한다.

using OpenCvSharp; // OpenCVSharp 라이브러리를 사용하기 위한 네임스페이스
using System.Windows.Threading; // DispatcherTimer를 사용하기 위한 네임스페이스

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
namespace OpenCvSharpProjects // OpenCvSharpProjects 네임스페이스 정의
{
    public partial class MainWindow :  System.Windows.Window // MainWindow 클래스는 Window 클래스를 상속받는다
    {
        private VideoCapture capture; // VideoCapture 객체를 저장할 필드, 웹캠에서 영상을 가져오는 데 사용된다
        private Mat frame; // Mat 객체를 저장할 필드, OpenCV에서 이미지를 표현하는 데 사용된다
        private DispatcherTimer timer; // DispatcherTimer 객체를 저장할 필드, UI 스레드에서 작업을 예약하는 데 사용된다

        public MainWindow() // 생성자
        {
            InitializeComponent(); // 윈도우를 초기화한다. XAML에서 정의된 컨트롤들을 초기화하고 이벤트 핸들러를 연결하는 등의 작업을 수행한다

            capture = new VideoCapture(0); // VideoCapture 객체를 생성하고 기본 웹캠 장치(0)를 사용하도록 설정한다
            frame = new Mat(); // Mat 객체를 생성한다
            timer = new DispatcherTimer(); // DispatcherTimer 객체를 생성한다
            timer.Interval = TimeSpan.FromMilliseconds(33); // 타이머 간격을 33밀리초로 설정한다. 약 30fps(초당 프레임 수)로 프레임을 업데이트한다
            timer.Tick += UpdateFrame; // timer의 Tick 이벤트에 UpdateFrame 메서드를 연결한다. 즉, 타이머가 발생할 때마다 UpdateFrame 메서드가 호출된다
            timer.Start(); // 타이머를 시작한다
        }

        private void UpdateFrame(object? sender, EventArgs e) // 타이머 이벤트가 발생할 때마다 호출되는 메서드이다
        {
            capture.Read(frame); // 웹캠에서 현재 프레임을 읽어와 frame 객체에 저장한다
            if (!frame.Empty()) // frame 객체가 비어 있지 않으면, 즉 프레임을 읽어오는 데 성공하면 다음 코드를 실행한다
            {
                Cv2.ImShow("Camera Output", frame); // Cv2.ImShow() 메서드를 사용하여 frame 객체(이미지)를 "Camera Output" 창(OpenCV 창)에 표시한다
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) // 윈도우가 닫힐 때 발생하는 이벤트를 처리하는 메서드이다
        {
            // 윈도우가 닫힐 때 리소스를 해제합니다.
            capture.Release(); // VideoCapture 객체를 해제하여 웹캠 장치를 닫는다
            frame.Dispose(); // Mat 객체를 해제하여 이미지 데이터가 차지하는 메모리를 해제한다
            Cv2.DestroyAllWindows(); // 모든 OpenCV 창을 닫는다
            timer.Stop(); // 타이머를 중지한다
        }
    }
}

수정된 내용

  • while 루프를 제거하고 DispatcherTimer를 사용하여 UpdateFrame 메서드를 주기적으로 호출하도록 했다.
  • Window_Closing 이벤트 핸들러를 추가하여 윈도우가 닫힐 때 리소스를 해제하도록 했다.

이렇게 수정하면 UI 스레드가 블록되지 않고 웹캠 영상이 OpenCV 창에 정상적으로 표시될 것이다.

WPF에서는 UI 요소를 UI 스레드에서만 업데이트할 수 있기 때문에, DispatcherTimer를 사용하여 UI 스레드에서 UpdateFrame 메서드를 호출해야 한다.

OpenCV 창과 WPF 창은 둘 다 화면에 이미지를 표시할 수 있는 창이지만, 서로 다른 기술을 사용해서 만들어진다.

실행 결과


OpenCV 창, WPF 창
OpenCV 창은 OpenCV 라이브러리를 사용하여 만들어진 창이다. OpenCV는 이미지 처리 및 컴퓨터 비전 라이브러리로, 이미지를 표시하고, 이미지 처리 결과를 시각화하는 데 사용된다.
Cv2.ImShow() 함수를 사용하면 OpenCV 창을 생성하고 이미지를 표시할 수 있다.

WPF 창은 WPF(Windows Presentation Foundation)를 사용하여 만들어진 창이다. WPF는 윈도우 애플리케이션의 UI를 구축하기 위한 프레임워크이다. WPF 창은 XAML을 사용하여 UI를 정의하고, C# 코드를 사용하여 UI의 동작을 구현한다.

위 코드에서 카메라 화면이 띄워진 창은 OpenCV 창이다. Cv2.ImShow() 함수를 사용하여 웹캠에서 가져온 이미지를 OpenCV 창에 표시하기 때문이다.

WPF 창은 OpenCV 창을 포함할 수 있다. 즉, WPF 창 안에 OpenCV 창을 띄워서 웹캠 영상을 표시할 수 있다.


메모리(리소스) 해제하는 방법

(1) 윈도우가 닫힐 때 리소스를 해제하는 방법.

 private void UpdateFrame(object sender, EventArgs e) // 타이머 이벤트가 발생할 때마다 호출되는 메서드이다
        {
            capture.Read(frame); // 웹캠에서 현재 프레임을 읽어와 frame 객체에 저장한다
            if (!frame.Empty()) // frame 객체가 비어 있지 않으면, 즉 프레임을 읽어오는 데 성공하면 다음 코드를 실행한다
            {
                Cv2.ImShow("Camera Output", frame); // Cv2.ImShow() 메서드를 사용하여 frame 객체(이미지)를 "Camera Output" 창(OpenCV 창)에 표시한다
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) // 윈도우가 닫힐 때 발생하는 이벤트를 처리하는 메서드이다
        {
            // 윈도우가 닫힐 때 리소스를 해제합니다.
            capture.Release(); // VideoCapture 객체를 해제하여 웹캠 장치를 닫는다
            frame.Dispose(); // Mat 객체를 해제하여 이미지 데이터가 차지하는 메모리를 해제한다
            Cv2.DestroyAllWindows(); // 모든 OpenCV 창을 닫는다
            timer.Stop(); // 타이머를 중지한다
        }
    }
        private void UpdateFrame(object sender, EventArgs e)
        {
            capture.Read(frame);
            if (!frame.Empty())
            {
                Cv2.ImShow("Camera Output", frame); // OpenCV 창에 이미지 표시
            }

            // UpdateFrame 메서드 내에서 리소스를 해제한다.
            capture.Release();
            frame.Dispose();
            Cv2.DestroyAllWindows();
            timer.Stop();
        }
    }
}

UpdateFrame 메서드 내에서 리소스를 해제하도록 수정했다. 이렇게 하면 윈도우가 닫힐 때 리소스를 해제하지 않고, UpdateFrame 메서드가 호출될 때마다 리소스를 해제하게 된다.

하지만 이 방법은 권장되지 않는다고 한다.

UpdateFrame 메서드는 타이머에 의해 반복적으로 호출되므로, 리소스를 반복적으로 해제하게 되어 비효율적이고, 예상치 못한 동작을 유발할 수 있기 때문이다.

그러니까 윈도우가 닫힐 때 리소스를 해제하는 것이 일반적인 방법이고, 리소스를 명시적으로 해제해야 하는 경우에는 try-finally 블록을 사용하거나, IDisposable 인터페이스를 구현하는 것이 좋다.

실제로 이렇게 수정하니 프레임이 업데이트 되자마자 해제돼서, 프로그램을 실행하자마자 웹캡을 띄운 창이 닫혀 버리는 것을 확인할 수 있었다.

0개의 댓글