CUDA:Texture 图像应用详解
什么时候使用texture
原则:
1.当对图片内存的读取效率不高时,即(memory reads do not follow the access patterns)
2.果涉及到插值,对影像数据的处理使用Texture。
第一种情况真的很少,因为现在缓存的颗粒都是32,所以在一般情况下,全局内存访问的效率都比较高。这时就没有必要使用texture了
第二种情况:有插值需要时,可以选择使用texture,因为在二维图像中,插值一个像素,需要周围的4个像素的值,所以需要取4个全局变量,而如果是texture插值一个像素,只需要取一个像素值。所以有插值需要的应用使用texture是很不错的。
使用texture的一般步骤:
- 定义纹理参考Texture Reference
- 声明CUDA数组
- 拷贝主机内存到CUDA数组
- 设置纹理参数(模式)
- 绑定纹理
- Kernel中取像素值改为Texture fetch
- 解除绑定
- 释放CUDA数组
上面提到有插值需要时,使用texture是可行的。但不是所有插值都会有好的效果,为什么呢,下面详解:
处理图像插值问题的几种情况
- 处理灰度图像插值问题
- 处理三通道影像数据,每通道是usigned char的情况
1.处理灰度图像插值问题
处理处理灰度图像插值问题,一般情况下都会有较高的提升,比较推荐这种情况使用texture。
使用细节注意:
灰度值插值,一般的灰度的像素值都是float的,按照上面的使用texture的一般步骤,
即可。
2.处理三通道影像数据,每通道是usigned char的情况
如果是轻量级计算问题(读取像素需要较少)不推荐,如果是显示问题或大量读取像素的问题(相对概念,小图像多多次是,大图像读一次不是)推荐使用texture。
为什么?:
因为图像像素的获取方式不同:
因为在图像处理领域,在默认的影像中像素的获取方式是u[x][y][z]=u[y*width*depth+x*depth+z],如opencv,gdal的数据都是这么存放的
但在texture中,tex3D(texref,x,y,z)中读取数据的是u[x][y][z]=u[x+y*pitch+z*pitch*height];(不懂pitch的可以先把pitch看成width)
为了正确的读取像素的值,需要做一次数据转换:
void changeImgData(ImageData& img)
{
//改变数据的访问方式,在默认的影像中像素的获取方式是u[x][y][z]=u[y*width*depth+x*depth+z],如opencv,gdal的数据都是这么存放的
//但在texture中,tex3D(texref,x,y,z)中读取数据的是u[x][y][z]=u[x+y*pitch+z*pitch*height];(不懂的可以先把pitch看成width)
int width = img.col;
int height = img.row;
int depth = img.nBand;
DT_8U* dataOld = img.data;
DT_8U* dataNew = new DT_8U[width*height*depth];
for (int z = 0; z < depth; z++)
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
dataNew[x + y*width + z*width*height] = dataOld[y*width*depth + x*depth + z];
}
delete img.data;
img.data = dataNew;
}
正因为计算消耗时间,所以即使texture对核函数有提速,但总耗时不一定减少。
但对于OpenGL等显示的应用,数据初始化和拷贝一次,后面的计算都是在GPU上的,所以推荐使用,但对于轻量级计算问题,要斟酌,不一定有好的效果。
有同学会说我不想改变数据的存储方式,我通过找到两种计算式的映射关系,来解决数据读取问题。
这种思路我也考虑过:
u[y*width*depth+x*depth+z]= u[x1+y1*pitch+z1*pitch*height]
这个式子在xyz和x1,y1,z1都是整数的时候可解,可以解决取值问题,但我们上面讨论了,纹理是在使用插值时会有好的效果,即x,y,z和x1,y1,z1都是小数,顶多z,z1是整数,但这还是无解的。所以找不到映射关系解决插值问题,不插值使用texture也就没有必要了。
使用细节注意:
三维的RGB可以有两种方式
使用texture laryer或使用tex3D,他们的不同在我看来除了使用的x,y,z大小限制不同外,也就是z是否是整数的不同,texture laryer要求z是整数,tex3D不要求,可以在三维上进行三线性插值。他们的要求的数据存储方式,和读取方式都是相同的,因为他们都复制自3D的CUDA Array纹理参数(模式)的选择
由于原数据是RGB,每一通道是8位的,所以texture的类型一定是unsigned char。但插值需要返回float的类型,所以这时的read mode必须选择cudaReadModeNormalizedFloat。即计算归一化的结果。但需要在结果上多乘以255(因为像素范围是[0,255))。- Z值的选择
如果使用的是tex3D,那么为保证z值是整数,需要z = z+0.5.
texture在插值的时候,会将x,y,z默认-0.5,以匹配其插值的算法,第三维,我们是不想让它插值,所以先+0.5f就能得到第三维在0,1,2的结果。即z轴不插值的结果。
具体见cuda programming guide的texture fetch的插值部分,或者见CUDA:Texture所需参数详解
由于,代码是项目中的我就不发了,实例见cuda sample中的关于texture的部分。