해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



github => https://github.com/fbf7290/ImageProcessing/tree/ImageCalculator

※ 영상의 사칙 연산

영상과 영상 사이의 연산은 다른 부분을 발견하고자 뺄셈을 수행한다거나, 영상의 픽셀 자체 값이 아닌 픽셀이 가지는 어떠한 특성값을 계산할 때 자주 사용한다. 영상과 상수 사이의 연산은 영상의 밝기나 명암을 조절하는 데 사용할 수 있고, 픽셀에 어떠한 가중치 값을 곱할 때도 사용할 수 있다.



일단, 대화상자 기반 프로젝트 "ImageCalculator"을 만들고 이전 포스트에서 사용했던 CMyImage 폴더 내 파일들을 추가한다. 그리고 CMyImage 폴더 안에 MyImageFunc.cpp, MyImageFunc.h 파일을 생성한다. 


template <typename T1, typename T2, typename TO>
void AddImage(const CMyImage<T1>& src1, const CMyImage<T2>& src2, CMyImage<TO>& dst)
{
	int nWStep = dst.GetWidth()*dst.GetChannel();
	int nHeight = dst.GetHeight();

	int r, c;

	if (sizeof(TO) == 1) // BYTE형 영상
	{
		for (r = 0; r<nHeight; r++)
		{
			T1* pSrc1 = src1.GetPtr(r);
			T2* pSrc2 = src2.GetPtr(r);
			TO* pDst = dst.GetPtr(r);

			for (c = 0; c<nWStep; c++)
			{
				pDst[c] = CLIP(pSrc1[c] + pSrc2[c]);
			}
		}
	}
	else // BYTE형 이외의 영상
	{
		for (r = 0; r<nHeight; r++)
		{
			T1* pSrc1 = src1.GetPtr(r);
			T2* pSrc2 = src2.GetPtr(r);
			TO* pDst = dst.GetPtr(r);

			for (c = 0; c<nWStep; c++)
			{
				pDst[c] = pSrc1[c] + pSrc2[c];
			}
		}
	}
}

위 AddImage 메서드는 두 개의 입력 영상을 더해서 출력 영상을 반환하는 메서드다. MyImageFunc.h 에 정의되어 있고, MyImageFunc.cpp 파일에 정의하지 않은 이유는 CMyImage 클래스가 템플릿으로 정의되어 있기 때문이다. 위 메서드를 보면 nWStep 멤버 변수를 이전 포스트들과 다르게 4의 배수로 맞추지 않고 그냥 너비에 채널을 곱했다. 그 이유는 두 입력 영상을 곱하고 출력을 할 때 nWStep 값을 4의 배수로 함으로써 생기는 Dummy data는 연산을 할 필요가 없기 때문이다. 그리고 연산을 하는 과정에서 템플릿의 타입이 BYTE 인것과 BYTE 아닌 것으로 나눴다. 이유는 BYTE 형끼리 덧셈을 하다가 연산의 결과가 255를 넘어가면 데이터에 잘못된 값이 입력되기 때문이다. 그래서 앞 포스트에서 정의한 CLIP 매크로를 사용해 255값을 넘지 않도록 해준다. BYTE 형이 아닌 다른 자료형들은 결과값이 해당 데이터 형의 표현 범위를 벗어나는 일이 거의 일어나지 않고 CLIP 매크로 함수를 자주 호출하면 프로그램의 수행 속도를 저하하는 요인이 되므로 CLIP 매크로를 쓰지 않도록 한다. 또한, 매개변수를 보면 입력 영상의 경우 변경할 일이 없으므로 const로 선언하였고 모든 매개변수에 참조값을 넘겨 주었다. 영상의 데이터 크기는 다른 변수들에 비해 매우 크기 때문에 값에 의한 호출 보다는 참조에 의한 호출로 인자 값을 전달하는 게 좋다.


template <typename T1, typename T2>
CMyImage<T1> operator+(const CMyImage<T1<& src1, const CMyImage<T2>& src2)
{
	int nWidth1 = src1.GetWidth();
	int nHeight1 = src1.GetHeight();
	int nWidth2 = src2.GetWidth();
	int nHeight2 = src2.GetHeight();
	int nChan1 = src1.GetChannel();
	int nChan2 = src2.GetChannel();

	ASSERT(nWidth1 == nWidth2 && nHeight1 == nHeight2 && nChan1 == nChan2);

	CMyImage<T1> ret(nWidth1, nHeight1, nChan1);
	AddImage(src1, src2, ret);
	return ret;
}

위 메서드는 Operator overloading을 한 메서드이다. 보면 assert 메서드로 입력 영상의 너비, 높이, 채널 값을 비교하고 있는 데 그 이유는 연산을 할 입력 영상들의 크기값이 같아야 하기 때문이다. operator+ 메서드는 AddImage 메서드와 다르게 결과 영상을 담을 변수를 매개변수로 전달하지 못하기 때문에 operator+ 메서드 내부에서 결과 영상을 담을 변수를 선언한다. 그래서 operator+ 메서드를 반복할 경우 메모리에 결과 영상의 변수 할당 작업이 계속 일어나 프로그램 속도가 저하될 수 있다. 

 


template <typename T1, typename T2, typename TO>
void AddConst(const CMyImage<T1>& src, const T2 val, CMyImage<TO>& dst)
{
	int nWStep = dst.GetWidth()*dst.GetChannel();
	int nHeight = dst.GetHeight();

	int r, c;

	if (sizeof(TO) == 1) // BYTE형 영상
	{
		for (r = 0; r<nHeight; r++)
		{
			T1* pSrc = src.GetPtr(r);
			TO* pDst = dst.GetPtr(r);

			for (c = 0; c<nWStep; c++)
			{
				pDst[c] = CLIP(pSrc[c] + val);
			}
		}
	}
	else // BYTE형 이외의 영상
	{
		for (r = 0; r<nHeight; r++)
		{
			T1* pSrc = src.GetPtr(r);
			TO* pDst = dst.GetPtr(r);

			for (c = 0; c<nWStep; c++)
			{
				pDst[c] = pSrc[c] + val;
			}
		}
	}
}

위 메서드는 상수와 입력 영상 사이의 연산이다. AddImage 메서드를 이해 했다면 위 메서드도 이해 될 것이다.


위에서 +연산에 관한 영상과 영상끼리의 메서드, 연산자 다중 정의, 영상과 상수의 연산을 보았다. + 뿐만 아니라 -,*,/ 도 똑같이 구현하면 된다. 포스트의 첫 부분에 있는 github 참조 하길 바란다.




※ 영상의 논리 연산

영상의 논리 연산은 사칙 연산보다 활용빈도가 떨어지지만, 때때로 유용하다. 예를 들어, 영상의 특정 영역만 선택하고자 할 때, 선택하려는 영역의 픽셀값이 255인 마스크 영상을 만들어 이를 원본 영상과 논리곱 연산을 하면 결과 영상에서 마스크와 일치하는 부분만 남기는 효과를 얻을 수 있다. 

영상의 논리 연산도 산술 연산과 똑같이 하면 되는 데 논리 연산의 경우 픽셀 데이터가 Byte형 경우에만 의미가 있어 템플릿을 사용하지 않고 오직 Byte 데이터 형으로 함수를 선언 및 정의한다. 그래서 산술 연산과 달리 MyImageFunc.cpp 파일에 정의해도 된다. 하지만 MyImageFunc.h 파일에 함수에 대한 선언을 해주어야 한다. 구체적인 소스코드는 github를 참조하길 바란다.



+ Recent posts