Union-Find 알고리즘을 이용하여 이진 영상의 Connected Component Labeling을 구현한 코드이다. 영상의 배경은 0으로 두고, 0이 아닌 픽셀은 모두 전경으로 처리한다.
각 전경 픽셀은 처음에는 독립된 집합의 root로 초기화한다. Union-Find의 parent 정보는 label 배열에 저장하며, 픽셀 위치를 pos라고 할 때 초기값은 \(\texttt{label[pos]=pos}\)로 둔다.
영상을 왼쪽 위에서 오른쪽 아래 방향으로 스캔하면서 현재 픽셀과 이미 조사된 이웃 픽셀의 연결관계를 확인한다. 4방향 연결에서는 왼쪽과 위쪽 픽셀을 조사하고, 8방향 연결에서는 여기에 위-왼쪽과 위-오른쪽 픽셀을 추가한다. 현재 픽셀과 전경 이웃이 연결되어 있으면 두 픽셀이 속한 집합의 root를 찾은 뒤, 하나의 집합으로 합병한다. 예를 들어 두 root 가운데 번호가 작은 쪽이 부모가 되도록 합병할 수 있다.
모든 픽셀을 조사한 뒤의 label 배열은 Union-Find 트리의 parent 정보를 담고 있다. 따라서 각 픽셀의 최종 연결요소를 얻으려면 FIND 연산을 통해 최종 root를 구해야 한다. 이 과정에서 경로 압축(path compression)을 적용하면 이후의 탐색을 빠르게 수행할 수 있다.
각 연결요소에는 하나의 root가 존재하지만, root 번호는 합병 과정 때문에 연속적이지 않을 수 있다. 예를 들어 연결요소의 root 번호가 3, 17, 42로 남을 수 있다. 순차적인 레이블이 필요하면 서로 다른 root에 1, 2, 3, …의 새 번호를 할당한 대응표를 만든 뒤, 각 전경 픽셀의 최종 root를 해당 번호로 변환한다.
이 방법은 연결관계를 구성하기 위한 영상 스캔을 한 번만 수행할 수 있다는 장점이 있다. 다만 모든 픽셀에 최종적이고 연속적인 연결요소 번호를 기록한 레이블 영상을 만들려면, 일반적으로 각 픽셀에 대해 FIND와 번호 변환을 수행하는 추가 순회가 필요하다. 따라서 연결관계의 구성은 한 번의 스캔으로 가능하지만, 완성된 레이블 영상을 생성하는 전체 과정은 구현 방식에 따라 두 번의 픽셀 순회를 포함할 수 있다.
#define CCL_BG (-1)
static int Find(int v, int* parent) {
if (v == parent[v]) return v;
return parent[v] = Find(parent[v], parent);
}
static void Union(int a, int b, int *parent) {
a = Find(a, parent);
b = Find(b, parent);
if (a > b) parent[a] = b;
else parent[b] = a;
}
int ConnectedComponentLabel(BYTE *image, int w, int h, int label[]) {
BYTE *q = &image[0] ;
for (int y = 0, pos = 0; y < h; y++) {
for (int x = 0; x < w; x++, pos++) {
if (*q++) { // Foreground;
label[pos] = pos; // 초기 root = 현위치
if ((y > 0) && q[-w])
Union(pos, pos-w, label);
if ((x > 0) && q[-1])
Union(pos, pos-1, label);
#if defined (_EIGHTCONN_)
if ((x + 1 < w) && (y > 0) && q[1-w])
Union(pos, pos+1-w, label);
if ((x > 0) && (y > 0) && q[-w-1])
Union(pos, pos-1-w, label);
#endif // _EIGHTCONN_
} else
label[pos] = CCL_BG; // BACKGROUND;
}
}
// 합병과정에서 빈 레이블이 생길 수 있으므로 순차적 레이블을 같도록 정리(선택 사항);
// label = -1은 배경임;
int curlab = 0; // 0-부터 시작;
for (int pos = 0 ; pos < w * h; pos++) {
int r = label[pos];
if (r == CCL_BG) continue; // background;
else if (r == pos) label[pos] = curlab++; // root: assign a new label;
else label[pos] = label[r]; // assign renewed parent's label.
}
return (curlab); //배경 제외;
};

<CCL을 이용하여 DataMatrix-code를 검출하는 과정>
<20000 성분의 체스보드에 대한 4-방향 연결 테스트;>
출력은 정수형으로 바꾸어야 한다. 8-방향 연결로 하면 1개 성분만 나온다.
<Percolation Experiment>

'Image Recognition' 카테고리의 다른 글
| Moving Average을 이용한 Thresholding (0) | 2020.11.26 |
|---|---|
| Expectation Maximization Algorithm for Two-Component Gaussian Mixture (0) | 2017.01.02 |
| RANSAC: Ellipse Fitting (1) | 2012.10.07 |
| Autofocus Algorithm (0) | 2012.06.03 |
| Statistical Region Merging (3) | 2012.03.25 |

