본문 바로가기

Robotics/Software Tech.

Intel C++ Compiler 11.1로 Histogram stretching 병렬처리 하기

Intel C++ 컴파일러 11.1을 설치하고, 정수형 Vector Class를 이용하여 이미지 처리를 병렬화 하는 프로그램을 구현해 봤습니다.
성능은 대략 Intel Core2Duo E8400 3.0G에서 약 5~6배의 성능 향상 효과를 보였습니다.

테스트를 위해 사용한 이미지는 lena.bmp 512 x 512 grayscale 이미지입니다. 

원본 이미지

Histogram stretching 결과 이미지

일반적인 SISD(Single Instruction Single Data)형태로 Histogram stretching을 구현한것과 SIMD 형태로 구현한 코드의 core 부분의 차이를 보십시오.


//영상의 histogram에서 최소값과 최대값을 구하는 함수
int imageProcess::getMinMax(int* min, int* max)
{
	int _min = COLOR_8BIT, _max = 0;

	if(_imageIn)
	{
		switch(_imageIn.GetBPP())
		{
		case	BPP_8:
			{
				for(int x=0;x<_imageInHeight;x++)
					for(int y=0;y<_imageInWidth;y++)
					{
						BYTE* p = (BYTE*)_imageIn.GetPixelAddress(x,y);
						int value = *p;
						_min = min_val(value, _min);
						_max = max_val(value, _max);
					}

					*min = _min;
					*max = _max;
			}
			break;
		}
	}

	return 0;
}

//정수 Vector 클래스를 이용한 historgram의 최소 최대값 구하는 함수
int imageProcess::SIMD_getMinMax(int *min, int *max)
{
	BYTE minOut = COLOR_8BIT-1, maxOut = 0;

	if(_imageIn)
	{
		switch(_imageIn.GetBPP())
		{
		case	BPP_8:
			{
				Iu8vec16 *pImage;
				Iu8vec16 max_value;
				Iu8vec16 min_value;

				BYTE* p = (BYTE*)_imageIn.GetPixelAddress(0, 0);
				for(int x=0; x<((_imageInHeight-1)*(_imageInWidth)); x+=16)
				{
					pImage = (Iu8vec16*)(p-x);	//heap 메모리에 데이터가 할당되기때문에.. 감소시키면서 읽는다.
					max_value = simd_max(max_value, *pImage);
					min_value = simd_min(min_value, *pImage);
				}
				
				BYTE _min[16] = {0};
				BYTE _max[16] = {0};
				Is8vec16* pMin = (Is8vec16*)_min;
				*pMin = min_value;
				Is8vec16* pMax = (Is8vec16*)_max;
				*pMax = max_value;

				for(int index = 0; index<sizeof(_min); index++)
				{
					minOut = min_val(_min[index], minOut);
					maxOut = max_val(_max[index], maxOut);
				}

				*min = (int)minOut;
				*max = (int)maxOut;
			}
			break;
		}
	}

	return 0;
}
위 함수에서 영상의 최소 최대값을 구하기 위해서 정수형 Vector 클래스에서 지원하는 simd_min()과 simd_max()함수를 사용하고 있습니다. 
//일반적인 SISD형태의 histogram stretching 함수
int imageProcess::historgramStretching()
{
	if(_imageIn)
	{
		int minThreshold = 0, maxThreshold = COLOR_8BIT-1;
		switch(_imageIn.GetBPP())
		{
		case	BPP_8:
			{
				//필요한값은 min과 max이므로 histogram으로 얻을 필요는 없음.
				getMinMax(&minThreshold, &maxThreshold);

				for(int y=0;y<_imageInHeight;y++)
					for(int x=0;x<_imageInWidth;x++)
					{
						BYTE* p = (BYTE*)_imageIn.GetPixelAddress(x,y);
						*p = (BYTE)((double)((int)*p-minThreshold)/(maxThreshold-minThreshold)*255);
					}
			}
			break;
		}
	}

	return 0;
}

//Vector 클래스를 이용하여 이미지의 histogram stretching을 연산하는 함수
int imageProcess::SIMD_historgramStretching()
{
	if(_imageIn)
	{
		int minThreshold = 0, maxThreshold = COLOR_8BIT-1;
		switch(_imageIn.GetBPP())
		{
		case	BPP_8:
			{
				SIMD_getMinMax(&minThreshold, &maxThreshold);

				Iu8vec16 *pImage;

				BYTE tmpImage[16];
				memset(tmpImage, 0, sizeof(BYTE)*16);
				Iu8vec16* ptmpImage = (Iu8vec16*)tmpImage;

				BYTE diff = maxThreshold-minThreshold;
				BYTE* p = (BYTE*)_imageIn.GetPixelAddress(0, 0);
				for(int x = 0; x<(_imageInHeight*(_imageInWidth-1)); x+=16)
				{
					pImage = (Iu8vec16*)(p-x);	//heap 메모리에 데이터가 할당되기때문에.. 감소시키면서 읽는다.
					*ptmpImage = *pImage;
					
					for(int i=0;i<16; i++)
						tmpImage[i] = (tmpImage[i]-minThreshold)*255/diff;
					
					*pImage = *ptmpImage;
				}
			}
			break;
		}
	}

	return 0;
}
위 함수에서는 정수형 Vector 클래스중 Iu8vec16, 즉 8bit의 크기를 가지는 16개 데이터를 하나의 package로 묶어서 연산시키는 작업만 하였습니다.

최종적으로 SISD형태로 구현했을때 처리시간(Intel Core2Duo 3.0G)은 약 54ms가 나왔고, Vector 클래스를 사용하여 처리했을경우는 약 8ms가 나왔습니다. 약 7배의 성능 효과가 있었습니다.

정수형 Vector 클래스를 사용하기 위해서는 dvec.h 헤더파일을 include해줘야 합니다. 사용한 컴파일러는 Intel C++ Compiler 11.1 Update7 최신버젼입니다. 정수형 Vector 클래스는 나누기 연산을 지원하지 않다는 것도 주의해야 합니다. 처리해야할 데이터량이 크고 많을때 이 병렬처리의 효과가 극대화될 것 같다는 생각이 듭니다.