본문 바로가기
코딩/OpenCV

[C++ opencv] Grayscale 변환에 대한 고찰 1

by DIYver 2020. 6. 17.

본문 목표

OpenCV 를 사용하다 보면 Grayscale을 정말 많이 사용한다. 

 

그 이유는 노이즈를 없애고 연산처리 속도를 빠르게 하면서 정확도를 향상시키기 위함이다.

 

하지만 imread( )에서 flag를 0으로 받아온 grayscale 이미지와

cvtColor( ) 함수를 사용해서 얻은 grayscale 이미지가 다를 수 있다는 사실을 아는 사람이 얼마나 될까 싶다.

 

이번 글에서는 Grayscale 에 대해서 전문적으로 다뤄보고 이에대한 여러가지 고찰에 대한 내용을 다뤄보려한다.

 

 

 

 

 

 

Grayscale 

- 흑백 채널, 회색조 ( Grayscale )

 

단순하게 해석하자면 컬러이미지에서 광도만을 표현한 색 채널이다.

 

밝은 부위는 하얀색, 어두운 부위는 검은색으로 표현되는 색 채널이다.

 

보통 0~255 까지의 값으로 표현되며, 0은 검은색이고 255는 하얀색이다.

 

컬러이미지에서 흑백이미지로 만드는 방법은 정말 여러가지가 있다.

 

Blue, Green, Red 값들의 평균을 내는 방법도 있을 것이고,

휘도를 고려한 복잡한 방법도 있다.

 

코딩을 통해서 직접 흑백화 하는 코드를 만들어 볼 필요가 있다.

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int ac, char** av) {

	Mat color_img = imread("Lenna.png");
	Mat grayscale_img(color_img.rows,color_img.cols, CV_8UC1);
		
	for (int y = 0; y < color_img.rows; y++)
	{
		for (int x = 0; x < color_img.cols; x++)
		{
			int avg_val = (color_img.at<Vec3b>(y, x)[0]+ color_img.at<Vec3b>(y, x)[1]+ color_img.at<Vec3b>(y, x)[2])/3;
			grayscale_img.at<uchar>(y, x) = avg_val;
		}
	}
    
	imshow("grayscale_img", grayscale_img);
	waitKey(0);

	return 0;
}

위의 코드는 필자가 직접 작성해 본 코드이다.

 

가장 편한 방법으로 컬러이미지를 흑백으로 바꿔본 것이다.

 

이제 원본과 imread( )상에서 받아온 grayscale 이미지와 직접 코딩으로 만든 grayscale 이미지를 비교해보자

원본 이미지
imread( ) 에서 grayscale로 불러온 이미지
직접 코딩으로 구현한 grayscale 이미지

 

결과만 본다면 imread( ) 상에서 흑백으로 불러온 것과, 직접 코딩으로 구현한 흑백 이미지가 상당히 유사한 것을 볼 수 있다.

 

두 이미지의 차이를 비교해 보자

두 이미지의 픽셀 차를 나타낸 이미지

이미지가 잘 안 보일 수 있다. 화면 밝기를 높이면 보인다.

높여도 안 보이면, 클릭해 보면 보인다.

(그래도 안 보이면... 보인다고 생각하라...)

 

위의 이미지가 흡사 심령 사진처럼 나온것 같아 무섭지만;

 

저만큼이 OpenCV에서 제공하는 함수와 나의 코딩의 결과값 차이이다.

 

두개 중에 정답은 없다.

 

굳이 정답을 고른다면 연산 속도가 빠른것이 정답이 되겠다.

 

과연 무엇이 빠를까? 

 

스스로 고민도 많이 해보고, 직접 코딩을 통해서 답을 얻으면 제일 좋겠지만

 

답을 알려주자면 imread( )를 통해서 흑백이미지로 받아오는 것이 4배 정도 빠른 결과값을 주었다.

 

즉, 굳이 잘 만들어진 함수가 있는데 만들어서 사용하면 오히려 작업 시간도 낭비인데, 연산 처리 시간도 낭비가 된다는 뜻이다.

 

물론 직접 제작한 코드는 .at 을 통한 픽셀값 접근만 시도해 보아서 33%만 정확하다.

 

포인터와 데이터 값으로 픽셀값에 접근한다면 그 속도는 at으로 접근 하는 것보다 약 2배 더 빠르다.

 

하지만 그래도 opencv 내장 함수로 작업하는 것이 약 2배 더 빠르다.

 

 

 

 

 

이제 글의 처음에서 다루었던 imread( ) 함수를 이용해 불러온 grayscale 이미지와 cvtColor( ) 함수를 이용해 얻은 grayscale 이미지의 차이를 살펴보자

 

 

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int ac, char** av) {

	Mat color_img = imread("Lenna.png");
	Mat grayscale_img_1 = imread("Lenna.png", 0);

	Mat grayscale_img_2;

	cvtColor(color_img, grayscale_img_2, COLOR_BGR2GRAY);
	
	imshow("grayscale_img_1", grayscale_img_1);
	imshow("grayscale_img_2", grayscale_img_2);

	waitKey(0);

	return 0;
}

grayscale_img_1 에는 imread( ) 함수를 이용한 grayscale 이미지를 저장하고,

grayscale_img_2 에는 cvtColor( ) 함수를 이용한 grayscale 이미지를 저장했다.

 

좌 : imread( ) 이용,   우 : cvtColor( ) 이용

두 사진을 사람의 눈으로 비교한다면 비슷해 보이고, 차이를 알기 어렵다.

 

이번에는 두 사진의 차이를 수치로 확인해 보자.

 

두 이미지의 감산 결과

위의 숫자 데이터 하나는 두 개의 grayscale 이미지의 같은 좌표값을 서로 감산한 값이다.

즉 픽셀 값이 똑같기는 커녕, 최소 2 최대 15까지 데이터 값이 차이가 나는 것을 볼 수 있다.

 

이게 뭐가 중요하냐고 물을 수 있다.

 

당장은 이 미세한 차이가 결과값에 큰 영향을 주지는 않을 것이다.

 

하지만 나중에 정밀한 연산을 요구하는 작업에서는 큰 차이로 원하지 않은 결과값을 얻을 수 있다.

 

template matching 에서는 imread( )grayscale( ) 의 결과 값이 다르면 연산이 거의 불가능하거나 이상한 결과값을 도출해낸다...

 

따라서 민감한 연산을 한다면 혼용해서 사용하면 안 된다는 말을 하고 싶었다.

 

그리고 연산 속도 차이도 있는데,

제일 빠른 것은 imread( )를 이용한 grayscale 적용이고,

cvtColor( ) 가 간소한 차이로 살짝 느린 결과를 보여줬다.

 

 

앞선 내용을 종합해보자면 민감한 연산이 아닌 상황에서 가지고 있는 이미지를 grayscale 색 채널로 사용하려면

cvtColor( ) 보다 imread( )를 사용하는 것이 연산속도에서 이득을 볼 수 있다는 것이고,

grayscale 이라고 해서 다 같은 값을 가지고 있지 않다는 것이다.

 

 

 

 

 

 

 

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

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

댓글