비전 신호 처리II_모폴로지, 레이블링

JuHwan Kim·2022년 4월 8일
0

Vision Signal Processing

목록 보기
2/6

모폴로지

모폴로지(morphology) : 형태 또는 모양에 관한 학문
영상 처리 분야에서 모폴로지는 영상에서 객체의 형태 및 구조에 대해 분석하고 처리하는 기법을 의미하며 수학적 모폴로지(mathematical morphology)라고도 함
모폴로지 기법은 그레이스케일 영상과 이진 영상에 대하여 모두 적용할 수 있지만, 주로 이진 영상에서 객체의 모양을 단순화시키거나 잡음을 제거하는 등 용도로 사용됨
모폴로지 연산을 정의하려면 먼저 구조 요소(structuring element)를 정의해야 함
구조 요소는 마치 필터링에서 사용되는 마스크처럼 모폴로지 연산의 동작을 결정하는 작은 크기의 행렬임

침식과 팽창

영상의 모폴로지 기법 중에서 가장 기본이 되는 연산은 침식(erosion)과 팽창(dilation)

침식 연산 : 객체 영역의 외곽을 골고루 깎아내는 연산
객체 영역은 축소되고 배경은 확대됨
원리 : 구조 요소를 영상 전체에 대해 스캔하면서, 구조 요소가 객체 영역 내부에 완전히 포함될 경우 고정점 위치 픽셀을 255로 설정함

팽창 연산 : 객체 영역의 외곽을 골고루 팽창시키는 연산
객체 영역은 확대되고, 배경 영역은 줄어듬
원리 : 구조 요소를 영상 전체에 대해 이동시키면서, 구조 요소와 객체 영역이 한 픽셀이라도 만날 경우 고정점 위치 픽셀을 255로 설정함

열기와 닫기

열기 연산은 입력 영상에 대하여 침식 연산을 수행한 후, 다시 팽창 연산을 수행하는 연산
침식 연산을 먼저 수행하기 때문에 한두 픽셀짜리 영역이 제거된 후, 팽창 연산이 수행됨
• 그 결과 입력 이진 영상에 존재하는 작은 크기의 객체가 효과적으로 제거됨

닫기 연산은 팽창 연산을 먼저 수행한 후, 다시 침식 연산을 수행하는 연산
팽창 연산을 먼저 수행하기 때문에 객체 내부의 작은 구멍이 메워진 후, 침식 연산이 수행됨
• 결과적으로 닫기 연산은 객체 내부의 작은 구멍을 제거함

morphologyEx()를 이용하여 모폴로지 열기와 닫기 연산을 수행할 수 있음
morphologyEx()는 열기와 닫기 뿐만이 아니라 침식과 팽창 같은 일반적인 모폴로지 연산도 수행할 수 있는 범용적인 모폴로지 연산 함수

void open_close()
{
	Mat src = imread("milkdrop.bmp", IMREAD_GRAYSCALE);

	if (src.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	Mat bin;
	threshold(src, bin, 0, 255, THRESH_BINARY | THRESH_OTSU);

	Mat dst1, dst2;
	morphologyEx(bin, dst1, MORPH_OPEN, Mat());
	morphologyEx(bin, dst2, MORPH_CLOSE, Mat());

	imshow("src", src);
	imshow("bin", bin);
	imshow("opening", dst1);
	imshow("closing", dst2);

	waitKey();
	destroyAllWindows();
}

int main(void)
{
	open_close();
	return 0;
}

레이블링(Labeling)

영상 내에 존재하는 객체 픽셀 집합에 고유 번호를 매기는 작업
연결된 구성 요소 레이블링(connected components labeling)이라고도 함

레이블링 기법을 이용하여 각 객체의 위치와 크기 등 정보를 추출하는 작업은 객체 인식을 위한 전처리과정으로 자주 사용
영상의 레이블링은 일반적으로 이진화된 영상에서 수행됨

특정 픽셀과 이웃한 픽셀의 연결 관계는 크게 2가지 방식이 존재
4-방향 연결성 (4-way connectivity)
8-방향 연결성 (8-way connectivity)

레이블 맵(label map) : 이진 영상에 레이블링을 수행하면 각각의 객체 영역에 고유의 번호가 매겨진 2차원 정수 행렬이 만들어짐
레이블링을 수행하는 알고리즘은 매우 다양하게 존재하지만 모두 같은 형태의 레이블 맵을 생성함
레이블 맵 생성 결과 각 객체 픽셀 영역에 고유의 번호가 매겨지게 됨

OpenCV에서는 connectedComponents()가 레이블링을 수행하는 함수
레이블링을 수행한 후에는 각각의 객체 영역이 어느 위치에 어느 정도의 크기로 존재하는지 확인할 필요가 있음

OpenCV에서는 레이블 맵과 각 객체 영역의 통계 정보를 한꺼번에 반환하는 connectedComponentsWithStats() 함수를 제공하고 있음

예제1_검출된 객체의 위치와 크기를 화면에 표시

입력 영상을 이진화한 후 레이블링을 수행
레이블링에 의해 얻은 객체 통계 정보를 이용하여 각 객체를 감싸는 바운딩 박스를 노란색 사각형으로 표시
객체의 픽셀 수가 20보다 작으면 잡음으로 간주하여 무시

void labeling_stats()

{
	Mat src = imread("keyboard.bmp", IMREAD_GRAYSCALE);
	
	if (src.empty())
	{
		cerr << "Image load failed" << endl;
		return;
	}

	Mat bin;
	threshold(src, bin, 0, 255, THRESH_BINARY | THRESH_OTSU);

	Mat labels, stats, centroids;
	int cnt = connectedComponentsWithStats(bin, labels, stats, centroids);

	Mat dst;
	cvtColor(src, dst, COLOR_GRAY2BGR);

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

		for (int x = 0; x < labels.cols; ++x)
		{
			if (label[x] == 5) //라벨이 5번인 경우
			{
				pixel[x][2] = 0;
				pixel[x][1] = 255;
				pixel[x][0] = 0; //초록색으로 표시하
			}
		}
	}

	for (int i = 1; i < cnt; i++)
	{
		int* p = stats.ptr<int>(i);

		if (p[4] < 20) continue;

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

		circle(dst, Point(x, y), 5, Scalar(0, 0, 255), 1);
		rectangle(dst, Rect(p[0], p[1], p[2], p[3]), Scalar(0, 255, 255));
		putText(dst, to_string(i), Point(p[0] - 10, p[1] - 10), FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 0, 0), 2);
        //Rect() = x, y, 가로길이, 세로길이
		//putText() = 글씨를 적어 줌
		//없는 번호는 제한 조건을 뒀기 때문. 작은 점이 있는 것으로 판단됨. 20픽셀 이하면 잡음으로 간주하고 무시하기 때문

	}

	imshow("src", src);
	imshow("dst", dst);

	waitKey();
	destroyAllWindows();
}
int main()
{
	labeling_stats();
	return 0;
}

아래와 같은 결과를 얻을 수 있음
dst는 키보드에서 흰색 글자만을 찾아서 노란색 사각형으로 표시한 결과 영상
키보드에 적힌 각 문자를 제대로 구분하여 표시한 것을 확인할 수 있음

profile
소소한 개발자

0개의 댓글