CUDA里的图像拉伸有很多方法,比较常用的方法是将图像放大后,利用卷积对图像进行滤波处理。这种方法时间复杂度高,处理复杂。相对而言,使用纹理内存进行图像拉伸,由于纹理内存本身对传入的图像数组有线性滤波作用,所以使用纹理拾取函数从源图像纹理内存取值,赋值给输出图像,即可完成图像拉伸及图像滤波两种功能。
利用纹理内存的硬件插值功能,直接使用浮点型的坐标读取相应的源图像“像素”值,并赋值给目标图像。这里没有进行对源图像读取的越界检查,这是因为纹理内存硬件插值功能可以处理越界访问的情况,越界访问会按照事先的设置得到一个相对合理的像素颜色值,不会引起错误。
使用cuda数组储存图像数据时,滤波效果更好。
但是texture纹理内存做数据寄存器是不可取的,实验证明其存取速度并不高。纹理内存更适于图像放缩及图像旋转处理。
kernel函数如下:
// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y
// 定义了默认的线程块尺寸。
#define DEF_BLOCK_X 32
#define DEF_BLOCK_Y 8
// Kernel 函数:_stretchImgKer(拉伸图像)
// 根据给定的拉伸倍数 N 和 M,将输入图像拉伸,将其尺寸从 width * height 变成
// (width * N) * (height * N)
// 全局变量:texRef(作为输入图像的纹理内存引用)
// 纹理内存只能用于全局变量,因此将硬件插值的旋转变换的 Kernel 函数的输入图像列
// 于此处。
static texture texRef;
// Kernel 函数:_StretchImgKer(拉伸图像)
static __global__ void _stretchImgKer(
ImageCuda inimg, ImageCuda outimg, int timesWidth, int timesHeight)//拉伸倍数 //timesWidth*timesHeight
{
// 计算当前线程的位置。
int c = blockIdx.x * blockDim.x + threadIdx.x;
int r = blockIdx.y * blockDim.y + threadIdx.y;
// 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算
// 资源,另一方面防止由于段错误导致程序崩溃。
if (r >= outimg.imgMeta.height || c >= outimg.imgMeta.width)
return;
// 计算第一个输出坐标点对应的图像数据数组下标。
int outidx = r * outimg.pitchBytes + c;
// 通过目标坐标点反推回源图像中的坐标点,这一步是关键,图像的旋转处理也可使用
float inc = c/(float)timesWidth;
float inr = r/(float)timesHeight;
// 通过上面的计算,求出了第一个输出坐标对应的源图像坐标。这里利用纹理内存的
// 硬件插值功能,直接使用浮点型的坐标读取相应的源图像“像素”值,并赋值给目
// 标图像。这里没有进行对源图像读取的越界检查,这是因为纹理内存硬件插值功能
// 可以处理越界访问的情况,越界访问会按照事先的设置得到一个相对合理的像素颜
// 色值,不会引起错误。纹理拾取同时也具有平滑滤波作用,输出图像噪声小。
outimg.imgMeta.imgData[outidx] = tex2D(texRef,inc,inr);
}
主函数中进行以下处理即可:
//为CUDA数组分配内存,并将输入图像拷贝到内存
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(sizeof (unsigned char) * 8, 0, 0, 0, cudaChannelFormatKindUnsigned);
//纹理和数组绑定
errcode = cudaBindTexture2D( NULL, &texRef, inimg->imgData, &channelDesc, inimg->width, inimg->height, inimgCud->pitchBytes);
if (errcode != NO_ERROR)
return errcode;
// 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。
dim3 blocksize, gridsize;
blocksize.x = DEF_BLOCK_X;
blocksize.y = DEF_BLOCK_Y;
gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x;
gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y;
// 调用核函数。
_stretchImgKer<<>>(
insubimgCud, outsubimgCud, timesWidth, timesHeight);
实验结果如下,纹理拾取处理得到的图像滤波效果还是很好的: