在图像处理中,颜色变换BGR到Gray,常见的一般有两种计算方式,一种是基于浮点数计算,一种是基于性能优化的通过移位的整数计算。

浮点数计算公式为: gray = 0.1140 * B  + 0.5870 * G + 0.2989 * R;

整数计算公式为: gray = (1868 * B + 9617 * G + 4899 * R) >> 14.

这里与OpenCV的实现方式一致,采用整数计算公式。

以下分别是C++和CUDA的BGR到Gray的实现测试代码:

bgr2gray.cpp:

#include "funset.hpp"
#include <chrono>
#include "common.hpp"int bgr2gray_cpu(const unsigned char* src, int width, int height, unsigned char* dst, float* elapsed_time)
{TIME_START_CPUconst int R2Y{ 4899 }, G2Y{ 9617 }, B2Y{ 1868 }, yuv_shift{ 14 };for (int y = 0; y < height; ++y) {for (int x = 0; x < width; ++x) {dst[y * width + x] = (unsigned char)((src[y*width * 3 + 3 * x + 0] * B2Y +src[y*width * 3 + 3 * x + 1] * G2Y + src[y*width * 3 + 3 * x + 2] * R2Y) >> yuv_shift);}}TIME_END_CPUreturn 0;
}

bgr2gray.cu:

#include "funset.hpp"
#include <iostream>
#include <chrono>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include "common.hpp"/* __global__: 函数类型限定符;在设备上运行;在主机端调用,计算能力3.2及以上可以在
设备端调用;声明的函数的返回值必须是void类型;对此类型函数的调用是异步的,即在
设备完全完成它的运行之前就返回了;对此类型函数的调用必须指定执行配置,即用于在
设备上执行函数时的grid和block的维度,以及相关的流(即插入<<<   >>>运算符);
a kernel,表示此函数为内核函数(运行在GPU上的CUDA并行计算函数称为kernel(内核函
数),内核函数必须通过__global__函数类型限定符定义);*/
__global__ static void bgr2gray(const unsigned char* src, int B2Y, int G2Y, int R2Y, int shift, int width, int height, unsigned char* dst)
{/* gridDim: 内置变量,用于描述线程网格的维度,对于所有线程块来说,这个变量是一个常数,用来保存线程格每一维的大小,即每个线程格中线程块的数量.一个grid为三维,为dim3类型;blockDim: 内置变量,用于说明每个block的维度与尺寸.为dim3类型,包含了block在三个维度上的尺寸信息;对于所有线程块来说,这个变量是一个常数,保存的是线程块中每一维的线程数量;blockIdx: 内置变量,变量中包含的值就是当前执行设备代码的线程块的索引;用于说明当前thread所在的block在整个grid中的位置,blockIdx.x取值范围是[0,gridDim.x-1],blockIdx.y取值范围是[0, gridDim.y-1].为uint3类型,包含了一个block在grid中各个维度上的索引信息;threadIdx: 内置变量,变量中包含的值就是当前执行设备代码的线程索引;用于说明当前thread在block中的位置;如果线程是一维的可获取threadIdx.x,如果是二维的还可获取threadIdx.y,如果是三维的还可获取threadIdx.z;为uint3类型,包含了一个thread在block中各个维度的索引信息 */int x = threadIdx.x + blockIdx.x * blockDim.x;int y = threadIdx.y + blockIdx.y * blockDim.y;//if (x == 0 && y == 0) {// printf("%d, %d, %d, %d, %d, %d\n", width, height, B2Y, G2Y, R2Y, shift);//}if (x < width && y < height) {dst[y * width + x] = (unsigned char)((src[y*width * 3 + 3 * x + 0] * B2Y +src[y*width * 3 + 3 * x + 1] * G2Y + src[y*width * 3 + 3 * x + 2] * R2Y) >> shift);}
}int bgr2gray_gpu(const unsigned char* src, int width, int height, unsigned char* dst, float* elapsed_time)
{const int R2Y{ 4899 }, G2Y{ 9617 }, B2Y{ 1868 }, yuv_shift{ 14 };unsigned char *dev_src{ nullptr }, *dev_dst{ nullptr };// cudaMalloc: 在设备端分配内存cudaMalloc(&dev_src, width * height * 3 * sizeof(unsigned char));cudaMalloc(&dev_dst, width * height * sizeof(unsigned char));/* cudaMemcpy: 在主机端和设备端拷贝数据,此函数第四个参数仅能是下面之一:(1). cudaMemcpyHostToHost: 拷贝数据从主机端到主机端(2). cudaMemcpyHostToDevice: 拷贝数据从主机端到设备端(3). cudaMemcpyDeviceToHost: 拷贝数据从设备端到主机端(4). cudaMemcpyDeviceToDevice: 拷贝数据从设备端到设备端(5). cudaMemcpyDefault: 从指针值自动推断拷贝数据方向,需要支持统一虚拟寻址(CUDA6.0及以上版本)cudaMemcpy函数对于主机是同步的 */cudaMemcpy(dev_src, src, width * height * 3 * sizeof(unsigned char), cudaMemcpyHostToDevice);/* cudaMemset: 存储器初始化函数,在GPU内存上执行。用指定的值初始化或设置设备内存 */cudaMemset(dev_dst, 0, width * height * sizeof(unsigned char));TIME_START_GPU/* dim3: 基于uint3定义的内置矢量类型,相当于由3个unsigned int类型组成的结构体,可表示一个三维数组,在定义dim3类型变量时,凡是没有赋值的元素都会被赋予默认值1 */// Note:每一个线程块支持的最大线程数量为1024,即threads.x*threads.y必须小于等于1024dim3 threads(32, 32);dim3 blocks((width + 31) / 32, (height + 31) / 32);/* <<< >>>: 为CUDA引入的运算符,指定线程网格和线程块维度等,传递执行参数给CUDA编译器和运行时系统,用于说明内核函数中的线程数量,以及线程是如何组织的;尖括号中这些参数并不是传递给设备代码的参数,而是告诉运行时如何启动设备代码,传递给设备代码本身的参数是放在圆括号中传递的,就像标准的函数调用一样;不同计算能力的设备对线程的总数和组织方式有不同的约束;必须先为kernel中用到的数组或变量分配好足够的空间,再调用kernel函数,否则在GPU计算时会发生错误,例如越界等 ;使用运行时API时,需要在调用的内核函数名与参数列表直接以<<<Dg,Db,Ns,S>>>的形式设置执行配置,其中:Dg是一个dim3型变量,用于设置grid的维度和各个维度上的尺寸.设置好Dg后,grid中将有Dg.x*Dg.y*Dg.z个block;Db是一个dim3型变量,用于设置block的维度和各个维度上的尺寸.设置好Db后,每个block中将有Db.x*Db.y*Db.z个thread;Ns是一个size_t型变量,指定各块为此调用动态分配的共享存储器大小,这些动态分配的存储器可供声明为外部数组(extern __shared__)的其他任何变量使用;Ns是一个可选参数,默认值为0;S为cudaStream_t类型,用于设置与内核函数关联的流.S是一个可选参数,默认值0. */// Note: 核函数不支持传入参数为vector的data()指针,需要cudaMalloc和cudaMemcpy,因为vector是在主机内存中bgr2gray << <blocks, threads >> >(dev_src, B2Y, G2Y, R2Y, yuv_shift, width, height, dev_dst);/* cudaDeviceSynchronize: kernel的启动是异步的, 为了定位它是否出错, 一般需要加上cudaDeviceSynchronize函数进行同步; 将会一直处于阻塞状态,直到前面所有请求的任务已经被全部执行完毕,如果前面执行的某个任务失败,将会返回一个错误;当程序中有多个流,并且流之间在某一点需要通信时,那就必须在这一点处加上同步的语句,即cudaDeviceSynchronize;异步启动reference: https://stackoverflow.com/questions/11888772/when-to-call-cudadevicesynchronize */cudaDeviceSynchronize();TIME_END_GPUcudaMemcpy(dst, dev_dst, width * height * sizeof(unsigned char), cudaMemcpyDeviceToHost);// cudaFree: 释放设备上由cudaMalloc函数分配的内存cudaFree(dev_dst);cudaFree(dev_src);return 0;
}

main.cpp:

#include "funset.hpp"
#include <random>
#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <algorithm>
#include "common.hpp"int test_image_process_bgr2gray()
{const std::string image_name{ "E:/GitCode/CUDA_Test/test_data/images/lena.png" };cv::Mat mat = cv::imread(image_name);CHECK(mat.data);const int width{ 1513 }, height{ 1473 };cv::resize(mat, mat, cv::Size(width, height));std::unique_ptr<unsigned char[]> data1(new unsigned char[width * height]), data2(new unsigned char[width * height]);float elapsed_time1{ 0.f }, elapsed_time2{ 0.f }; // millisecondsCHECK(bgr2gray_cpu(mat.data, width, height, data1.get(), &elapsed_time1) == 0);CHECK(bgr2gray_gpu(mat.data, width, height, data2.get(), &elapsed_time2) == 0);cv::Mat dst(height, width, CV_8UC1, data1.get());cv::imwrite("E:/GitCode/CUDA_Test/test_data/images/bgr2gray_cpu.png", dst);cv::Mat dst2(height, width, CV_8UC1, data2.get());cv::imwrite("E:/GitCode/CUDA_Test/test_data/images/bgr2gray_gpu.png", dst2);fprintf(stdout, "image bgr to gray: cpu run time: %f ms, gpu run time: %f ms\n", elapsed_time1, elapsed_time2);CHECK(compare_result(data1.get(), data2.get(), width*height) == 0);return 0;
}

执行结果如下:由结果可知,C++和CUDA实现结果是一致的。

GitHub:  https://github.com/fengbingchun/CUDA_Test

CUDA Samples: Image Process: BGR to Gray相关推荐

  1. CUDA Samples: Image Process: BGR to BGR565

    图像像素格式BGR565是每一个像素占2个字节,其中Blue占5位,Green占6位,Red占5位.在OpenCV中,BGR到BGR565的每一个像素的计算公式是: unsigned short ds ...

  2. CUDA Samples: 获取设备属性信息

    通过调用CUDA的cudaGetDeviceProperties函数可以获得指定设备的相关信息,此函数会根据GPU显卡和CUDA版本的不同得到的结果也有所差异,下面code列出了经常用到的设备信息: ...

  3. CUDA Samples: matrix multiplication(C = A * B)

    以下CUDA sample是分别用C++和CUDA实现的两矩阵相乘运算code即C= A*B,CUDA中包含了两种核函数的实现方法,第一种方法来自于CUDA Samples\v8.0\0_Simple ...

  4. CUDA Samples:Vector Add

    以下CUDA sample是分别用C++和CUDA实现的两向量相加操作,参考CUDA 8.0中的sample:C:\ProgramData\NVIDIA Corporation\CUDA Sample ...

  5. 安装cuda后却没有CUDA Samples怎么办?

    Cuda 11.6版本之后将不再编译cuda,所以必须自己从github下载后自行编译, 下载网址为 cuda samples 对于windows用户, Windows示例是使用Visual Stud ...

  6. CUDA Samples 之 Simulations 之 Particles (1)

    CUDA Samples 之 Simulations 之 Particles源码学习(1) 自己用C++编程做颗粒堆积,但效率很低,所以想将程序并行,所以开始接触CUDA.但是完全不知道如何搭一个并行 ...

  7. OpenCV之 BGR、GRAY、HSV色彩空间色彩通道专题 【Open_CV系列(三)】

    文章目录 1.色彩空间 1.1 BGR色彩空间 1.2 GRAY色彩空间 1.3 HSV色彩空间 1.4 空间转换 1.4.1 BGR 转 GRAY 1.4.2 BGR 转 HSV 2. 色彩通道 2 ...

  8. CUDA Samples

    最近准备再挖个坑,翻译下cuda_samples,给入门想看代码又不知道看点啥的小同学提供一些指引(顺便指引下自己).本文简要介绍samples里的项目的主要功能. 简介 Simple Referen ...

  9. CUDA Samples目录

    简介 Simple Reference  基础CUDA示例,适用于初学者, 反映了运用CUDA和CUDA runtime APIs的一些基本概念. Utilities Reference  演示如何查 ...

最新文章

  1. Java中String、StringBuffer和StringBuilder的区别
  2. Java的字符串常量池
  3. 科大星云诗社动态20210830
  4. oppoJava面试题,腾讯社招三面多久联系
  5. html修改details范围,HTML details 标签
  6. 王军生老师---银行领域高端讲师(王军生)
  7. 【转】64位ORACLE客户端上plsql无法识别ORACLE_HOME解决方案
  8. 二叉树前序、中序、后序遍历(八)
  9. 对Json数据的处理(模拟获取数据是来自于本地)
  10. 探索硅谷奇迹的本质--周末荐书之《硅谷之谜》
  11. [渝粤教育] 重庆工商职业学院 生活中的大数据 参考 资料
  12. [云原生专题-16]:容器 - 在Windows主机上搭建Docker环境
  13. 介绍近期的一篇综述:AI系统安全的实用方法
  14. Flash ECC介绍
  15. 001、JDK环境配置
  16. 微信小程序获取手机号,含java后台接口实现
  17. Objective-C---8---@property KVC
  18. openstack neutron基本原理
  19. android 健康 计步器,健康运动计步器
  20. 打工人必备,850多份合同范文模板合集免费下载

热门文章

  1. Python压缩目录文件夹,解压目录文件夹及耗时效率统计
  2. 平台允许同时在线人数 显示_糖豆人:终极淘汰赛 热度持续飙升 Steam同时在线人数排名前四...
  3. 三菱触摸屏usb驱动安装_2020年三菱EXPRESS SWB和LWB:竟然是日系中唯一没有屏幕的面包车...
  4. 【3】npm run build Vue的项目,如何修改相对路径配置
  5. 在ubuntu 14.04 64bit上安装酷我音乐盒Linux客户端kwplayer
  6. Blender赛车动画制作学习教程 Learn Race Car Animation with Blender
  7. linux valgrind memCheck ---内存检查工具的可视化方法valkyrie
  8. Winder摆杆不稳除了PID还可能的原因
  9. [.NET] 《Effective C#》快速笔记 - C# 中的动态编程
  10. pcntl_fork 导致 MySQL server has gone away 解决方案