3단계: ProcessImage 메서드 구현
ProcessImage 메서드는 템플릿 매칭을 사용하여 게임 화면 영역을 검출하고, 검출된 영역을 GameWindowRect 속성에 저장한다.
// ViewModels/CameraViewModel.cs
// ... (생략) ...
private void ProcessImage(Mat image)
{
try
{
// 템플릿 이미지 파일 경로
string[] templatePaths = { "Resources/top_left.png", "Resources/bottom_right.png" }; // 템플릿 이미지 파일 경로를 Resources 폴더로 변경
// 각 템플릿 이미지에 대한 매칭 결과를 저장할 리스트
List<Point> matchPoints = new List<Point>();
// 각 템플릿 이미지에 대해 템플릿 매칭 수행
foreach (string templatePath in templatePaths)
{
// 템플릿 이미지 로드
Mat template = Cv2.ImRead(templatePath, ImreadModes.Grayscale);
// 템플릿 이미지 로드 확인
if (template.Empty())
{
Console.WriteLine($"템플릿 이미지 로드 실패: {templatePath}");
continue;
}
// 템플릿 이미지 크기 확인
if (template.Width == 0 || template.Height == 0)
{
Console.WriteLine($"템플릿 이미지 크기 오류: {templatePath}");
continue;
}
// 이미지 타입 확인
if (image.Type() != template.Type())
{
Console.WriteLine($"이미지 타입 불일치: 입력 이미지 - {image.Type()}, 템플릿 이미지 - {template.Type()}");
Cv2.CvtColor(image, image, ColorConversionCodes.BGR2GRAY);
}
// 템플릿 이미지가 입력 이미지보다 큰 경우 크기 조정
if (template.Width > image.Width || template.Height > image.Height)
{
Cv2.Resize(template, template, new OpenCvSharp.Size(image.Width / 2, image.Height / 2));
}
// 템플릿 매칭 결과를 저장할 Mat 객체 생성
Mat result = new Mat();
// MatchTemplate() 함수를 사용하여 템플릿 매칭 수행
Cv2.MatchTemplate(image, template, result, TemplateMatchModes.CCoeffNormed);
// 매칭 결과에서 최댓값과 그 위치를 찾음
double minVal, maxVal;
Point minLoc, maxLoc;
Cv2.MinMaxLoc(result, out minVal, out maxVal, out minLoc, out maxLoc);
// 최댓값이 임계값보다 크면 객체를 찾은 것으로 판단
if (maxVal > 0.8)
{
// 템플릿의 크기를 고려하여 객체의 중심 좌표 계산
Point center = new Point(maxLoc.X + template.Width / 2, maxLoc.Y + template.Height / 2);
matchPoints.Add(center);
}
}
// 매칭 결과를 이용하여 게임 화면 영역 검출
if (matchPoints.Count == 2)
{
Point topLeft = matchPoints[0];
Point bottomRight = matchPoints[1];
int x = (int)topLeft.X;
int y = (int)topLeft.Y;
int width = (int)(bottomRight.X - topLeft.X);
int height = (int)(bottomRight.Y - topLeft.Y);
Rect gameWindowRect = new Rect(x, y, width, height);
// 게임 화면 영역 업데이트
GameWindowRect = gameWindowRect;
}
}
catch (OpenCvSharp.OpenCVException ex)
{
Console.WriteLine($"OpenCV 예외 발생: {ex.Message}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"파일을 찾을 수 없습니다: {ex.FileName}");
}
catch (Exception ex)
{
Console.WriteLine($"예외 발생: {ex.Message}");
}
}
// ... (생략) ...
4단계: View (MainWindow.xaml) 수정
MainWindow.xaml은 CameraViewModel의 속성과 Command를 바인딩하여 웹캠 화면, 게임 화면 영역, 캡처 시작/중지 버튼을 표시한다.
<Window ...>
<Window.DataContext>
<local:CameraViewModel />
</Window.DataContext>
<Grid>
<Image Source="{Binding CameraImage}" />
<Rectangle Stroke="Red" StrokeThickness="2"
Visibility="{Binding GameWindowRect, Converter={StaticResource RectToVisibilityConverter}}"
Width="{Binding GameWindowRect.Width}" Height="{Binding GameWindowRect.Height}"
Margin="{Binding GameWindowRect.Left}, {Binding GameWindowRect.Top}, 0, 0" />
<Button Content="캡처 시작" Command="{Binding StartCaptureCommand}" />
<Button Content="캡처 중지" Command="{Binding StopCaptureCommand}" />
</Grid>
</Window>
5단계: RectToVisibilityConverter 추가
RectToVisibilityConverter는 GameWindowRect 속성 값에 따라 Rectangle 컨트롤의 Visibility를 설정하는 컨버터다.
// Converters/RectToVisibilityConverter.cs
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace OpenCvSharpProjects
{
public class RectToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Rect rect && !rect.IsEmpty)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
6단계: ViewModelBase 클래스 및 RelayCommand 클래스 구현
나는 ViewModelBase와 RelayCommand 클래스를 직접 구현할 것이다. 굳이 안해도 되는 단계니까, 혹시 내 글을 보면서 만드시는 분이 있다면 참고바란다.
ViewModelBase 클래스와 RelayCommand 클래스는 MVVM 패턴을 구현할 때 자주 사용되는 클래스들이다. ViewModelBase 클래스를 사용하여 ViewModel 클래스에서 반복적인 코드를 줄이고, RelayCommand 클래스를 사용하여 Command를 간결하게 구현할 수 있다.
ViewModelBase 클래스
INotifyPropertyChanged 인터페이스 구현: ViewModelBase 클래스는 INotifyPropertyChanged 인터페이스를 구현하여 ViewModel의 속성 값이 변경될 때 View에 알림을 전달하는 역할을 한다. 이를 통해 View는 ViewModel의 변경 사항을 자동으로 반영하여 UI를 업데이트할 수 있다.ViewModelBase 클래스는 OnPropertyChanged() 메서드를 제공하여 속성 변경 알림을 발생시킨다. ViewModel에서 속성 값을 변경할 때마다 OnPropertyChanged() 메서드를 호출하면 View에 변경 사항이 전달된다.ViewModelBase 클래스를 사용하면 모든 ViewModel에서 INotifyPropertyChanged 인터페이스를 구현하고 OnPropertyChanged() 메서드를 작성해야 하는 번거로움을 줄일 수 있다.RelayCommand 클래스
ICommand 인터페이스 구현: RelayCommand 클래스는 ICommand 인터페이스를 구현하여 View에서 ViewModel의 메서드를 실행할 수 있도록 하는 Command를 구현하는 역할을 한다.RelayCommand 객체를 생성할 때 ViewModel의 메서드를 인자로 전달하면, View에서 해당 Command를 실행할 때 전달된 메서드가 호출된다.RelayCommand는 CanExecute() 메서드를 통해 Command 실행 가능 여부를 제어할 수 있다. 예를 들어, 특정 조건을 만족해야만 버튼이 활성화되도록 할 수 있다.RelayCommand 클래스를 사용하면 View에서 이벤트 핸들러를 작성하지 않고도 ViewModel의 메서드를 직접 실행할 수 있어 코드가 간결해진다.사용 방법
ViewModelBase 클래스를 상속받습니다. OnPropertyChanged() 메서드를 호출하여 View에 변경 사항을 알립니다.RelayCommand 클래스를 사용합니다. ViewModel에서 RelayCommand 객체를 생성하고, 실행할 메서드를 인자로 전달합니다. (RelayCommand 생성자에 실행할 메서드와 실행 조건(선택 사항임)을 전달합니다.)RelayCommand 객체를 컨트롤 (예: Button)의 Command 속성에 바인딩합니다.// ViewModels/ViewModelBase.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace OpenCvSharpProjects
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
// ViewModels/RelayCommand.cs
using System;
using System.Windows.Input;
namespace OpenCvSharpProjects
{
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
}
}
7단계: App.xaml에 리소스 추가
RectToVisibilityConverter를 사용하기 위해 App.xaml에 리소스를 추가한다.
<Application ...>
<Application.Resources>
<local:RectToVisibilityConverter x:Key="RectToVisibilityConverter" />
</Application.Resources>
</Application>
MVVM 패턴을 적용하면 코드가 더욱 구조화되고, 각 요소의 역할이 명확해져 유지보수와 테스트가 용이해진다.