5.25. 블롭 찾기¶
임계값 처리는 캡처한 프레임을 이진 마스크로 바꿉니다. 모든 픽셀은 임계값 테스트를 통과하거나 통과하지 못합니다. 이는 애플리케이션이 관심을 두는 색상이 장면에 나타나는가에는 답하지만, 어디에 있는지는 알려주지 않습니다. 마스크는 그저 1과 0의 바다일 뿐입니다. 다음 단계는 블롭 검출입니다. 마스크를 따라가며 통과한 픽셀들이 연속으로 이어진 영역을 찾고, 각 영역을 위치, 크기, 방향, 그리고 애플리케이션이 활용할 수 있는 그 밖의 속성을 가진 객체로 반환합니다.
find_blobs() 는 이 단계의 주력 메서드이며, image 모듈의 결과 객체 세계로 들어가는 가장 흔한 진입점입니다. 색깔 공 추적하기, 바닥에 그려진 선 따라가기, 열 센서가 보는 밝은 점의 개수 세기, 파란 LED가 켜졌는지 꺼졌는지 판단하기 – 이 모든 것이 같은 호출로 처리됩니다. 입력값(임계값, 검색 영역, 결과에 적용하는 필터)은 달라지지만 호출 패턴은 동일합니다.
5.25.1. 기본 호출¶
find_blobs 는 임계값 리스트를 받아 블롭 결과 객체의 리스트를 반환합니다:
thresholds = [(30, 100, 15, 127, 15, 127)] # LAB threshold for red
blobs = img.find_blobs(thresholds)
for b in blobs:
img.draw_rectangle(b.rect, color=(255, 0, 0))
img.draw_cross(b.cx, b.cy, color=(255, 0, 0))
각 임계값 튜플은 binary() 에 전달하는 임계값과 같은 형식을 가집니다 – RGB565 이미지의 경우 여섯 개의 항목 (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) (경계값은 LAB 기준), 그레이스케일 이미지의 경우 두 개의 항목 (lo, hi). 한 번의 호출에 최대 32개의 임계값을 제공할 수 있는데, 바로 이 점이 find_blobs() 를 매우 유연하게 만듭니다. 빨강, 초록, 파랑 비콘을 동시에 추적할 수 있으며, 각 임계값이 자신만의 블롭을 반환 리스트에 기여하고, 각 블롭의 code 속성이 어떤 임계값과 일치했는지 식별합니다.
위의 draw_rectangle() 와 draw_cross() 호출은 IDE 미리보기를 위해 캡처한 프레임에 주석을 답니다. 블롭 결과는 이미 b.rect (4-튜플 형태의 경계 상자)와 b.cx / b.cy (정수 중심점)를 가지고 있으므로, 검출 결과를 프레임에 다시 그리는 작업은 두 번의 메서드 호출이면 됩니다.
5.25.2. 결과에 담긴 내용¶
각 Blob 는 검출기가 영역에 대해 측정한 모든 것을 함께 담은 속성 튜플입니다. 속성은 네 그룹으로 나뉩니다.
경계 상자와 중심점 그룹 – x, y, w, h, rect, cx, cy, cxf, cyf – 은 블롭의 위치를 기술합니다. rect 은 그리기 메서드가 기대하는 (x, y, w, h) 4-튜플입니다. cx 와 cy 는 정수 픽셀 좌표의 중심점입니다. cxf 와 cyf 는 서브픽셀 부동소수점 좌표의 중심점으로, 상위 단계의 보정 과정이 소수점 단위 위치를 신경 쓸 때 유용합니다.
형태 디스크립터 – pixels, area, density, perimeter, roundness, elongation, compactness, rotation – 는 블롭이 어떻게 생겼는지를 기술합니다. pixels 는 통과한 픽셀의 개수입니다. area 는 축 정렬 경계 상자의 면적(w * h)입니다. density 는 둘의 비율로, 꽉 찬 사각형에서는 1.0 에 가까워지고 가느다란 대각선 획에서는 0.0 쪽으로 떨어집니다. roundness 와 compactness 는 모두 블롭이 얼마나 둥근지를 서로 다른 기하학적 관점에서 점수화합니다(roundness 는 2차 모멘트로부터, compactness 는 둘레-면적 비율로부터). elongation 은 편의를 위한 1.0 - roundness 값입니다. rotation 은 장축의 방향을 라디안으로 나타낸 값으로, 길쭉한 블롭에서 가장 정확하고 거의 둥근 블롭에서는 노이즈가 심해집니다(모호한 축은 명확한 방향이 없기 때문입니다).
임계값 및 병합 메타데이터 – code, count – 는 어떤 임계값이 일치했는지, 그리고 몇 개의 원본 블롭이 반환된 블롭으로 병합되었는지를 식별합니다. code 는 일치한 임계값마다 한 비트씩 설정되는 32비트 비트맵입니다(단일 임계값이면 code == 1, 병합된 다색 블롭은 여러 비트가 설정될 수 있습니다). count 는 merge=True 로 여러 검출이 하나로 합쳐진 경우가 아니면 1 입니다.
코너 그룹 – corners, min_corners – 는 블롭의 회전된 기하 정보를 제공합니다. corners 는 블롭의 윤곽선에서 추출한 (x, y) 극점들의 4-튜플로, 좌상단에서 시계 방향으로 정렬됩니다. min_corners 는 블롭을 둘러싸는 최소 면적 회전 사각형의 코너 4-튜플입니다. 최소 면적 사각형은 꼭 맞는 사각형이고, 축 정렬 rect 은 픽셀 격자에 맞춰 정렬된 느슨한 사각형입니다. 하위 단계가 방향이 있는 상자를 필요로 하는지 단순한 상자를 필요로 하는지에 따라 둘 다 유용합니다.
블롭은 축 정렬 경계 상자(rect, x, y, w, h), 중심점(cx, cy 또는 서브픽셀 cxf, cyf), 최소 면적 회전 사각형(min_corners 와 rotation), 그리고 아래의 모듈 수준 헬퍼로 계산하는 선택적 장축 / 단축 선을 가집니다.¶
5.25.3. 검색 필터링¶
캡처한 프레임에는 보통 애플리케이션이 관심을 두는 객체 외의 이유로 임계값과 일치하는 픽셀들이 포함됩니다. 정반사 하이라이트, 멀리 있는 배경 객체, 우연히 LAB 범위에 들어온 이미지 노이즈 픽셀 등입니다. find_blobs() 의 키워드 인자가 1차 방어선 역할을 합니다.
roi 는 다른 모든 image 모듈 메서드와 마찬가지로 검색을 프레임의 한 영역으로 제한합니다. 객체가 시야의 아래쪽 절반에만 나타날 수 있음을 아는 애플리케이션은 roi=(0, h//2, w, h//2) 를 전달하여 그 위의 모든 것을 무시하며, 절약된 시간은 프레임 속도로 환원됩니다.
area_threshold 와 pixels_threshold 는 둘 다 신경 쓰기에 너무 작은 블롭을 걸러냅니다. area_threshold 는 경계 상자의 면적 픽셀 수가 해당 값보다 작은 블롭을 버립니다(흩어진 노이즈를 거르는 데 좋습니다). pixels_threshold 는 통과한 픽셀 수가 해당 값보다 작은 블롭을 버립니다(크지만 듬성듬성한 블롭, 예를 들어 여기저기 한두 픽셀만 일치하는 임계값 처리된 점묘 패턴을 거르는 데 좋습니다). 둘 다 기본값은 10 입니다. 몇 센티미터 크기의 전경 대상을 위해 이 값들을 수백 단위로 끌어올리면 작은 노이즈의 티끌까지 모두 걸러낼 수 있습니다.
x_stride 와 y_stride 는 스캐너가 추적을 시작할 블롭을 찾는 동안 내딛는 픽셀 보폭을 설정합니다. 보폭은 추적 해상도가 아닙니다 – 추적은 항상 단일 픽셀 단위로 실제 블롭 경계를 따라갑니다 – 하지만 스캔이 시작 픽셀을 얼마나 빨리 찾는지를 제어합니다. 블롭이 크다고 알려진 경우(카메라에서 30센티미터 떨어진 주먹 크기의 색깔 대상은 쉽게 100픽셀이 넘습니다) x_stride=4, y_stride=4 는 검출 손실 없이 실질적으로 스캔 시간을 16분의 1로 줄입니다. 블롭이 작은 경우(몇 픽셀 크기의 멀리 있는 LED 비콘) 보폭이 블롭을 완전히 건너뛰지 않도록 1 로 유지해야 합니다. invert 는 임계값 테스트를 뒤집습니다. 일치가 불일치가 되고, 루틴은 대신 통과하지 못한 픽셀들의 블롭을 반환합니다.
threshold_cb 는 임계값 처리 후, 그리고 최종 결과 리스트가 만들어지기 전에 각 블롭에 대해 호출되는 Python 콜백입니다. 콜백은 블롭을 받아서 유지하려면 True 를, 버리려면 False 를 반환합니다. 이는 키워드 인자가 직접 노출하지 않는 속성에 대해 임의의 Python 수준 필터를 적용하는 자리입니다 – 최소 밀도, 특정 회전 범위, 병합 후의 커스텀 코드 비트 조합 등입니다. 키워드 인자는 네이티브 코드의 필터로 빠르게 실행됩니다. 콜백은 Python에서 실행되어 더 느리지만 표현할 수 있는 것에 제한이 없습니다.
5.25.4. 겹치는 블롭 병합하기¶
merge=True 는 결과 리스트를 후처리하여 경계 사각형이 겹치는 블롭들을 합칩니다. 자연스러운 용도는 정반사 하이라이트, 그림자 선, 또는 객체 전체에 걸쳐 고르지 않은 조명 때문에 카메라가 그 색상을 여러 개의 임계값 처리된 영역으로 보는 대상을 검출하는 것입니다. 하나의 빨간 공이 세 개나 네 개의 작은 빨간 블롭으로 돌아올 수 있는데, 이들을 합치면 공의 윤곽을 그리게 됩니다. merge=True 를 사용하면 세 개의 블롭이 하나의 큰 블롭이 되고, rect 은 그 합집합을 덮으며, code 는 병합된 블롭들의 코드를 비트 OR한 값이 되고(따라서 다색 병합은 어떤 색상들이 기여했는지 식별합니다), count 는 몇 개의 원본 블롭이 합쳐졌는지 알려줍니다.
margin 은 겹침 테스트 전에 경계 사각형을 키우거나 줄입니다. margin=2 를 사용하면 경계 사각형이 서로 2픽셀 이내로 가까워지는 블롭들도 병합됩니다. margin=-2 를 사용하면 경계 사각형이 최소 2픽셀 이상 겹치는 블롭들만 병합됩니다. 자연스러운 조정 방법은 다음과 같습니다. 양수 마진은 임계값이 인접한 조각으로 쪼개 놓은 블롭을 처리하는 데, 음수 마진은 빽빽하게 모여 있는 별개의 객체들을 분리된 상태로 유지하는 데 사용합니다.
merge_cb 는 병합이 일어나기 전에 각 후보 쌍에 대해 실행됩니다. 콜백은 두 블롭을 받아서 병합을 허용하려면 True 를, 막으려면 False 를 반환합니다. 이는 기하학적 규칙이 놓치는 병합을 교차 검증하기에 적합한 도구입니다 – 예를 들어 rotation 각도가 임계값 이상 차이 나는 두 블롭의 병합을 거부하거나, 작은 블롭이 단지 얼룩일 뿐이라면 그것을 훨씬 큰 블롭에 병합하기를 거부하는 식입니다.
5.25.5. 투영 히스토그램¶
x_hist_bins_max 와 y_hist_bins_max 는 각 블롭에 선택적인 투영 히스토그램 을 첨부합니다. 투영 히스토그램은 한 축을 따라 통과한 픽셀의 개수입니다. X축 히스토그램은 블롭 경계 상자 내부의 열마다 통과한 픽셀을 합산하고, Y축 히스토그램은 행마다 합산합니다. 둘 다 기본값은 0입니다 – 0이 아닌 max 가 제공되지 않으면 히스토그램은 계산되지 않는데, 그렇지 않으면 모든 검출에 작업이 추가되기 때문입니다.
히스토그램이 계산되면, 애플리케이션이 추가 분석을 수행할 수 있는 저렴한 1차원 신호를 제공합니다. 블롭 내부 수직 줄무늬의 위치 검출, 두 색상으로 된 대상의 경계점 찾기, 장축을 따라 나타나는 간격의 개수 세기 등입니다. 이들은 각 Blob 의 x_hist_bins 와 y_hist_bins 속성으로 채워집니다.
5.25.6. 추가 기하 헬퍼¶
그 밖의 몇 가지 기하학적 측정값은 블롭을 받아 요청된 측정값을 반환하는 모듈 수준 함수로 제공됩니다:
image.get_solidity()는 블롭의 solidity(밀집도), 즉 픽셀 수를 볼록 껍질(convex hull)의 면적으로 나눈 값을 반환합니다. 꽉 채워진 영역은1.0에 가깝고, 오목한 부분이 있는 블롭(말굽이나 손가락을 편 손 모양)은 그보다 훨씬 낮은 값을 갖습니다.image.get_convexity()는 convexity(볼록도), 즉 볼록 껍질의 둘레를 블롭의 둘레로 나눈 값을 반환합니다. 완벽하게 볼록한 블롭은1.0이고, 들쭉날쭉하거나 홈이 파인 블롭은 더 낮은 값을 갖습니다.image.get_major_axis_line()와image.get_minor_axis_line()는 회전된 최소 면적 사각형에서 유도된, 블롭의 장축과 단축을 따라가는Line객체를 반환합니다.image.get_enclosing_circle()는 블롭을 둘러싸는Circle를 반환합니다 – 하위 단계가 그리거나 검사할 원을 원할 때 유용합니다.image.get_enclosed_ellipse()는 블롭의 최소 면적 사각형에 내접하는 타원에 대한 5-튜플(cx, cy, rx, ry, rotation)을 반환합니다. 이 값들은draw_ellipse()에 바로 넣을 수 있습니다.
5.25.7. 임계값 자동 학습¶
블롭 검출기는 함께 사용하는 임계값만큼만 좋으며, 대상 색상에 맞는 적절한 임계값을 찾는 작업은 그 자체로 하나의 문제입니다. 두 가지 흔한 패턴이 그 작업을 줄여 줍니다.
첫 번째는 IDE에서의 대화형 선택 입니다. 프레임을 캡처하고, 대상 색상의 예시 주위에 사각형을 드래그하면, IDE의 임계값 편집기 가 자신이 본 LAB 경계값을 알려줍니다. 그 경계값을 스크립트의 find_blobs() 임계값으로 넣으면 검출기가 준비됩니다.
두 번째는 프로그래밍적 자동 학습입니다. 카메라에서 실행되는 보정 루틴이 프레임을 캡처하고, 대상이 있는 알려진 패치의 히스토그램을 취한 다음(roi= 와 함께 get_histogram()), get_percentile() 로 히스토그램에서 패치의 값 범위를 읽습니다. 5번째 백분위수가 각 채널의 하한을, 95번째가 상한을 설정하여 양쪽 끝의 이상치 픽셀을 무시합니다. RGB565 이미지에서는 한 번의 백분위수 호출이 세 LAB 채널을 한꺼번에 알려주므로, 두 번의 호출로 find_blobs() 가 기대하는 여섯 개의 숫자가 만들어집니다:
h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
lo.a_value, hi.a_value,
lo.b_value, hi.b_value)