CUDA图像旋转的实现

CUDA图像旋转的实现

     由于最近工作比较忙,有一段时间没写博客了,今天就将以前基于CUDA做的图像旋转的demo奉献给大家。

在网上看到很多图像旋转的博客文章,可以说大部分做的只是图像旋转后还保持原来图像的大小,那么这就带来一个问题,旋转后 图像有一部分内容会造成缺失,所以为了保持图像的完整性,需要确定旋转后图像的大小,即多少行和列。

     由于本人主要从事3S方面的研发工作,读取图像我就用GDAL开源库,图像就采用我所熟悉的遥感影像。这里我采用图像绕着图像的中心旋转,那么旋转之后究竟图像有多大,其实很简单,只要求出以图像中心点为坐标原点,求出原始图像四个角点变换之后所在的行列号,然后求出最大和最小坐标就OK了,具体的代码如下:

  

// 打开原始图像
	GDALDataset* poInDs = (GDALDataset*)GDALOpen(pszFileName, GA_ReadOnly);
	if (NULL == poInDs)
	{
		return ;
	}

	double dbGeonTran[6];
	poInDs->GetGeoTransform(dbGeonTran);

	//获得原始图像的行数和列数
	int nXsize = poInDs->GetRasterXSize();
	int nYsize =  poInDs->GetRasterYSize();

	unsigned char* poDataIn = new unsigned char[nXsize*nYsize];

	int n = 1;
	poInDs->RasterIO(GF_Read,0,0,nXsize,nYsize,poDataIn,nXsize,nYsize,GDT_Byte,1,&n,0,0,0);

	//设置旋转的角度
	float fAngle = 45.0;
	float sinTheta = sin(fAngle*M_PI/180.0);
	float cosTheta = cos(fAngle*M_PI/180.0);

	//求出旋转后图像的长和宽
	float x0 = nXsize/2.0f;
	float y0 = nYsize/2.0f;

	float dfPixel[4];
	float dfLine[4];
	dfPixel[0] = (int) ( cosTheta*(0-x0) + sinTheta*(0-y0) + x0 );
	dfLine[0] = (int) ( -sinTheta*(0-x0) + cosTheta*(0-y0) + y0 );

	dfPixel[1] = (int) ( cosTheta*(nXsize-1-x0) + sinTheta*(0-y0) + x0 );
	dfLine[1] = (int) ( -sinTheta*(nXsize-1-x0) + cosTheta*(0-y0) + y0 );

	dfPixel[2] = (int) ( cosTheta*(nXsize-1-x0) + sinTheta*(nYsize-1-y0) + x0 );
	dfLine[2] = (int) ( -sinTheta*(nXsize-1-x0) + cosTheta*(nYsize-1-y0) + y0 );

	dfPixel[3] = (int) ( cosTheta*(0-x0) + sinTheta*(nYsize-1-y0) + x0 );
	dfLine[3] = (int) ( -sinTheta*(0-x0) + cosTheta*(nYsize-1-y0) + y0 );

	float fminx = *( std::min_element(dfPixel,dfPixel+4) );
	float fmaxx = *( std::max_element(dfPixel,dfPixel+4) );
	float fminy = *( std::min_element(dfLine,dfLine+4) );
	float fmaxy = *( std::max_element(dfLine,dfLine+4) );

	int nNewXsize = int(fmaxx-fminx);
	int nNewYsize = int(fmaxy-fminy);
这样,上面代码中nNewXsize和nNewYsize就是求出来的图像大小,然后分配内存,读取原始图像,我这里只拿第一个波段以及8位的数据作为实验。到这里数据都准备好了,下面只管往显卡里面送数据,图像旋转可以借助点的旋转公式解决,可以参考我的博客 二维图形旋转公式的推导,其中核心的GPU内核函数如下:

__global__ void ImageRotate_kernel(unsigned char* poDataIn,
							  unsigned char* poDataOut,
							  int nWidth,
							  int nHeight,
							  int nNewWidth,
							  int nNewHeight,
							  float sinTheta, 
							  float cosTheta)
{
	int idy = blockIdx.y*blockDim.y + threadIdx.y;	//行
	int idx = blockIdx.x*blockDim.x + threadIdx.x;	//列

	float x0 = nNewWidth/2.0f;
	float y0 = nNewHeight/2.0f;

	float x1 = nWidth/2.0f;
	float y1 = nHeight/2.0f;

	//求出idx,idy所在原始图像上的坐标
	int x2 = (int) ( cosTheta*(idx-x0) + sinTheta*(idy-y0) + x0 );
	int y2 = (int) ( -sinTheta*(idx-x0) + cosTheta*(idy-y0) + y0 );

	//还要减去偏移量
	x2 -= (x0-x1);
	y2 -= (y0-y1);

	if (idx < nNewWidth && idy < nNewHeight)
	{
		if (x2 < 0 || x2 >= nWidth || y2 < 0 || y2 >= nHeight)
		{
			poDataOut[ idy*nNewWidth + idx] = 0;
		}

		else
		{
			poDataOut[ idy*nNewWidth + idx] = poDataIn[y2*nWidth + x2];
		}
	}
}

上面函数中poDataOut变量就存储计算好的新图像的数据,然后将结果拷贝到主机端,再写回到文件。当然,这中间还需要一个中间的函数,用来启动CUDA设备,调用核函数以及主机端和GPU设备端数据的来回拷贝,该函数如下:

void ImageRotateCUDA(unsigned char* poDataIn,
					 unsigned char* poDataOut,
					 int nWidth,
					 int nHeight,
					 int nNewWidth,
					 int nNewHeight,
					 float sinTheta, 
					 float cosTheta)
{
	unsigned char* poDataIn_d = NULL;
	cudaMalloc(&poDataIn_d,nWidth*nHeight*sizeof(unsigned char));
	cudaMemcpy(poDataIn_d,poDataIn,nWidth*nHeight*sizeof(unsigned char),cudaMemcpyHostToDevice);

	unsigned char* poDataOut_d = NULL;
	cudaMalloc(&poDataOut_d,nNewWidth*nNewHeight*sizeof(unsigned char));

	//调用核函数
	dim3 dimBlock(32,32,1);
	dim3 dimGrid(ceil(nNewWidth/32.0),ceil(nNewHeight/32.0),1);
	ImageRotate_kernel<<>>(
		poDataIn_d,poDataOut_d,nWidth,nHeight,nNewWidth,nNewHeight,sinTheta,cosTheta);

	cudaMemcpy(poDataOut,poDataOut_d,nNewWidth*nNewHeight*sizeof(unsigned char),cudaMemcpyDeviceToHost);

	//释放显卡显存
	cudaFree(poDataOut_d);
	cudaFree(poDataOut_d);
}

下面的图像时原始图像:


旋转后的图像如下:

CUDA图像旋转的实现_第1张图片

可能有些读者有些疑问,为什么旋转45度后成了这个样子,那是因为在遥感中图像一般都是以左上角为原点,如果你不习惯,可以自己画一个坐标系,然后上下颠倒看就明白了。

当然,这个只是一个简单的demo,如果图像非常大,那么就需要分块来做。这样就不会导致程序崩溃。


你可能感兴趣的:(OpenCL)