美文网首页
cuDNN03_01cuDNN的卷积运算

cuDNN03_01cuDNN的卷积运算

作者: 杨强AT南京 | 来源:发表于2020-01-20 19:02 被阅读0次

  掌握CUDA的编程思路,利用GPU实现高性能计算就基本上可以应用到实际项目中;本主题简单利用CUDA的工具包提供的cuDNN深度学习库实现一个Sobel- y方向的梯度计算。cuDNN主要实现了深度学习中卷积运算与循环递归运算的GPU封装。这种封装思想与Tensorflow,Torch基本上如出一辙。
  本主题主要理顺了计算数据的布局关系与函数参数的理解。


  一如既往的我们使用VSCode + Makefile,而没有使用VS IDE,可以有助于理解这些开发环境的工作细节。


Makefile的截图

cuDNN的安装

  1. 下载地址
    • https://developer.nvidia.com/rdp/cudnn-download
    • 需要注册与调查,使用微信或者QQ登录即可。


      选择合适的cuDNN的版本
  1. 下载的注意事项

    • 确保有一块支持CUDA的GPU显卡;
    • 确保GPU指出CUDA对应的版本;
  2. 安装

    • Window下载的是zip文件,安装不按照一般的套路,需要手工完成。因为下载的主要是三个文件:
      1. 头文件h文件;
      2. 运行库文件dll文件;
      3. 编译时的链接符号文件lib文件
    • 手工把三个文件拷贝到Nvidia CUDA对应的目录即可:
      1. include目录;
      2. bin目录;
      3. lib目录;

编程模式

模式说明

    1. 创建cuDNN句柄;
    • cudnnStatus_t cudnnCreate(cudnnHandle_t *handle)
    1. 以Host方式调用在Device上运行的函数;
    • 比如卷积运算:cudnnConvolutionForward
    1. 释放cuDNN句柄;
    • cudnnStatus_t cudnnDestroy(cudnnHandle_t handle)
  • 编程模式的例子

#include <iostream>
#include <cudnn.h>

int main(int argc, const char **argv){
    cudnnStatus_t cudnn_re;
    cudnnHandle_t h_cudnn;
    // 创建cuDNN上下文句柄
    cudnn_re = cudnnCreate(&h_cudnn);
    if(cudnn_re != CUDNN_STATUS_SUCCESS){
        std::cout << "创建cuDNN上下文失败!" << std::endl;
    }
    std::cout << "cuDD创建成功!" << std::endl;
    // =========================================cudnn操作
    // 获取版本
    size_t  version = cudnnGetVersion();
    size_t  rt_version = cudnnGetCudartVersion();
    std::cout << "cuDNN的版本:" << version << std::endl;
    std::cout << "CUDA的运行时版本:" << rt_version << std::endl; 
    // =================================================
    // 释放cuDNN
    cudnnDestroy(h_cudnn);

}

卷积运算的编程例子

  • cudnn是使用C++语法封装,因为cudnn提供了运算符重载与泛型操作,这两个语法都只有C++提供。

Window环境下的编译配置


CPLUS_ARGS     = /EHsc 
INCLUDES =/I "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\include" /source-charset:utf-8  /execution-charset:utf-8
LIBS     =/link /LIBPATH:"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\lib\x64" /DYNAMICBASE \
          "cudnn.lib" \
          "cuda.lib" \
          "cudart.lib" 

# main: nn01_model.cpp
#   @cl /nologo $(CPLUS_ARGS) $(INCLUDES) nn01_model.cpp $(LIBS) /out:main.exe

main: nn02_conv.cpp
    @cl /nologo $(CPLUS_ARGS) $(INCLUDES) nn02_conv.cpp $(LIBS) /out:main.exe
clean:
    @del *.exe *.exp *.obj  gpu_out.bmp 2>Nul
    
    

cuDNN的卷积函数定义

  • cuDNN定义定义的卷积函数为cudnnConvolutionForward
    • 该函数第一个参数就需要是cudnn的上下文句柄cudnnHandle_t。
cudnnStatus_t cudnnConvolutionForward(
    cudnnHandle_t                       handle,
    const void                         *alpha,             
    const cudnnTensorDescriptor_t       xDesc,
    const void                         *x,
    const cudnnFilterDescriptor_t       wDesc,
    const void                         *w,
    const cudnnConvolutionDescriptor_t  convDesc,
    cudnnConvolutionFwdAlgo_t           algo,
    void                               *workSpace,
    size_t                              workSpaceSizeInBytes,
    const void                         *beta,
    const cudnnTensorDescriptor_t       yDesc,
    void                               *y)
  • 基于对卷积数学运算的常识,上面的参数比较容易理解。反而是其中的惨数的数据类型需要精确理解与使用。

  • cudnn对一些命名习惯遵循了惯例:

    • x输入;
    • y输出;
    • w权重;
    • C,H,W图像的通道,高度、宽度;
    • c,C当前与总的输入通道;
    • n当前输入批次大小;
    • k,K当前与总的输出通道;
    • p,q当前输出图像的像素的y坐标与x坐标;
    • G分组数量;
    • pad补丁值;
    • u,v采样的垂直与水平步长;
    • dil_h, dil_wh与w方向的偏差;
    • r,s当前滤波核的高度、宽度;
    • R,S总的滤波核的高度、宽度
    • C_g, K_g分别为\frac{C}{G}, \frac{K}{G}
  • 卷积的定义如下:

    • y_{n,k,p,q} = \sum \limits _c ^ C \sum \limits _r ^ R \sum \limits _s ^ S (x_{n,c,p+r, q+s} \ \times \ w_{k,c,r,s})

卷积的实现过程

加载数据

  • 我们使用原来使用过的函数加载BMP图像。
    • 图像像素的通道是挨着的RGBA。我们考虑不对A做卷积运算;
    • 对前面的读取与保存函数略作修改
      1. 读取RGB
      2. 保存为RGB

数据格式布局选择

  • 根据上面的描述我们的格式选择NHWC是做合适的。
  1. NHWC 布局格式


    NHWC数据布局
  2. NCHW 布局格式


    NCHW布局格式

数据类型的选择

  • 由于卷积运算不支持整数计算,所有整个过程全部使用float运算

数据结构与数据定义

#include <iostream>
#include <cuda.h>
#include <cudnn.h>

// 结构体定义
#pragma pack(1)
struct img_header{
    // 文件头
    char                  magic[2];                  // 魔法字
    unsigned int          file_size;                 // 文件大小
    unsigned char         reserve1[4];               // 跳4字节
    unsigned int          data_off;                  // 数据区开始位置
    // 信息头
    unsigned char         reserve2[4];               // 跳4字节
    int                   width;                     // 图像宽度
    int                   height;                    // 图像高度
    unsigned char         reserve3[2];               // 跳2字节
    unsigned short int    bit_count;                 // 图像位数1,4,8,16,24,32
    unsigned char         reserve4[24];              // 跳24字节
};


// 偷懒写一个匿名全局类

// 全局数据
struct img_header  header;
uchar3             *img;                             // 使用gpu的扩展类型(只读取RGB)
float3             *f_img;                           // 临时存储浮点数的像素的。
float3             *img_gpu;                         // 输入
float3             *conv_gpu;                        // 输出
float3             *kernel_gpu;                      // 卷积核

// 输入/输出文件名
const char *in_filename  = "gpu.bmp";
const char *out_filename = "gpu_out.bmp";


// 打开图像
void read_bmp();                                     // 无参数,采用全局成员
// 保存图像
void save_bmp();
// 内存释放
void free_mem();
// Host <-> Device
void move_to_device();  
void move_to_host(); 
void create_kernel();
// cudnn卷积计算封装
void cudnn_conv();

图像读取与保存

  • 其中读取的时候,只读取RGB,并存储为浮点数;
    • 可以使用OpenCV的图像读取函数。
  • 保存的时候保存为24位图;
    • 具体的代码见附录。

GPU内存与Host内存

  • 从GPU拷贝的数据,直接转换拷贝到unsigned char的Host缓冲中。然后保存。
void move_to_host(){
    // 把选装后的图像拷贝到Host内存,用来保存到磁盘
    cudaMemcpy((void*)f_img, (void*)conv_gpu, header.height * header.width * sizeof(float3), cudaMemcpyDeviceToHost);
    // 循环把图像转换为uchar3
    for(int i = 0; i < header.height * header.width; i++){
        img[i].x = (unsigned char)f_img[i].x;
        img[i].y = (unsigned char)f_img[i].y;
        img[i].z = (unsigned char)f_img[i].z;
    }
}

卷积核

  • 为了只管,直接使用了Sobel核;
void create_kernel(){
    cudaMalloc((void**)&kernel_gpu, 3 * 3 * 3 * sizeof(float3));   // 返回指针,则参数就需要二重指针。
    // 卷积核
    float3 data_kernel[] = {
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f),
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f),
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f)
    };
    // 拷贝到device
    cudaMemcpy((void*)kernel_gpu, (void*)data_kernel, 3 * 3 * 3 * sizeof(float3), cudaMemcpyHostToDevice);

}

核心的cudd4nn的卷积运行模式

void cudnn_conv(){
    // 返回状态
    cudnnStatus_t status;
    //cudnn句柄
    cudnnHandle_t h_cudnn;
    // 创建cuDNN上下文句柄
    cudnnCreate(&h_cudnn);
    // =================================================输入输出张量
    // 1. 定义一个张量对象
    cudnnTensorDescriptor_t  ts_in, ts_out;    
    // 2. 创建输入张量
    status = cudnnCreateTensorDescriptor(&ts_in);
    if(CUDNN_STATUS_SUCCESS == status){
        std::cout << "创建输入张量成功!" << std::endl;
    }
    // 3. 设置输入张量数据
    status = cudnnSetTensor4dDescriptor(
        ts_in,                            // 张量对象
        CUDNN_TENSOR_NHWC,                // 张量的数据布局
        CUDNN_DATA_FLOAT,                 // 张量的数据类型
        1,                                // 图像数量
        3,                                // 图像通道
        1080,                             // 图像高度
        1920                              // 图像宽度
    );
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建输出张量成功!" << std::endl;
    // 类似创建输出的张量
    cudnnCreateTensorDescriptor(&ts_out);
    status = cudnnSetTensor4dDescriptor(ts_out, CUDNN_TENSOR_NHWC, CUDNN_DATA_FLOAT, 1, 3, 1080, 1920);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "设置输出张量成功!" << std::endl;
    // =================================================滤波核
    cudnnFilterDescriptor_t kernel;
    cudnnCreateFilterDescriptor(&kernel);
    status = cudnnSetFilter4dDescriptor(kernel, CUDNN_DATA_FLOAT, CUDNN_TENSOR_NHWC, 3, 3, 3, 3);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建卷积核张量成功!" << std::endl;
    // =================================================卷积描述
    cudnnConvolutionDescriptor_t conv;
    status = cudnnCreateConvolutionDescriptor(&conv);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建卷积成功!" << std::endl;
    status = cudnnSetConvolution2dDescriptor(conv, 1, 1, 1, 1, 1, 1, CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "设置卷积成功!" << std::endl;
    // =================================================卷积算法
    cudnnConvolutionFwdAlgo_t algo;
    status = cudnnGetConvolutionForwardAlgorithm(h_cudnn, ts_in, kernel, conv, ts_out, CUDNN_CONVOLUTION_FWD_PREFER_FASTEST, 0, &algo);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "获取算法成功!" << std::endl;
    // =================================================工作区
    size_t workspace_size = 0;
    status = cudnnGetConvolutionForwardWorkspaceSize(h_cudnn, ts_in, kernel, conv, ts_out, algo, &workspace_size);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "获取工作空间大小成功!" << std::endl;
    std::cout << "卷积计算空间大小" << workspace_size << std::endl;
    // 分配工作区空间
    void * workspace;
    cudaMalloc(&workspace, workspace_size);
    // =================================================线性因子
    float alpha = 1.0f;
    float beta  = -100.0f;

    // =================================================卷积执行
    status =  cudnnConvolutionForward(
        h_cudnn,
        &alpha,
        ts_in,
        img_gpu,                      // 输入
        kernel, 
        kernel_gpu,                      // 核
        conv,
        algo,
        workspace,
        workspace_size,
        &beta,
        ts_out,
        conv_gpu                       // 输出
    );
    if (status == CUDNN_STATUS_SUCCESS) {
        std::cout << "卷积计算成功!" << std::endl; 
    }else{
        std::cout << "卷积计算失败!" << std::endl; 
    }
    // 释放cuDNN
    cudnnDestroy(h_cudnn);

}

运算后的图像效果

  • 与我们实现的Sobel滤波一样的效果,贴图如下:


    Sobel滤波

附录

  1. 卷积运算的全部实现代码
#include <iostream>
#include <cuda.h>
#include <cudnn.h>

// 结构体定义
#pragma pack(1)
struct img_header{
    // 文件头
    char                  magic[2];                  // 魔法字
    unsigned int          file_size;                 // 文件大小
    unsigned char         reserve1[4];               // 跳4字节
    unsigned int          data_off;                  // 数据区开始位置
    // 信息头
    unsigned char         reserve2[4];               // 跳4字节
    int                   width;                     // 图像宽度
    int                   height;                    // 图像高度
    unsigned char         reserve3[2];               // 跳2字节
    unsigned short int    bit_count;                 // 图像位数1,4,8,16,24,32
    unsigned char         reserve4[24];              // 跳24字节
};


// 偷懒写一个匿名全局类

// 全局数据
struct img_header  header;
uchar3             *img;                             // 使用gpu的扩展类型(只读取RGB)
float3             *f_img; 
float3             *img_gpu;                         // 输入
float3             *conv_gpu;                        // 输出
float3             *kernel_gpu;                      // 卷积核

// 输入/输出文件名
const char *in_filename  = "gpu.bmp";
const char *out_filename = "gpu_out.bmp";


// 打开图像
void read_bmp();                                     // 无参数,采用全局成员
// 保存图像
void save_bmp();
// 内存释放
void free_mem();
// Host <-> Device
void move_to_device();  
void move_to_host(); 
void create_kernel();
// cudnn卷积计算封装
void cudnn_conv();

int main(int argc, const char **argv){
    read_bmp();
    move_to_device();
    create_kernel();
    cudnn_conv();
    move_to_host();
    save_bmp();
    free_mem();
}

void cudnn_conv(){
    // 返回状态
    cudnnStatus_t status;
    //cudnn句柄
    cudnnHandle_t h_cudnn;
    // 创建cuDNN上下文句柄
    cudnnCreate(&h_cudnn);
    // =================================================输入输出张量
    // 1. 定义一个张量对象
    cudnnTensorDescriptor_t  ts_in, ts_out;    
    // 2. 创建输入张量
    status = cudnnCreateTensorDescriptor(&ts_in);
    if(CUDNN_STATUS_SUCCESS == status){
        std::cout << "创建输入张量成功!" << std::endl;
    }
    // 3. 设置输入张量数据
    status = cudnnSetTensor4dDescriptor(
        ts_in,                            // 张量对象
        CUDNN_TENSOR_NHWC,                // 张量的数据布局
        CUDNN_DATA_FLOAT,                 // 张量的数据类型
        1,                                // 图像数量
        3,                                // 图像通道
        1080,                             // 图像高度
        1920                              // 图像宽度
    );
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建输出张量成功!" << std::endl;
    // 类似创建输出的张量
    cudnnCreateTensorDescriptor(&ts_out);
    status = cudnnSetTensor4dDescriptor(ts_out, CUDNN_TENSOR_NHWC, CUDNN_DATA_FLOAT, 1, 3, 1080, 1920);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "设置输出张量成功!" << std::endl;
    // =================================================滤波核
    cudnnFilterDescriptor_t kernel;
    cudnnCreateFilterDescriptor(&kernel);
    status = cudnnSetFilter4dDescriptor(kernel, CUDNN_DATA_FLOAT, CUDNN_TENSOR_NHWC, 3, 3, 3, 3);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建卷积核张量成功!" << std::endl;
    // =================================================卷积描述
    cudnnConvolutionDescriptor_t conv;
    status = cudnnCreateConvolutionDescriptor(&conv);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "创建卷积成功!" << std::endl;
    status = cudnnSetConvolution2dDescriptor(conv, 1, 1, 1, 1, 1, 1, CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "设置卷积成功!" << std::endl;
    // =================================================卷积算法
    cudnnConvolutionFwdAlgo_t algo;
    status = cudnnGetConvolutionForwardAlgorithm(h_cudnn, ts_in, kernel, conv, ts_out, CUDNN_CONVOLUTION_FWD_PREFER_FASTEST, 0, &algo);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "获取算法成功!" << std::endl;
    // =================================================工作区
    size_t workspace_size = 0;
    status = cudnnGetConvolutionForwardWorkspaceSize(h_cudnn, ts_in, kernel, conv, ts_out, algo, &workspace_size);
    if(CUDNN_STATUS_SUCCESS == status) std::cout << "获取工作空间大小成功!" << std::endl;
    std::cout << "卷积计算空间大小" << workspace_size << std::endl;
    // 分配工作区空间
    void * workspace;
    cudaMalloc(&workspace, workspace_size);
    // =================================================线性因子
    float alpha = 1.0f;
    float beta  = -100.0f;
    // =================================================数据准备
    // 见全局函数的实现。
    // =================================================卷积执行
    status =  cudnnConvolutionForward(
        h_cudnn,
        &alpha,
        ts_in,
        img_gpu,                         // 输入
        kernel, 
        kernel_gpu,                      // 核
        conv,
        algo,
        workspace,
        workspace_size,
        &beta,
        ts_out,
        conv_gpu                         // 输出
    );
    if (status == CUDNN_STATUS_SUCCESS) {
        std::cout << "卷积计算成功!" << std::endl; 
    }else{
        std::cout << "卷积计算失败!" << std::endl; 
    }
    // 释放cuDNN
    cudnnDestroy(h_cudnn);

}
void create_kernel(){
    cudaMalloc((void**)&kernel_gpu, 3 * 3 * 3 * sizeof(float3));   // 返回指针,则参数就需要二重指针。
    // 卷积核
    float3 data_kernel[] = {
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f),
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f),
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f), 
        make_float3(-2.0f, -2.0f, -2.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(2.0f, 2.0f, 2.0f), 
        make_float3(-1.0f, -1.0f, -1.0f), make_float3(0.0f, 0.0f, 0.0f), make_float3(1.0f, 1.0f, 1.0f)
    };
    // 拷贝到device
    cudaMemcpy((void*)kernel_gpu, (void*)data_kernel, 3 * 3 * 3 * sizeof(float3), cudaMemcpyHostToDevice);

}

void move_to_host(){
    // 把选装后的图像拷贝到Host内存,用来保存到磁盘
    cudaMemcpy((void*)f_img, (void*)conv_gpu, header.height * header.width * sizeof(float3), cudaMemcpyDeviceToHost);
    // 循环把图像转换为uchar3
    for(int i = 0; i < header.height * header.width; i++){
        img[i].x = (unsigned char)f_img[i].x;
        img[i].y = (unsigned char)f_img[i].y;
        img[i].z = (unsigned char)f_img[i].z;
    }
}
void move_to_device(){
    // 分配GPU内存
    cudaMalloc((void**)&img_gpu, header.height * header.width * sizeof(float3));   // 返回指针,则参数就需要二重指针。
    // 拷贝数据
    cudaMemcpy((void*)img_gpu, (void*)f_img,  header.height * header.width * sizeof(float3), cudaMemcpyHostToDevice);

    // 用来存储卷积计算输出
    cudaMalloc((void**)&conv_gpu, header.height * header.width * sizeof(float3));   // 返回指针,则参数就需要二重指针。
    // 初始化为指定为0
    cudaMemset(conv_gpu, 0, header.height * header.width * sizeof(float3));
}

void read_bmp(){ 
    /* 读取头,分配内存,读取数据,这里数据采用了一维数组,使用的时候,需要转换处理下。*/
    FILE *file = fopen(in_filename, "rb");
    // 读取头
    size_t n_bytes = fread(&header, 1, 54, file); 
    
    // 计算读取的大大小,并分配空间,并读取。
    header.height = header.height >= 0? header.height : -header.height;
    img = (uchar3 *)malloc(header.height * header.width * sizeof(uchar3));
    f_img = (float3 *)malloc(header.height * header.width * sizeof(float3));
    uchar4 buffer;  
    // 循环只读取RGB
    for(int i = 0; i < header.height * header.width; i++){
        fread((void*)&buffer, sizeof(uchar4), 1, file);
        memcpy(&img[i], &buffer, sizeof(uchar3));
        f_img[i].x = (float)buffer.x;
        f_img[i].y = (float)buffer.y;
        f_img[i].z = (float)buffer.z;
    } 

    fclose(file); // 关闭文件
    
}
void save_bmp(){
    /* 使用与读取一样的头信息保存图像 */
    FILE *file = fopen(out_filename, "wb");
    // 写头
    header.bit_count = 24;    //修改图像的位数
    header.file_size = 54 + header.height * header.width * sizeof(uchar3);   // 修改文件大小
    header.height = -header.height;
    size_t n_bytes = fwrite(&header, 1, 54, file);
    header.height = -header.height;
    // 写图像数据
    n_bytes = fwrite(img, sizeof(uchar3), header.height * header.width, file);
    // 关闭文件
    fclose(file);
}
void free_mem(){
    /* 释放Host与Device内存 */
    free(img); // 直接释放(不需要指定大小,malloc系列函数有内部变量管理分配的内存)
    cudaFree(img_gpu);
    cudaFree(conv_gpu);
    cudaFree(kernel_gpu);
}



相关文章

  • cuDNN03_01cuDNN的卷积运算

      掌握CUDA的编程思路,利用GPU实现高性能计算就基本上可以应用到实际项目中;本主题简单利用CUDA的工具包提...

  • 2020-02-17

    互相关运算与卷积运算 卷积层得名于卷积运算,但卷积层中用到的并非卷积运算而是互相关运算。我们将核数组上下翻转、左右...

  • 第二次打卡 Task05

    一、卷积神经网络基础 卷积层得名于卷积运算,但卷积层中用到的并非卷积运算而是互相关运算。我们将核数组上下翻转、左右...

  • Task7 卷积神经网络

    任务: 卷积运算的定义、动机(稀疏权重、参数共享、等变表示)。一维卷积运算和二维卷积运算。 反卷积(tf.nn.c...

  • Tensorflow学习笔记--CNN

    输入向量----------------------》与卷积核卷积运算----------------------...

  • DFT与卷积

    利用DFT可以大大加快卷积运算的速度。卷积定理:空间域的卷积运算可以转换为频域的乘法运算。具体实现是将图片转换到频...

  • 卷积神经网络

    知识点 卷积运算图像处理中的卷积运算与数学定义中的卷积不太一样,更准确的是指数学中的互相关运算。输入二矩阵通过每个...

  • 线性时不变系统(LTI)的卷积和性质

    卷积 线性时不变系统的响应可以由它的单位脉冲响应来表征 表示卷积运算,是该线性时不变系统的单位脉冲响应。 卷积运算...

  • 卷积运算

    卷积运算 1 No zero padding, unit strides (无填充,单位滑动) 输入特征图大小 i...

  • 卷积运算

    为什么卷积神经网络这么好?要理解 Conv2D 层和 MaxPooling2D 层的作用。 卷积运算 Dense ...

网友评论

      本文标题:cuDNN03_01cuDNN的卷积运算

      本文链接:https://www.haomeiwen.com/subject/rrtszctx.html