앱개발

wpf - Opencv Blur

제갈티 2025. 6. 16. 22:09
<!-- WPF 윈도우의 루트 요소 정의 -->
<!-- x:Class: 이 XAML과 연결될 C# 코드-비하인드 클래스 지정 -->
<!-- xmlns: XML 네임스페이스 선언 - WPF 컨트롤들을 사용하기 위한 필수 선언 -->
<!-- Title: 윈도우 제목 표시줄에 나타날 텍스트 -->
<!-- Height/Width: 윈도우의 초기 크기 (픽셀 단위) -->
<Window x:Class="wpf_opencv_blur.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="Image Blur App" Height="768" Width="1024">

    <!-- Grid: WPF의 가장 유연한 레이아웃 컨테이너 -->
    <!-- 행과 열을 정의하여 자식 요소들을 배치할 수 있음 -->
    <Grid>
        <!-- Grid의 행(Row) 정의 섹션 -->
        <Grid.RowDefinitions>
            <!-- 첫 번째 행: Height="Auto" - 내용물의 크기에 맞춰 자동 조절 -->
            <!-- 버튼들이 들어갈 공간으로, 버튼 높이만큼만 차지 -->
            <RowDefinition Height="Auto"/>

            <!-- 두 번째 행: Height="*" - 남은 공간을 모두 차지 -->
            <!-- 별표(*)는 비례 크기를 의미하며, 첫 번째 행을 제외한 모든 공간 사용 -->
            <!-- 이미지 표시 영역으로 사용됨 -->
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- StackPanel: 자식 요소들을 한 방향으로 순차적으로 배열하는 컨테이너 -->
        <!-- Grid.Row="0": 첫 번째 행(인덱스 0)에 배치 -->
        <!-- Orientation="Horizontal": 자식 요소들을 수평(가로)으로 배열 -->
        <!-- Margin="10": 사방으로 10픽셀의 여백 설정 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">

            <!-- 이미지 로딩 버튼 -->
            <!-- x:Name: C# 코드에서 이 버튼을 참조할 때 사용할 변수명 -->
            <!-- Content: 버튼에 표시될 텍스트 -->
            <!-- Width/Height: 버튼의 고정 크기 (픽셀 단위) -->
            <!-- Margin="5": 사방으로 5픽셀 여백 (버튼 간 간격 조절) -->
            <!-- Click: 버튼 클릭 시 호출될 C# 메서드명 지정 -->
            <Button x:Name="LoadImageButton" Content="Load Image" 
                   Width="100" Height="30" Margin="5" Click="LoadImageButton_Click"/>

            <!-- 블러링 처리 버튼 -->
            <!-- LoadImageButton과 동일한 스타일과 크기로 일관성 유지 -->
            <Button x:Name="BlurButton" Content="Blurring" 
                   Width="100" Height="30" Margin="5" Click="BlurButton_Click"/>
        </StackPanel>

        <!-- 이미지 표시용 컨트롤 -->
        <!-- Grid.Row="1": 두 번째 행(인덱스 1)에 배치 -->
        <!-- Stretch="Uniform": 이미지의 가로세로 비율을 유지하면서 컨트롤 크기에 맞춤 -->
        <!-- 다른 Stretch 옵션들: -->
        <!-- - None: 원본 크기 유지 -->
        <!-- - Fill: 비율 무시하고 컨트롤 크기에 완전히 맞춤 -->
        <!-- - UniformToFill: 비율 유지하면서 컨트롤을 완전히 채움 (잘림 가능) -->
        <!-- Margin="10": 이미지 주변에 10픽셀 여백으로 깔끔한 외관 -->
        <Image x:Name="ImageDisplay" Grid.Row="1" 
              Stretch="Uniform" Margin="10,10,-247,-1153"/>
    </Grid>
</Window>

- MainWindow.xaml

// System 네임스페이스: .NET Framework의 기본 시스템 타입들을 포함
// 주요 클래스: Exception, EventArgs, ArgumentException 등
// 이 프로젝트에서 사용: Exception 처리, EventArgs 매개변수
using System;

// System.Runtime.InteropServices 네임스페이스: 관리 코드와 비관리 코드 간의 상호 운용성 제공
// 주요 클래스: Marshal - 메모리 관리 및 포인터 조작을 위한 정적 메서드들 포함
// 이 프로젝트에서 사용: Marshal.Copy() - OpenCV Mat의 네이티브 메모리 데이터를 관리 배열로 복사
using System.Runtime.InteropServices;

// System.Windows.Media 네임스페이스: WPF의 미디어 관련 기능 제공
// 주요 클래스: PixelFormat, PixelFormats - 이미지의 픽셀 형식 정의
// 이 프로젝트에서 사용: PixelFormats.Bgr24, PixelFormats.Gray8 등 이미지 형식 지정
using System.Windows.Media;

// System.Windows.Media.Imaging 네임스페이스: WPF의 이미지 처리 및 비트맵 관련 기능
// 주요 클래스: BitmapSource - WPF에서 이미지를 표시하기 위한 기본 클래스
// 이 프로젝트에서 사용: BitmapSource.Create() - Mat 데이터로부터 WPF 표시용 비트맵 생성
using System.Windows.Media.Imaging;

// Microsoft.Win32 네임스페이스: Windows 운영체제와의 상호작용을 위한 클래스들
// 주요 클래스: OpenFileDialog - 파일 선택 대화상자 제공
// 이 프로젝트에서 사용: 이미지 파일 선택을 위한 파일 대화상자 표시
using Microsoft.Win32;

// OpenCvSharp 네임스페이스: OpenCV 라이브러리의 C# 래퍼
// 주요 클래스: Mat (이미지 데이터 컨테이너), Cv2 (OpenCV 함수들), ImreadModes, Size 등
// 이 프로젝트에서 사용: 
// - Mat: 이미지 데이터 저장 및 조작
// - Cv2.GaussianBlur(): 가우시안 블러 필터 적용
// - ImreadModes.Color: 컬러 이미지로 읽기 지정
// - Size: 블러 커널 크기 지정
using OpenCvSharp;

namespace wpf_opencv_blur
{
    public partial class MainWindow : System.Windows.Window
    {
        private Mat originalImage;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void LoadImageButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "Image files (*.jpg;*.jpeg;*.png;*.bmp)|*.jpg;*.jpeg;*.png;*.bmp",
                Title = "이미지 파일을 선택하세요"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    // 기존 이미지 해제
                    originalImage?.Dispose();

                    // 새 이미지 로드
                    originalImage = new Mat(openFileDialog.FileName, ImreadModes.Color);
                    DisplayImage(originalImage);
                }
                catch (Exception ex)
                {
                    System.Windows.MessageBox.Show($"이미지 로딩 중 오류가 발생했습니다: {ex.Message}");
                }
            }
        }

        private void BlurButton_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (originalImage == null || originalImage.Empty())
            {
                System.Windows.MessageBox.Show("먼저 이미지를 로드해주세요.");
                return;
            }

            try
            {
                Mat blurredImage = new Mat();

                // 가우시안 블러 적용
                Cv2.GaussianBlur(originalImage, blurredImage, new OpenCvSharp.Size(15, 15), 3);

                // 블러링된 이미지 표시
                DisplayImage(blurredImage);

                // 메모리 해제
                blurredImage.Dispose();
            }
            catch (Exception ex)
            {
                System.Windows.MessageBox.Show($"블러링 처리 중 오류가 발생했습니다: {ex.Message}");
            }
        }

        private void DisplayImage(Mat image)
        {
            if (image != null && !image.Empty())
            {
                BitmapSource bitmapSource = MatToBitmapSource(image);
                if (bitmapSource != null)
                {
                    ImageDisplay.Source = bitmapSource;
                }
            }
        }

        private BitmapSource MatToBitmapSource(Mat mat)
        {
            if (mat.Empty())
                return null;

            try
            {
                // Mat의 데이터를 byte 배열로 변환
                byte[] imageData = new byte[mat.Rows * mat.Cols * mat.Channels()];
                Marshal.Copy(mat.Data, imageData, 0, imageData.Length);

                // PixelFormat 결정
                PixelFormat pixelFormat;
                switch (mat.Channels())
                {
                    case 1:
                        pixelFormat = PixelFormats.Gray8;
                        break;
                    case 3:
                        pixelFormat = PixelFormats.Bgr24;
                        break;
                    case 4:
                        pixelFormat = PixelFormats.Bgra32;
                        break;
                    default:
                        throw new ArgumentException("지원되지 않는 채널 수입니다.");
                }

                // BitmapSource 생성
                int stride = mat.Cols * mat.Channels();
                BitmapSource bitmapSource = BitmapSource.Create(
                    mat.Cols, mat.Rows,
                    96, 96,
                    pixelFormat,
                    null,
                    imageData,
                    stride
                );

                return bitmapSource;
            }
            catch (Exception ex)
            {
                System.Windows.MessageBox.Show($"이미지 변환 중 오류: {ex.Message}");
                return null;
            }
        }

        /// <summary>
        /// 윈도우가 닫힐 때 호출되는 메서드를 재정의합니다.
        /// 이 메서드는 윈도우가 완전히 닫히기 전에 필요한 정리 작업을 수행합니다.
        /// </summary>
        /// <param name="e">윈도우 닫기 이벤트와 관련된 이벤트 데이터</param>
        protected override void OnClosed(EventArgs e)
        {
            // null 조건부 연산자(?.)를 사용하여 originalImage가 null이 아닌 경우에만 Dispose() 호출
            // OpenCV의 Mat 객체는 네이티브 메모리를 사용하므로 명시적으로 해제해야 함
            // Dispose()를 호출하지 않으면 메모리 누수(memory leak)가 발생할 수 있음
            originalImage?.Dispose();

            // 부모 클래스(System.Windows.Window)의 OnClosed 메서드를 호출
            // 이는 WPF 윈도우의 기본 정리 작업을 수행하기 위해 반드시 필요함
            // 예: 이벤트 핸들러 해제, 윈도우 리소스 정리 등
            base.OnClosed(e);
        }
    }
}