Programming/OpenCV

[OpenCV] 라벨링 개념, 객체 카운팅 + 색상 추출 + 라벨링 예제

꾸준희 2018. 10. 22. 14:11
728x90
반응형


라벨링(Labeling)이란?

이진화 한 이미지에서 객체를 각각 분별하기 위해 인접한 픽셀 값들끼리 그룹화하여 번호를 매긴 것이다.

즉, 인접한 화소들을 묶어 하나의 객체로 판단하는 방식이며 "같은 번호"를 부여한다. 

라벨링은 4방향 라벨링과 8방향 라벨링으로 이루어져 있으며, 이는 OpenCV 3.0에 함수로 구현되어있다.



라벨링은 좌측 상단부터 우측 상단 방향까지 이동하면서 번호를 부여한다. 

첫 행의 번호 부여가 끝나면 다음 줄 좌측 부터 우측 방향까지 쭉~ 마지막 픽셀까지 번호를 부여한다. 



4방향 라벨링은 한 픽셀을 중심으로 십자가 모양으로 인접한 픽셀을 그룹화한다.

 

 ↑ 

 

 ← 

 

 → 

 

 ↓

 








 

 

 

 

 

 

 1

 1

 

 

 

 2

 2

 2

 

 

 

 

 

 

 

 

 2

 

 

 


 3

 

 

 

 

 

 

 

 

 3

 3

 3

 

 

 

 

 

 

 


 3

 

 

 

 

 

 

 

 

 4

 

 4

 

 

 

 

 

 

 

 4

 4

 4

 

 

 

 

 

 

 

 4

 

 

 5

 6

 6

 6

 

 

 

 

 

 

 

 

 

 

 7

 

 

 

 

 

 






8방향 라벨링은 십자가 모양에 대각선 방향까지 고려하여 인접한 픽셀을 그룹화하는 방식이다. 

 ↖

 ↑ 

 ↗

 ← 

 

 → 

 ↙

 ↓

 ↘






 

 

 

 

 

 

 1

 1

 

 

 

 2

 2

 2

 

 

 

 

 

 

 

 

 2

 

 

 

 

 3

 

 

 

 

 

 

 

 

 3

 3

 3

 

 

 

 

 

 

 

 

 3

 

 

 

 

 

 

 

 

 3

 

 3

 

 

 

 

 

 

 

 3

 3

 3

 

 

 

 

 

 

 

 3

 

 

 3

 4

 4

 4

 

 

 

 

 

 

 

 

 

 

 4

 

 

 

 

 

 









다음은 OpenCV 3.0의 라벨링 함수를 이용하여 객체를 라벨링하고, 


라벨링 된 객체의 원 영상 픽셀에 접근하여 평균 색상을 추출하는 예제이다. 









먼저 원영상(그레이스케일 이미지)을 이진화 하는 과정을 거치고, 


adaptiveThreshold(image, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 21, 20);




객체를 라벨링 한다. 


int numOfLables = connectedComponentsWithStats(binary, labels,
		stats, centroids, 8, CV_32S);




라벨링 된 객체 영역의 평균 색상(그레이 영상이니까 명암) 값을 구한다.


	for (int y = 0; y < labels.rows; ++y) {
		int *label = labels.ptr<int>(y);
		uchar* pixel = image.ptr<uchar>(y);

  		for (int x = 0; x < labels.cols; ++x) {
			
			int intensity = pixel[x];
			
			// label[x] == 0 은 배경영역에 대한 Blob 계산
			if (label[x] == 0) {
				cnt[label[x]] = 0;
				sum[label[x]] = 0;
			}
			else {
				cnt[label[x]] = cnt[label[x]] + 1;
				sum[label[x]] = sum[label[x]] + intensity;
			}
		}
	}
	
	for (int i = 0; i < numOfLables; i++) {

		if (i == 0) {
			mean[i] = 0;
		}
		else {
			mean[i] = sum[i] / cnt[i];
		}
		//cout << i << " cnt : " << cnt[i] << endl;
		//cout << i << " sum : " << sum[i] << endl;
		//cout << i << " mean : " << mean[i] << endl;
	} 




그 다음 라벨링 된(카운팅 된) 숫자와 명암 값을 이미지 위에 띄운다.



	for (int j = 1; j < numOfLables; j++) {
		int area = stats.at<int>(j, CC_STAT_AREA);
		int left = stats.at<int>(j, CC_STAT_LEFT);
		int top = stats.at<int>(j, CC_STAT_TOP);
		int width = stats.at<int>(j, CC_STAT_WIDTH);
		int height = stats.at<int>(j, CC_STAT_HEIGHT);

		int x = centroids.at<double>(j, 0); //중심좌표
		int y = centroids.at<double>(j, 1);

		//rectangle(color, Point(left, top), Point(left + width, top + height),
		//	Scalar(0, 0, 255), 1);

		putText(color, to_string(j), Point(left + 20, top + 20), FONT_HERSHEY_SIMPLEX,
			0.4, Scalar(255, 0, 0), 1);

		putText(color, to_string(mean[j]), Point(left + 10, top + 10), FONT_HERSHEY_SIMPLEX,
			0.4, Scalar(0, 255, 0), 1);
		
	}










전체 코드와 결과 영상은 아래와 같다.


#include <stdio.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main() {

	Mat image = imread("C:/Users/seohee/Desktop/image.jpg", IMREAD_GRAYSCALE);
	resize(image, image, Size(), 0.6, 0.6);
	imshow("original image", image);


	Mat binary;
	adaptiveThreshold(image, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 21, 20);
        imshow("threshold", binary);

	Mat color;
	cvtColor(binary, color, CV_GRAY2BGR);


	Mat labels, stats, centroids;
	int numOfLables = connectedComponentsWithStats(binary, labels,
		stats, centroids, 8, CV_32S);

	int cnt[1000] = { 0, };
	int sum[1000] = { 0, };
	int mean[1000] = { 0, };

	for (int y = 0; y < labels.rows; ++y) {
		int *label = labels.ptr<int>(y);
		uchar* pixel = image.ptr<uchar>(y);

  		for (int x = 0; x < labels.cols; ++x) {
			
			int intensity = pixel[x];
			
			// label[x] == 0 은 배경영역에 대한 Blob 계산
			if (label[x] == 0) {
				cnt[label[x]] = 0;
				sum[label[x]] = 0;
			}
			else {
				cnt[label[x]] = cnt[label[x]] + 1;
				sum[label[x]] = sum[label[x]] + intensity;
			}
		}
	}
	
	for (int i = 0; i < numOfLables; i++) {

		if (i == 0) {
			mean[i] = 0;
		}
		else {
			mean[i] = sum[i] / cnt[i];
		}
		//cout << i << " cnt : " << cnt[i] << endl;
		//cout << i << " sum : " << sum[i] << endl;
		//cout << i << " mean : " << mean[i] << endl;
	} 
	


	//cvtColor(image, image, CV_GRAY2BGR);

	for (int j = 1; j < numOfLables; j++) {
		int area = stats.at<int>(j, CC_STAT_AREA);
		int left = stats.at<int>(j, CC_STAT_LEFT);
		int top = stats.at<int>(j, CC_STAT_TOP);
		int width = stats.at<int>(j, CC_STAT_WIDTH);
		int height = stats.at<int>(j, CC_STAT_HEIGHT);

		int x = centroids.at<double>(j, 0); //중심좌표
		int y = centroids.at<double>(j, 1);

		//rectangle(color, Point(left, top), Point(left + width, top + height),
		//	Scalar(0, 0, 255), 1);

		putText(color, to_string(j), Point(left + 20, top + 20), FONT_HERSHEY_SIMPLEX,
			0.4, Scalar(255, 0, 255), 1);

		putText(color, to_string(mean[j]), Point(left, top + 20), FONT_HERSHEY_SIMPLEX,
			0.4, Scalar(0, 255, 0), 1);
		
	}


	imshow("result", color);

	waitKey(0);
		
	return 0;
}







이진화 된 결과 영상







객체 카운팅 및 색상추출, 라벨링(직사각형 표시는 제외하였음)


녹색은 원영상의 명암(색상) 값이고, 분홍색은 카운팅된 숫자이다.








[라벨링 참고자료]

참고자료 1 : http://devmonster.tistory.com/22

참고자료 2 : http://webnautes.tistory.com/823

참고자료 3 : http://poorman.tistory.com/176


[픽셀 접근 참고자료]

참고자료 1 : http://webnautes.tistory.com/1169

참고자료 2 : http://bskyvision.com/




728x90
반응형
1 2 3 4 5 6 7 ··· 34