1. 程式人生 > >OpenCV+CUDA入門教程之五---GpuMat詳解

OpenCV+CUDA入門教程之五---GpuMat詳解

目錄

一、簡介

一、簡介

GpuMat可以從其命名看出,它是“GPU”版本的Mat,絕大部分介面和Mat相同,功能也類似。

和Mat相比,GpuMat多了兩個成員函式upload和download,分別用於把資料從記憶體上傳(通過匯流排傳輸)到視訊記憶體和從視訊記憶體下載(通過匯流排傳輸)到記憶體。

GpuMat和Mat都有資料指標(指向一塊儲存區域,其中存放著影象資料),不過GpuMat的資料指標是指向視訊記憶體上的某一塊區域,而Mat的資料指標是指向記憶體上的某一塊區域。

GpuMat僅支援二維資料;GpuMat::isContinuous() == false,

這是為了對齊記憶體,當一行的資料不是8位元組(具體是幾字節不知道)的倍數時,為了提高訪問速度,這一行資料的末尾會填充空白位元組,所以一行的實際記憶體大小儲存在GpuMat的成員step裡面。

二、建構函式

OpenCV3中,GpuMat的建構函式如下;OpenCV2中,建構函式沒有allocator這個引數。

//! 預設建構函式
explicit GpuMat(Allocator* allocator = defaultAllocator());

//! 根據指定大小(size)和型別(type)建立GpuMat例項
GpuMat(int rows, int cols, int type, Allocator* allocator = defaultAllocator());
GpuMat(Size size, int type, Allocator* allocator = defaultAllocator());

//! 根據指定大小和型別建立GpuMat,並用s初始化所有元素
GpuMat(int rows, int cols, int type, Scalar s, Allocator* allocator = defaultAllocator());
GpuMat(Size size, int type, Scalar s, Allocator* allocator = defaultAllocator());

//! 複製建構函式(淺複製)
GpuMat(const GpuMat& m);

//! 建立一個指定大小和型別的GpuMat;並使用由使用者分配的d資料data作為初始資料
GpuMat(int rows, int cols, int type, void* data, size_t step = Mat::AUTO_STEP);
GpuMat(Size size, int type, void* data, size_t step = Mat::AUTO_STEP);

//! 建立ROI矩陣,就是把m的一部分作為新的矩陣
GpuMat(const GpuMat& m, Range rowRange, Range colRange);
GpuMat(const GpuMat& m, Rect roi);

//! 根據arr建立GpuMat,並把arr中的資料複製到GpuMat指定的視訊記憶體區域
explicit GpuMat(InputArray arr, Allocator* allocator = defaultAllocator());

因此,構造GpuMat 的方式有如下幾種

const int rows = 16*50;
const int cols = 16*60;
const int type = CV_8UC3;

//第一種
GpuMat gpuMat;
//第二種
GpuMat gpuMat1(rows,cols,type);
GpuMat gpuMat2(Size(cols,rows),type);
//第三種,
GpuMat gpuMat3(rows,cols,type,Scalar(0,255,0));
GpuMat gpuMat4(Size(cols,rows),type,Scalar(0,255,0));
//第四種
GpuMat gpuMat5 = gpuMat1;
//第五種
uchar *data = new uchar[rows*cols*3]();//畫素個數乘以3
GpuMat gpuMat6(rows,cols,type,data);
GpuMat gpuMat7(Size(cols,rows),type,data);
//第六種
Range rowRange(0,16*10),colRange(0,16*10);
GpuMat gpuMat8(gpuMat1,rowRange,colRange);
Rect rect(0,0,16*10,16*10);
GpuMat gpuMat9(gpuMat1,rect);
//第七種
Mat mat(rows,cols,type,Scalar(0,255,0));
GpuMat gpuMat10(mat);

二、GpuMat::upload、GpuMat::download

 下面是OpenCV2的函式

//! 把m中的資料上傳到GpuMat指定的視訊記憶體中
void upload(const Mat& m);

//! 把GpuMat指定的視訊記憶體中,把資料下載到m裡
void download(Mat& m) const;

下面是opencv3的函式

//! 把arr中的資料上傳到GpuMat,上傳完成前會阻塞當前執行緒
void upload(InputArray arr);

//! 非同步地把arr中的資料上傳到GpuMat,不會阻塞當前執行緒
void upload(InputArray arr, Stream& stream);

//! 把GpuMat中的資料下載到dst,下載完成前會阻塞當前執行緒
void download(OutputArray dst) const;

//! 非同步地把GpuMat中的資料下載到dst,下載完成前不會阻塞當前執行緒
void download(OutputArray dst, Stream& stream) const;

可以看出,opencv3中,upload和download都過載有引數stream的版本,這是把上傳下載的操作放到stream中,讓stream去管理,然後立即返回,CPU可以繼續做其他事情,這就是非同步的上傳/下載資料。在opencv2中,非同步上傳下載的函式在stream裡面。非同步的上傳下載資料,將在後面的部落格中講解。接下來演示如何使用upload/download函式。

//main.cpp
#include <iostream>
//--------------------OpenCV標頭檔案---------------
#include <opencv2/opencv.hpp>
#include <opencv2/core/version.hpp>
using namespace cv;

#if CV_VERSION_EPOCH == 2
#define OPENCV2
#include <opencv2/gpu/gpu.hpp>
using namespace cv::gpu;

#elif CV_VERSION_MAJOR == 3
#define  OPENCV3
#include <opencv2/core/cuda.hpp>
using namespace cv::cuda;

#else
#error Not support this OpenCV version
#endif
//--------------------OpenCV標頭檔案---------------

using namespace std;

int main() {
    // 首先要檢查是否CUDA模組是否可用
    if(getCudaEnabledDeviceCount()==0){
        cerr<<"此OpenCV編譯的時候沒有啟用CUDA模組"<<endl;
        return -1;
    }

    const int rows = 16*50;
    const int cols = 16*60;
    const int type = CV_8UC3;

    // 初始化一個黑色的GpuMat
    GpuMat gpuMat(rows,cols,type,Scalar(0,0,0));
    // 定義一個空Mat
    Mat dst;
    // 把gpuMat中資料下載到dst(從視訊記憶體下載到記憶體)
    gpuMat.download(dst);
    // 顯示
    imshow("show",dst);
    waitKey(0);

    // 讀取一張圖片
    Mat arr = imread("../../GpuMat/lena.jpg");
    imshow("show",arr);
    waitKey(0);

    // 上傳到gpuMat(若gpuMat不為空,會先釋放原來的資料,再把新的資料上傳上去)
    gpuMat.upload(arr);
    // 定義另外一個空的GpuMat
    GpuMat gray;
    // 把gpuMat轉換為灰度圖gray
#ifdef OPENCV3
    cuda::cvtColor(gpuMat,gray,CV_BGR2GRAY);
#else
    gpu::cvtColor(gpuMat,gray,CV_BGR2GRAY);
#endif
    // 下載到dst,如果dst不為空,舊資料會被覆蓋
    gray.download(dst);
    // 顯示
    imshow("show",dst);
    waitKey(0);
    return 0;
}

注意:有的函式,例如cvtColor,在cv和cv::cuda(在opencv2中是cv::gpu)這兩個名稱空間中有相同的定義,可在函式名字前面加上cuda::(在opencv2是gpu::)就不會出現衝突。

三、GpuMat與PtrStepSz、PtrStep

PtrStepSz和PtrStep是兩個輕量級的類,一般作為CUDA核函式的引數,為了提高效能而設計(越簡單,複製的代價越小)。

PtrStep和PtrStepSz都是由GpuMat的成員函式生成,我們看下這兩個函式的實現

template <class T> inline
GpuMat::operator PtrStepSz<T>() const
{
    return PtrStepSz<T>(rows, cols, (T*)data, step);
}

template <class T> inline
GpuMat::operator PtrStep<T>() const
{
    return PtrStep<T>((T*)data, step);
}

可見, PtrStepSz僅僅包含了GpuMat中的rows、cols、step、data(資料指標);而PtrStep僅包含GpuMat中的step和data這兩個成員。PtrStepSz中的Ptr(指標)代表data;Step代表step;Sz則是Size的縮寫,代表rows和cols。PtrStep中的Ptr代表data;Step則代表step。

那麼如何通過GpuMat得到PtrStepSz和PtrStep呢?請看如下程式碼

//common.h
#ifndef OCSAMPLE_COMMON_H
#define OCSAMPLE_COMMON_H

#include <iostream>
//--------------------OpenCV標頭檔案---------------
#include <opencv2/opencv.hpp>
#include <opencv2/core/version.hpp>
using namespace cv;

#if CV_VERSION_EPOCH == 2
#define OPENCV2
#include <opencv2/gpu/gpu.hpp>
using namespace cv::gpu;

#elif CV_VERSION_MAJOR == 3
#define  OPENCV3
#include <opencv2/core/cuda.hpp>
using namespace cv::cuda;

#else
#error Not support this OpenCV version
#endif
//--------------------OpenCV標頭檔案---------------

using namespace std;
#endif //OCSAMPLE_COMMON_H

//main.cu
#include "../common.h"

//---------------------CUDA標頭檔案----------------
#include <cuda.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <cuda_device_runtime_api.h>
//---------------------CUDA標頭檔案----------------

__global__ void kernel(PtrStepSz<uchar3> src){

}

int main(){
    // 首先要檢查是否CUDA模組是否可用
    if(getCudaEnabledDeviceCount()==0){
        cerr<<"此OpenCV編譯的時候沒有啟用CUDA模組"<<endl;
        return -1;
    }
    const int rows = 16*50;
    const int cols = 16*60;
    const int type = CV_8UC3;

    // 初始化一個黑色的GpuMat
    GpuMat gpuMat(rows,cols,type,Scalar(0,0,0));
    kernel<<<1,1>>>(gpuMat);
}

 kernel是一個CUDA Kernel,引數型別是PtrStepSz<uchar3>,當把gpuMat作為函式kernel的引數傳進去的時候,會自動呼叫GpuMat::operator PtrStepSz<T>() const這個函式,生成一個PtrStepSz的例項src;當修改src裡面的資料時,gpuMat裡面的資料也會同步的發生變化。至於怎麼修改src裡面的資料,將會在下篇中講解。

四、深複製與淺複製

和Mat一樣,淺複製只複製GpuMat頭資訊和資料指標,兩個GpuMat的資料指標指向同一塊區域,修改其中任意一個GpuMat的資料都會相互影響。而深複製則是把資料也複製到新的GpuMat裡面,兩個GpuMat的資料指標不會相互影響。

//main.cpp
#include "common.h"

int main(){
    // 首先要檢查是否CUDA模組是否可用
    if(getCudaEnabledDeviceCount()==0){
        cerr<<"此OpenCV編譯的時候沒有啟用CUDA模組"<<endl;
        return -1;
    }

    const int rows = 16*50;
    const int cols = 16*60;
    const int type = CV_8UC3;

    // 初始化一個黑色的GpuMat
    GpuMat gpuMat(rows,cols,type,Scalar(0,0,0));

    // 淺複製,gpuMat或gpuMat1兩者的資料指標指向同一塊區域
    GpuMat gpuMat1 = gpuMat;

    // 深複製,gpuMat2和gpuMat3的資料指標指向的資料區域和gpuMat的不同
    GpuMat gpuMat2 = gpuMat.clone();
    GpuMat gpuMat3;
    gpuMat.copyTo(gpuMat3);

    // 測試
    Mat dst1,dst2,dst3,dst4;
    // 把gpuMat設定為綠色
    gpuMat.setTo(Scalar(0,255,0));
    gpuMat.download(dst1);
    gpuMat1.download(dst2);
    gpuMat2.download(dst3);
    gpuMat3.download(dst4);
    imshow("dst1",dst1);//綠色
    imshow("dst2",dst2);//綠色
    imshow("dst3",dst3);//黑色
    imshow("dst4",dst4);//黑色
    waitKey(0);
}

五、其他成員函式

其他成員函式的用法和Mat的相同,不再一一贅述。