5.5. 영역과 마스크¶
image 모듈의 모든 연산은 기본적으로 원본 이미지의 모든 픽셀을 처리합니다. 이는 설명하기 가장 단순한 동작이며, 알고리즘의 작업이 실제로 프레임 전체에 걸쳐 있을 때 – 균일한 색상 보정, 전역 히스토그램, 전송을 위한 인코딩 패스 등 – 올바른 선택입니다. 하지만 실제로는 대부분의 알고리즘이 그보다 적은 영역만 보기를 원합니다. 색이 있는 마커를 추적하는 블롭 추적기는 마커가 나타날 수 있는 장면의 일부에만 관심이 있을 뿐, 그 뒤의 벽에는 관심이 없습니다. 형태학적 정리 패스는 이전 단계에서 후보로 표시한 픽셀에 대해서만 안전합니다. 얼굴 검출기는 더 거친 검출기가 이미 좁혀 놓은 경계 상자 안에서만 실행될 수도 있습니다. image 모듈은 연산을 픽셀의 일부로 한정하는 두 가지 메커니즘을 통해 이러한 작업을 지원합니다. 직사각형 관심 영역(ROI)과 이진 마스크입니다. 이 둘은 자유롭게 조합되며, 픽셀을 다루는 거의 모든 메서드가 둘 중 하나 – 또는 둘 다 – 를 키워드 인자로 받습니다.
5.5.1. 관심 영역(ROI)¶
관심 영역(ROI)은 좌표 페이지에서 소개한 (x, y, w, h) 4-튜플로 지정되는 픽셀의 직사각형입니다. 인터페이스의 약 서른 개 메서드가 roi 키워드 인자를 받습니다. 이 인자가 있으면 연산은 해당 직사각형 안의 픽셀에 대해서만 실행되고 이미지의 나머지 부분은 그대로 둡니다. roi가 None이거나 생략되면 연산은 이미지 전체에 대해 실행됩니다 – 이는 roi=(0, 0, width, height)를 전달한 것과 동일합니다.
코드에서 이 키워드는 연산이 받는 다른 인자들과 나란히 위치합니다:
# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))
ROI가 가져다주는 첫 번째 이점은 오탐 제어입니다. 테이블만 보는 색상 추적기는 그 옆을 지나가는 셔츠에 반응하지 않으며, 정의된 작업 영역 안에서만 실행되는 에지 검출기는 카메라 마운트 자체의 에지를 절대 보고하지 않습니다. 알고리즘이 실제로 관심을 두는 장면의 일부로 검색 영역을 줄이는 것은 파이프라인이 스스로의 신뢰성을 높이기 위해 할 수 있는 가장 저렴한 개선입니다.
두 번째 이점은 거친 단계에서 정밀한 단계로 이어지는 파이프라인입니다. 검출 결과 객체 – blob, rect, apriltag 등 – 는 자신의 경계 상자를 roi가 받는 것과 동일한 (x, y, w, h) 4-튜플로 노출합니다. 따라서 거친 첫 단계가 경계 상자를 반환하면, 그 상자가 곧바로 다음 단계의 roi로 들어가고, 두 번째 단계는 더 좁은 영역에 대해 실행됩니다. 검색 공간이 이미 필터링되어 있기 때문에, 이렇게 점진적으로 좁혀 나가는 각 단계는 다음 단계의 속도를 높이는 동시에 결과를 더 신뢰할 수 있게 만듭니다.
5.5.2. 이진 마스크¶
관심 영역이 축에 정렬되어 있을 때는 직사각형이 올바른 형태입니다. 그렇지 않을 때 – 곡선 영역, 볼록하지 않은 영역, 어떤 이전 단계가 “일치”로 분류한 픽셀 – 에는 연산을 임의의 픽셀 패턴으로 한정하도록 지시해야 합니다. 그 메커니즘이 바로 이진 마스크입니다. 원본과 동일한 크기의 별도 Image로, 픽셀 단위 켜기/끄기 스위치로 사용됩니다. 마스크에서 0이 아닌 픽셀은 “대응하는 원본 픽셀을 포함하라”는 의미이고, 0인 픽셀은 “원본 픽셀을 그대로 두라”는 의미입니다.
마스크는 보통 BINARY 이미지 – 바로 이 목적을 위해 존재하는 픽셀당 1비트 형식 – 이지만, 소비자가 0이 아닌 모든 값을 켜짐으로 취급하기 때문에 단일 채널 이미지라면 무엇이든 동작합니다.
필터링, 임계값 처리, 산술 메서드는 mask 키워드 인자를 받습니다. 형태는 모두 동일합니다. 원본과 같은 크기로 별도로 할당된 이진 이미지를 전달하는 것입니다.
ROI와 마스크는 조합됩니다. 둘 다 전달하면 연산은 ROI 안에 있으면서 마스크에서 켜져 있는 픽셀에 대해서만 실행됩니다. 두 메커니즘은 애플리케이션 코드에 독립적인 레버를 제공합니다 – 하나는 직사각형 관심 영역용, 다른 하나는 그 안의 임의 패턴용 – 어느 한 형태가 다른 형태로부터 제약을 물려받지 않게 하면서 말입니다.
ROI는 연산을 축에 정렬된 직사각형으로 한정합니다. 마스크는 이를 임의의 픽셀 패턴으로 더 좁힙니다. 둘은 조합됩니다. ROI 안에 있으면서 마스크에서 켜져 있는 픽셀만 수정됩니다.¶
5.5.3. 마스크 만들기¶
세 가지 Image 메서드는 선택한 영역 바깥의 픽셀을 0으로 만들어 일반적인 마스크 형태를 제자리에서 생성합니다:
mask_rectangle()은 직사각형을 유지합니다.mask_circle()은 원을 유지합니다.mask_ellipse()는 타원을 유지합니다.
각 메서드는 (x, y, w, h) (직사각형과 타원의 경우) 또는 (x, y, radius) (원의 경우)를 받습니다. 인자 없이 호출하면 도형을 가운데에 배치하고 이미지를 채우도록 크기를 조정하는데, 이는 모서리만 가리고 그 외에는 아무것도 가리지 않는 단순한 전체 이미지 타원이나 원을 원할 때 애플리케이션이 찾는 형태입니다.
mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear() # start from all zeros
mask.mask_ellipse() # centred, full-size oval
흥미로운 마스크는 mask_* 메서드만으로 만들어지는 경우는 드뭅니다. 그것들은 파이프라인의 이전 단계에서 나옵니다. 임계값 처리 패스는 0이 아닌 픽셀이 일치를 표시하는 이진 이미지를 생성하는데, 이는 다음 단계의 mask= 인자에 넣기에 정확히 알맞은 형태입니다. 형태학적 정리 패스는 그 형태를 바꾸지 않으면서 마스크를 다듬습니다. 결국 단일 채널 이미지가 되는 것이라면 무엇이든 그 자체로 유효한 마스크입니다.
5.5.4. 연산이 이미지를 수정하는 방식¶
지난 몇 페이지의 모든 코드 조각에서 보이는 패턴 – 연쇄 호출을 위해 같은 img를 반환하는 연산 – 은 새 메서드가 소개될 때마다 다시 설명할 필요가 없도록 명시적으로 짚어 둘 가치가 있습니다. Image 인터페이스에는 세 부류의 메서드가 등장하며, 각각 원본 이미지를 다르게 취급합니다:
연산 메서드는 원본의 픽셀을 제자리에서 수정하고 연쇄 호출을 위해 같은 이미지를 반환합니다. 그리기, 산술, 임계값, 필터 부류가 모두 이렇게 동작합니다.
img.gaussian(1)은img를 흐리게 하고 같은img를 반환합니다. 재할당 –img = img.gaussian(1)– 은 무해하지만 불필요합니다.변환 메서드는 기본적으로 연산 메서드와 같은 방식으로 제자리에서 동작하지만, 원본을 보존해야 할 때 별도의 결과 이미지를 할당하기 위해
copy=True와copy_to_fb=True를 받습니다. 형식 변환과 기하학적 복사가 이 부류의 주요 구성원입니다.검사 메서드는 픽셀을 읽고 결과 객체 – 검출된 특징의 리스트, 히스토그램, 통계 집합 – 를 반환하며, 원본 이미지는 전혀 수정하지 않습니다.
이 세 가지 분류는 전체 인터페이스에 걸쳐 일관됩니다. 어떤 메서드가 어느 부류에 속하는지 알면 애플리케이션은 호출에서 무엇을 기대할지 알 수 있습니다. 원본의 픽셀이 온전히 유지될지, 별도의 결과 이미지가 할당될지, 반환값이 원본 자체인지 아니면 다른 것인지 말입니다.