OpenCV二維Mat數組(二級指針)在CUDA中的使用


  CUDA用於並行計算非常方便,但是GPU與CPU之間的交互,比如傳遞參數等相對麻煩一些。在寫CUDA核函數的時候形參往往會有很多個,動輒達到10-20個,如果能夠在CPU中提前把數據組織好,比如使用二維數組,這樣能夠省去很多參數,在核函數中可以使用二維數組那樣去取數據簡化代碼結構。當然使用二維數據會增加GPU內存的訪問次數,不可避免會影響效率,這個不是今天討論的重點了。

  舉兩個代碼栗子來說明二維數組在CUDA中的使用(親測可用):

1. 普通二維數組示例:

輸入:二維數組A(8行4列)

輸出:二維數組C(8行4列)

函數功能:將數組A中的每一個元素加上10,並保存到C中對應位置。

  這個是一個簡單的示例,以一級指針和二級指針開訪問二維數組中的數據,主要步驟如下:

(1)為二級指針A、C和一級指針dataA、dataC分配CPU內存。二級指針指向的內存中保存的是一級指針的地址。一級指針指向的內存中保存的是輸入、輸出數據。

(2)在設備端(GPU)上同樣建立二級指針d_A、d_C和一級指針d_dataA、d_dataC,並分配GPU內存,原理同上,不過指向的內存都是GPU中的內存。

(3)通過主機端一級指針dataA將輸入數據保存到CPU中的二維數組中。

(4)關鍵一步:將設備端一級指針的地址保存到主機端二級指針指向的CPU內存中。

(5)關鍵一步:使用cudaMemcpy()函數,將主機端二級指針中的數據(設備端一級指針的地址)拷貝到設備端二級指針指向的GPU內存中。這樣在設備端就可以使用二級指針來訪問一級指針的地址,然后利用一級指針訪問輸入數據。也就是A[][]、C[][]的用法。

(6)使用cudaMemcpy()函數將主機端一級指針指向的CPU內存空間中的輸入數據,拷貝到設備端一級指針指向的GPU內存中,這樣輸入數據就算上傳到設備端了。

(7)在核函數addKernel()中就可以使用二維數組的方法進行數據的讀取、運算和寫入。

(8)最后將設備端一級指針指向的GPU內存中的輸出數據拷貝到主機端一級指針指向的CPU內存中,打印顯示即可。

#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <opencv2\opencv.hpp>
#include <iostream>
#include <string>

using namespace cv;
using namespace std;

#define Row  8  
#define Col 4  
  
__global__ void addKernel(int **C,  int **A)  
{  
    int idx = threadIdx.x + blockDim.x * blockIdx.x;  
    int idy = threadIdx.y + blockDim.y * blockIdx.y;  
    if (idx < Col && idy < Row)
    {  
        C[idy][idx] = A[idy][idx] + 10;  
    }  
}  

int main()  
{  
    int **A = (int **)malloc(sizeof(int*) * Row);  
    int **C = (int **)malloc(sizeof(int*) * Row);  
    int *dataA = (int *)malloc(sizeof(int) * Row * Col);  
    int *dataC = (int *)malloc(sizeof(int) * Row * Col);  

    int **d_A;  
    int **d_C;  
    int *d_dataA;  
    int *d_dataC;  
    //malloc device memory
    cudaMalloc((void**)&d_A, sizeof(int **) * Row);  
    cudaMalloc((void**)&d_C, sizeof(int **) * Row);  
    cudaMalloc((void**)&d_dataA, sizeof(int) *Row*Col);  
    cudaMalloc((void**)&d_dataC, sizeof(int) *Row*Col);  
    //set value  
    for (int i = 0; i < Row*Col; i++) 
    {  
        dataA[i] = i+1;  
    }  
    //將主機指針A指向設備數據位置,目的是讓設備二級指針能夠指向設備數據一級指針
    //A 和 dataA 都傳到了設備上,但是二者還沒有建立對應關系
    for (int i = 0; i < Row; i++) 
    {  
        A[i] = d_dataA + Col * i;  
        C[i] = d_dataC + Col * i;  
    }  
      
    cudaMemcpy(d_A, A, sizeof(int*) * Row, cudaMemcpyHostToDevice);  
    cudaMemcpy(d_C, C, sizeof(int*) * Row, cudaMemcpyHostToDevice);  
    cudaMemcpy(d_dataA, dataA, sizeof(int) * Row * Col, cudaMemcpyHostToDevice);  
    dim3 block(4, 4);  
    dim3 grid( (Col + block.x - 1)/ block.x, (Row + block.y - 1) / block.y );  
    addKernel << <grid, block >> > (d_C, d_A);  
    //拷貝計算數據-一級數據指針  
    cudaMemcpy(dataC, d_dataC, sizeof(int) * Row * Col, cudaMemcpyDeviceToHost);  
  
    for (int i = 0; i < Row*Col; i++) 
    {  
        if (i%Col == 0) 
        {  
            printf("\n");  
        }  
        printf("%5d", dataC[i]);  
    }  
    printf("\n");  
}  

2.OpenCV中Mat數組示例

輸入:圖像Lena.jpg

輸出:圖像moon.jpg

函數功能:求兩幅圖像加權和

  原理和上面一樣,流程上的差別就是輸入的二維數據是下面兩幅圖像數據,然后在CUDA中進行加權求和。

 

效果如下:

代碼在此,以供參考

#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <opencv2\opencv.hpp>
#include <iostream>
#include <string>

using namespace cv;
using namespace std;
  
__global__ void addKernel(uchar **pSrcImg,  uchar* pDstImg, int imgW, int imgH)  
{  
    int tidx = threadIdx.x + blockDim.x * blockIdx.x;  
    int tidy = threadIdx.y + blockDim.y * blockIdx.y;  
    if (tidx<imgW && tidy<imgH)
    {
        int idx=tidy*imgW+tidx;
        uchar lenaValue=pSrcImg[0][idx];
        uchar moonValue=pSrcImg[1][idx];
        pDstImg[idx]= uchar(0.5*lenaValue+0.5*moonValue);
    }
}  

int main()  
{  
    //OpenCV讀取兩幅圖像
    Mat img[2];
    img[0]=imread("data/lena.jpg", 0);
    img[1]=imread("data/moon.jpg", 0);
    int imgH=img[0].rows;
    int imgW=img[0].cols;
    //輸出圖像
    Mat dstImg=Mat::zeros(imgH, imgW, CV_8UC1);
    //主機指針
    uchar **pImg=(uchar**)malloc(sizeof(uchar*)*2); //輸入 二級指針

    //設備指針
    uchar **pDevice;//輸入 二級指針
    uchar *pDeviceData;//輸入 一級指針
    uchar *pDstImgData;//輸出圖像對應設備指針

    //分配GPU內存
    cudaError err;
    //目標輸出圖像分配GPU內存
    err=cudaMalloc(&pDstImgData, imgW*imgH*sizeof(uchar));
    //設備二級指針分配GPU內存
    err=cudaMalloc(&pDevice, sizeof(uchar*)*2);
    //設備一級指針分配GPU內存
    err=cudaMalloc(&pDeviceData, sizeof(uchar)*imgH*imgW*2);
    
    //關鍵:主機二級指針指向設備一級指針位置,這樣才能使設備的二級指針指向設備的一級指針位置
    for (int i=0; i<2; i++)
    {
        pImg[i]=pDeviceData+i*imgW*imgH;
    }

    //拷貝數據到GPU
    //拷貝主機二級指針中的元素到設備二級指針指向的GPU位置 (這個二級指針中的元素是設備中一級指針的地址)
    err=cudaMemcpy(pDevice, pImg, sizeof(uchar*)*2, cudaMemcpyHostToDevice);
    //拷貝圖像數據(主機一級指針指向主機內存) 到 設備一級指針指向的GPU內存中
    err=cudaMemcpy(pDeviceData, img[0].data, sizeof(uchar)*imgH*imgW, cudaMemcpyHostToDevice);
    err=cudaMemcpy(pDeviceData+imgH*imgW, img[1].data, sizeof(uchar)*imgH*imgW, cudaMemcpyHostToDevice);

    //核函數實現lena圖和moon圖的簡單加權和
    dim3 block(8, 8);
    dim3 grid( (imgW+block.x-1)/block.x, (imgH+block.y-1)/block.y);
    addKernel<<<grid, block>>>(pDevice, pDstImgData, imgW, imgH);
    cudaThreadSynchronize();

    //拷貝輸出圖像數據至主機,並寫入到本地
    err=cudaMemcpy(dstImg.data, pDstImgData, imgW*imgH*sizeof(uchar), cudaMemcpyDeviceToHost);
    imwrite("data/synThsis.jpg", dstImg);
}  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM