WPF MIL Project 를 개발함에 있어 사용하게 되는 MIL X 라이브러리의 개념 부분으로 MIL (or AIL) 상용 라이브러리의 Architecture (구조) 에 대한 내용에 대해 다루겠습니다.
이전 포스팅(#1)에서는 WPF MIL Project 를 진행하기 위해 진행 방향과 간략한 MIL X 상용 라이브러리에 대한 내용 그리고, Windows 환경에 개발 진행 WPF MIL 프로젝트의 개발 환경에 대해 정리했었습니다. 들여다 보면 정리라고 표현하기 민망할 정도로 간단했었네요. ^^;
Machine Vision 프로그램을 개발하다 보면, 아무래도 무료 영상 처리 라이브러리를 사용하기 보다는 상용의 영상 처리 라이브러리를 사용하게 됩니다. 이유야 명확하죠. 검증되어 있고, 쉽게 접근 가능하다는 것인데요. 여기서 쉽다는 표현이 조금 애매하긴 하죠. 여러 영상 처리 개발을 많이 해본 사람은 어떤 라이브러리가 이런 영상 처리 개발에는 더 적합하다, 뭐가 더 좋다, 이런 판단이 됩니다. 하지만 이런 판단이 가능하다는 것은 아마도, 사용하려는 라이브러리에 대한 이해 도가 높아야만 가능합니다. 비싼 돈 주고 샀으니, 쉽게 개발할 수 있겠지 하고 무작정 덤비면 장담컨데 개발 진행이 안됩니다. 프로그래밍 언어를 안다는 것과 API를 활용해서 알고 있는 프로그래밍 언어로 특정 프로젝트를 개발한다는 건 접근은 빠를 수 있지만 쉽지 않습니다. 서두가 길었는데요, 앞으로 몇 개의 포스팅 되는 글에서는 앞으로 만들 Windows 에서 실행되는 Machine Vision 응용프로그램 (WPF MIL Project)에 사용할 MIL 사용 라이브러리에 대한 구조에 대해 다룰 겁니다.
몇 가지만 더 언급하고 본격적으로 진행하겠습니다.
앞서도 언급했지만, 영상 처리를 다루는 개발자들은 많은 영상 처리 상용 라이브러리를 다루게 됩니다. 사실 많다는 표현이 좀 그러네요. 대표적으로 Halcon, Cognex(Vision Pro, CVL), Eurosys, MATLAB, MIL(AIL) 정도 사용하게 되죠. 본인이 원해서라기 보다는 회사 내에 선배 또는 선임자가 이미 만들어 둔 영상 처리 프로그램이 이것들 중에 하나를 써서 만들었기 때문에 사용하게 되는 경우가 대부분이긴 하죠. 아니면 상당한 기능을 가졌지만, 무료인 OpenCV를 사용하기도 하구요. 때로는 고객사에서 라이브러리를 지정해 주는 경우도 있긴 하더군요. 어찌됐던 좋은 라이브러리들인 건 확실합니다. 정말 똑똑한 사람들이 만들었으니, 저희 같은 사람(회사)이 편하게 개발해 보자고 돈 주고 사서 쓰는것 아니겠어요? 모르긴 해도 이 포스팅을 보고 있는 사람들은 저런 라이브러리를 만들려고 하는 개발자는 아닐테죠. 그냥 기능을 잘 파악해서 필요한 녀석을 찾아 잘 가져다 쓰면 되는 그런 개발자들이죠. 물론 저를 포함해서요.
그래서 이제 부터 라이센스가 필요한 영상 처리 라이브러 중 MIL (AIL)에 대해 다룰 겁니다. 첫 번째 포스팅 내용으로 MIL(이후로 AIL은 생략하겠습니다.) 라이브러리 구조를 다뤄 보도록 하겠습니다. 아래 포스팅 되는 글의 reference는 Zebra Academy 의 영상을 많이 참조 했으니, 해당 영상도 참고해 주세요. 행여나 제가 거짓말 하는지 체크하는 것도 소소한 재미가 있을 않을까요? (ㅠ.ㅠ)
MIL (Matrox Imaging Library) Architecture
안정적이고 효율적인 영상 처리 애플리케이션을 구축하기 위해서는 기반이 되는 영상 처리 라이브러리의 구조를 명확히 이해하는 것이 필수적입니다. 이제 MIL 라이브러리의 계층적 설계 철학을 시작으로, 애플리케이션을 구성하는 핵심 객체들의 역할과 기능을 조금 상세히 분석해 보도록 하죠. 또한, 일관된 API 명명 규칙을 통해 개발 생산성을 높이는 방법과, Resource(리소스) 누수를 방지하고 시스템 안정성을 보장하는 데 가장 중요한 MIL 라이브러리에서 객체 할당 및 해제 절차를 차근 차근 이야기해 보겠습니다.
Architecture
Architecture 를 큰 틀에서 요약해 보죠.
MIL 라이브러리는 물리적 하드웨어 리소스를 추상화된 소프트웨어 객체에 매핑하는 계층적이고 모듈적인 구조를 가지고 있다고 합니다. 계층적 구조이다 보니 최상위 계층이 있고, 최하위 계층이 있을 거라 짐작할 수 있겠죠? 모든 MIL 라이브러리를 활용한 응용프로그램은 애플리케이션을 관장하는 Application Context 가 존재합니다. 자세한 건 아래에서 개별적으로 다루겠지만, 이 녀석은 최최상위에 존재하는 놈이다 라고 이해해 주세요. 그리고 최상위 계층에는 물리적 장치 (Frame Graber)를 대표하는 시스템 객체가 있습니다. System (시스템) 객체에서는 이미지 획득, 메모리 버퍼, 화면 출력 과 같은 하위 기능들이 각각 독립적인 객체(디지타이저, 버퍼, …)로 모듈화 되어 특정 System(시스템) 객체에 종속 됩니다. 이렇게 부모-자식 관계의 계층 구조는 복잡한 하드웨어 제어를 단순하게 만들어 주고, 코드의 재사용성 을 높여주죠. MIL 라이브러리는 C++, C, C#, Python 을 지원하지만, Visual Basic 은 언젠가 부터 지원 안하고 있다고 하니까 참고해 주세요. MIL의 기본적인 구조는 아래의 그림을 참고해 주세요.

이런 MIL 라이브러리 구조에서 중요한 것 하나는 객체의 소유권인데요. 만약 여러 개의 하드웨어 장치(프레임 그래버)를 사용하는 경우, 각 장치에 대해 별도의 System (시스템) 객체를 할당해야 하며, 디지타이저나 버퍼 같은 다른 하위 계층의 객체는 반드시 특정 System (시스템) 객체에 연관되어 할당 되어야 합니다. 정리하면, System을 포함하여 하위 객체들은 전역적으로 존재하지 않으며 항상 특정 하드웨어 컨텍스트에 속해 있어야 하는 거죠. 이렇게 해야만 다중 장치 환경(여러개의 프레임 그래버와 카메라)에서 리소스를 명확히 분리하고 관리할 수 있게 된다는 매우 중요한 내용입니다. 언급한 내용과 아래의 그림을 참고해서 꼭꼭 반드시 기억해 두세요.

이제 앞서 MIL 라이브러리 Architecture 에서 언급했던 각각의 내용을 본격적으로 하나 씩 자근 자근 정리해 보겠습니다.
Application Context
Application Context (애플리케이션 컨텍스트)는 모든 MIL 애플리케이션의 시작점이자 최상위 컨테이너입니다. 이 녀석은 애플리케이션 내에서 생성되고 사용되는 모든 객체(시스템, 버퍼, 디스플레이 등)의 식별자를 보유하고 관리하는 역할을 합니다. 그래서 라이브러리 기능을 사용하기 위해서는 반드시 첫 번째 단계에 항상 이 애플리케이션 컨텍스트 객체를 할당해야 합니다.
System
System (시스템) 객체는 Frame Grabber Board (프레임 그래버 보드)와 같은 단일 하드웨어 장치를 나타내는 핵심 소프트웨어 객체로서, MIL 라이브러리는 각 시스템 객체를 하나의 물리적 하드웨어 논리 유닛에 일대일로 매핑합니다. 만약에 Application (애플리케이션)에서 여러 개의 독립적인 하드웨어 장치를 동시에 제어해야 한다면, 각 하드웨어에 대해 반드시 별도의 System (시스템) 객체를 할당해야 합니다.
Digitizer
도대체 왜 이름을 이렇게 지었는지 잘 이해가 가지 않는데요. 제가 알고 있는 digitizer는 터치 스크린 아래에 위치해서 펜이나 손가락의 옴직임을 인식해서 좌표와 압력을 디지털 정보로 바꿔 주는 녀석이었습니다. 아마도 제가 모르는 이유가 있겠지만, Analog 영상을 영상 처리를 위해 Digital 데이터로 변환 해준다는 의미로 짓지 않았을까 하고 조심스럽게 예상해 봅니다. 아무튼 Digitizer (디지타이저)는 하드웨어로부터 이미지를 수집(grabbing)하는 데 필요한 설정을 포함하는 객체입니다. 그래서 Digitizer(디지타이저)는 특정 System (시스템) 객체의 컨텍스트 내에서 할당되며, 프레임 그래버나 USB 3 포트로부터 이미지를 획득하는 방식을 정의합니다. 또한, 특정 유형의 카메라에서 흑백 (monochrome) 또는 컬러 이미지를 획득하기 위한 하나 이상의 경로를 지정하는 기능을 포함하고 있습니다.

Buffer
영상 처리 프로그램 뿐만 아니라, S/W 개발자라면 Buffer (버퍼)는 다들 잘 알잖아요?
맞습니다. 그거요! ㅎㅎ
MIL 라이브러리에서 말하는 Buffer 는 주로 이미지 데이터를 저장하는 데 사용되는 메모리 리소스를 가리키는 식별자입니다. 특정 System(시스템) 객체와 연관되어 할당되며, 이놈의 특징은 유연성에 있는데요, 기본적으로 컴퓨터의 주 RAM에 할당되지만, 하드웨어 및 할당 설정에 따라 Frame Grabber Board (프레임 그래버 보드)나 그래픽 컨트롤러의 전용 메모리에 직접 할당될 수도 있습니다. 이것들을 통해 데이터 전송 병목 현상을 최소화하고 성능을 최적화할 수 있다고 합니다.

Display
Display (디스플레이) 객체는 선택된 Buff (버퍼)의 이미지 데이터를 모니터에 어떻게 표시할지 정의하는 특성들의 집합입니다. 특정 시스템 객체 하에 할당되어 이미지의 확대/축소, 스크롤, 색상 맵 적용 등 다양한 시각화 관련 설정을 제어합니다. MIL 라이브러리는 단일 스크린 환경 뿐만 아니라 다중 스크린 구성도 지원합니다.

Graphics Context
Graphics Context (그래픽 컨텍스트)는 이미지 위에 텍스트, 도형과 같은 주석(annotation)을 그리는 데 사용되는 객체입니다. 이 객체의 가장 중요한 기술적 특징은 비파괴적(non-destructive) 방식으로 작동한다는 점인데요. 무엇이냐 하면, 그래픽 컨텍스트를 통해 그려진 주석(선, 도형, 글자, …) 은 원본 이미지 데이터에 영향을 주지 않는 별도의 overlay (오버레이) Layer (레이어)를 두어 그 위에서 Rendering (렌더링) 한다는 겁니다. 이 방법은 원본 데이터를 보존하면서 분석 결과를 시각적으로 표현해야 하는 머신 비전 애플리케이션에서는 매우 중요한 기능하면서도 기본이 되는 부분입니다.

Processing Object
Processing Object (처리 객체)는 이미지 분석이나 수정을 수행하는 실제 구현 프로젝트에서 다룰 특정 처리 모듈(예: 블롭 분석, 패턴 매칭)에 적용되는 설정, 데이터, 또는 결과 값을 담는 컨테이너입니다. 각 처리 모듈은 고유한 기능 수행을 위해 필요한 자신만의 컨텍스트 및 결과 객체들을 가지고 있으며, 이러한 객체들이 처리 객체의 한 종류에 해당합니다. 이 부분은 WPF MIL 프로젝트를 구현 하면서 자주 보게 될것이기에 요정도로 알면 됩니다.
MIL 라이브러리 API 명명 규칙
이 포스팅을 보는 대부분이 S/W 개발자라서 잘 알겠지만, 잘 설계된 API 명명 규칙은 라이브러리의 학습 시간을 낮추고 개발자의 생산성을 향상시키는 데 중요한 역할하죠. 일관된 명명 규칙은 개발자가 함수의 기능을 쉽게 유추하고, 코드 가독성을 높여 장기적인 유지보수를 용이하게 만듭니다.
MIL 라이브러리의 모든 함수는 M<ModuleAbbr><Purpose> 라는 체계적인 명명 규칙을 따릅니다.
• 모든 함수는 접두사 M 으로 시작합니다.
• 그 다음에는 함수가 속한 모듈의 이름 축약형이 옵니다.
(예: sys는 시스템, buf는 버퍼, disp는 디스플레이 모듈을 의미.)
• 마지막으로 함수의 구체적인 목적을 나타내는 이름이 따라옵니다.
(예: Alloc은 할당, Free는 해제, Control은 제어를 의미.)
예를 들어, System (시스템) 객체를 할당하는 함수는 MsysAlloc (M + sys + Alloc)이며, 버퍼 객체를 해제하는 함수는 MbufFree (M + buf + Free)가 됩니다.
이러한 체계적인 명명법은 개발자가 특정 모듈의 함수를 쉽게 식별할 뿐만 아니라, 다른 모듈에 있는 유사한 목적의 함수들을 쉽게 찾아 활용할 수 있도록 돕는 실질적인 이점을 제공합니다. 이렇게 명명 규칙은 아래에 언급할 객체의 생명 주기를 관리하는 필수 함수들에서 더욱 빛을 발하게 되죠.

객체 생명 주기 관리: 할당 및 해제
객체 생명주기 관리는 MIL 라이브러리를 사용한 애플리케이션 개발에서 가장 중요한 내용 중 하나입니다. 모든 소프트웨어 객체는 시스템 리소스(주로 메모리)를 사용하므로, 이것을 정확하게 할당(Allocation)하고 사용이 끝난 후 반드시 해제(Freeing)하는 것은 애플리케이션의 안정성을 보장하고 리소스 누수(resource leak)와 언제 시스템이 죽을지모를 문제를 방지하기 위한 핵심적인 개발 원칙입니다.
Allocation : 객체 할당
MIL 라이브러리의 객체 할당에 대한 핵심 원칙은 모든 소프트웨어 객체는 사용하기 전에 반드시 명시적으로 할당해야 한다는 것입니다. 할당되지 않은 객체에 접근하려고 시도하면 예측할 수 없는 오류가 발생할 수 있습니다. 일반적인 객체 할당 순서는 라이브러리의 앞서 언급한 계층적 구조를 따릅니다.
1. Application Context (애플리케이션 컨텍스트) 할당: 모든 작업의 시작으로, 최상위 컨테이너인 애플리케이션 컨텍스트를 가장 먼저 할당합니다.
2. 하나 이상의 System (시스템) 할당: 제어할 물리적 하드웨어 장치마다 시스템 객체를 할당합니다.
3. 필요에 따라 기타 객체 (Digitizer, Buffer, Display, Graphics Context, …) 할당: 이미지 획득이 필요하면 디지타이저를, 이미지 데이터를 저장할 공간이 필요하면 버퍼를, 화면 출력이 필요하면 디스플레이와 그래픽 컨텍스트 등을 순서대로 할당합니다. 만약 특정 이미지 처리 모듈을 사용하고자 한다면, 해당 모듈의 기능을 실행하는 데 필요한 컨텍스트(Context) 객체와 결과 저장을 위한 버퍼(Result Buffer)와 같은 추가적인 객체들을 할당해야 합니다.

Freeing : 객체 해제
애플리케이션의 역할이 끝나거나 더 이상 특정 객체가 필요하지 않을 때, 그리고 프로그램이 종료되기 전에는 할당된 모든 객체를 해제하여 사용했던 리소스를 시스템에 반환해야 합니다.
객체 해제 시 반드시 지켜야 할 가장 중요한 규칙은 다음과 같습니다.
객체는 할당된 순서의 역순으로 해제해야 한다.
이 규칙은 라이브러리의 계층적 아키텍처에 직접적으로 기인합니다. 버퍼나 디지타이저와 같은 객체는 특정 시스템 객체에 종속된 자식(child) 객체입니다. 부모(parent)인 시스템 객체를 먼저 해제하면, 여전히 할당된 자식 객체들이 고아(orphaned) 상태가 되어 리소스 누수나 예측 불가능한 충돌을 일으킬 수 있습니다. 따라서 항상 자식 객체를 먼저 해제하여 부모와의 종속성을 안전하게 끊어낸 후 부모 객체를 해제해야 합니다.
C++이나 C와 같은 언어에서는 특정 조건 하에 객체가 유효 범위를 벗어날 때 자동으로 해제되는 경우가 있을 수 있있다고 하지만, 사실 명시적으로 해제해 주어야 합니다. C#은 가비지 컬렉터 (GC) 가 알아서 처리하지 않나요? 하고 묻는 다면 그렇지 않다는 걸 명심하세요!!!! 그냥 MIL 라이브러리에서 객체를 할당하면 명시적으로 반드시 꼭 해제하는 코드를 넣어야 한다는 것 다시 한번 강조 합니다.

정리
생각 보다 정리 내용이 길었습니다. MIL 라이브러리의 구조를 한번 알아두면 당연 하다시피 사용하게 되긴 하는데, 대부분 그렇지만, 익숙해질 때 까지 가 불편합니다. 이 번 포스팅에서 언급한 내용을 살펴 보면, MIL 라이브러리를 이용해 개발할 때 기본적으로 구조는 알아야 하기 때문에 라이브러리 구조를 하드웨어적인 부분과 함께 정리하였습니다. 계층적인 구조를 가지고 있고, API의 명명 규칙을 알면 개발 시 관련 기능과 관련된 함수를 찾을 때 아주 편합니다. 그리고 계층 구조의 객체를 할당했다면, 그것을 역순으로 해제 해야 한다는 것 까지 언급했네요. 아래의 그림이 이번 포스팅의 내용을 모두 담고 있다고 해도 무리는 아닐 듯 합니다. 참고해 주세요.

다음 포스팅에서는 MIL 라이브러리를 이용해서 Image Acquisition (이미지 획득) 할때 구성 방법과 제어 하는 방법에 대해 다뤄 보겠습니다.
참고 자료
Zebra Vision Academy: Aurora Imaging Library Courses (Architecture Section)
Microsoft Learn: Unmanaged Resources and IDisposable (C# 비관리 리소스 처리 이론)