引言

在我的第一篇文章中我简单介绍了CUDA以及我的一些个人学习见解,在本文中我将开始正式开始CUDA实践之旅,众做周知CUDA目前应用的领域十分广泛,它能把一些普通的CPU代码提速几十倍甚至几百倍。在本人所从事的图像处理领域,在一些大图像的处理上(4K以上图像),仅仅依靠CPU进行计算已经完全无法满足工程项目所要求的运行时间,这时候我们就需要利用CUDA对代码进行加速。本文以一个8000*1000图像为例,进行代码实践。

任务要求

将一个8000*1000的单通道图分割为40x40 (可调整)的块, 计算每个块内的像素值的和、最大 、最小、均值,将计算结果保存在CPU端。

实现思路

由于我的本本的显卡最大支持的thread数为1024,而任务中40*40的数量显然已经超过了1024,故在计算任务的分配上,我综合考虑后选择了通过两次运算(每次计算数为40*20)来完成,运用共享内存保存每个块中传入图像的像素数据,最后利用归约算法对加法进行优化。

实现环境

VS2013 + CUDA7.5 + Opencv2.4.13

实现代码

1)8000*1000实验图像获取

我用了一段简单的Matlab代码来获取实验图像,代码如下:

img = uint8(zeros(8000,1000));
for i=1:8000for j=1:1000img(i,j) = uint8(255*rand(1));end
end
imwrite(img,'test.jpg')

该程序的目的在于产生一个随机产生0-255值的灰度图像,大小为8000*1000,作为我们的实验图像。 
 
(2)CPU代码实现

我先使用Opencv的方式在CPU端实现了任务,得到了计算结果(方便与GPU实现相比较,同时也可验证CUDA代码的准确性),具体的代码如下:

#include <iostream>
#include <opencv2\opencv.hpp>
#include <stdlib.h>
using namespace std;
using namespace cv;int operateMat(Mat img, int startx, int starty, int size, int thresh);int main()
{Mat img = imread("test.jpg",0);int size = 40;int thresh = 0;cout << "Plaese Input Size:";cin >> size;   //分块区域大小cout << "Plaese Input Thresh value:";cin >> thresh; //阈值double time0 = static_cast<double>(getTickCount()); //计时器开始int row = img.rows;int col = img.cols;int length = row / size;int width = col / size;int sumResult[10000];     //区域内求和结果int averageResult[10000]; //区域内均值结果int maxResult[10000]; //区域内最大值结果int minResult[10000]; //区域内最小值结果int threshNumber[10000];  //区域内阈值结果int count = 0;int x = 0;for (int i = 0; i < length; i++){for (int j = 0; j < width; j++){sumResult[count], maxResult[count], minResult[count], threshNumber[count]  = operateMat(img, i*size, j*size, size, 35);averageResult[count] = sumResult[count] / (size * size);count += 1;}}time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束cout << time0 << "s" << endl; //输出运行时间system("Pause");return 0;
}int operateMat(Mat img, int startx, int starty, int size, int thresh)
{int sum = 0;int max = 0;int min = 255;int average = 0;int threshCount = 0;for (int i = startx; i < startx + size; i++){uchar *data = img.ptr<uchar>(i);for (int j = starty; j < starty + size; j++){sum += data[j];if (max < data[j]){max = data[j];}if (min > data[j]){min = data[j];}if (data[j] > thresh){threshCount += 1;}}}average = sum / (size*size);return sum, max, min, threshCount;
}

(3)CUDA代码 
在CUDA实现上,本程序利用了每个块独有的共享内存,将需要计算的40*40的小块中所有的像素数据通过两次导入的方式(每次导入40*20)全部赋值给大小为1600的共享内存,接着通过规约算法优化运算即可得到所需结果。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <device_functions.h>
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;__global__ void matSum(uchar *dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{//__shared__ int _data[1600];const int number = 2048;extern __shared__ int _sum[];  //小图像块中求和共享数组__shared__ int _max[number];  //小图像块中求最大值共享数组__shared__ int _min[number];  //小图像块中求最小值共享数组int thread = threadIdx.x + threadIdx.y * blockDim.x; //一个block中所有thread的索引值int threadIndex = threadIdx.x + threadIdx.y * imgWidth; //每个小块中存放数据的thread索引值//每个小块中存放数据的block索引值int blockIndex1 = blockIdx.x * blockDim.x + 2 * blockIdx.y * blockDim.y * imgWidth; //40*20的上半block索引值int blockIndex2 = blockIdx.x * blockDim.x + (2 * blockIdx.y + 1) * blockDim.y * imgWidth; //40*20的下半block索引值int index1 = threadIndex + blockIndex1; //每个block中上半部分索引值int index2 = threadIndex + blockIndex2; //每个block中下半部分索引值//将待计算的40*40小图像块中的所有像素值分两次传送到共享数组中_sum[thread] = dataIn[index1]; //将上半部分的40*20中所有数据赋值到共享数组中_sum[thread + blockDim.x * blockDim.y] = dataIn[index2]; //将下半部分的40*20中所有数据赋值到共享数组中_max[thread] = dataIn[index1];_max[thread + blockDim.x * blockDim.y] = dataIn[index2];_min[thread] = dataIn[index1];_min[thread + blockDim.x * blockDim.y] = dataIn[index2];//memcpy(_sum, _data, 1600 * sizeof(int));//memcpy(_max, _data, 1600 * sizeof(int));//memcpy(_min, _data, 1600 * sizeof(int));  在GPU(Device)中用memcpy函数进行拷贝会导致显卡混乱,故不选择此种方式//利用归约算法求出40*40小图像块中1600个像素值中的和、最大值以及最小值for (unsigned int s = number / 2; s > 0; s >>= 1){if (thread < s){ _sum[thread] += _sum[thread + s]; if (_max[thread] < _max[thread + s]) { _max[thread] = _max[thread + s]; }if (_min[thread] > _min[thread + s]) { _min[thread] = _min[thread + s]; }}__syncthreads(); //所有线程同步}if (threadIndex == 0) { //将每个小块中的结果储存到输出中dataOutSum[blockIdx.x + blockIdx.y * gridDim.x] = _sum[0]; dataOutMax[blockIdx.x + blockIdx.y * gridDim.x] = _max[0];dataOutMin[blockIdx.x + blockIdx.y * gridDim.x] = _min[0];}}int main()
{Mat image = imread("test.jpg", 0); //读取待检测图片int sum[5000]; //求和结果数组int max[5000]; //最大值结果数组int min[5000]; //最小值结果数组imshow("src", image);size_t memSize = image.cols*image.rows*sizeof(uchar);int size = 5000 * sizeof(int);uchar *d_src = NULL;int *d_sum = NULL;int *d_max = NULL;int *d_min = NULL;cudaMalloc((void**)&d_src, memSize);cudaMalloc((void**)&d_sum, size);cudaMalloc((void**)&d_max, size);cudaMalloc((void**)&d_min, size);cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);int imgWidth = image.cols;int imgHeight = image.rows;dim3 threadsPerBlock(40, 20); //每个block大小为40*20dim3 blockPerGrid(25, 200); //将8000*1000的图片分为25*200个小图像块double time0 = static_cast<double>(getTickCount()); //计时器开始matSum << <blockPerGrid, threadsPerBlock, 4096 * sizeof(int) >> >(d_src, d_sum, d_max, d_min, imgHeight, imgWidth);time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束cout << "The Run Time is :" << time0 << "s" << endl; //输出运行时间cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);cout << "The sum is :" << sum[0] << endl;cout << "The max is :" << max[0] << endl;cout << "The min is :" << min[0] << endl;waitKey(0);cudaFree(d_src);cudaFree(d_sum);cudaFree(d_max);cudaFree(d_min);return 0;
}

(4)运算结果比对

CPU与GPU代码运算结果一致,在性能上CPU端代码耗时约0.1S,GPU端代码为0.2ms,加速500倍~

CUDA精进之路(一):图像处理——大图像分块处理(包括求均值、最大值)相关推荐

  1. CUDA精进之路(五):图像处理——OTSU二值算法(最大类间方差法、大津法)

    引言 最近在做医疗设备相关的项目,故在项目中大量用到了各类图像分割的算法,为了在图像中分割出特定目标,用到的算法可以有很多,比如阈值分割,多通道分割,边缘分割以及一些前沿的组合分割.而对大多数图像来说 ...

  2. CUDA精进之路(三):图像处理——图像灰度化、灰度直方图统计

    引言 在大部分的图像处理程序中,其中必不可少的一步就是对传入的彩图进行灰度处理,将三个通道的RGB图片转化为单通道的Gray图,而对于灰度图进行直方图统计同样是观察检测图像特征的常用方法.在OpenC ...

  3. CUDA精进之路(四):图像处理——Sobel算子边缘检测

    引言 关于图像边缘检测,记得刚开始接触图像处理时,第一个自己实现的程序是通过笔记本摄像头采集图像,利用OpenCV自带的算法库进行Canny算子边缘检测,那时候当看到程序运行后,视频窗口实时显示经Ca ...

  4. CUDA精进之路(二):图像处理——形态学滤波(膨胀、腐蚀、开闭运算)

    引言 从这篇文章起,开始将一些较为典型的OpenCV算法通过CUDA进行实现,本文实现的为图像处理中最为常见的形态学腐蚀以及膨胀,由于本文目的在于算法移植后的验证,故在图片的选择上用小图像作为输入的示 ...

  5. CUDA精进之路(零):CUDA开篇

    前言 着手机器视觉项目时接触到了并行编程这一概念,那时候的目的是为了在图像识别的时候通过多个线程同时对多张传入的图片进行并行处理以达到加速程序运行速度,运用的方法主要是利用了C++自带的future库 ...

  6. OpenCV精进之路(四):图像处理——图片的缩放和图像金字塔

    前言 对图像进行缩放的最简单方法当然是调用resize函数啦! resize函数可以将源图像精确地转化为指定尺寸的目标图像. 要缩小图像,一般推荐使用CV_INETR_AREA来插值:若要放大图像,推 ...

  7. OpenCV精进之路(十六):图像分解和融合技术——图像拼接和图像融合技术

    图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...

  8. 【图像处理】——图像的二值化操作及阈值化操作(固定阈值法(全局阈值法——大津法OTSU和三角法TRIANGLE)和自适应阈值法(局部阈值法——均值和高斯法))

    目录 一.二值化的概念(实际上就是一个阈值化操作) 1.概念: 2.实现方法 3.常用方法 二.阈值类型 1.常见阈值类型(主要有五种类型) (1)公式描述 (2)图表描述 2.两种特殊的阈值算法(O ...

  9. c++gdal如何在大图像中截取小图像并获取其图像信息_OpenCV 图像处理大纲

    图像的取样和量化 取样就是取像素点,量化把灰度值量化到256个灰度级 数字图像的表示 图像定义为二位函数f(x, y),x,y是空间坐标f(x, y)是点的幅值 数字图像的质量 灰度级:表明像素明暗程 ...

最新文章

  1. 芯片初创公司一亿融资可以烧多久
  2. Python中if__name__==__main__:该如何理解
  3. 【puthon】把大量csv文件写入h5文件制作数据集
  4. Java并发源码之ReentrantLock
  5. c语言机器人编程软件,Coconut编程机器人软件官方版下载_Coconut编程机器人软件 v1.3.4官方版 - Win7旗舰版...
  6. pacemaker集群管理相关命令
  7. java 装饰器模式
  8. 【OpenCV】OpenCV访问像素点的三种方式
  9. 在RHEL4.0下面安装oracle10g数据库
  10. iMazing与iTunes 两款iOS设备管理器区别 在备份操作上的对比
  11. ul列表中包含input时line-height属性失效的解决办法
  12. 用于开启php绘图扩展配置为,女儿墙屋面排水列项应选择()。A.雨水管B.雨水斗C.雨水口D.出水弯管E.檐沟...
  13. 武汉市房价数据挖掘与可视化分析(Python)
  14. 二阶系统阶跃响应实验_二阶系统的阶跃响应实验报告
  15. [转载]AdapterView.OnItemClickListener
  16. 微信小程序401unauthorized授权问题解决方法
  17. MSOCache文件,带你一文看懂。
  18. sample函数—R语言
  19. [Halcon例程学习]增强指纹纹理的coherence_enhancing_diff
  20. 电脑安装android2.0,应用多开 这才是最适用电脑的安卓—凤凰系统2.0

热门文章

  1. JSP和JSTL获取服务器参数
  2. Python pip 用法大全
  3. UIView layer 的对应关系
  4. 迁移是10g-11g ogg正好有用武之地N种方法
  5. context c语言作用,理解 Go context
  6. C#在Web项目中关闭Excel进程的方法
  7. C语言编程一个人活了多少天,来用代码算一算在这个世界上活了多少天吧
  8. f77编程和c语言的区别,在fortran中l用F77编译器编译程序时出现问题?
  9. Arcgis执行Raster Project时报Error001143 : Background server threw an exception
  10. 浅谈格斗游戏的精髓——方块的战争