본문 바로가기
코딩/OpenCV

[C++ opencv] erode, dilate 사용하여 물체 명확하게 하기

by DIYver 2020. 7. 1.

본문 목표

이 전까지 grayscale 과 threshold 에 대해서 배웠었다.

 

이제 노이즈를 제거함에 있어서 획기적인 방법 중 하나인 erode 또는 dilate 기법에 대해서 알아보도록 하자.

 

ErodeDilate 연산을 모폴로지 기법이라고도 한다.

 

erode 를 해석하면 "침식시키다" 또는 "약화시키다" 라는 뜻을 가지고 있다.

 

erosion 이라고도 한다. 그저 erode 의 명사일 뿐이다.

 

dilate 를 해석하면 "확장하다" 또는 "키우다" 라는 뜻을 가지고 있다.

 

dilation 이라고도 한다. 역시 명사형일 뿐이다.

 

이진화 된 영상에서 사용자가 중요하게 취급하는 것이 object 인데,

 

object를 1 (white) 로 취급할 것인지, 0 (black) 으로 취급할 것인지에 따라서 

 

erode와 dilate의 사용법이 조금씩 달라진다.

 

이에 대한 이해가 중요하다.

 

erdoe 와 dilate 기법을 한번만 사용해서는 원하는 결과물을 얻기는 힘들지만,

 

응용해서 다양하게 사용할 수 있으므로 중요한 내용이다.

 

 

이제부터 그 원리와 과정을 알아보도록 하자.

 

 

 

 

키워드 : erode, dilate, erosion, dilation, 침식, 확장

 

 

 

 

 

알아볼 함수 원형

- 침식연산 ( Erode )

...

	Mat img = imread("Lenna.png", 0);	//이미지를 grayscale로 불러옴

	Mat img_erode;
	Mat img_erode2;

	Mat img_threshold;

	threshold(img, img_threshold, 180, 255, THRESH_BINARY);

	erode(img_threshold, img_erode, Mat::ones(Size(3,3),CV_8UC1),Point(-1,-1),2);

	erode(img_threshold, img_erode2, Mat());

...

erode( src, dst, kernel, anchor, iteration, borderType, borderValue)

 

  ○ src : 입력할 이미지 변수 (grayscale 이미지를 입력해야함! )

 

  ○ dst : 필터가 적용되어 저장될 이미지 변수

 

  ○ kernel : 침식연산을 할 mask 크기를 포함한 필터에 대한 정보(커널) 행렬,

  편하게 사용하려면 Mat( ) 을 입력하면 3x3 사이즈로 적용된다.

  mask 크기를 조절하고 싶다면 Mat::ones( Size(5, 5), CV_8UC1) 와 같이 사용하면 된다.

 

  ○ anchor : 굳이 안 건드려도 된다. 그냥 Point(-1,-1) 을 사용하면 된다.

  설명하자면 커널이 적용될 때, 변경될 픽셀의 위치를 말하며, Point(-1,-1)은 중앙을 뜻한다.

 

  ○ iteration : 반복할 횟 수, 침식연산을 반복해서 적용한다. 너무 많이 반복하면 object가 다 없어져버릴 수 있다.

 

  ○ borderType : 이미지 테두리 부분에서 경계 밖의 가상의 픽셀들을 처리할 방법을 선택,

  기본값은 mirror padding 이고, 굳이 바꿀 필요 없다.

 

  ○ borderValue : 굳이 변경하고 언급할 이유가 없다.

 

 

 

만약 Background 가 0 으로 취급 되게끔 했을 경우,

 

위와 같은 threshold 가 적용된 원본이미지에

 

침식연산( Erode )을 적용하게 되면, 오른쪽 이미지처럼 된다.

 

object의 영역이 줄어든 것을 볼 수 있다.

 

말 그대로 하얀색 부분이 침식된 것이다.

 

 

원리는 위와 같다.

 

커널 행렬의 크기가 3x3 일 경우에

 

필터를 적용하려는 중심 픽셀로 부터의 범위에서

 

가장 작은 값을 찾고

 

그 가장 작은 값을 필터의 중심 픽셀에 적용시키는 것이다.

 

input image 에서 보면 필터 범위 안에 0이라는 가장 작은 값이 있기 때문에

 

필터 중심에 있던 원래 245의 값을 갖는 픽셀이 0으로 바뀌어 output image로 출력되는 것이다.

 

 

위는 grayscale 에서의 예시이지만, 보통은 이진화 된 이미지에서 사용하므로 0 아니면 255 값을 띄게 된다.

 

 

 

 

좀 더 자세히 살펴보자면

input image 에서 3x3 필터를 적용한다 했을 때,

 

빨간 사각형 안에 0 이라는 최소값이 존재하므로

 

필터 영역을 나타내는 빨간 사각형 중심 픽셀은 0으로 바뀌게 되는 것이다.

 

 

그리고 우측으로 한 픽셀 옮겨서 필터를 적용해보면

 

이번에는 필터 영역내에 최소값이 255 이므로,

 

필터 영역 중심 값은 255로 그대로 남게 된다.

 

 

또 우측으로 한 픽셀 옮겨서 필터를 적용해보면

 

필터 영역 내에 최소값 0이 존재하므로

 

필터 중심 값은 0으로 바뀌게 된다.

 

이렇게 모든 픽셀로 이동하면서 필터를 적용하게 되면

 

아래와 같은 결과를 얻게 된다.

 

 

위의 이미지에는 테두리쪽은 연산이 안되는 경우로 하였다.

 

아무튼 Erode 기법으로 인해서 결과 이미지가 입력 이미지에서 침식된 것처럼 보이게 된다.

 

 

만약 여기서 mask 크기가 5x5 였다면 어떻게 되었을까?

 

답을 말해주자면 그냥 이미지 전체의 픽셀 값은 0이 되었을 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

알아볼 함수 원형

- 팽창연산 ( Dilate )

...

	Mat img = imread("Lenna.png", 0);	//이미지를 grayscale로 불러옴

	Mat img_threshold;

	threshold(img, img_threshold, 180, 255, THRESH_BINARY);

	dilate(img_threshold, img_dilate, Mat::ones(Size(3, 3), CV_8UC1), Point(-1, -1), 2);

...

dilate( src, dst, kernel, anchor, iteration, borderType, borderValue)

 

  ○ src : 입력할 이미지 변수 (grayscale 이미지를 입력해야함! )

 

  ○ dst : 필터가 적용되어 저장될 이미지 변수

 

  ○ kernel : 팽창연산을 할 mask 크기를 포함한 필터에 대한 정보(커널) 행렬,

  편하게 사용하려면 Mat( ) 을 입력하면 3x3 사이즈로 적용된다.

  mask 크기를 조절하고 싶다면 Mat::ones( Size(5, 5), CV_8UC1) 와 같이 사용하면 된다.

 

  ○ anchor : 굳이 안 건드려도 된다. 그냥 Point(-1,-1) 을 사용하면 된다.

  설명하자면 커널이 적용될 때, 변경될 픽셀의 위치를 말하며, Point(-1,-1)은 중앙을 뜻한다.

 

  ○ iteration : 반복할 횟 수, 침식연산을 반복해서 적용한다. 너무 많이 반복하면 object가 다 없어져버릴 수 있다.

 

  ○ borderType : 이미지 테두리 부분에서 경계 밖의 가상의 픽셀들을 처리할 방법을 선택,

  기본값은 mirror padding 이고, 굳이 바꿀 필요 없다.

 

  ○ borderValue : 굳이 변경하고 언급할 이유가 없다.

 

 

 

 

 

 

 

원리는 딱 erosion 과 반대이다.

 

필터내에 가장 큰 값을 필터 중심 픽셀 값에 적용시키는 것이 원리이다.

 

 

빨간 영역이 필터의 범위라면,

 

그 안에 가장 큰 값인 255를 필터 중심 픽셀에 적용시킨다.

 

그리고 오른쪽으로 한 픽셀만큼 움직여 필터를 적용하면

 

 

역시 필터 영역 내에 255 값이 있으므로

 

그대로 255를 유지한다.

 

 

또 우측으로 한 픽셀 옮겨서 필터를 적용하면

 

역시 최대값 255 가 있으므로 영역 중심 픽셀에 적용시킨다.

 

이렇게 모든 픽셀에 필터를 적용하게 된다면

 

결과는 아래와 같을 것이다.

 

위처럼 object 의 영역이 팽창(확장) 되었음을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

코드 테스트 결과

- CODE

#include <opencv2/opencv.hpp>


using namespace cv;
using namespace std;

int main(int ac, char** av) {
	Mat img = imread("Lenna.png", 0);	//이미지를 grayscale로 불러옴

	Mat img_dilate;
	Mat img_erode;

	Mat img_threshold;

	threshold(img, img_threshold, 180, 255, THRESH_BINARY);

	dilate(img_threshold, img_dilate, Mat::ones(Size(3, 3), CV_8UC1), Point(-1, -1));
	erode(img_threshold, img_erode, Mat::ones(Size(3, 3), CV_8UC1), Point(-1, -1));

	imshow("original", img_threshold);

	imshow("img_dilate", img_dilate);
	imshow("img_erode", img_erode);
	//imshow("erode2", img_erode2);
	waitKey(0);


	return 0;
}

 

- RESULT

 

Lenna 이미지에서  임계값 180을 적용한 binary 이미지이다.

 

여기에 erode와 dilate 연산을 시킨 결과는 아래와 같다.

 

 

erode 연산이 적용된 결과이다.

 

침식작용이 되어서, 밝은 픽셀들이 없어진 것을 쉽게 확인할 수 있다.

 

없어진 픽셀들은 주변에 0 값을 갖고 있었던 것이다.

 

점들이 없어진것으로 보아, 어느정도 노이즈도 없어졌다고 볼 수 있겠다.

 

 

dilate 연산이 적용된 결과이다.

 

팽창작용이 되어서, 밝은 픽셀들이 확실히 증가한 것을 확인할 수 있다.

 

그런데 약간 좀 부자연스러워 보인다.

 

아무튼 흰색 점들이 많아지고 굵게 보여서 좋은 결과는 아니다.

 

반대로 Object가 어두운 색을 갖고 있었다면

 

dilate 연산을 적용했을때 노이즈를 억제하는 결과를 가져다 주었을 것이다.

 

 

좌) 원본 이미지, 우) dilate 연산 이미지

확실히 검은 점들이 없어진 것을 확인할 수 있다.

 

 

 

 

 

 

이번에는 dilate 와 erode 연산에 대해서 알아봤다.

 

사실 이 방식들은 object를 명확하게 할 때 사용하곤 한다.

 

object 가 밝은 색을 띄고 있을 때,

 

dilate(팽창) 연산을 적용하면 object가 더 명확해지게 된다.

 

커널 크기를 키우거나, 연산을 반복하게 된다면

 

각각의 물체를 나타내는 영역이 밝은 색으로 뭉치게 될 것이다.

 

이렇게 되면 연산처리할 때, 영역을 구하기에도 쉽고,

 

라벨링(laveling) 이라고 해서 물체의 개수를 셀 때에도 유용하다.

 

 

예를 들어보자면

 

위와같은 식물 줄기에서, 세포의 개수를 세는 프로그램을 만든다 한다면,

 

어떻게 세포를 셀 것인가? 고민해야한다.

 

오늘 다뤘던 방식중에 object가 0으로 표현되고 있고,

 

애매한 부분을 없애야 하므로 Dilate (팽창) 연산을 적용해야 함을 알 수 있다.

 

 

 

이 방식은 그냥 팽창연산을 한 것이 아니라

 

팽창연산을 3번 진행하고, 침식연산을 3번 진행한 결과이다.

 

팽창연산을 3번 함으로써 조그마한 점들은 거의 제거가 되어버리고

 

확실히 세포 크기만큼 큰 점들만 조그맣게 남게된다.

 

다시 보기쉽게 하기 위해서 침식연산을 해 주어 점들을 키우는 작업을 하게 되는 것이다.

 

 

 

 

아무튼 오늘 이렇게 Erode 와 Dilate에 대해서 알아봤고,

 

OpenCV에서는 어떻게 적용하는지 알아보았다.

 

다음에는 이 두가지 기법을 이용해서 또 다른 연산을 할 수 있음을 소개하고자 한다.

 

잘 기억해 두기를 바란다.

 

 

 

 

 

 

 

 

 

 

 

 

 

도움이 되었거나, 문제가 있는 경우 댓글로 알려주세요~!

감사의 댓글은 작성자에게 큰 힘이 됩니다 ^^

댓글