在体验了OpenCV-OpenCL的使用之后,发现CPU和GPU之间的数据传输相当的耗时,既然我这个小菜鸟都发现了这个瓶颈,大佬们肯定也早就发现也有一些解决方案吧!在网上看到一篇与这个有关的文章《OpenCL2.0特性之SVM》,可以先了解一下,看完之后有所收获,但也有许多疑问如下:
1、SVM支持CPU和GPU之间不需要数据拷贝就可以数据共享,那这个SVM是软件实现的还是硬件实现的?如果是软件实现的,那么是不是只要GPU的驱动版本为opencl2.0之后的就可以支持SVM?
2、SVM是不是需要硬件(CPU和GPU可以统一寻址的内存,就是数据都同时在同一块物理内存上操作的硬件?)配合才能实现?如果单纯的由软件实现,CPU数据在CPU内存,GPU数据在显存,他们之间要实现数据共享,因为两者的物理内存不统一,只能通过COPY或者说MAP操作不是吗?那这样不还是要在两者之间进行数据拷贝?那么这种SVM还有什么意义呢?
于是,我开始重新编译opencv的opencl支持SVM特性,在cmake的时候勾选如下即可(我发现opencv2.x是没有SVM特性可以选择的,由此说明opencv2.x使用的opencl版本是1.2,这边使用的是opencv3.x的源码):
编译之后的opencv-opencl就支持SVM了。
那么如何看现在的版本支持的是opencl的那个版本以及是否支持SVM呢?通过以下代码就可以知道:
std::vector<cv::ocl::PlatformInfo> plats;
cv::ocl::getPlatfomsInfo(plats);
const cv::ocl::PlatformInfo *platform = &plats[0];
std::cout << "Platform name: " << platform->name().c_str() << std::endl;
std::cout << "OpenCL CL_PLATFORM_VERSION: " << platform->version().c_str() << std::endl;
std::cout << "OpenCL CL_PLATFORM_VENDOR: " << platform->vendor().c_str() << std::endl;
std::cout << "OpenCL deviceNumber: " << platform->deviceNumber() << std::endl;
cv::ocl::Device current_device;
platform->getDevice(current_device, 0);
std::cout << "Device name: " << current_device.name().c_str() << std::endl;
current_device.set(0);
bool is_have_opencl = cv::ocl::haveOpenCL();
bool is_have_svm = cv::ocl::haveSVM();
bool is_use_opencl = cv::ocl::useOpenCL();
bool is_have_amd_blas = cv::ocl::haveAmdBlas();
bool is_have_amd_fft = cv::ocl::haveAmdFft();
std::cout << "is_have_opencl:" << is_have_opencl << std::endl;
std::cout << "is_have_amd_blas:" << is_have_amd_blas << std::endl;
std::cout << "is_have_amd_fft:" << is_have_amd_fft << std::endl;
std::cout << "is_have_svm:" << is_have_svm << std::endl;
std::cout << "is_use_opencl:" << is_use_opencl << std::endl;
cv::ocl::setUseOpenCL(true);
在没有勾选SVM特性的CV库跑起来的结果如下:
可以看到当前的CV-OCL版本是2.0,但是没有使用SVM。
在勾选了SVM特性的CV库跑起来的结果如下:
可以看到当前的CV-OCL版本是2.0,并且有SVM特性。
所以,还是测试之前的灰度模板匹配算法是否有了更快的速度,代码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/flann/flann.hpp>
#include <opencv2/core/ocl.hpp>
#include <iostream>
#include <string>
#define SRC_IMG "1.bmp"
#define TMP_IMG "tmp.png"
using namespace cv;
using namespace cv::ocl;
using namespace std;
void initOpenCL();
void runMatchGrayUseCpu(int method);
void runMatchGrayUseGpu(int method);
int main(int argc, char **argv){
initOpenCL();
int method = CV_TM_SQDIFF;
runMatchGrayUseCpu(method);
runMatchGrayUseGpu(method);
return 0;
}
void initOpenCL(){
// launch OpenCL environment...
std::vector<cv::ocl::PlatformInfo> plats;
cv::ocl::getPlatfomsInfo(plats);
const cv::ocl::PlatformInfo *platform = &plats[0];
std::cout << "Platform name: " << platform->name().c_str() << std::endl;
std::cout << "OpenCL CL_PLATFORM_VERSION: " << platform->version().c_str() << std::endl;
std::cout << "OpenCL CL_PLATFORM_VENDOR: " << platform->vendor().c_str() << std::endl;
std::cout << "OpenCL deviceNumber: " << platform->deviceNumber() << std::endl;
cv::ocl::Device current_device;
platform->getDevice(current_device, 0);
std::cout << "Device name: " << current_device.name().c_str() << std::endl;
current_device.set(0);
bool is_have_opencl = cv::ocl::haveOpenCL();
bool is_have_svm = cv::ocl::haveSVM();
bool is_use_opencl = cv::ocl::useOpenCL();
bool is_have_amd_blas = cv::ocl::haveAmdBlas();
bool is_have_amd_fft = cv::ocl::haveAmdFft();
std::cout << "is_have_opencl:" << is_have_opencl << std::endl;
std::cout << "is_have_amd_blas:" << is_have_amd_blas << std::endl;
std::cout << "is_have_amd_fft:" << is_have_amd_fft << std::endl;
std::cout << "is_have_svm:" << is_have_svm << std::endl;
std::cout << "is_use_opencl:" << is_use_opencl << std::endl;
cv::ocl::setUseOpenCL(true);
}
void runMatchGrayUseCpu(int method){
std::cout << "===Test Match Template Use CPU===" << std::endl;
double t = 0.0;
cv::Mat src = cv::imread(SRC_IMG, 1);
cv::Mat tmp = cv::imread(TMP_IMG, 1);
cv::Mat gray_src, gray_tmp;
if (src.channels() == 1) gray_src = src;
else cv::cvtColor(src, gray_src, CV_RGB2GRAY);
if (tmp.channels() == 1) gray_tmp = tmp;
else cv::cvtColor(tmp, gray_tmp, CV_RGB2GRAY);
int result_cols = gray_src.cols - gray_tmp.cols + 1;
int result_rows = gray_src.rows - gray_tmp.rows + 1;
cv::Mat result = cv::Mat(result_cols, result_rows, CV_32FC1);
t = (double)cv::getTickCount();
cv::matchTemplate(gray_src, gray_tmp, result, method);
t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
cv::Point point;
double minVal, maxVal;
cv::Point minLoc, maxLoc;
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
switch (method){
case CV_TM_SQDIFF:
point = minLoc;
break;
case CV_TM_SQDIFF_NORMED:
point = minLoc;
break;
case CV_TM_CCORR:
case CV_TM_CCOEFF:
point = maxLoc;
break;
case CV_TM_CCORR_NORMED:
case CV_TM_CCOEFF_NORMED:
default:
point = maxLoc;
break;
}
std::cout << "CPU time :" << t << " second" << std::endl;
std::cout << "obj.x :" << point.x << " obj.y :" << point.y << std::endl;
std::cout << " " << std::endl;
}
void runMatchGrayUseGpu(int method){
std::cout << "===Test Match Template Use GPU===" << std::endl;
double t = 0.0;
cv::UMat src = cv::imread(SRC_IMG, 1).getUMat(cv::ACCESS_RW);
cv::UMat tmp = cv::imread(TMP_IMG, 1).getUMat(cv::ACCESS_RW);
cv::UMat gray_src, gray_tmp;
if (src.channels() == 1) gray_src = src;
else cv::cvtColor(src, gray_src, CV_RGB2GRAY);
if (tmp.channels() == 1) gray_tmp = tmp;
else cv::cvtColor(tmp, gray_tmp, CV_RGB2GRAY);
int result_cols = gray_src.cols - gray_tmp.cols + 1;
int result_rows = gray_src.rows - gray_tmp.rows + 1;
cv::UMat result = cv::UMat(result_cols, result_rows, CV_32FC1);
t = (double)cv::getTickCount();
cv::matchTemplate(gray_src, gray_tmp, result, method);
t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
cv::Point point;
double minVal, maxVal;
cv::Point minLoc, maxLoc;
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::UMat());
switch (method){
case CV_TM_SQDIFF:
point = minLoc;
break;
case CV_TM_SQDIFF_NORMED:
point = minLoc;
break;
case CV_TM_CCORR:
case CV_TM_CCOEFF:
point = maxLoc;
break;
case CV_TM_CCORR_NORMED:
case CV_TM_CCOEFF_NORMED:
default:
point = maxLoc;
break;
}
std::cout << "GPU time :" << t << " second" << std::endl;
std::cout << "obj.x :" << point.x << " obj.y :" << point.y << std::endl;
std::cout << " " << std::endl;
}
运行结果:
提示SVM的Capabilities为0。并且运行速度还是辣么的慢!那么这个SVMCapabilities是个什么东东呢?这个其实就是上面那篇文章所说的粗粒度缓冲SVM,细粒度缓冲SVM,细粒度系统SVM这三种SVM类型,GPU可能支持其中的一种、两种或者三种,或者0。
如下对于SVMCapabilities的说明引用来自《OpenCL2.0特性之SVM》
对于SVM的创建,OpenCL2.0中有两种方式,一种缓冲分配(buffer allocation),另一种是系统分配(System allocation):
1、所谓缓冲分配,就是我们使用OpenCL API函数clSVMAlloc来分配,然后使用clSetKernelArgSVMPointer把分配的SVM作为内核参数传入
2、所谓系统分配,就是在主机端,我们可以使用malloc,new之类的系统分配内存函数来分配空间,然后使用clSetKernelArgSVMPointer把分配的SVM作为内核参数传入。
对于SVM的类型,OpenCL2.0也是有两种类型:一种是粗粒度;另一种是细粒度:
1、粗粒度SVM:共享发生在OpenCL缓冲内存对象区域粒度。在同步点强制内存一致性,使用map/unmap命令来更新主机与设备间的数据。粗粒度的SVM与OpenCL1.2中使用缓冲对象类似,不过唯一不同的是:我们不需要来回拷贝数据,设备与主机可以直接访问对方的数据,这才是重点!
2、所谓细粒度SVM:共享发生在OpenCL缓冲对象单个的加载/存储粒度。内存一致性在同步点得到保证。
好,结合SVM分配方式和SVM类型,可以把OpenCL2.0中的SVM分为:粗粒度缓冲SVM,细粒度缓冲SVM,细粒度系统SVM。(木有粗粒度系统SVM)。
进ocl.cpp可以看到以下代码(列出主要的出错地方代码):
#ifdef HAVE_OPENCL_SVM
bool svmInitialized;
bool svmAvailable;
bool svmEnabled;
svm::SVMCapabilities svmCapabilities;
svm::SVMFunctions svmFunctions;
void svmInit()
{
CV_Assert(handle != NULL);
const Device& device = devices[0];
cl_device_svm_capabilities deviceCaps = 0;
CV_Assert(((void)0, CL_DEVICE_SVM_CAPABILITIES == CL_DEVICE_SVM_CAPABILITIES_AMD)); // Check assumption
cl_int status = clGetDeviceInfo((cl_device_id)device.ptr(), CL_DEVICE_SVM_CAPABILITIES, sizeof(deviceCaps), &deviceCaps, NULL);
if (status != CL_SUCCESS)
{
CV_OPENCL_SVM_TRACE_ERROR_P("CL_DEVICE_SVM_CAPABILITIES via clGetDeviceInfo failed: %d\n", status);
goto noSVM;
}
CV_OPENCL_SVM_TRACE_P("CL_DEVICE_SVM_CAPABILITIES returned: 0x%x\n", (int)deviceCaps);
CV_Assert(((void)0, CL_DEVICE_SVM_COARSE_GRAIN_BUFFER == CL_DEVICE_SVM_COARSE_GRAIN_BUFFER_AMD)); // Check assumption
// CL_DEVICE_SVM_COARSE_GRAIN_BUFFER,CL_DEVICE_SVM_FINE_GRAIN_BUFFER,CL_DEVICE_SVM_FINE_GRAIN_SYSTEM
// 对应粗粒度缓冲SVM,细粒度缓冲SVM,细粒度系统SVM
svmCapabilities.value_ =
((deviceCaps & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER) ? svm::SVMCapabilities::SVM_COARSE_GRAIN_BUFFER : 0) |
((deviceCaps & CL_DEVICE_SVM_FINE_GRAIN_BUFFER) ? svm::SVMCapabilities::SVM_FINE_GRAIN_BUFFER : 0) |
((deviceCaps & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM) ? svm::SVMCapabilities::SVM_FINE_GRAIN_SYSTEM : 0) |
((deviceCaps & CL_DEVICE_SVM_ATOMICS) ? svm::SVMCapabilities::SVM_ATOMICS : 0);
svmCapabilities.value_ &= svm::getSVMCapabilitiesMask();
if (svmCapabilities.value_ == -1)
{
// 代码死在这里,说明本GPU没有支持以上任何一种的SVM(GPU为 AMD Radeon R5 M430)
CV_OPENCL_SVM_TRACE_ERROR_P("svmCapabilities is empty\n");
goto noSVM;
}
try
{
// Try OpenCL 2.0
CV_OPENCL_SVM_TRACE_P("Try SVM from OpenCL 2.0 ...\n");
// ···略
}
// ···略
}
所以,我就开始纳闷了,既然读取出来的opencl版本是2.0的,理论上应该支持SVM,但是为什么是这个结果?
于是我换个显卡,在集成显卡上跑下上面的代码,只要将上面代码的plats[0]改成plats[1]即可,前提得有集显。结果还是一样的,没有支持任何一种SVM,如下图:
这结果不顺我意啊···还是回答不了我上面的2个疑问啊!!!
条件有限啊,我还想再其他的显卡上做一下实验啊···,可惜本人只有一个PC啊···所以将上面的那个代码打包成可执行文件(在这儿)不好意思啊,下载分系统不让我填0分···,希望有兴趣的下一下在电脑上跑一下看看什么情况···!也希望能解答上面两个问题的大佬高台贵手为小弟解惑一下下(我已经度娘了一大圈,没能找到满意的答案)!