(3) MVVM 패턴 적용하여 코드 개선_6

00·2025년 2월 1일

개요

카메라 기능을 제외하고 나머지 기능을 먼저 구현한 후에 마지막에 카메라 기능을 추가하기로 했다.

카메라 기능을 나중에 추가하는 이유

  • 디버깅 용이성: 카메라 기능을 제외하면 프로그램 실행 속도가 빨라지고, 디버깅 과정이 간편해집니다. 특히, 이미지 처리 및 객체 인식 로직을 개발할 때 카메라 입력 없이 테스트 이미지를 사용하면 디버깅 시간을 단축할 수 있습니다.
  • 모듈화: 카메라 기능을 분리하면 코드 모듈화가 더 잘 이루어집니다. WebcamService 클래스는 카메라 관련 기능만 담당하고, MainWindowViewModel은 이미지 처리 및 게임 로직에 집중할 수 있습니다.
  • 테스트 용이성: 카메라 기능을 분리하면 각 기능을 개별적으로 테스트하기 용이합니다. 예를 들어, ImageProcessingServiceProcessImage() 메서드를 테스트할 때, 실제 카메라 입력 없이 테스트 이미지를 사용하여 테스트할 수 있습니다.

카메라 기능 제외 방법

  1. MainWindowViewModel 수정: MainWindowViewModel에서 WebcamService를 사용하는 부분을 주석 처리하거나 제거합니다. StartCapture(), StopCapture(), UpdateFrame() 메서드를 수정해야 합니다.

    // ViewModels/MainWindowViewModel.cs
    
    public MainWindowViewModel()
    {
        // ...
    
        // webcamService = new WebcamService(); // 주석 처리 또는 제거
    
        // ...
    }
    
    private async void StartCapture()
    {
        // await webcamService.StartCaptureAsync(); // 주석 처리 또는 제거
        // CompositionTarget.Rendering += UpdateFrame; // 주석 처리 또는 제거
    }
    
    private void StopCapture()
    {
        // webcamService.StopCapture(); // 주석 처리 또는 제거
        // CompositionTarget.Rendering -= UpdateFrame; // 주석 처리 또는 제거
    }
    
    private async void UpdateFrame(object? sender, EventArgs e)
    {
        // Mat frame = await webcamService.GetFrameAsync(); // 주석 처리 또는 제거
    
        // if (!frame.Empty()) // 주석 처리 또는 제거
        // {
            // ...
        // }
    }
  2. 테스트 이미지 사용: ImageProcessingServiceProcessImage() 메서드를 테스트할 때, 실제 카메라 입력 대신 테스트 이미지를 사용합니다.

    // Services/ImageProcessingService.cs
    public GameInfo ProcessImage(Mat image)
    {
        // ...
    
        // 테스트 이미지 로드
        Mat testImage = Cv2.ImRead("test_image.png"); 
    
        // 테스트 이미지를 사용하여 이미지 처리 로직을 수행합니다.
        // ...
    
        return gameInfo;
    }

카메라 기능 추가

나머지 기능 구현을 완료한 후, 카메라 기능을 추가할 때는 주석 처리했던 코드를 다시 활성화하고, WebcamService를 사용하는 코드를 추가하면 됩니다.

주의 사항

  • 카메라 기능을 제외하고 개발하는 동안에는 CameraImage 속성과 GameWindowRect 속성이 제대로 업데이트되지 않을 수 있습니다.
  • 카메라 기능을 추가할 때는 WebcamService 클래스가 제대로 동작하는지 확인해야 합니다.

웹캠에서 캡처한 이미지를 표시하는 Image 컨트롤과 캡처 시작/중지 버튼을 제거

웹캠에서 캡처한 이미지를 표시하는 Image 컨트롤과 캡처 시작/중지 버튼을 제거한 후에는, MainWindow.xaml에서 노트북 화면을 인식하고 있다는 것을 직접적으로 확인할 수 있는 UI 요소가 없습니다.

하지만, ImageProcessingService에서 맵 영역을 인식하면 GameWindowRect 속성에 맵 영역의 좌표와 크기 정보가 저장됩니다. 이 정보는 MainWindowViewModelGameWindowRect 속성에 바인딩되고, MainWindow.xaml에서 Rectangle 컨트롤을 사용하여 빨간색 테두리로 표시됩니다.

따라서, 맵 영역이 인식되면 MainWindow 창에 빨간색 테두리가 표시되어 미니맵 영역을 시각적으로 확인할 수 있습니다.

게임 플레이 시 확인

게임을 플레이하면서 미니맵 영역이 화면에 표시되는지 확인할 수 있습니다.

  • 만약 미니맵 영역이 제대로 인식된다면, Rectangle 컨트롤이 미니맵 영역을 따라 이동하면서 빨간색 테두리로 표시될 것입니다.
  • 미니맵 영역이 인식되지 않는다면, Rectangle 컨트롤이 표시되지 않거나 잘못된 위치에 표시될 것입니다.

추가적인 확인 방법

  • 디버깅: Visual Studio의 디버거를 사용하여 GameWindowRect 속성 값을 확인하고, 미니맵 영역이 제대로 인식되는지 확인할 수 있습니다.
  • 로그 출력: ImageProcessingService에서 미니맵 영역 인식 결과를 로그로 출력하여 확인할 수 있습니다.

결론

웹캠 기능을 제외하고 MainWindow.xaml에서 Image 컨트롤과 캡처 시작/중지 버튼을 제거한 경우, 미니맵 영역 인식 여부는 Rectangle 컨트롤의 빨간색 테두리를 통해 확인할 수 있습니다. 게임을 플레이하면서 미니맵 영역이 제대로 인식되는지 확인하고, 필요하다면 디버깅 또는 로그 출력을 통해 문제를 해결할 수 있습니다.

로그 출력

하나 하나 중단점 설정하고 디버깅하기가 힘들어서, 로그 출력을 설정하기로 했다.

로그 출력을 설정하면 맵 영역 인식 결과를 쉽게 확인하고 디버깅할 수 있다.

ImageProcessingService에서 맵 영역 인식 결과를 로그로 출력하여 확인하기 위한 작업은 다음과 같은 순서로 진행했다.

1. 로깅 라이브러리 추가

로그 출력을 위해 NLog 라이브러리를 사용. NuGet 패키지 관리자를 사용하여 NLog 패키지를 프로젝트에 추가할 수 있다.

2. NLog 설정 파일 추가

프로젝트에 NLog.config 파일을 추가하고, 로그 출력 형식, 출력 위치 등을 설정한다.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" 
      internalLogFile="c:\temp\nlog-internal.log">

  <targets>
    <target xsi:type="File" name="logfile" fileName="logs/log.txt" 
            layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
    <target xsi:type="Console" name="logconsole" 
            layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
  </targets>

  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile, logconsole" />
  </rules>
</nlog>

이 XML 코드는 NLog 라이브러리의 설정을 정의한다.

  • targets: 로그 출력 대상을 정의한다. 여기서는 파일 (logfile)과 콘솔 (logconsole) 두 개의 대상을 정의했다.
  • rules: 로깅 규칙을 정의한다. 여기서는 모든 로거 (*)에서 Debug 레벨 이상의 로그를 logfilelogconsole 대상에 출력하도록 설정했다.

추가 정보

  • NLog 구성 파일은 XML 형식으로 작성됩니다.
  • NLog 구성 파일은 프로젝트의 루트 폴더에 있어야 NLog 라이브러리가 자동으로 찾을 수 있습니다.
  • NLog 구성 파일의 자세한 설정 방법은 NLog 공식 문서를 참조할 수 있습니다.

3. 코드 수정

ImageProcessingService 클래스의 ProcessImage() 메서드에서 NLog를 사용하여 미니맵 영역 인식 결과를 로그로 출력한다.

// Services/ImageProcessingService.cs
using NLog;
using OpenCvSharp;
using OpenCvSharpProjects.Models;
// ...

public class ImageProcessingService
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger(); // Logger 객체 생성

    // ...

    public GameInfo ProcessImage(Mat image)
    {
        // ...

        try
        {
            // ...

            // 미니맵 영역 검출
            if (matchPoints.Count == 4)
            {
                // ...

                logger.Info("미니맵 영역 검출 성공"); // 로그 출력
            }
            else
            {
                logger.Warn("미니맵 영역 검출 실패"); // 로그 출력
            }

            // ...
        }
        // ...
    }
}

코드 설명

  • private static readonly Logger logger = LogManager.GetCurrentClassLogger();: 현재 클래스의 Logger 객체를 생성한다.
  • logger.Info("미니맵 영역 검출 성공");: 미니맵 영역 검출 성공 시 로그를 출력한다.
  • logger.Warn("미니맵 영역 검출 실패");: 미니맵 영역 검출 실패 시 로그를 출력한다.

로그 확인

프로그램을 실행하면 logs 폴더에 log.txt 파일이 생성되고, 로그 메시지가 파일에 기록된다. Visual Studio의 "출력" 창에서도 로그 메시지를 확인할 수 있다.

추가 정보

  • NLog는 다양한 로그 출력 방식 (파일, 콘솔, 데이터베이스 등)을 지원합니다.
  • NLog.config 파일에서 로그 출력 형식, 출력 위치, 로그 레벨 등을 설정할 수 있습니다.
  • NLog는 로그 메시지에 추가적인 정보 (예: 시간, 로그 레벨, 클래스 이름)를 포함할 수 있습니다.

네, ImageProcessingService에서 미니맵 영역 인식 결과를 로그로 출력하여 확인하는 방법을 추천합니다.

로그 출력 장점

  • 디버깅 용이성: 로그를 통해 미니맵 영역 인식 과정을 단계별로 확인할 수 있으므로, 오류 발생 시 원인을 파악하고 디버깅하기 용이해집니다. 특히, 이미지 처리 및 객체 인식은 복잡한 과정을 거치기 때문에 로그를 통해 각 단계의 결과를 확인하는 것이 중요합니다.
  • 문제 분석: 로그를 분석하여 미니맵 인식 실패 원인을 파악하고, 인식률을 개선하는 데 도움을 얻을 수 있습니다. 예를 들어, 특정 템플릿 이미지에서 매칭이 실패하는 경우, 템플릿 이미지를 변경하거나 특징점 매칭 알고리즘을 조정하는 등의 개선을 시도할 수 있습니다.
  • 성능 측정: 로그에 시간 정보를 포함하면 미니맵 인식 속도를 측정하고 성능을 평가할 수 있습니다. 이를 통해 프로그램의 성능을 개선하는 데 도움이 됩니다.
  • 상태 모니터링: 로그를 통해 프로그램의 현재 상태를 실시간으로 모니터링할 수 있습니다. 미니맵 인식 성공 여부, 인식된 영역의 좌표, 처리 시간 등을 로그로 출력하여 프로그램의 동작 상태를 파악할 수 있습니다.

오류 발생

콘솔 창에 "미니맵 영역 검출 성공"나 "미니맵 영역 검출 실패"가 안뜬다.

시도

1. 출력 창 설정 확인

  • 출력 창이 Visual Studio 하단에 표시되는지 확인합니다. 혹시 탭이 숨겨져 있는 경우 "보기" 메뉴 > "출력"을 클릭하여 출력 창을 엽니다.
  • 출력 창 왼쪽 위의 드롭다운 메뉴에서 "디버그"가 선택되어 있는지 확인합니다. "빌드", "빌드 순서" 등 다른 옵션이 선택되어 있으면 Console.WriteLine() 출력이 표시되지 않습니다.
  • 출력 창에 이전 내용이 많아서 새로운 내용이 덮어씌워지는 경우, 출력 창을 마우스 오른쪽 버튼으로 클릭하고 "지우기"를 선택하여 내용을 비웁니다.

-> 이상 없음

2. 빌드 구성 확인

  • "디버그" 모드로 빌드하고 실행 중인지 확인합니다. "릴리스" 모드에서는 Console.WriteLine() 출력이 표시되지 않을 수 있습니다.
  • 솔루션 구성이 "Debug"로 설정되어 있는지 확인합니다.

-> 이상 없음

3. 코드 실행 확인

  • ImageProcessingServiceProcessImage() 메서드가 실제로 실행되는지 확인합니다.
    • ProcessImage() 메서드 안에 중단점을 설정하고 디버깅 모드로 실행하여 메서드가 호출되는지 확인합니다.
    • ProcessImage() 메서드 안에 다른 Console.WriteLine() 문을 추가하여 테스트합니다.

4. NLog 설정 확인

  • NLog.config 파일이 프로젝트의 루트 폴더에 있는지 확인합니다.
  • NLog.config 파일의 내용이 올바른지 확인합니다. 특히, targets 섹션에서 logconsole 타겟이 제대로 설정되어 있는지 확인합니다.
  • NLog.config 파일의 rules 섹션에서 minlevel 속성이 Debug 또는 더 낮은 레벨로 설정되어 있는지 확인합니다.

-> 이상 없음

5. NLog 초기화 확인

  • ImageProcessingService 클래스에서 NLog를 사용하기 전에 LogManager.GetCurrentClassLogger() 메서드를 호출하여 Logger 객체를 초기화했는지 확인합니다.

-> 이상 없음

6. Visual Studio 재시작

  • Visual Studio를 다시 시작하여 문제가 해결되는지 확인합니다.

-> 이상 없음


시도

MainWindowViewModel.cs 코드 수정

// ViewModels/MainWindowViewModel.cs
// ...

private async void UpdateFrame(object? sender, EventArgs e)
{
    // ...

    await Task.Run(() =>
    {
        gameInfo = imageProcessingService.ProcessImage(frame.Clone());
        GameWindowRect = gameInfo.GameWindowRect;

        // 미니맵 영역 검출 결과 확인
        if (GameWindowRect.Width > 0 && GameWindowRect.Height > 0)
        {
            Console.WriteLine("미니맵 영역 검출 성공");
        }
        else
        {
            Console.WriteLine("미니맵 영역 검출 실패");
        }
    });

    // ...
}

// ...

시도

  • ImageProcessingService에서 로그를 출력할 때 사용하는 로그 레벨이 minlevel 설정보다 높은 레벨이 아닌지 확인합니다. 예를 들어, minlevelDebug로 설정되어 있는데 logger.Info() 메서드를 사용하여 로그를 출력하면 로그가 표시되지 않습니다.

-> rules 섹션에서 minlevel 속성이 Debug 또는 더 낮은 레벨로 설정.

minlevelDebug로 설정되어 있으면 logger.Info() 메서드로 출력한 로그는 표시되지 않습니다. logger.Info() 메서드는 Info 레벨의 로그를 출력하는 메서드이고, minlevelDebug로 설정된 경우 Debug 레벨 이상의 로그만 출력되기 때문입니다.

Debug 레벨의 로그를 출력하려면 logger.Debug() 메서드를 사용해야 합니다.

// Services/ImageProcessingService.cs
// ...

private Rect DetectGameWindow(Mat image)
{
    // ...

    // 꼭짓점 좌표를 이용하여 게임 화면 영역 계산
    if (matchPoints.Count == 4)
    {
        // ...

        logger.Debug("미니맵 영역 검출 성공"); // Debug 레벨 로그 출력
        return new OpenCvSharp.Rect(topLeft.X, topLeft.Y, width, height);
    }
    else
    {
        logger.Debug("미니맵 영역 검출 실패"); // Debug 레벨 로그 출력
        return new OpenCvSharp.Rect(); // 빈 Rect 객체 반환
    }
}

// ...

NLog 로그 레벨

NLog는 다음과 같은 로그 레벨을 제공합니다.

  • Trace: 가장 상세한 로그 레벨입니다.
  • Debug: 디버깅 정보를 출력하는 로그 레벨입니다.
  • Info: 일반적인 정보를 출력하는 로그 레벨입니다.
  • Warn: 경고 메시지를 출력하는 로그 레벨입니다.
  • Error: 오류 메시지를 출력하는 로그 레벨입니다.
  • Fatal: 치명적인 오류 메시지를 출력하는 로그 레벨입니다.

minlevel 속성에 설정된 로그 레벨 이상의 로그만 출력됩니다. 따라서 minlevelDebug로 설정된 경우 Debug, Info, Warn, Error, Fatal 레벨의 로그가 출력됩니다.

추가 정보

  • NLog는 각 로그 레벨에 해당하는 메서드를 제공합니다. (예: logger.Trace(), logger.Debug(), logger.Info(), logger.Warn(), logger.Error(), logger.Fatal())
  • 로그 레벨을 적절히 사용하면 로그 메시지를 효율적으로 관리하고 분석할 수 있습니다.

시도

gameInfo.GameWindowRect 값은 DetectGameWindow() 메서드 내에서 계산되므로, DetectGameWindow() 메서드 안에서 로그를 출력해야 합니다.

수정된 ImageProcessingService.cs 코드

// Services/ImageProcessingService.cs
// ...

private Rect DetectGameWindow(Mat image)
{
    // ...

    // 꼭짓점 좌표를 이용하여 게임 화면 영역 계산
    if (matchPoints.Count == 4)
    {
        // ...

        logger.Info("미니맵 영역 검출 성공"); // 로그 출력
        return new OpenCvSharp.Rect(topLeft.X, topLeft.Y, width, height);
    }
    else
    {
        logger.Warn("미니맵 영역 검출 실패"); // 로그 출력
        return new OpenCvSharp.Rect(); // 빈 Rect 객체 반환
    }
}

// ...

코드 설명

  • DetectGameWindow() 메서드:
    • if (matchPoints.Count == 4): 미니맵 영역 검출에 성공하면 logger.Info("미니맵 영역 검출 성공");을 실행하여 로그를 출력합니다.
    • else: 미니맵 영역 검출에 실패하면 logger.Warn("미니맵 영역 검출 실패");를 실행하여 로그를 출력합니다.

시도

UpdateFrame() 메서드 안에 아무런 코드도 없다면, ImageProcessingService.ProcessImage() 메서드가 호출되지 않아 미니맵 영역 검출이 이루어지지 않고, 따라서 "미니맵 영역 검출 성공" 또는 "미니맵 영역 검출 실패" 메시지가 출력되지 않는다.

UpdateFrame() 메서드는 웹캠에서 프레임을 가져와 이미지 처리를 수행하고, CameraImage 속성을 업데이트하는 역할을 합니다. 웹캠 기능을 제외했더라도, ImageProcessingService.ProcessImage() 메서드를 호출하여 미니맵 영역을 검출하고 GameWindowRect 속성을 업데이트해야 합니다.

MainWindowViewModel.cs 코드 수정

// ViewModels/MainWindowViewModel.cs
// ... (생략) ...

// 프레임 업데이트 메서드
private async void UpdateFrame(object? sender, EventArgs e)
{
    // 테스트 이미지를 사용하여 이미지 처리를 수행합니다.
    Mat frame = Cv2.ImRead("Resources/minimap_template.png", ImreadModes.Color); // 테스트 이미지 로드

    if (!frame.Empty())
    {
        // 이미지 처리 및 게임 정보 업데이트
        await Task.Run(() =>
        {
            gameInfo = imageProcessingService.ProcessImage(frame.Clone());
            GameWindowRect = gameInfo.GameWindowRect;
            IsMinimapDetected = gameInfo.IsMinimapDetected;
        });
    }
}

// ... (생략) ...

코드 설명

  • Mat frame = Cv2.ImRead("Resources/minimap_template.png", ImreadModes.Color);: Resources 폴더에 있는 minimap_template.png 파일을 컬러 이미지로 로드합니다.
  • if (!frame.Empty()): 이미지가 제대로 로드되었는지 확인합니다.
  • await Task.Run(() => ...);: Task.Run() 메서드를 사용하여 이미지 처리 및 게임 정보 업데이트 작업을 별도 스레드에서 비동기적으로 실행합니다.
    • gameInfo = imageProcessingService.ProcessImage(frame.Clone());: ImageProcessingServiceProcessImage() 메서드를 호출하여 이미지 처리를 수행하고, 결과를 gameInfo 객체에 저장합니다.
    • GameWindowRect = gameInfo.GameWindowRect;: gameInfo 객체에서 게임 화면 영역 정보를 가져와 GameWindowRect 속성에 설정합니다.
    • IsMinimapDetected = gameInfo.IsMinimapDetected;: gameInfo 객체에서 미니맵 인식 여부를 가져와 IsMinimapDetected 속성에 설정합니다.

시도

DetectGameWindow() 메서드 안에 로그 출력 코드를 추가했지만, 여전히 로그가 출력되지 않는 문제가 발생.

ImageProcessingService.cs 코드를 살펴보니, 특징점 매칭 결과를 저장하는 matchPoints 리스트가 템플릿 이미지마다 초기화되고 있는 것을 발견했다.

즉, foreach 루프 안에서 matchPoints 리스트를 매번 새로 생성하고 있기 때문에, 두 개의 템플릿 이미지에서 매칭된 특징점을 모두 저장할 수 없다. 따라서 matchPoints.Count는 항상 2 이하가 되고, if (matchPoints.Count == 4) 조건문이 실행되지 않아 로그가 출력되지 않는다.

해결 방법

matchPoints 리스트를 foreach 루프 밖에서 한 번만 생성하고, 루프 안에서는 matchPoints 리스트에 특징점을 추가하는 방식으로 코드를 수정해야 한다.

// Services/ImageProcessingService.cs
// ...

private Rect DetectGameWindow(Mat image)
{
    // 템플릿 이미지 파일 경로
    string[] templatePaths = { "Resources/top_left.png", "Resources/bottom_right.png" };

    // 각 템플릿 이미지에 대한 매칭 결과를 저장할 리스트
    var matchPoints = new List<Point2f>(); // foreach 루프 밖에서 리스트 생성

    // 각 템플릿 이미지에 대해 특징점 매칭 수행
    foreach (var templatePath in templatePaths)
    {
        // ... (생략) ...

        // 매칭 결과를 이용하여 템플릿 위치 계산
        if (goodMatches.Count >= 4) // 최소 4개의 매칭점 필요
        {
            // ... (생략) ...

            matchPoints.AddRange(imageCorners); // matchPoints 리스트에 특징점 추가
        }
    }

    // 꼭짓점 좌표를 이용하여 게임 화면 영역 계산
    if (matchPoints.Count == 4) // matchPoints 리스트의 크기가 4인지 확인
    {
        // ... (생략) ...

        logger.Debug("미니맵 영역 검출 성공"); // 로그 출력
        return new OpenCvSharp.Rect(topLeft.X, topLeft.Y, width, height);
    }
    else
    {
        logger.Debug("미니맵 영역 검출 실패"); // 로그 출력
        return new OpenCvSharp.Rect(); // 빈 Rect 객체 반환
    }
}

// ...

코드 설명

  • var matchPoints = new List<Point2f>();: foreach 루프 밖에서 matchPoints 리스트를 한 번만 생성합니다.
  • matchPoints.AddRange(imageCorners);: foreach 루프 안에서 matchPoints 리스트에 특징점을 추가합니다.
  • if (matchPoints.Count == 4): matchPoints 리스트의 크기가 4인지 확인합니다. 즉, 두 개의 템플릿 이미지에서 매칭된 특징점이 모두 저장되었는지 확인합니다.

이렇게 수정하면 두 개의 템플릿 이미지에서 매칭된 특징점을 모두 matchPoints 리스트에 저장하고, 미니맵 영역 검출 결과에 따라 로그가 올바르게 출력될 수 있도록 수정된다.


시도(해결)

UpdateFrame() 메서드 안의 코드를 제거하거나 주석 처리.

rivate async void UpdateFrame(object? sender, EventArgs e)

{





    // 테스트 이미지를 사용하여 이미지 처리를 수행합니다.

    Mat frame = Cv2.ImRead("Resources/minimap_template.png", ImreadModes.Color); // 테스트 이미지 로드



    if (!frame.Empty())

    {

        // 이미지 처리 및 게임 정보 업데이트

        await Task.Run(() =>

        {

            gameInfo = imageProcessingService.ProcessImage(frame.Clone());

            GameWindowRect = gameInfo.GameWindowRect;

            IsMinimapDetected = gameInfo.IsMinimapDetected;

        });

    }

}

UpdateFrame() 메서드는 웹캠에서 프레임을 가져와 이미지 처리를 수행하고, CameraImage 속성을 업데이트하는 역할을 합니다.

현재 웹캠 기능을 제외했으므로, UpdateFrame() 메서드 자체는 필요하지 않습니다. 따라서, 메서드 전체를 삭제하거나 주석 처리해도 괜찮습니다.

하지만, ImageProcessingService.ProcessImage() 메서드를 호출하여 미니맵 영역을 검출하고 GameWindowRect 속성과 IsMinimapDetected 속성을 업데이트하는 부분은 필요합니다. 따라서, 이 부분은 UpdateFrame() 메서드에서 제거하고, MainWindowViewModel의 다른 메서드 (예: 생성자 또는 StartGame() 메서드)에서 호출해야 합니다.

수정된 MainWindowViewModel.cs 코드

// ViewModels/MainWindowViewModel.cs
// ... (생략) ...

public MainWindowViewModel()
{
    // ... (생략) ...

    // 테스트 이미지를 사용하여 이미지 처리를 수행합니다.
    Mat frame = Cv2.ImRead("Resources/minimap_template.png", ImreadModes.Color); // 테스트 이미지 로드

    if (!frame.Empty())
    {
        // 이미지 처리 및 게임 정보 업데이트
        Task.Run(() =>
        {
            gameInfo = imageProcessingService.ProcessImage(frame.Clone());
            GameWindowRect = gameInfo.GameWindowRect;
            IsMinimapDetected = gameInfo.IsMinimapDetected;
        });
    }
}

// ... (생략) ...

// 프레임 업데이트 메서드 (제거 또는 주석 처리)
// private async void UpdateFrame(object? sender, EventArgs e)
// {
//     // ...
// }

// ... (생략) ...

코드 설명

  • UpdateFrame() 메서드를 제거하거나 주석 처리했습니다.
  • MainWindowViewModel 생성자에서 테스트 이미지를 사용하여 이미지 처리를 수행하고, 게임 정보를 업데이트하는 코드를 추가했습니다.

이렇게 수정하면 웹캠 기능 없이도 ImageProcessingService.ProcessImage() 메서드가 호출되어 미니맵 영역 검출이 이루어지고, GameWindowRect 속성과 IsMinimapDetected 속성이 업데이트됩니다.

해결

UpdateFrame() 메서드를 제거하고 MainWindowViewModel.cs 코드를 수정하니까 콘솔창에 메시지가 뜨지 않던 오류가 해결됐다. 그리고 디버깅으로 메서드 내부도 확인이 가능해졌다.

콘솔창에 아래와 같은 오류가 메시지를 확인할 수 있었다.

OpenCV 예외 발생: The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array'
OpenCV 예외 발생: The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array'
2025-02-01 19:18:02.1649|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생
2025-02-01 19:18:02.1649|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생

오류

콘솔창에 아래와 같은 오류가 메시지를 확인할 수 있었다.

OpenCV 예외 발생: The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array'
OpenCV 예외 발생: The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array'
2025-02-01 19:18:02.1649|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생
2025-02-01 19:18:02.1649|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생

위 오류는 OpenCV 함수에서 입력 배열의 크기 또는 채널 수가 일치하지 않을 때 발생하는 일반적인 오류입니다.

이 오류는 Cv2.AddWeighted() 함수에서 발생하는 것으로 보입니다. Cv2.AddWeighted() 함수는 두 개의 이미지를 가중치를 적용하여 합성하는 함수인데, 두 이미지의 크기와 채널 수가 일치해야 합니다.

오류 원인 분석

  • Cv2.AddWeighted(testImage, 1.2, new Mat(), 0, 1.5, testImage); 코드를 보면, testImageminimap_template.png 파일을 로드한 이미지입니다. 이 이미지는 컬러 이미지이므로 3개의 채널 (B, G, R)을 가지고 있습니다.
  • new Mat()는 빈 이미지를 생성하는데, 이 이미지는 채널 수가 지정되지 않았으므로 기본적으로 1개의 채널을 갖습니다.
  • 따라서 Cv2.AddWeighted() 함수를 호출할 때, 3개의 채널을 가진 이미지와 1개의 채널을 가진 이미지를 합성하려고 하기 때문에 오류가 발생합니다.

해결 방법

Cv2.AddWeighted() 함수를 호출할 때, 두 이미지의 채널 수를 일치시켜야 합니다.

빈 이미지를 생성할 때 채널 수를 3으로 지정하거나, testImage를 그레이스케일 이미지로 변환하여 채널 수를 1로 만들 수 있습니다.

1. 빈 이미지 채널 수 변경

// 밝기/대비 조절 (예: 밝기 1.2배, 대비 1.5배)
Cv2.AddWeighted(testImage, 1.2, new Mat(testImage.Size(), testImage.Type()), 0, 1.5, testImage); // 빈 이미지의 크기와 타입을 testImage와 동일하게 설정

new Mat(testImage.Size(), testImage.Type()) 코드는 testImage와 크기와 타입이 같은 빈 이미지를 생성합니다. 이렇게 하면 두 이미지의 채널 수가 모두 3이 되어 오류가 해결됩니다.

2. testImage를 그레이스케일로 변환

// 밝기/대비 조절 (예: 밝기 1.2배, 대비 1.5배)
Mat grayTestImage = new Mat();
Cv2.CvtColor(testImage, grayTestImage, ColorConversionCodes.BGR2GRAY); // testImage를 그레이스케일로 변환
Cv2.AddWeighted(grayTestImage, 1.2, new Mat(), 0, 1.5, grayTestImage); // 그레이스케일 이미지 사용

Cv2.CvtColor() 함수를 사용하여 testImage를 그레이스케일 이미지 (grayTestImage)로 변환합니다. 이렇게 하면 두 이미지의 채널 수가 모두 1이 되어 오류가 해결됩니다.

어떤 방법을 선택해야 할까?

두 가지 방법 모두 오류를 해결할 수 있지만, 1번 방법이 더 간단하고 효율적입니다. 2번 방법은 이미지를 변환하는 과정이 추가되므로, 성능이 다소 떨어질 수 있습니다.

추가 정보

  • Cv2.AddWeighted() 함수는 두 이미지의 크기와 타입이 같아야 합니다.
  • Mat 클래스는 OpenCV에서 이미지를 표현하는 클래스입니다.
  • Mat.Size() 메서드는 이미지의 크기를 반환합니다.
  • Mat.Type() 메서드는 이미지의 타입을 반환합니다.
  • Cv2.CvtColor() 함수는 이미지의 색상 공간을 변환하는 함수입니다.
  • ColorConversionCodes 열거형은 색상 공간 변환 코드를 정의합니다.

오류

matchPoints.AddRange(imageCorners); 부분은 잘 수정되었지만, 여전히 "미니맵 영역 검출 실패" 로그가 출력되고 오류가 발생하는 것 같습니다.

문제 원인 분석

ImageProcessingServiceProcessImage() 메서드에서 testImage 변수에 minimap_template.png 이미지를 로드하고 있습니다. 이 이미지는 미니맵의 테두리 부분만 캡처한 이미지이기 때문에, 특징점 매칭을 수행할 때 imagetemplate 이미지가 크게 다릅니다.

즉, image는 미니맵을 포함한 전체 게임 화면이고, template은 미니맵 테두리 부분만 있는 이미지이므로, 특징점 매칭이 제대로 이루어지지 않아 matchPoints 리스트에 4개의 꼭짓점 좌표가 저장되지 않는 것입니다.

해결 방법

DetectGameWindow() 메서드에서 특징점 매칭을 수행하기 전에, 입력 이미지 (image)에서 미니맵 영역을 추출해야 합니다. 미니맵 영역을 추출하려면 먼저 미니맵의 대략적인 위치를 알아야 합니다.

1. 미니맵 템플릿 매칭

미니맵 템플릿 이미지 (minimap_template.png)를 사용하여 템플릿 매칭을 수행하고, 미니맵의 대략적인 위치를 찾습니다.

// Services/ImageProcessingService.cs
// ...

private Rect DetectGameWindow(Mat image)
{
    // ...

    // 미니맵 템플릿 매칭
    Mat result = new Mat();
    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) // 템플릿 매칭 성공
    {
        // 미니맵 영역 추출
        OpenCvSharp.Rect minimapRect = new OpenCvSharp.Rect(maxLoc.X, maxLoc.Y, template.Width, template.Height);
        Mat minimapImage = new Mat(image, minimapRect);

        // 특징점 검출 및 기술 (minimapImage 사용)
        KeyPoint[] keypoints1, keypoints2;
        Mat descriptors1 = new Mat(), descriptors2 = new Mat();
        orb.DetectAndCompute(minimapImage, null, out keypoints1, descriptors1);
        orb.DetectAndCompute(template, null, out keypoints2, descriptors2);

        // ... (나머지 특징점 매칭 로직) ...
    }
    else
    {
        logger.Debug("미니맵 템플릿 매칭 실패"); // 로그 출력
        return new OpenCvSharp.Rect(); // 빈 Rect 객체 반환
    }

    // ...
}

코드 설명

  • Cv2.MatchTemplate(): 템플릿 매칭을 수행합니다.
  • Cv2.MinMaxLoc(): 매칭 결과에서 최댓값과 그 위치를 찾습니다.
  • if (maxVal > 0.8): 템플릿 매칭 성공 시 미니맵 영역을 추출합니다.
  • OpenCvSharp.Rect minimapRect = new OpenCvSharp.Rect(maxLoc.X, maxLoc.Y, template.Width, template.Height);: 템플릿 매칭 결과를 기반으로 미니맵 영역을 계산합니다.
  • Mat minimapImage = new Mat(image, minimapRect);: 미니맵 영역을 추출합니다.
  • orb.DetectAndCompute(minimapImage, null, out keypoints1, descriptors1);: 추출된 미니맵 영역에서 특징점을 검출합니다.

2. 특징점 매칭

추출된 미니맵 영역에서 특징점 매칭을 수행합니다. 이때, 템플릿 이미지는 미니맵 테두리 이미지를 그대로 사용합니다.

주의 사항

  • 템플릿 매칭의 정확도를 높이기 위해 템플릿 이미지의 품질을 높이고, 조명 변화, 객체 회전 등에 대한 대비책을 마련해야 합니다.
  • 템플릿 매칭 결과를 검증하여 오탐을 제거해야 합니다.

이렇게 템플릿 매칭과 특징점 매칭을 함께 사용하면 미니맵 인식률을 높일 수 있습니다.

오류

testImage를 전달하도록 해놓은 상태다. 그래도 여전히 Index was outside the bounds of the array 오류가 발생하고있다.

코드 설명

  • DetectGameWindow() 메서드에서 템플릿 매칭을 수행하여 미니맵의 대략적인 위치를 찾습니다.
  • 템플릿 매칭 성공 시 미니맵 영역을 추출하고, 추출된 영역에서 특징점 매칭을 수행합니다.
  • 특징점 매칭 결과를 사용하여 미니맵 영역을 계산하고 반환합니다.

이제 미니맵 템플릿 매칭과 특징점 매칭을 함께 사용하여 미니맵 영역을 검출한다.

오류

OpenCV 예외 발생: (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2

OpenCV 예외 발생: (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2

2025-02-01 20:02:54.0987|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생

2025-02-01 20:02:54.0987|ERROR|OpenCvSharpProjects.Services.ImageProcessingService|OpenCV 예외 발생

OpenCV 예외 발생: (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 오류는 Cv2.MatchTemplate() 함수를 호출할 때 입력 이미지와 템플릿 이미지의 깊이(depth) 또는 타입(type)이 일치하지 않거나, 입력 이미지의 차원(dims)이 2보다 클 때 발생합니다.

현재 코드에서 이 오류가 발생하는 원인은 DetectGameWindow() 메서드에서 Cv2.MatchTemplate() 함수를 호출할 때, image (전체 화면 이미지)와 template (미니맵 템플릿 이미지)의 타입이 일치하지 않기 때문입니다.

  • imageCv2.ImRead() 함수를 사용하여 컬러 이미지 (BGR) 로 로드됩니다.
  • templateCv2.ImRead(templatePath, ImreadModes.Grayscale)를 사용하여 그레이스케일 이미지로 로드됩니다.

따라서 Cv2.MatchTemplate() 함수를 호출하기 전에 image를 그레이스케일 이미지로 변환해야 합니다.

수정된 DetectGameWindow() 메서드

private Rect DetectGameWindow(Mat image)
{
    // ...

    // 미니맵 템플릿 매칭
    Mat result = new Mat();

    // image를 그레이스케일로 변환합니다.
    Mat grayImage = new Mat();
    Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY);

    Cv2.MatchTemplate(grayImage, template, result, TemplateMatchModes.CCoeffNormed); // grayImage를 사용합니다.
    // ...
}

코드 설명

  • Mat grayImage = new Mat();: 그레이스케일 이미지를 저장할 Mat 객체를 생성합니다.
  • Cv2.CvtColor(image, grayImage, ColorConversionCodes.BGR2GRAY);: image를 그레이스케일 이미지로 변환하여 grayImage에 저장합니다.
  • Cv2.MatchTemplate(grayImage, template, result, TemplateMatchModes.CCoeffNormed);: Cv2.MatchTemplate() 함수를 호출할 때 grayImage를 사용합니다.

이렇게 수정하면 imagetemplate의 타입이 일치하게 되어 Cv2.MatchTemplate() 함수에서 발생하는 오류를 해결할 수 있습니다.

개선

템플릿 이미지 개수를 늘리면 미니맵 인식률을 높이는 데 도움이 될 수 있습니다.

따라서 템플릿 이미지 개수를 늘리기로 했다.

특히 현재 미니맵의 크기와 비율이 맵에 따라 다르다는 점을 고려하면 더욱 효과적일 수 있습니다.

여러 개의 템플릿 이미지를 사용하는 방법

  1. 다양한 미니맵 캡처: 여러 맵에서 미니맵의 테두리 부분을 캡처하여 다양한 크기와 비율의 템플릿 이미지를 준비합니다.
  2. 이미지 저장: 캡처한 이미지들을 Resources 폴더에 저장합니다. 이미지 이름은 minimap_template_1.png, minimap_template_2.png 등과 같이 구분하기 쉽도록 짓습니다.
  3. ImageProcessingService 수정: ImageProcessingService 클래스의 DetectGameWindow() 메서드를 수정하여 여러 개의 템플릿 이미지를 사용하도록 변경합니다.
// Services/ImageProcessingService.cs
// ...

private Rect DetectGameWindow(Mat image)
{
    // 템플릿 이미지 파일 경로
    string[] templatePaths = { "Resources/minimap_template_1.png", "Resources/minimap_template_2.png", ... }; // 템플릿 이미지 파일 경로 배열

    // 각 템플릿 이미지에 대한 매칭 결과를 저장할 리스트
    var matchPoints = new List<Point2f>();

    // 각 템플릿 이미지에 대해 특징점 매칭 수행
    foreach (var templatePath in templatePaths)
    {
        // ... (템플릿 매칭 및 특징점 매칭 로직) ...
    }

    // ... (나머지 코드) ...
}

템플릿 이미지 개수 늘리기의 장점

  • 다양한 미니맵 크기 및 비율에 대응: 여러 크기와 비율의 미니맵에 대해 인식률을 높일 수 있습니다.
  • 오탐 감소: 여러 템플릿 이미지를 사용하면 미니맵과 유사한 다른 UI 요소를 미니맵으로 잘못 인식하는 오탐을 줄일 수 있습니다.
  • 인식률 향상: 여러 템플릿 이미지에서 매칭되는 특징점이 많을수록 미니맵 인식률이 높아집니다.

주의 사항

  • 템플릿 이미지 개수가 너무 많으면 처리 속도가 느려질 수 있습니다.
  • 각 템플릿 이미지는 미니맵의 테두리 부분만 포함해야 합니다.
  • 템플릿 이미지의 크기와 비율은 실제 미니맵과 유사해야 합니다.

결과

미니맵이 인식되면 MainWindow.xaml에서 Rectangle 컨트롤을 사용하여 빨간색 테두리로 표시되도록 구현되어 있다. 그러나 현재 미니맵이 인식되지 않아 빨간색 테두리가 표시되지 않는 것으로 보인다.

미니맵 인식 문제 해결

미니맵 인식 문제를 해결하기 위해 다음과 같은 방법들을 시도해 볼 수 있다.

1. 특징점 매칭 개선

  • 특징점 검출: orb.DetectAndCompute() 메서드를 사용하여 특징점을 검출할 때, nfeatures 파라미터를 늘려서 더 많은 특징점을 검출해 볼 수 있습니다.
  • 매칭 품질 향상: 좋은 매칭 결과만 선택하는 로직을 개선하여 매칭 품질을 높일 수 있습니다. 예를 들어, 거리 비율 테스트 (distance ratio test)를 사용하여 잘못된 매칭을 제거할 수 있습니다.
  • 매칭 알고리즘: BFMatcher 대신 FlannBasedMatcher를 사용하여 매칭 속도를 높일 수 있습니다.

2. 이미지 전처리

  • 이미지 크기 조정: 이미지 크기를 조정하여 특징점 검출 및 매칭 성능을 높입니다.
  • 색상 공간 변환: HSV, LAB 등 다른 색상 공간으로 변환하여 특징점 검출 성능을 향상시킬 수 있습니다.
  • 노이즈 제거: 가우시안 블러 등을 사용하여 이미지 노이즈를 제거합니다.
  • 밝기/대비 조절: 이미지 밝기와 대비를 조절하여 미니맵 영역을 더욱 뚜렷하게 만듭니다.
  • 히스토그램 평활화: 히스토그램 평활화를 통해 이미지 대비를 개선합니다.
  • 엣지 검출: 엣지 검출 알고리즘을 사용하여 미니맵 테두리를 강조합니다.

3. 템플릿 이미지 개선

  • 다른 템플릿 이미지: 미니맵의 다른 부분을 캡처하여 템플릿 이미지로 사용합니다. 예를 들어, 미니맵의 중앙 부분이나 특징적인 아이콘이 있는 부분을 캡처할 수 있습니다.
  • 여러 템플릿 이미지: 여러 개의 템플릿 이미지를 사용하여 인식률을 높일 수 있습니다. 각 템플릿 이미지는 미니맵의 다른 부분을 캡처하거나, 다른 크기 또는 회전 각도를 가질 수 있습니다.

4. 디버깅

  • Visual Studio의 디버거를 사용하여 특징점 매칭 과정을 단계별로 확인하고, 문제가 발생하는 부분을 파악합니다.
  • 특징점이 제대로 검출되는지, 매칭이 제대로 이루어지는지, 호모그래피 계산이 제대로 되는지 등을 확인합니다.

5. 추가적인 방법

  • 머신 러닝 기법을 활용하여 미니맵 인식률을 높일 수 있습니다.
  • 미니맵 영역 수동 지정 기능을 추가하여 사용자가 직접 미니맵 영역을 지정할 수 있도록 할 수 있습니다.

어떤 방법부터 시도해볼까?

  • 먼저 특징점 매칭 개선이미지 전처리를 시도해 보려고 한다.
  • 이러한 방법으로 문제가 해결되지 않으면, 템플릿 이미지를 개선하거나 머신 러닝 기법을 활용하는 것을 고려해 볼 생각이다.

추가적인 정보

  • 특징점 매칭, 이미지 전처리, 머신 러닝 등에 대한 자세한 내용은 OpenCV 문서를 참조할 수 있습니다.
  • 미니맵 인식률을 높이기 위해 다양한 방법을 조합하여 적용할 수 있습니다.
  • 프로그램을 실행하면서 미니맵 인식 결과를 확인하고, 필요에 따라 코드를 수정하면서 인식률을 개선해 나가는 것이 좋습니다.

개선

특징점 매칭 결과를 눈으로 확인할 수 있도록 기능을 추가했다.

0개의 댓글