一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢?

2023-05-16

一、原因总结

最近项目需要,发现了这个问题。网上找原因,汇总起来,有以下几点原因:

1、首先对于任何一个CUDA程序,在调用它的第一个CUDA API时后都要花费秒级的时间去初始化运行环境,后续还要分配显存,传输数据,启动内核,每一样都有延迟。这样如果你一个任务CPU运算都仅要几十毫秒,相比而言必须带上这些延迟的GPU程序就会显得非常慢。

2、其次,一个运算量很小的程序,你的CUDA内核不可能启动太多的线程,没有足够的线程来屏蔽算法执行时从显存加载数据到GPU SM中的时延,这就没有发挥GPU的真正功能。

3、数据从内存传递到显存和cudaMalloc耗时很长,NVIDIA提供的nsight中的profile可以看每一个部分的耗时。基本上OpenCV的算法都归纳为三个部分:upload(gpu::Mat), processCodeBlock, download(gpu::Mat)。你看看是不是80%以上的时间都花在第一个和最后一个上,问题就迎刃而解了。因为gpu在计算上虽然比cpu快,但实际上在使用gpu的时候有一步非常耗时,那就是将内存与显存中的数据进行互相拷贝,同时这也是使用gpu运算时逃不掉的一步。

4、GPU擅长的是大规模并行计算,比起cpu只是以巨额核心数取得优势的,单核速度其实被cpu碾压。如果数据规模小的话GPU并不能用上太多核,所以比cpu慢。减少数据在CPU和GPU之间的传递次数;运算量非常小的部分不要用GPU,数据量非常大、循环次数非常多的时候才使用GPU。

//执行这些简单算子,CPU比GPU更快

cvtColor,GaussianBlur,Canny

//执行这些耗时算子,GPU比CPU更快

HoughCircles,HoughLines,matchTemplate

5、如果问题规模较小,逻辑控制较为复杂,并行性很小优先使用CPU处理该问题,如果包含较大规模的数据处理,则考虑使用GPU进行处理。

CPU上线程是重量级实体,可以开启1~32个线程,且上下文切换较为缓慢,GPU上线程是高度轻量级的,可以开几百甚至上千个线程。

CUDA通过两种API来对设备GPU设备进行控制,包括驱动API和运行API,其中驱动API较难编程,但是设备控制能力和利用率高。两者只能选择其中一种,不能混合使用。

一个CUDA程序包含了两个部分代码,在CPU上运行的主机代码和在GPU上运行的设备代码。

6、总结一句,GPU的并行处理的确很快,但数据传入GPU和传出的开销实在太大,往往影响了代码的整体效率,运算量非常小的计算不要用GPU。

二、举例opencv

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
#include <opencv2/cudafilters.hpp>
#include <opencv2/cudawarping.hpp>

#define IMAGE_TEST_PATHNAME "D:\\test_src.jpg"
#define IMAGE_SOURCE        "D:\\test_src.jpg"
#define IMAGE_TEMPLATE      "D:\\test_templ.jpg"

void checkCuda() //旧版本的是cv::gpu,#include <opencv2/gpu/gpu.hpp>,已弃用
{
    int64 begintime, endtime;
    int num_devices = cv::cuda::getCudaEnabledDeviceCount();
    if (num_devices <= 0)
    {
        std::cerr << "There is no cuda device" << std::endl;
        return;
    }

    int enable_device_id = -1;
    for (int i = 0; i < num_devices; i++)
    {
        cv::cuda::DeviceInfo dev_info(i);
        if (dev_info.isCompatible())
        {
            enable_device_id = i;
        }
    }

    if (enable_device_id < 0)
    {
        std::cerr << "GPU module isn't built for GPU" << std::endl;
        return;
    }

    cv::cuda::setDevice(enable_device_id); //指定显卡

    //有一个问题是,一般使用GPU加速的话,第一次调用GPU,会很慢很慢,一条简单的语句都用了10多秒左右。
    //治标不治本的解决方法是在程序的开头加上一句cv::gpu::GpuMata(10, 10, CV_8U);
    //这样会令耗时的操作放在一开头,不那么影响后面的操作时间
    //为什么第一次函数调用很慢
    //那是因为初始化开销;在第一个GPU函数调用Cuda Runtime API被隐式初始化;
    cv::cuda::GpuMat(10, 10, CV_8U);

    //测试用例
    cv::Mat src_image = cv::imread(IMAGE_PATHNAME);
    cv::Mat dst_image;
    cv::cuda::GpuMat d_src_img(src_image); //upload src image to gpu
    //或者d_src_img.upload(src_image);
    cv::cuda::GpuMat d_dst_img;

    begintime = cv::getTickCount();
    cv::cuda::cvtColor(d_src_img, d_dst_img, cv::COLOR_BGR2GRAY); //canny
    d_dst_img.download(dst_image);                                //download dst image to cpu
    endtime = cv::getTickCount();
    std::cerr << 1000 * (endtime - begintime) / cv::getTickFrequency() << std::endl;

    cv::namedWindow("checkCuda", cv::WINDOW_NORMAL);
    cv::imshow("checkCuda", dst_image);
}

void calcEdgesCuda()
{
    cv::ocl::setUseOpenCL(false);

    double start = cv::getTickCount();
    cv::cuda::GpuMat gpuGray, gpuBlur, gpuEdges;
    cv::Mat cpuEdges;

    cv::Mat cpuFrame = cv::imread(IMAGE_PATHNAME);

    cv::cuda::registerPageLocked(cpuFrame); //锁页内存

    cv::cuda::GpuMat gpuFrame;
    gpuFrame.upload(cpuFrame);

    cv::cuda::cvtColor(gpuFrame, gpuGray, cv::COLOR_BGR2GRAY);

    cv::Ptr<cv::cuda::Filter> gaussFilter = cv::cuda::createGaussianFilter(CV_8UC1, CV_8UC1, cv::Size(3, 3), 15, 15);
    gaussFilter->apply(gpuGray, gpuBlur);

    cv::Ptr<cv::cuda::CannyEdgeDetector> cannyEdge = cv::cuda::createCannyEdgeDetector(50, 100, 3);
    cannyEdge->detect(gpuBlur, gpuEdges);

    cv::cuda::GpuMat gpuLines; //This should be GpuMat...
#if 0                          //find line
    std::vector<cv::Vec2f> vtLines;
    cv::Ptr<cv::cuda::HoughLinesDetector> hough = cv::cuda::createHoughLinesDetector(1, CV_PI / 180, 120);
    hough->detect(gpuEdges, gpuLines);
    hough->downloadResults(gpuLines, vtLines);
#else
    cv::Ptr<cv::cuda::HoughCirclesDetector> hough1 = cv::cuda::createHoughCirclesDetector(1.5, 15, 300, 1, 1, 100);
    hough1->detect(gpuEdges, gpuLines);
    cv::Ptr<cv::cuda::HoughCirclesDetector> hough2 = cv::cuda::createHoughCirclesDetector(1, 15, 100, 30, 1, 100);
    hough2->detect(gpuEdges, gpuLines);
#endif

    gpuEdges.download(cpuEdges);
    cv::cuda::unregisterPageLocked(cpuFrame); //解除锁页

    std::cout << "Cuda cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::namedWindow("Canny Edges Cuda", cv::WINDOW_NORMAL);
    cv::imshow("Canny Edges Cuda", cpuEdges);
}

void matchTemplateCPU()
{
    cv::Mat src = cv::imread(IMAGE_SOURCE, cv::IMREAD_GRAYSCALE);
    cv::Mat templ = cv::imread(IMAGE_TEMPLATE, cv::IMREAD_GRAYSCALE);
    cv::Mat dst;
    double minVal = 0;
    double maxVal = 0;
    cv::Point minLoc;
    cv::Point maxLoc;

    double start = cv::getTickCount();
    cv::matchTemplate(src, templ, dst, cv::TM_CCOEFF_NORMED); //用6种匹配方式
    cv::normalize(dst, dst, 1, 0, cv::NORM_MINMAX);
    cv::minMaxLoc(dst, &minVal, &maxVal, &minLoc, &maxLoc); //找到最佳匹配点
    std::cout << "matchTemplateCPU cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::rectangle(src, cv::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows), 1, 8, 0);

    cv::namedWindow("matchTemplateCPU", cv::WINDOW_NORMAL);
    cv::imshow("matchTemplateCPU", src);
}

void matchTemplateCuda()
{
    cv::Mat src = cv::imread(IMAGE_SOURCE, cv::IMREAD_GRAYSCALE);
    cv::Mat templ = cv::imread(IMAGE_TEMPLATE, cv::IMREAD_GRAYSCALE);
    cv::Mat dst;
    double minVal = 0;
    double maxVal = 0;
    cv::Point minLoc;
    cv::Point maxLoc;
    cv::cuda::GpuMat gsrc, gtempl, gdst;

    double start = cv::getTickCount();
    gsrc.upload(src);
    gtempl.upload(templ);
    cv::Ptr<cv::cuda::TemplateMatching> matcher;
    matcher = cv::cuda::createTemplateMatching(CV_8U, cv::TM_CCOEFF_NORMED);
    matcher->match(gsrc, gtempl, gdst);
    cv::cuda::minMaxLoc(gdst, &minVal, &maxVal, &minLoc, &maxLoc);
    std::cout << "matchTemplateCuda cost time:(s)" << ((cv::getTickCount() - start) / cv::getTickFrequency()) << std::endl;

    cv::rectangle(src, cv::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows), 1, 8, 0);

    cv::namedWindow("matchTemplateCuda", cv::WINDOW_NORMAL);
    cv::imshow("matchTemplateCuda", src);
}

锁页能够加速数据在CPU和GPU之间的传递

cv::cuda::registerPageLocked(img);//锁页内存
gimg.upload(img);//上传数据至GPU

gimg.download(img);//下载数据至CPU
cv::cuda::unregisterPageLocked(img);//解除锁页

---

姊妹篇

OpenCV算法加速(4)官方源码v4.5.5的默认并行和优化加速的编译选项是什么?请重点关注函数cv::getBuildInformation()的返回值_opencv 编译选项_利白的博客-CSDN博客

参考文献

为什么opencv用GPU实现比用CPU实现的慢?_opencv在显卡上和cpu上跑程序哪个快_THMAIL的博客-CSDN博客

opencv(C++)GPU、CPU 模板匹配_opencv 操作gpu_1037号森林里一段干木头的博客-CSDN博客

《通用图形处理器设计——GPGPU编程模型与架构原理》

作者:景乃锋、柯晶、梁晓 出版社:清华大学出版社 出版时间:2022年05月

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文彻底搞懂为什么OpenCV用GPU/cuda跑得比用CPU慢? 的相关文章

  • 在 python 中使用 opencv 进行 Blob 过滤

    需要从图像中检测红色并根据屏幕尺寸获取坐标 使用掩模获取图像中具有红色的部分 将其转换为 BW 对其应用高斯滤波器 最终图像具有较小的主体 我需要将其删除并获取其余部分的坐标 我尝试了 SimpleBlobDetector 但没有帮助 这是
  • OpenCV SURF功能未实现

    当我尝试运行示例时find obj cpp或任何 OpenCV SURF 程序 在执行代码时 我在命令提示符中收到以下错误 该项目构建时没有错误和警告 我使用的是 VS2011 beta OpenCV 2 4 和 windows7 错误信息
  • opencv中以下代码行的含义是什么?

    该代码行的含义是什么以及如何将此代码转换为 javacv gray Scalar all 255 这是与此代码行相关的完整代码 Mat src imread in jpg gray cvtColor src gray CV BGR2GRAY
  • CUDA Thrust 和 sort_by_key

    我正在寻找 CUDA 上的排序算法 它可以对元素数组 A 双精度 进行排序 并返回该数组 A 的键 B 数组 我知道sort by keyThrust 库中的函数 但我希望元素数组 A 保持不变 我能做些什么 我的代码是 void sort
  • Visual Studio - 过滤掉 nvcc 警告

    我正在编写 CUDA 程序 但收到令人讨厌的警告 Warning Cannot tell what pointer points to assuming global memory space 这是来自 nvcc 我无法禁用它 有没有办法过
  • 在多处理进程之间将 opencv 视频帧共享为 Numpy 数组的正确方法

    我想与我的多处理子进程共享 OpenCV 中的捕获帧 但是video capture read 创建一个新对象 并且不会写入我将通过包装它来共享的 numpy 数组multiprocessing Array 这是代码 ret frame v
  • 如何使用浮点数组中的数据初始化 cv::Mat

    我需要创建一个cv Mat用我的数据初始化的变量float 大批 这应该是基本的 但我很难弄清楚 我有代码 float matrixAB lt 120 floating point array created elsewhere gt cv
  • 无法保存从网络摄像头捕获的图像(OpenCV 2.3 的 imwrite 编译错误)

    我正在使用 OpenCV 2 3 制作简单的网络摄像头程序 但遇到了编译错误 任何想法将不胜感激 编译后 我在 imwrite 处收到以下错误 在下面代码的 read 函数中 这个样本 https code ros org svn open
  • 3D 空间中的激光投影仪校准

    我正在研究一种在现实世界中校准激光投影仪的解决方案 该项目有几个目标 1 Take in a minimum of four points measured in the real world in 3d space that repres
  • 如何从Python使用OpenCV的C++函数?

    我正在使用 OpenCV 的 Python 绑定 它真的很棒 但是 C 版本中的某些函数在 Python 绑定 BackgroundSubstractorMOG2 和许多特征检测算法中缺失 从 Python 调用它们的最简单方法是什么 我希
  • 如何对使用 SimpleITK 读取的 DICOM 图像进行直方图均衡化

    我正在尝试对从 nii gz 文件读取的所有图像进行直方图均衡 我试过这段代码 import SimpleITK as sitk flair file content gdrive My Drive Colab Notebooks FLAI
  • 如何在Python中删除图像的背景

    我有一个包含全角人类图像的数据集 我想删除这些图像中的所有背景 只留下全角人物 我的问题 有没有Python代码可以做到这一点 我是否需要每次都指定人员对象的坐标 这是使用 Python OpenCV 的一种方法 读取输入 转换为灰色 阈值
  • 如何确定完整的 CUDA 版本 + 颠覆版本?

    Linux 上的 CUDA 发行版曾经有一个名为version txt例如 CUDA Version 10 2 89 这非常有用 但是 从 CUDA 11 1 开始 该文件不再存在 我如何在 Linux 上通过命令行确定并检查 path t
  • 如何并行从数组中删除零值

    如何使用 CUDA 并行有效地从数组中删除零值 有关零值数量的信息是预先可用的 这应该可以简化这项任务 重要的是数字必须保持源数组中的顺序 当被复制到结果数组时 Example 该数组将例如包含以下值 0 0 19 7 0 3 5 0 0
  • 在python中使用tesseract 3.02的C API与ctypes和cv2

    我正在尝试在 python 中将 Tesseract 3 02 与 ctypes 和 cv2 一起使用 Tesseract 提供了一组公开的 DLL C 风格 API 其中之一如下 TESS API void TESS CALL TessB
  • 使用python opencv从zip加载图像

    我能够成功地从 zip 加载图像 with zipfile ZipFile test zip r as zfile data zfile read test jpg how to open this using imread or imde
  • CUDA 估计 2D 网格数据的每块线程数和块数

    首先我要说的是 我已经仔细阅读了所有类似的问题 确定每个块的线程和每个网格的块 https stackoverflow com questions 4391162 cuda determining threads per block blo
  • Nvcc 的版本与 CUDA 不同

    我安装了 cuda 7 但是当我点击 nvcc version 时 它打印出 6 5 我想在 GTX 960 卡上安装 Theano 库 但它需要 nvcc 7 0 我尝试重新安装cuda 但它没有更新nvcc 当我运行 apt get i
  • 尝试导入 cv2(opencv-python) 包时出错

    我正在尝试使用 cv2 opencv python 包访问我的网络摄像头 当我尝试导入它时 出现此错误 Traceback most recent call last File server py line 6 in
  • 如何加载4通道的png图像?

    我一直在尝试加载带有透明通道 RGB 和 Alph 的 png 文件 但没有成功 看来 openCV 从图像中剥离了第四个通道 即使我必须修改 OpenCV 源代码并重建它 是否有任何方法可以加载包含 alpha 通道在内的完整 4 个通道

随机推荐