Programming/Image Processing

[OpenCV] Labeling(레이블링, 라벨링) 이론 및 소스코드

DevMonster 2014. 1. 14. 21:04

OpenCV IplImage를 사용해서 Labeling 함수 구현


1. Labeling이란?

  우선 1-channel의 gray scale 영상이 필요합니다. 이진화된 아래의 이미지처럼 0 or 255(1-channel)의 값을 갖을 경우 인접한 영역끼리 그룹을 짓는 것을 Labeling(레이블링)이라고 합니다.

[그림 1]


아래의 그림처럼 인접 영역끼리 label Number를 매겨 그룹화를 하게됩니다.

 

[그림 2]


 여기서 인접한 pixel을 탐색할 때 8-neighbor 방식을 사용하였습니다. neighbor는 '이웃'이라는 뜻처럼 8-neighbor는 현재 pixel을 기준으로 몇 개의 이웃 pixel들을 탐색하는지를 뜻합니다. 아래의 왼쪽 그림은 현재 pixel을 기준으로 위, 아래, 왼쪽, 오른쪽 총 4방향을 탐색하는 4-neighbor 탐색입니다. 오른쪽 그림은 4-neighbor에 4개의 대각방향을 더 탐색하는 8-neighbor 탐색입니다. 저는 8-neighbor를 사용하였기 때문에 [그림 2]에서 label number가 '4'인 대각 픽셀이 그룹된 것을 확인할 수 있습니다. 이렇게 인접한 영역들을 Grouping한 것이 Labeling입니다.

[그림 3] 4-neighbor & 8-neighbor


2. Labeling 구현 전략

1) 이미지에서 화소 값(255)이 있는 지점까지 탐색

2) 이미 Labeling된 화소인지 확인

3) 아니라면 stack에 현재 x, y 좌표를 저장

4) 8-neighbor 탐색하면서 픽셀화소가 255이면서 Labeling이 안된 지역을     발견하면 stack에 모두 넣는다. (8-neighbor 모두 탐색한다.)

5) stack의 맨 위 좌표를 받아 4)번 과정을 반복한다.

6) 더이상 grouping할 행렬이 없을 때(stack이 비었을 때)까지 수행

7) 이미지의 다음 화소 값이 있는 곳까지 탐색하는 1) 과정부터 다시 시작


3. Labeling 구현 설명

제일 첫 좌표를 (0, 0)이라고 할 때 이미지에서 화소 값(255)이 있는 지점인 (3, 3)까지 오게된다. 이 점을 stack에 push한다.

[그림 4]


[그림 5] stack


  (3, 3)을 기준으로 8-neighbor 탐색을 한다. 해당 pixel에 값이 있고 Labeling이 안된 영역을 stack에 push 한다. [그림 6]을 보면 (3, 3)을 기준으로 3칸이 그림영역이고 현재 Labeling이 안되었기 때문에 [그림 7]처럼 stack에 push 한다.

[그림 6]


  stack의 top인 (4, 3)를 기준으로 다시 8-neighbor 탐색을 하면서 Label Number를 매기면 된다. 

[그림 7]


4. Iplimage로 구현한 Labeling 함수

stl stack을 사용하기 위해 #include <stack> 헤더파일을 선언한다. 
- 함수 호출방법: Labeling(src, dst);
- 함수 인자설명: src(원본이미지), dst(출력이미지)
(labelNumber 값이 출력이미지로 바로 출력되어 잘 안보일 수 있다. 눈에 보이게 하고 싶으면 labelNumber = 100; 이렇게 초기 값을 설정하면 잘 보일 것이다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stack>
 
void    Labeling    (const IplImage *src, IplImage *dst)
{
    // Only 1-Channel
    if( src->nChannels != 1 )
        return;
 
    // image size load
    int height    = src->height;
    int width    = src->width;
 
    // input image, result image를 image size만큼 동적할당
    unsigned char*    inputImage    = new unsigned char    [height * width];
    int*            resultImage    = new int            [height * width];    
 
    // before labeling prcess, initializing 
    forint y = 0; y < height; y++ ){
        forint x = 0; x < width; x++ ){
            // image copy
            inputImage[width * y + x] = src->imageData[width * y + x];
 
            // initialize result image
            resultImage[width * y + x] = 0;
        }
    }
 
    //// 8-neighbor labeling
    // Labeling 과정에서 stack overflow 방지를 위한 stl <stack>사용 
    stack<Point> st;
    int labelNumber = 0;
    forint y = 1; y < height - 1; y++ ){
        forint x = 1; x < width - 1; x++ ){
            // source image가 255일 경우 + Labeling 수행되지 않은 픽셀에서만 labeling process 시작
            if( inputImage[width * y + x] != 255 || resultImage[width * y + x] != 0 ) continue;
            
            labelNumber++;
            
            // 새로운 label seed를 stack에 push
            st.push(Point(x, y));
 
            // 해당 label seed가 labeling될 때(stack이 빌 때) 까지 수행
            while( !st.empty() ){
                // stack top의 label point를 받고 pop
                int ky = st.top().y;
                int kx = st.top().x;
                st.pop();
 
                // label seed의 label number를 result image에 저장
                resultImage[width * ky + kx] = labelNumber;
 
                // search 8-neighbor
                forint ny = ky - 1; ny <= ky + 1; ny++ ){
                    // y축 범위를 벗어나는 점 제외
                    if( ny < 0 || ny >= height ) continue;
                    forint nx = kx - 1; nx <= kx + 1; nx++ ){
                        // x축 범위를 벗어나는 점 제외
                        if( nx < 0 || nx >= width ) continue;
                        
                        // source image가 값이 있고 labeling이 안된 좌표를 stack에 push
                        if( inputImage[width * ny + nx] != 255 || resultImage[width * ny + nx] != 0 ) continue;
                        st.push(Point(nx, ny));
                        
                        // 탐색한 픽셀이니 labeling
                        resultImage[width * ny + nx] = labelNumber;
                    }
                }
            }        
        }
    }
 
    // dst image에 복사
    forint y = 0; y < height; y ++ ){
        forint x = 0; x < width; x++ ){
            dst->imageData[width * y + x] = resultImage[width * y + x];
        }
    }
 
    // 메모리 해제
    delete[] inputImage;
    delete[] resultImage;
}


728x90
반응형