要直观理解 一个线程处理一个像素 的 CUDA 核函数执行逻辑,我们可以通过 三层结构示意图 来展示:GPU 线程网格(Grid)→ 线程块(Block)→ 线程(Thread)→ 图像像素 的映射关系。
以下是分层次的图示说明,结合之前的灰度化核函数案例(假设图像尺寸为 8×6=48 像素,线程块大小设为 16 线程 / 块)。

一、 整体映射关系总览

GPU Grid(线程网格)
├─ Block 0(16线程) → 处理 像素ID 0~15
├─ Block 1(16线程) → 处理 像素ID 16~31
└─ Block 2(16线程) → 处理 像素ID 32~47 (实际只用12个,剩余4个线程闲置)

↓ 每个线程ID 映射到 图像的(x,y)坐标
像素ID = y * 图像宽度 + x  →  x = ID % width; y = ID / width

二、 分层详细图示

1. 图像像素与像素 ID 的对应(行优先存储)

假设图像宽度 width=8,高度 height=6,像素 ID 从左到右、从上到下编号,对应关系如下:

在这里插入图片描述
核心公式:
像素 ID = y * width + x
反推坐标:x = ID % width; y = ID /width
例:ID=10 → x=10%8=2; y=10/8=1 → 对应 (x=2,y=1) 像素

2. 线程块(Block)与像素 ID 的映射

设置 线程块大小 = 16 线程(blockDim.x=16),总像素数 = 48,因此需要 ceil(48/16)=3 个线程块(gridDim.x=3)。

线程块 ID(blockIdx.x 包含线程 ID 范围(threadIdx.x 对应处理的像素 ID 范围 实际有效线程数
0 0~15 0~15 16(全部有效)
1 0~15 16~31 16(全部有效)
2 0~15 32~47 12(仅 0~11 有效,12~15 闲置

线程全局 ID 计算:
global_thread_idx = blockIdx.x * blockDim.x + threadIdx.x
例:blockIdx.x=1, threadIdx.x=5 → global ID=1*16+5=21 → 对应像素 ID=21

3. GPU 线程网格与线程的执行逻辑图示

在这里插入图片描述

4. 单个线程与单个像素的对应关系(核心并行逻辑)

每个线程只做 3 件事,完全独立无依赖:

  1. 计算全局 ID → 判断是否超过总像素数(边界检查);
  2. 全局 ID 映射为图像坐标 (x,y);
  3. 读取 (x,y) 彩色像素 → 计算灰度值 → 写入输出图像。
线程(global ID=10)
│
├─ 步骤1:10 < 48 → 有效线程
├─ 步骤2:x=10%8=2; y=10/8=1 → 坐标(2,1)
├─ 步骤3:读取 src[1*8*3 + 2*3] → BGR值 → 计算灰度 → 写入 dst[1*8+2]
└─ 完成任务,退出

线程(global ID=60)
│
├─ 步骤1:60 > 48 → 无效线程
└─ 直接return,不执行任何操作

三、 图示关键结论

并行粒度:

  • 1 个线程对应 1 个像素,线程间无数据竞争、无同步开销,最大化 GPU 并行效率。
  • 边界检查必要性:最后一个线程块通常会有闲置线程,必须通过 if (global_idx >= total_pixels) return 避免内存越界。
  • 线程块大小选择:建议设为 32 的倍数(如 16、32、64、256),匹配 GPU 的 Warp 调度单元(32 线程为 1 个 Warp),减少调度浪费。

四、多维处理

先看一下这段代码

void VideoOsd::drawLine(cv::cuda::GpuMat& nv12Frame, TyPoint start, TyPoint end, int thickness, cv::Scalar color)
{
	int Y = 0, U = 0, V = 0;
	RGBtoYUV(color[2], color[1], color[0], Y, U, V);

	const dim3 block(32, 16);
	const dim3 grid(divUp(nv12Frame.cols, block.x), divUp(nv12Frame.rows, block.y));
	if (!cudaStream) {
		cudaSafeCall(cudaStreamCreate(&cudaStream));
	}
	drawLineKernel << <grid, block, 0, cudaStream >> > (nv12Frame, nv12Frame.step, nv12Frame.cols, nv12Frame.rows/3*2, start, end, thickness, (uchar)Y, (uchar)U, (uchar)V);
	
	int ret = cudaGetLastError();
	if (ret != cudaSuccess) {
		printf("%s cudaGetLastError:%d\n", __FUNCTION__, ret);
	}
	if (!cudaStream)
		cudaSafeCall(cudaStreamSynchronize(cudaStream));
}
  • dim3:CUDA 用于表示三维线程 / 块维度的结构体,这里用二维(x/y);
  • block(32,16):线程块大小为 32×16=512 线程 / 块(x 方向 32 线程,y 方向 16 线程),是 GPU 友好的配置(32 的倍数,匹配 warp 大小);
  • divUp:自定义向上取整函数(如 divUp(a,b) = (a + b - 1) / b),用于计算网格大小;
  • grid(divUp(cols,32), divUp(rows,16)):线程网格大小,x 方向块数 = 图像宽度 / 32(向上取整),y 方向块数 = 图像高度 / 16(向上取整);
  • 对比之前 “1 线程 1 像素” 的一维配置,这里用二维线程块更贴合图像的二维结构(x 对应列,y 对应行),减少坐标映射计算。
Logo

更多推荐