深入理解CUDA图像并行处理

举报
HouYanSong 发表于 2025/11/18 18:40:22 2025/11/18
【摘要】 深入理解CUDA图像并行处理在CUDA编程中,一个CUDA Kernel是由众多线程(threds)组成,而这些线程又可以被组织成一个或多个block块。在同一线程块中,线程ID是从0开始连续编号的,可以通过内置变量threadIdx来获取:// 获取本线程的索引,blockIdx 指的是线程块的索引,blockDim 指的是线程块的大小,threadIdx 指的是本线程块中的线程索引in...

深入理解CUDA图像并行处理

CUDA编程中,一个CUDA Kernel是由众多线程(threds)组成,而这些线程又可以被组织成一个或多个block块。在同一线程块中,线程ID是从0开始连续编号的,可以通过内置变量threadIdx来获取:

// 获取本线程的索引,blockIdx 指的是线程块的索引,blockDim 指的是线程块的大小,threadIdx 指的是本线程块中的线程索引
int tid = blockIdx.x * blockDim.x + threadIdx.x;

以对图像的归一化处理为例,需要对图片中的每一个像素点的三个通道值分别除以255,相比于使用CPU进行串行计算,我们可以使用CUDA核函数创建更多的线程和线程块来充分利用GPU的并行处理能力:

// 计算需要的线程总量(高度 x 宽度):640*640=409600
int jobs = dst_height * dst_width;
// 一个线程块包含256个线程
int threads = 256;
// 计算线程块的数量(向上取整)
int blocks = ceil(jobs / (float)threads);

// 调用kernel函数
preprocess_kernel<<<blocks, threads>>>(
  img_buffer_device, dst, dst_width, dst_height, jobs); // 函数的参数

这里我们定义每个线程块的线程数量为256,线程块的数量为ceil(jobs / (float)threads),总的线程总量要大于等图片的像素数量。当启动Kernel函数时,GPU上的每个线程都会执行相同的程序代码,从而实现更高效的并行计算,函数具体实现如下:

// 一个线程处理一个像素点
__global__ void preprocess_kernel(
    uint8_t *src, float *dst, int dst_width,
    int dst_height, int edge)
{
  int tid = blockDim.x * blockIdx.x + threadIdx.x;
  if (tid >= edge)
    return;

  int dx = tid % dst_width; // 计算当前线程对应的目标图像的x坐标
  int dy = tid / dst_width; // 计算当前线程对应的目标图像的y坐标

  // normalization(对原图中(x,y)坐标的像素点3个通道进行归一化)
  float c0 = src[dy * dst_width * 3 + dx * 3 + 0] / 255.0f;
  float c1 = src[dy * dst_width * 3 + dx * 3 + 1] / 255.0f;
  float c2 = src[dy * dst_width * 3 + dx * 3 + 2] / 255.0f;

  // bgr to rgb
  float t = c2;
  c2 = c0;
  c0 = t;

  // rgbrgbrgb to rrrgggbbb
  // NHWC to NCHW
  int area = dst_width * dst_height;
  float *pdst_c0 = dst + dy * dst_width + dx;
  float *pdst_c1 = pdst_c0 + area;
  float *pdst_c2 = pdst_c1 + area;
  *pdst_c0 = c0;
  *pdst_c1 = c1;
  *pdst_c2 = c2;
}

其中tid是本线程的索引,dst_widthdst_height是图像的宽和高,edge是图片的像素数量,每一个线程处理一个像素点。由于线程索引是从0开始计数的,我们要确保tid不能超过图片的像素数量edge

int tid = blockDim.x * blockIdx.x + threadIdx.x;
  if (tid >= edge)
    return;

由于图像数据以行优先(row-major)顺序连续存储在内存中,每个像素由3个字节表示(BGR)。为了获取每个线程所处理的像素点在内存中的起始位置,我们可以先计算当前线程所对应图像的xy坐标即dxdy

int dx = tid % dst_width; // 计算当前线程对应的目标图像的x坐标
int dy = tid / dst_width; // 计算当前线程对应的目标图像的y坐标

然后获取当前线程所处理的像素点在内存中的起始位置:dy * dst_width * 3 + dx * 3*3是因为每个像素点有3个通道值,在内存中的排列方式为:BGRBGRBGR...,最后再/255对原图中(x,y)坐标的像素点3个通道值进行归一化:

// normalization
float c0 = src[dy * dst_width * 3 + dx * 3 + 0] / 255.0f;
float c1 = src[dy * dst_width * 3 + dx * 3 + 1] / 255.0f;
float c2 = src[dy * dst_width * 3 + dx * 3 + 2] / 255.0f;
  • dy * dst_width * 3:定位到第dy行的起始位置
  • dx * 3:在当前行中定位到第dx个像素的起始位置
  • + 0, + 1, + 2:分别访问B、G、R三个通道的值除以255

交换变量做BGRRGB的通道转换:

// bgr to rgb
float t = c2;
c2 = c0;
c0 = t;

目标图像(RGB)像素点在内存中的排列方式为RRR...GGG...BBB,当前像素点R通道的值在目标图像中内存地址为(dst + dy * dst_width + dx)G通道的值在目标图像中内存地址为(dst + dy * dst_width + dx) + area,加上1个通道的偏移量area,以此类推,完成对图像的通道转换:

// NHWC to NCHW
// rgbrgbrgb to rrrgggbbb
int area = dst_width * dst_height;
float *pdst_c0 = dst + dy * dst_width + dx;
float *pdst_c1 = pdst_c0 + area;
float *pdst_c2 = pdst_c1 + area;
*pdst_c0 = c0;
*pdst_c1 = c1;
*pdst_c2 = c2;
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。