반응형
아래는 EmguCV에서 자주 만나게 되는 Emgu.CV.InputArray, Emgu.CV.IInputArray, Emgu.CV.Mat 세 타입을 초보자도 헷갈리지 않도록 정리한 설명입니다.
핵심은 “데이터 그릇(Mat)” vs “함수 인자 어댑터(IInputArray/InputArray)”의 역할 분리예요.
전체 그림 (왜 3가지?)
OpenCV(C++)에는 많은 함수들이 인자로 cv::_InputArray를 받습니다. 이는 cv::Mat, cv::UMat, cv::Matx, std::vector<Point> 등 여러 형태의 데이터를 한 번에 받기 위한 어댑터 타입입니다.
EmguCV는 이를 .NET에 옮겨오면서:
- IInputArray: “이건 입력으로 읽을 수 있어요”를 나타내는 인터페이스
- InputArray: 위 인터페이스 구현체를 네이티브로 넘길 때 쓰는 일시적 래퍼(클래스, IDisposable)
- Mat: 실제 이미지 데이터 컨테이너(소유자)
로 나눕니다. 즉,
- 보관/수정/저장의 주인공 = Mat
- 함수 인자 형태 통일 = IInputArray(인터페이스) + InputArray(래퍼)
각 타입의 정체성과 쓰임새
1) Emgu.CV.Mat — “데이터 그릇(소유자)”
- 무엇? OpenCV의 cv::Mat에 해당. 픽셀 버퍼를 직접 소유(참조카운팅)하고, 크기/채널/깊이/스트라이드 같은 메타데이터도 가짐.
- 언제? 이미지를 오래 들고 여러 번 수정/처리/저장해야 할 때. 클래스의 필드로 보관할 타입.
- 수명 관리: Dispose()가 매우 중요(네이티브 메모리 해제).
- 복사: Clone()은 깊은 복사(픽셀 복제), 단순 대입은 얕은 복사(버퍼 공유).
- 예시:
- Mat img = CvInvoke.Imread("a.png", ImreadModes.Color); CvInvoke.CvtColor(img, img, ColorConversion.Bgr2Gray); CvInvoke.Imwrite("out.png", img); img.Dispose();
2) Emgu.CV.IInputArray — “읽기 전용 입력” 인터페이스
- 무엇? “이 객체는 입력으로 읽을 수 있다”를 표현하는 인터페이스.
Mat, UMat, Image<TColor,TDepth>, VectorOfPoint 등 여러 타입이 구현합니다. - 언제? 함수의 파라미터 타입으로 쓰면 좋아요. “Mat이든 UMat이든 Image<>든 다 받아요” 같은 범용 API 작성 가능.
- 주의: 데이터를 소유하지 않음. 그저 “읽기”를 위한 형태 통일 장치.
- 예시(시그니처):
- // 어떤 입력이 와도( Mat, UMat, Image<> 등 ) 읽기만 할 함수 public static void ShowHistogram(IInputArray src) { /* ... */ }
3) Emgu.CV.InputArray — IInputArray를 감싸는 일시적 래퍼(클래스, IDisposable)
- 무엇? OpenCV의 _InputArray를 .NET에서 안전하게 다루기 위한 래퍼 객체.
IInputArray를 받아 내부에서 네이티브에 넘길 수 있는 핸들을 만들어 줍니다. - 언제? 내부 구현에서 잠깐 씁니다. 보통은같은 using 패턴으로 “잠깐 빌려 쓰고 바로 정리”합니다.
- using (InputArray ia = someIInputArray.GetInputArray()) using (Mat mat = ia.GetMat()) { // mat 뷰로 잠깐 접근 }
- 주의: 필드에 보관 금지(수명/원본 의존). 늘 using으로 짧게!
왜 이렇게 나눴나? (디자인 이유)
- 유연한 함수 인자 처리: 이미지/벡터/UMat 등 다양한 타입을 하나의 시그니처로 받으려면 어댑터가 필요 → IInputArray/InputArray.
- 성능/복사 최소화: 어댑터는 뷰이므로 불필요한 복사 없이 네이티브로 넘길 수 있음.
- 소유권 명확화: 실제 데이터 소유는 Mat. 어댑터는 읽기 통로일 뿐 → 수명/해제 책임이 분리되어 **버그(특히 해제 후 접근)**를 줄임.
언제 무엇을 쓰면 유리한가?
상황 권장 타입 이유
이미지를 보관/수정/저장 | Mat | 데이터 소유, 참조카운팅, 대부분의 연산 기본 단위 |
여러 타입을 받는 유틸 함수 인자 | IInputArray | 다양성/범용성. 호출자는 Mat/UMat/Image<> 등 아무거나 전달 |
내부에서 네이티브에 잠깐 넘길 핸들 | InputArray | GetInputArray()로 얻어 using으로 짧게 사용 |
GPU/OpenCL(UMat)도 함께 지원하는 API 설계 | 인자에 IInputArray | 같은 함수에 Mat/UMat 모두 전달 가능 |
내부 보관은 안 하고, 읽기만 필요 | IInputArray | 복사/제약 없이 입력만 받음 |
자주 하는 실수 vs 안전한 패턴
❌ 피해야 할 것
- InputArray(또는 IInputArray에서 얻은 InputArray)를 필드에 저장해 오래 쓰기
→ 원본 수명에 의존하므로 원본이 Dispose되면 댕글링(무효 참조) 위험. - InputArray.GetMat()로 얻은 Mat의 DataPointer를 캐싱
→ 원본이 사라지면 포인터 무효. 필요하면 Clone()으로 내 소유 Mat 확보.
✅ 안전한 패턴
// (1) 내부에 보관: 반드시 Mat를 소유
private Mat _mat;
public void Load(string path)
{
_mat?.Dispose();
_mat = CvInvoke.Imread(path, ImreadModes.Color); // 소유권 확보
}
// (2) 인자 범용화: IInputArray로 받기
public static double MeanGray(IInputArray src)
{
using (var ia = src.GetInputArray()) // 잠깐 핸들
using (var m = ia.GetMat()) // 필요하면 Mat 뷰
using (var gray = new Mat())
{
if (m.NumberOfChannels == 1) m.CopyTo(gray);
else CvInvoke.CvtColor(m, gray, ColorConversion.Bgr2Gray);
var mean = CvInvoke.Mean(gray);
return mean.V0;
}
}
// (3) 다른 모듈에 오래 넘길 필요가 있으면 Clone으로 깊은 복사
var deepCopy = _mat.Clone(); // 깊은 복사로 독립본 생성
요약 한 줄
- Mat: 데이터를 소유하는 “이미지 그릇”. 오래 들고 수정/저장할 때 사용.
- IInputArray: “읽기 가능한 입력”을 통일하는 인터페이스(함수 인자용).
- InputArray: IInputArray를 네이티브로 넘기기 위한 일시적 래퍼—using으로 잠깐만.
이렇게 역할을 분리했기 때문에, EmguCV는 유연한 API(다양한 입력 지원)와 안전한 수명 관리(소유자=Mat)를 동시에 달성합니다.
반응형