OpenCV+CUDA入門教程之五---GpuMat詳解
目錄
一、簡介
GpuMat可以從其命名看出,它是“GPU”版本的Mat,絕大部分介面和Mat相同,功能也類似。
和Mat相比,GpuMat多了兩個成員函式upload和download,分別用於把資料從記憶體上傳(通過匯流排傳輸)到視訊記憶體和從視訊記憶體下載(通過匯流排傳輸)到記憶體。
GpuMat和Mat都有資料指標(指向一塊儲存區域,其中存放著影象資料),不過GpuMat的資料指標是指向視訊記憶體上的某一塊區域,而Mat的資料指標是指向記憶體上的某一塊區域。
GpuMat僅支援二維資料;GpuMat::isContinuous() == false,
二、建構函式
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的相同,不再一一贅述。