WPF OpenCV 프로젝트 #10: ROI(관심 영역) 자르기 및 저장 구현

이번에는 ROI(관심 영역) 자르기저장 기능WPF OpenCV 프로젝트에 구현해 보죠.
지난 포스팅에서 OverlayCanvas를 이용해 ROI를 그릴 UI 뼈대를 만들었습니다. 이제 그 뼈대에 살을 붙여 실제로 작동하는 코드를 만들 차례입니다.
오늘은 작업량이 좀 있습니다.

  1. Model: OpenCV로 이미지를 자르고(Crop) 저장하는 기능 구현
  2. ViewModel: UI와 Model을 연결
  3. View (Code Behind): 마우스로 사각형을 그리고 좌표를 계산하는 로직 구현

특히 마지막 좌표 계산 부분은 중요하니 끝까지 잘 따라와 주세요!

Model: OpenCVService에 자르기 기능 추가

영상 처리에서 관심 영역을 잘라내는 것을 **크롭(Crop)**이라고 하죠? OpenCVSharp을 이용해 원본 이미지(_srcImage)에서 특정 영역(Rect)을 잘라내고, 저장하는 함수를 OpenCVService.cs에 추가하겠습니다.

특별히 어려운 내용은 없습니다. OpenCvSharp.Rect로 범위를 지정해서 잘라내는 것이 핵심입니다.

ViewModel: 연결 다리 놓기

이제 MainViewModel.cs에서 방금 만든 함수들을 호출할 수 있도록 연결해 줍니다. UI에서 마우스 좌표(x, y, w, h)를 던져주면, 여기서 받아서 서비스로 넘기는 역할입니다.

View (Code Behind): 마우스 이벤트의 향연

자, 이제 가장 중요한 MainWindow.xaml.cs입니다. 여기서는 **”사용자가 마우스로 사각형을 그리는 동작“**을 구현해야 합니다.

3-1. 변수 및 열거형(Enum) 선언

먼저 클래스 내부에 필요한 변수들을 선언합니다. 나중에 도형 그리기 기능이 추가될 것을 대비해 Enum으로 모드를 관리하겠습니다.

3-2. Context Menu (우-클릭) 처리

사용자가 우클릭을 했을 때, “배경에서 클릭했는지” vs **”이미 그려진 ROI 사각형 위에서 클릭했는지“**를 구분해야 합니다. ROI 위에서 클릭했다면 ‘자르기/저장’ 메뉴가 떠야 하고, 배경이라면 ‘ROI 그리기 시작’ 메뉴가 떠야 하니까요.

WPF ContextMenu 실행 화면

3-3. MouseDown (그리기 시작)

메뉴에서 Draw Roi를 선택하면 커서(Cursor)가 십자가로 바뀌고 그리기 모드가 됩니다. 이제 왼쪽 마우스를 눌렀을 때 ROI 그리기를 시작하는 코드를 ZoomBorder_MouseDown에 추가합니다.

3-4. MouseMove (사각형 그리기)

마우스를 드래그(Mouse Drag)하는 동안 실시간으로 사각형이 커졌다 작아졌다 해야겠죠? ZoomBorder_MouseMove에 로직을 추가합니다.

정상적으로 빌드가 되었다면 아래와 같이 ROI 영역 잡기가 동작할 겁니다.

WPF ROI 영역 잡기

3-5. 그리기 완료 (MouseUp)

마우스 버튼을 떼면 그리기를 멈추고 마우스 캡처(ReleaseMouseCapture)를 해제합니다. 아래 코드를 추가하여 작성해 주세요.

핵심: UpdateRoiVisual (좌표 변환의 마법)

이번 포스팅의 하이라이트입니다.
“이미지가 확대/이동 된 상태에서, 어떻게 사각형을 정확한 위치에 그릴 것인가?”

단순히 Canvas에 그리면 이미지가 확대될 때 사각형은 제자리에 있거나 엉뚱한 크기가 됩니다. 그래서 우리는 **”이미지 기준 좌표“**를 계산하고, 그것을 다시 **”현재 화면 기준 좌표“**로 변환해서 그려줘야 합니다.

Tip!

RoiRect.RenderTransform = null; 이 코드가 없으면 사각형이 이중으로 확대되거나 이상한 곳으로 튈 수 있습니다. 정신 건강을 위해 꼭 넣어두시길 권장합니다.

Canvas.SetLeft: 사각형의 위치를 부모 캔버스(ImgCanvas) 내의 절대 좌표로 강제 지정하는 함수입니다.
(링크된 사이트를 참조하면, 왜 Canvas.SetLeft 를 써야 하는지, 좌표가 어떻게 계산되는지 이해하는데 많은 도움이 될 겁니다.)

실행 결과

이제 실행해 볼까요? 이미지를 불러오고, 우-클릭으로 Draw ROI를 선택한 뒤 드래그(Drag)하면 빨간색 사각형이 예쁘게 그려집니다.

그리고 그려진 사각형 위에서 다시 우-클릭하면 Crop Image 메뉴가 뜹니다.

아직 Crop Image 메뉴를 눌렀을 때 실제로 자르는 이벤트 연결(MenuItem_Crop_Click)은 안 했지만, UI와 좌표 계산 로직은 완벽하게 돌아갑니다.

다음 포스팅에서는 드디어 “자르기(Crop)” 버튼을 눌렀을 때의 동작과, 잘라낸 이미지를 저장하는 마무리를 하도록 하죠. ^^

참고자료

OpenCvSharp Rect:
https://shimat.github.io/opencvsharp_docs/html/T_OpenCvSharp_Rect.htm
WPF Canvas.SetLeft:
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.controls.canvas.setleft
Math.Min/Max:
https://learn.microsoft.com/ko-kr/dotnet/api/system.math.min
좌표 공간 변환:
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.uielement.translatepoint

댓글 남기기