基于CUDA的图像亮度直方图统计
2012-03-10 10:39
555 查看
算法:
1、先计算原始图像每个像素的亮度:u = (unsigned char)(0.299f * r + 0.587f * g + 0.114f * b)。
2、用一个256大小的数组统计每个亮度的点的数量。
C++实现方法:
对1280×1024大小的图像,在QX6600(2.4GHz)上单线程运行时间为17.5ms。
CUDA实现的方法比较复杂。因为如果每个线程处理一个像素然后累加到统计数组中的话,多个线程同时累加一个地址会造成数据错误,需要用原子操作进行排队,大量的线程排队操作会造成计算单元空闲,无法发挥大部分的计算性能。因此从GPU内部架构考虑,最好的方法是分两步。第一步把整个图像分成很多个部分,每个部分用一个线程块(含很多个线程)独立统计,统计结果放在线程块共有的shared memory中(同样需要用原子操作,但是shared
memory在GPU内部,速度很快,而且每个SM有独立的shared memory,因此这样操作并行度很高,能使计算单元满载运行);第二步则把各个部分独立统计的结果再累加起来得到总的统计结果。代码比较复杂:
对1280×1024大小的图像,在Geforce GTX 285上计算时间为0.54ms,比QX6600上单线程提速约30倍。
使用CUDA编程时要合理利用GPU内部的shared memory以及熟悉其优化规则,以达到最高效率。
1、先计算原始图像每个像素的亮度:u = (unsigned char)(0.299f * r + 0.587f * g + 0.114f * b)。
2、用一个256大小的数组统计每个亮度的点的数量。
C++实现方法:
memset(out, 0, sizeof(out)); unsigned long offset; unsigned long p; unsigned char r, g, b, u; offset = 0; for(y = 0; y < h; y ++) for(x = 0; x < w; x ++) { p = ((unsigned long *)in)[offset ++]; b = p & 0xff; g = (p >> 8) & 0xff; r = (p >> 16) & 0xff; // 计算亮度,结果0~255 u = (unsigned char)(0.299f * r + 0.587f * g + 0.114f * b); // 统计 out[u] ++; } |
CUDA实现的方法比较复杂。因为如果每个线程处理一个像素然后累加到统计数组中的话,多个线程同时累加一个地址会造成数据错误,需要用原子操作进行排队,大量的线程排队操作会造成计算单元空闲,无法发挥大部分的计算性能。因此从GPU内部架构考虑,最好的方法是分两步。第一步把整个图像分成很多个部分,每个部分用一个线程块(含很多个线程)独立统计,统计结果放在线程块共有的shared memory中(同样需要用原子操作,但是shared
memory在GPU内部,速度很快,而且每个SM有独立的shared memory,因此这样操作并行度很高,能使计算单元满载运行);第二步则把各个部分独立统计的结果再累加起来得到总的统计结果。代码比较复杂:
#define THREAD_N 128 #define LOOP_N 64 __global__ void histKernel(unsigned char *in, unsigned long *out) { __shared__ unsigned long smem[256]; // shared memory const unsigned long tid = threadIdx.x; const unsigned long bid = blockIdx.x; unsigned long offset = __umul24((__umul24(bid, THREAD_N) + tid), LOOP_N); // (bid * THREAD_N + tid) * LOOP_N int i; unsigned char r, g, b, u; unsigned long p; smem[tid] = smem[tid + 128] = 0; __syncthreads(); // 每个线程块有THREAD_N(128)个线程,每个线程处理LOOP_N(64)个点,统计结果存储在每个线程块的smem[256]中 for(i = 0; i < LOOP_N; i ++) { p = *(unsigned long *)&in[offset << 2]; b = p & 0xff; g = (p >> 8) & 0xff; r = (p >> 16) & 0xff; offset ++; // 计算亮度(0~255) u = (unsigned char)(0.299f * r + 0.587f * g + 0.114f * b); // 用原子操作统计,防止线程同时进行“读-修改-写”操作时造成冲突 atomicAdd((int *)&smem[u], 1); } __syncthreads(); // 线程块统计计算完成后,汇总各线程块的统计结果 // 把结果从smem[256]累加到global memory中 // 128字交替访存,以满足各线程的合并访问要求以及防止shared memory的bank conflict,提高效率 atomicAdd((int *)&out[tid], smem[tid]); atomicAdd((int *)&out[tid + 128], smem[tid + 128]); } extern "C" float histCall(unsigned char *in, unsigned long *out, int w, int h) { unsigned char *device_src = 0; unsigned long *device_dest = 0; cudaMalloc((void **)&device_src, w * h * sizeof(unsigned long)); cudaMalloc((void **)&device_dest, 256 * sizeof(unsigned long)); cudaMemset(device_dest, 0, 256 * sizeof(unsigned long)); unsigned int timer = 0; cutCreateTimer(&timer); cudaMemcpy(device_src, in, w * h * sizeof(unsigned long), cudaMemcpyHostToDevice); cutStartTimer(timer); // 各线程块并行统计,每个线程块处理THREAD_N*LOOP_N个点,然后汇总累加至最终结果 histKernel<<<w * h / THREAD_N / LOOP_N, THREAD_N>>>(device_src, device_dest); cudaThreadSynchronize(); cutStopTimer(timer); cudaMemcpy(out, device_dest, 256 * sizeof(unsigned long), cudaMemcpyDeviceToHost); float ms = cutGetTimerValue(timer); cutDeleteTimer(timer); cudaFree(device_dest); cudaFree(device_src); return ms; } |
使用CUDA编程时要合理利用GPU内部的shared memory以及熟悉其优化规则,以达到最高效率。
相关文章推荐
- 原创]基于CUDA的图像亮度直方图统计 转载
- 基于GPU平台利用CUDA加速图像处理算法 实时处理高清图像
- 基于OpenCV调整图像的对比度和亮度
- 基于图像内插值和外插值的亮度/对比度调整及模糊/锐化方法
- 图像去模糊算法在CUDA上的实现,基于MATLAB平台
- OpenCUDA-基于CUDA的图像并行算法开源程序库
- OpenCV 基于RGB三原色的基本线性变换 改变图像颜色和亮度 对比度增强算法
- 基于CUDA和OpenCV实现的图像GAMMA变换
- 小白玩Caffe——基于Ubuntu14.04+CUDA8.0+cudnn5.1的Caffe安装与MNIST实验
- 基于暗点优先膨胀的图像暗通道优化算法
- 基于LDA的图像语义分析
- 基于Vuforia的Hololens图像识别
- 非Anaconda纯PYTHON环境下Theano基于WIN10的纯净CUDA安装与GPU配置
- 图像五值化与基于三值图像的车牌识别(1)
- 基于灰度世界、完美反射、动态阈值等图像自动白平衡算法的原理、实现及效果
- SSE图像算法优化系列二十四: 基于形态学的图像后期抗锯齿算法--MLAA优化研究。
- 基于Otsu算法的图像自适应阈值切割
- 基于MATLAB图像处理工具箱
- 图像亮度增强
- 基于PCA进行多光谱和全色图像融合