본문 바로가기

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

[OpenCV][C++] 모폴로지(morphology) 연산 총정리(2) - 열림, 닫힘, 탑햇, 그래디언트 morphologyEx 오픈 open close tophat

지난번에는 모폴로지 연산 기본에 대해서 알아 봤습니다.

 

이번에는 기본 연산인 침식과 팽창을 응용한 모폴로지 연산에 대해 알아보겠습니다.

열기 연산은 침식 → 팽창 하는 연산이고,

닫기 연산은 팽창 → 침식 하는 연산입니다.

팽창과 침식 연산 순서에 따라 효과가 달라집니다.

이번에 살펴볼 모폴로지 연산 원형은 아래와 같습니다.

나머지 변수는 앞에서 살펴본 기본 모폴로지 연산과 같고, 여기에서 추가된 모폴로지 연산 타입(type)에 대한 상수값들은 아래와 같습니다.

 
MORPH_ERODE
침식 연산
MORPH_DILATE
팽창 연산
MORPH_OPEN
열림 연산
MORPH_CLOSE
닫힘 연산
MORPH_GRADIENT
그래디언트 연산, 팽창 연산 - 침식 연산 = 경계 검출
MORPH_TOPHAT
탑햇 연산, 원본 - 열림 = 밝은 영역 강조
MORPH_BLACKHAT
블랙햇 연산, 닫힘 - 원본 = 어두운 영역 강조
MORPH_HITMISS
히트미스 연산, 영상의 전경이나 특정 패턴 찾는데 사용
열림 Opening

열림 연산은 침식 → 팽창하는 연산으로 침식을 하면서 한두픽셀짜리는 사라지고 다시 팽창을 하게 되므로 큰 객체들은 사이즈 변화가 없게 됩니다. 노이즈 제거에 효과적이겠죠?

기본 연산은 앞에서 설명을 했기 때문에 이번에는 바로 소스부터 확인하겠습니다.

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

int main()
{
	cv::Mat src = cv::imread("./binImg2.bmp", cv::IMREAD_GRAYSCALE);

	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 1));	
	cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));	
	
	cv::Mat dst1, dst2;	

	cv::morphologyEx(src, dst1, cv::MORPH_OPEN, element1);
	cv::morphologyEx(src, dst2, cv::MORPH_OPEN, element2);
	
	return 0;
}
 

결과를 잘 생각해 보시기 바랍니다. 왜 아래와 같은 결과들이 나왔는지요.

말로 설명드리려면 너무 복잡할 것 같아 설명은 생략할께요.

닫힘 Closing

닫힘은 팽창 → 침식을 하는 연산으로 제거가 아닌 이어주는 것이 목적입니다.

소스를 보겠습니다.

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

int main()
{
	cv::Mat src = cv::imread("./binImg2.bmp", cv::IMREAD_GRAYSCALE);

	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 1));	
	cv::Mat element2 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));	
	
	cv::Mat dst1, dst2;	

	cv::morphologyEx(src, dst1, cv::MORPH_CLOSE, element1);
	cv::morphologyEx(src, dst2, cv::MORPH_CLOSE, element2);
	
	return 0;
}
 

십자모양과 같이 동떨어져 있는 모양은 그대로 보존되면서 다른 영역과 근접해 있는 모양들은 서로 이어진게 보이시죠?

그래디언트 (Gradient)

모폴로지 그래디언트라고 하는데, 물체의 외곽 또는 에지를 찾을 때 사용됩니다.

앞서 설명드린 것과 같이 팽창 영상 - 침식 영상을 한 것으로 에지 부분만 남게 됩니다.

dst = dilate(src) - erode(src)

입력 영상과 비교했을 때 팽창 연산은 밝은 영역이 더 크며, 침식 연산은 밝은 영역이 더 작습니다. 따라서 에지 부분이 남는 원리 입니다.

이번에는 실제 영상으로 한번 확인해보겠습니다.

영상을 읽어 오고 영상이 gray-scale 영상이기 때문에 일단 이진화를 합니다.

cv::Mat src = cv::imread("./LONDON256.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat bin;	
cv::threshold(src, bin, 100, 255, cv::THRESH_BINARY);
 

너무 자질구레한 검은색 점들이 많네요. 일단 위에서 배운 open으로 없애볼께요. Open은 흰색을 없애는 거니까 다시말하면 검은 색을 넓히는 게 되겠죠?

cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::morphologyEx(bin, open, cv::MORPH_OPEN, element1);
 

그럼 아래 영상처럼 이어집니다.

이제 MORPH_GRADIENT를 사용해봅시다.

cv::morphologyEx(open, dst, cv::MORPH_GRADIENT, element1);
 

결과 영상은 아래와 같아요.

전체 소스는 아래에 있습니다.

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

int main()
{
	cv::Mat src = cv::imread("./LONDON256.jpg", cv::IMREAD_GRAYSCALE);
	cv::Mat bin, dst, open;	

	cv::threshold(src, bin, 100, 255, cv::THRESH_BINARY);
		
	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));

	cv::morphologyEx(bin, open, cv::MORPH_OPEN, element1);

	cv::morphologyEx(open, dst, cv::MORPH_GRADIENT, element1);
	
	return 0;
}
 
탑햇 (Top-Hat)

탑햇 연산은 위에서 말씀드린 것과 같이

원본영상 - 열림 영상을 한 것으로 주변대비 밝은 영역이 강조되는 효과가 있습니다.

dst = src - open(src)

열림 연산이 적용된 영상은 스페클이 사라지고 객체의 크기가 보존된 결과입니다. 이 결과를 원본 영상에서 빼면 밝은 영역이 분리되어 사라졌던 스페클이나 작은 부분들이 표시되게 됩니다. 즉 원본 영상의 객체들이 제외되고 국소적으로 밝았던 부분들이 분리되는 결과를 볼 수 있습니다. 탑햇 영상은 열림 연산에서 사라질 요소들을 표시합니다.

다른 영상을 가져와보겠습니다.

이번에는 binary 영상보다는 gray-scale 영상에서 연산 결과를 확인해보겠습니다.

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

int main()
{
	cv::Mat src = cv::imread("./PEPPERS256.jpg", cv::IMREAD_GRAYSCALE);	
	cv::Mat dst;
		
	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(11, 11));

	cv::morphologyEx(src, dst, cv::MORPH_TOPHAT, element1);
	
	return 0;
}
 
블랙햇 (Black-hat)

블랙햇 연산은 닫힘 연산 영상 - 원본 영상을 한 것으로

탑햇과 반대로 주변대비 어두운 영역이 강조가 되는 효과가 있습니다.

dst = close(src) - src

닫힘 연산은 영상 객체의 내부 홀이 사라지고 객체의 크기가 보존되는 연산입니다. 이 결과에 원본 영상을 빼면 어두운 영역이 채워져 사라졌던 홀 등이 표시가 됩니다. 즉 원본 영상의 객체들이 제외되고 국소적으로 어두웠던 홀들이 분리되는 효과가 나타납니다. 블랙햇 연산은 닫힘 연산에서 사라질 요소들을 표시하는 결과를 볼 수 있습니다.

소스와 결과를 한번 보겠습니다.

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

int main()
{
	cv::Mat src = cv::imread("./LONDON256.jpg", cv::IMREAD_GRAYSCALE);	
	cv::Mat dst;
		
	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));

	cv::morphologyEx(src, dst, cv::MORPH_BLACKHAT, element1);
	
	return 0;
}
 
히트미스 (Hit-Miss)

히트미스 연산은 영상의 전경이나 특정 패턴을 찾는데 사용되기도 합니다. 이는 이진 형태학으로서의 구조 요소의 형태에 큰 영향을 받습니다.

히트미스 연산 단일 채널 영상에서 활용하며, 주로 이진화 영상에 적용됩니다.

히트미스 연산은 모서리를 검출하는데 활용하기도 합니다.

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

int main()
{
	cv::Mat src = cv::imread("./LONDON256.jpg", cv::IMREAD_GRAYSCALE);	
	cv::Mat dst;
		
	cv::Mat element1 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));

	cv::morphologyEx(src, dst, cv::MORPH_HITMISS, element1);
	
	return 0;
}
 

결과 영상을 보면, 아래와 같이 나타납니다.