본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] 영상 라벨링(Labeling) 총정리(1) - 이미지 레이블링 connectedComponents ccl

이번에는 영상 라벨링(레이블링)이라고 하는 기법에 대해 설명할께요.

 

영상 라벨링 (Image Labeling) 이란?

 

영상 라벨링은 영상 내에서 주위 같은 밝기의 픽셀값을 가지는 픽셀들을 그룹화하여

그룹별로 번호를 매기는 방법을 말합니다.

object detection, segmentation 등에 많이 사용되는 기법이에요.

이전에 이진화 기법을 통해 배경과 전경을 구분할 수 있었는데요.

https://blog.naver.com/dorergiverny/223059732009

 

[OpenCV][C++] Thresholding 이진화 영상 만들기 총정리 (1) - image threshold binarization otsu triangle 히스토그램

지난번에는 함수 실행 시간을 측정하는 쉬운 방법에 대해 알아봤습니다. https://m.blog.naver.com/dorergi...

blog.naver.com

 

전경 객체(Object)라고 표현할 수도 있겠죠. 하나의 객체들은 덩어리로 있기 때문에

몇개의 객체가 있는지를 확인하기 위해서는 연결된 덩어리를 구분할 필요가 있습니다.

이렇게 연결된 구성요소(connected component) 레이블링을 하는 것을 CCL(Connected Components Labeling)이라고 부릅니다.

라벨링 방법은?

 

픽셀의 연결관계를 정의하는 방법은 크게 3가지가 있습니다.

4방향 연결과 8방향 연결이 있고, 4방향 연결 방법 중에는 십자가 모양과 대각선 모양이 있습니다.

하지만 대각선은 많이 사용되지 않아 OpenCV에서는 지원하고 있지 않습니다.

그래서 이번에는 4방향 연결(십자가)과 8방향 연결에 대해서만 살펴볼께요.

그럼 어떻게 라벨링이 되는지 확인해볼까요?

위의 그림만 보셔도 어떤 방식으로 라벨링이 되는지 아실 수 있습니다.

 

OpenCV 에서 라벨링 함수

 

함수 원형은 아래와 같습니다.

src
입력 영상, gray 또는 binary 영상
label
라벨링된 영상
connectivity
4방향 연결성, 8방향 연결성, 기본값 8
ltype
출력 영상 type, CV_16S 또는 CV_32S, 기본값 CV_32S
반환값
레이블 개수, 전경 개수 +1(0은 배경, 나머지는 전경 인덱스)

 

사용 소스를 한번 볼께요.

#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{
    cv::Mat src = cv::imread("../ccl.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat label;
    cv::connectedComponents(src, label, 8);   
    
    return 0;
}
 

 

레이블링의 응용

 

라벨링을 어떻게 활용할까요?

뭔가 정보가 있어야 활용을 할것 같은데.. 라벨된 객체의 통계적인 정보를 계산할 수 있습니다.

함수 원형을 살펴보면,

src
입력 영상, gray 또는 binary 영상
label
라벨링된 영상
stats
각 라벨 영역에 대한 통계 정보 담은 행렬, CV_32S
centroids
각 라벨 영역의 무게 중심 좌표를 담은 행렬, CV_64F
connectivity
4방향 연결성, 8방향 연결성, 기본값 8
ltype
출력 영상 type, CV_16S 또는 CV_32S, 기본값 CV_32S
반환값
라벨 개수, 전경 개수 +1(0은 배경, 나머지는 전경 인덱스)

 

소스를 한번 살펴볼께요.

#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{    
    cv::Mat src = cv::imread("../coins_bin.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat label, stats, centroids;
    int num = cv::connectedComponentsWithStats(src, label, stats, centroids);
    
    return 0;
}
 

결과를 한번 살펴보면, 총 6개의 object가 있어서 num = 7이 나옵니다.

stats와 centroids의 설명을 드리면, 아래와 같습니다.

이걸 영상에 표시해 볼까요?

일단 bounding box를 먼저 그려볼께요.

#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{    
    cv::Mat src = cv::imread("../coins_bin.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat label, stats, centroids;
    int num = cv::connectedComponentsWithStats(src, label, stats, centroids, 8, CV_32S);
    cv::Mat src_color;
    cv::cvtColor(src, src_color, cv::COLOR_GRAY2BGR);

    cv::RNG rng(12345);

    for (int i = 1; i < num; ++i) {
        int x = static_cast<int>(stats.at<int>(i, cv::CC_STAT_LEFT));
        int y = static_cast<int>(stats.at<int>(i, cv::CC_STAT_TOP));
        int width = static_cast<int>(stats.at<int>(i, cv::CC_STAT_WIDTH));
        int height = static_cast<int>(stats.at<int>(i, cv::CC_STAT_HEIGHT));

        cv::rectangle(src_color, cv::Rect(x, y, width, height), cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)));
    }

    return 0;
}
 

그럼 아래와 같은 영상을 얻을 수 있어요.

이걸 중심점과 원으로 그려볼께요.

stats는 int 로, centroids는 double로 받는 거 잊지 마세요~!

#include <iostream>
#include "opencv2/opencv.hpp"

int main()
{    
    cv::Mat src = cv::imread("../coins_bin.bmp", cv::IMREAD_GRAYSCALE);
    cv::Mat label, stats, centroids;
    int num = cv::connectedComponentsWithStats(src, label, stats, centroids, 8, CV_32S);
    cv::Mat src_color;
    cv::cvtColor(src, src_color, cv::COLOR_GRAY2BGR);

    cv::RNG rng(12345);

    for (int i = 1; i < num; ++i) {
        int* p = stats.ptr<int>(i);
        double* c = centroids.ptr<double>(i);

        cv::circle(src_color, cv::Point((int)c[0], (int)c[1]), 5, cv::Scalar(0, 0, 0), cv::FILLED);
        cv::circle(src_color, cv::Point((int)c[0], (int)c[1]), (p[2]/2), 
                   cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)), 4);        
    }

    return 0;
}
 

결과는 아래와 같이 그릴 수 있어요.