10

一文详解OpenCV中的CUDA模块

 4 years ago
source link: https://mp.weixin.qq.com/s/wvD9rmJkcNtMQEOpTLot0A
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一文详解OpenCV中的CUDA模块

天啦噜 3D视觉工坊 1 week ago
计算机视觉工坊
计算机视觉工坊
专注于计算机视觉、VSLAM、目标检测、语义分割、自动驾驶、深度学习、AI芯片、产品落地等技术干货及前沿paper分享。这是一个由多个大厂算法研究人员和知名高校博士创立的平台,我们坚持工坊精神,做最有价值的事~
106篇原创内容
Official Account
如果您使用OpenCV已有一段时间,那么您应该已经注意到,在大多数情况下,OpenCV都使用CPU,这并不总能保证您所需的性能。为了解决这个问题,OpenCV在2010年增加了一个新模块,该模块使用CUDA提供GPU加速。您可以在下面找到一个展示GPU模块优势的基准测试:
640?wx_fmt=png
简单列举下本文要交代的几个事情:
  • 概述已经支持CUDA的OpenCV模块。
  • 看一下cv :: gpu :: GpuMat(cv2.cuda_GpuMat)。
  • 了解如何在CPU和GPU之间传输数据。
  • 了解如何利用多个GPU。
  • 编写一个简单的演示(C ++和Python),以了解OpenCV提供的CUDA API接口并计算我们可以获得的性能提升。
一、支持的模块据称,尽管并未涵盖所有库的功能,但该模块“仍在继续增长,并正在适应新的计算技术和GPU架构。”让我们看一下CUDA加速的OpenCV的官方文档。在这里,我们可以看到已支持的模块:
  • Core part
  • Operations on Matrices
  • Background Segmentation
  • Video Encoding/Decoding
  • Feature Detection and Description
  • Image Filtering
  • Image Processing
  • Legacy support
  • Object Detection
  • Optical Flow
  • Stereo Correspondence
  • Image Warping
  • Device layer
二、GpuMat为了将数据保留在GPU内存中,OpenCV引入了一个新的类cv :: gpu :: GpuMat(或Python中的cv2.cuda_GpuMat)作为主要数据容器。其界面类似于cv :: Mat(cv2.Mat),从而使向GPU模块的过渡尽可能平滑。值得一提的是,所有GPU函数都将GpuMat接收为输入和输出参数。通过这种在代码中链接了GPU算法的设计,您可以减少在CPU和GPU之间复制数据的开销。三、CPU/GUP数据传递要将数据从GpuMat传输到Mat,反之亦然,OpenCV提供了两个函数:
  • 上传,将数据从主机内存复制到设备内存
  • 下载,将数据从设备内存复制到主机内存。
以下是用C ++写的一个简单示例:
#include <opencv2/highgui.hpp> #include <opencv2/cudaimgproc.hpp> cv::Mat img = cv::imread("image.png", IMREAD_GRAYSCALE); cv::cuda::GpuMat dst, src; src.upload(img); cv::Ptr<cv::cuda::CLAHE> ptr_clahe = cv::cuda::createCLAHE(5.0, cv::Size(8, 8)); ptr_clahe->apply(src, dst); cv::Mat result; dst.download(result); cv::imshow("result", result); cv::waitKey();
四、多个GPU的使用
默认情况下,每种OpenCV CUDA算法都使用单个GPU。如果需要利用多个GPU,则必须在GPU之间手动分配工作。要切换活动设备,请使用cv :: cuda :: setDevice(cv2.cuda.SetDevice)函数。五、代码示例OpenCV提供了有关如何使用C ++ API在GPU支持下与已实现的方法一起使用的示例。让我们在使用Farneback的算法进行密集光流计算的示例中,实现一个简单的演示,演示如何将CUDA加速的OpenCV与C ++一起使用。我们首先来看一下如何使用CPU来完成此操作。然后,我们将使用GPU进行相同的操作。最后,我们将比较经过的时间以计算获得的加速比。FPS计算由于我们的主要目标是找出算法在不同设备上的运行速度,因此我们需要选择测量方法。在计算机视觉中,这样做的常用方法是计算每秒处理的帧数(FPS)。CPU端1.视频及其属性我们将从视频捕获初始化开始,并获取其属性,例如帧频和帧数。这部分是CPU和GPU部分的通用部分:
// init video capture with video VideoCapture capture(videoFileName); if (!capture.isOpened()) {     // error in opening the video file     cout << "Unable to open file!" << endl;     return; } // get default video FPS double fps = capture.get(CAP_PROP_FPS); // get total number of video frames int num_frames = int(capture.get(CAP_PROP_FRAME_COUNT));
2.读取第一帧
由于算法的特殊性,该算法使用两帧进行计算,因此我们需要先读取第一帧,然后再继续。还需要一些预处理,例如调整大小并转换为灰度:
// read the first frame cv::Mat frame, previous_frame; capture >> frame; if (device == "cpu") {     // resize frame     cv::resize(frame, frame, Size(960, 540), 0, 0, INTER_LINEAR);     // convert to gray     cv::cvtColor(frame, previous_frame, COLOR_BGR2GRAY);     // declare outputs for optical flow     cv::Mat magnitude, normalized_magnitude, angle;     cv::Mat hsv[3], merged_hsv, hsv_8u, bgr;     // set saturation to 1     hsv[1] = cv::Mat::ones(frame.size(), CV_32F);
3.读取并预处理其他帧
在循环读取其余帧之前,我们启动两个计时器:一个计时器将跟踪整个流程的工z作时间,第二个计时器–读取帧时间。由于Farneback的光流法适用于灰度帧,因此我们需要确保将灰度视频作为输入传递。这就是为什么我们首先对其进行预处理以将每帧从BGR格式转换为灰度的原因。另外,由于原始分辨率可能太大,因此我们将其调整为较小的尺寸,就像对第一帧所做的一样。我们再设置一个计时器来计算在预处理阶段花费的时间:
while (true) {     // start full pipeline timer     auto start_full_time = high_resolution_clock::now();     // start reading timer     auto start_read_time = high_resolution_clock::now();     // capture frame-by-frame     capture >> frame;     if (frame.empty())         break;     // end reading timer     auto end_read_time = high_resolution_clock::now();     // add elapsed iteration time     timers["reading"].push_back(duration_cast<milliseconds>(end_read_time - start_read_time).count() / 1000.0);     // start pre-process timer     auto start_pre_time = high_resolution_clock::now();     // resize frame     cv::resize(frame, frame, Size(960, 540), 0, 0, INTER_LINEAR);     // convert to gray     cv::Mat current_frame;     cv::cvtColor(frame, current_frame, COLOR_BGR2GRAY);     // end pre-process timer     auto end_pre_time = high_resolution_clock::now();     // add elapsed iteration time     timers["pre-process"].push_back(duration_cast<milliseconds>(end_pre_time - start_pre_time).count() / 1000.0);
4.计算密集光流
我们使用称为calcOpticalFlowFarneback的方法来计算两帧之间的密集光流:
// start optical flow timer auto start_of_time = high_resolution_clock::now(); // calculate optical flow cv::Mat flow; calcOpticalFlowFarneback(previous_frame, current_frame, flow, 0.5, 5, 15, 3, 5, 1.2, 0); // end optical flow timer auto end_of_time = high_resolution_clock::now(); // add elapsed iteration time timers["optical flow"].push_back(duration_cast<milliseconds>(end_of_time - start_of_time).count() / 1000.0);
5.后处理
Farneback的“光流法“输出二维流矢量。我们将这些输出转换为极坐标,以通过色相获得流动的角度(方向),并通过HSV颜色表示的值获得流动的距离(幅度)。对于可视化,我们现在要做的就是将结果转换为BGR空间。之后,我们停止所有剩余的计时器以获取经过的时间:
// start post-process timer auto start_post_time = high_resolution_clock::now(); // split the output flow into 2 vectors cv::Mat flow_xy[2], flow_x, flow_y; split(flow, flow_xy); // get the result flow_x = flow_xy[0]; flow_y = flow_xy[1]; // convert from cartesian to polar coordinates cv::cartToPolar(flow_x, flow_y, magnitude, angle, true); // normalize magnitude from 0 to 1 cv::normalize(magnitude, normalized_magnitude, 0.0, 1.0, NORM_MINMAX); // get angle of optical flow angle *= ((1 / 360.0) * (180 / 255.0)); // build hsv image hsv[0] = angle; hsv[2] = normalized_magnitude; merge(hsv, 3, merged_hsv); // multiply each pixel value to 255 merged_hsv.convertTo(hsv_8u, CV_8U, 255); // convert hsv to bgr cv::cvtColor(hsv_8u, bgr, COLOR_HSV2BGR); // update previous_frame value previous_frame = current_frame; // end post pipeline timer auto end_post_time = high_resolution_clock::now(); // add elapsed iteration time timers["post-process"].push_back(duration_cast<milliseconds>(end_post_time - start_post_time).count() / 1000.0); // end full pipeline timer auto end_full_time = high_resolution_clock::now(); // add elapsed iteration time timers["full pipeline"].push_back(duration_cast<milliseconds>(end_full_time - start_full_time).count() / 1000.0);
6.可视化
我们将尺寸调整为960×540的原始帧可视化,并使用imshow函数显示结果:
// visualization imshow("original", frame); imshow("result", bgr); int keyboard = waitKey(1); if (keyboard == 27)     break;
这是一个示例“ boat.mp4”视频的内容:
640?wx_fmt=png
7.时间和FPS计算我们要做的就是计算流程中每一步花费的时间,并测量光流部分和整个流程的FPS:
// elapsed time at each stage cout << "Elapsed time" << std::endl; for (auto const& timer : timers) {     cout << "- " << timer.first << " : " << accumulate(timer.second.begin(),         timer.second.end(), 0.0) << " seconds"<< endl; } // calculate frames per second cout << "Default video FPS : "  << fps << endl; float optical_flow_fps  = (num_frames - 1) / accumulate(timers["optical flow"].begin(),  timers["optical flow"].end(),  0.0); cout << "Optical flow FPS : "   << optical_flow_fps  << endl; float full_pipeline_fps = (num_frames - 1) / accumulate(timers["full pipeline"].begin(), timers["full pipeline"].end(), 0.0); cout << "Full pipeline FPS : "  << full_pipeline_fps << endl;
GPU端
该算法在将其移至CUDA时保持不变,但在GPU使用方面存在一些差异。让我们再次遍历整个流程,看看有什么变化:1.视频及其属性此部分在CPU和GPU部分都是通用的,因此保持不变。2.读取第一帧注意,我们使用相同的CPU函数来读取和调整大小,但是将结果上传到cv :: cuda :: GpuMat(cuda_GpuMat)实例:
// resize frame cv::resize(frame, frame, Size(960, 540), 0, 0, INTER_LINEAR); // convert to gray cv::cvtColor(frame, previous_frame, COLOR_BGR2GRAY); // upload pre-processed frame to GPU cv::cuda::GpuMat gpu_previous; gpu_previous.upload(previous_frame); // declare cpu outputs for optical flow cv::Mat hsv[3], angle, bgr; // declare gpu outputs for optical flow cv::cuda::GpuMat gpu_magnitude, gpu_normalized_magnitude, gpu_angle; cv::cuda::GpuMat gpu_hsv[3], gpu_merged_hsv, gpu_hsv_8u, gpu_bgr; // set saturation to 1 hsv[1] = cv::Mat::ones(frame.size(), CV_32F); gpu_hsv[1].upload(hsv[1]);
3.读取和预处理其它帧
while (true) {     // start full pipeline timer     auto start_full_time = high_resolution_clock::now();     // start reading timer     auto start_read_time = high_resolution_clock::now();     // capture frame-by-frame     capture >> frame;     if (frame.empty())         break;     // upload frame to GPU     cv::cuda::GpuMat gpu_frame;     gpu_frame.upload(frame);     // end reading timer     auto end_read_time = high_resolution_clock::now();     // add elapsed iteration time     timers["reading"].push_back(duration_cast<milliseconds>(end_read_time - start_read_time).count() / 1000.0);     // start pre-process timer     auto start_pre_time = high_resolution_clock::now();     // resize frame     cv::cuda::resize(gpu_frame, gpu_frame, Size(960, 540), 0, 0, INTER_LINEAR);     // convert to gray     cv::cuda::GpuMat gpu_current;     cv::cuda::cvtColor(gpu_frame, gpu_current, COLOR_BGR2GRAY);     // end pre-process timer     auto end_pre_time = high_resolution_clock::now();     // add elapsed iteration time     timers["pre-process"].push_back(duration_cast<milliseconds>(end_pre_time - start_pre_time).count() / 1000.0);
4.计算密集光流
我们首先使用cv :: cuda :: FarnebackOpticalFlow :: create(cv2.cudaFarnebackOpticalFlow.create)创建cudaFarnebackOpticalFlow类的实例,然后调用cv :: cuda:FarnebackOpticalFlow :: calc(cv2.cuda_FarnebackOpticalFlow.calc)计算两个帧之间的光流,而不是使用cv :: calcOpticalFlowFarneback(cv2.calcOpticalFlowFarneback)函数调用。
// start optical flow timer auto start_of_time = high_resolution_clock::now(); // create optical flow instance Ptr<cuda::FarnebackOpticalFlow> ptr_calc = cuda::FarnebackOpticalFlow::create(5, 0.5, false, 15, 3, 5, 1.2, 0); // calculate optical flow cv::cuda::GpuMat gpu_flow; ptr_calc->calc(gpu_previous, gpu_current, gpu_flow); // end optical flow timer auto end_of_time = high_resolution_clock::now(); // add elapsed iteration time timers["optical flow"].push_back(duration_cast<milliseconds>(end_of_time - start_of_time).count() / 1000.0);
5.后处理
对于后处理,我们使用与CPU端使用的功能相同的GPU变体:
// start post-process timer auto start_post_time = high_resolution_clock::now(); // split the output flow into 2 vectors cv::cuda::GpuMat gpu_flow_xy[2]; cv::cuda::split(gpu_flow, gpu_flow_xy); // convert from cartesian to polar coordinates cv::cuda::cartToPolar(gpu_flow_xy[0], gpu_flow_xy[1], gpu_magnitude, gpu_angle, true); // normalize magnitude from 0 to 1 cv::cuda::normalize(gpu_magnitude, gpu_normalized_magnitude, 0.0, 1.0, NORM_MINMAX, -1); // get angle of optical flow gpu_angle.download(angle); angle *= ((1 / 360.0) * (180 / 255.0)); // build hsv image gpu_hsv[0].upload(angle); gpu_hsv[2] = gpu_normalized_magnitude; cv::cuda::merge(gpu_hsv, 3, gpu_merged_hsv); // multiply each pixel value to 255 gpu_merged_hsv.cv::cuda::GpuMat::convertTo(gpu_hsv_8u, CV_8U, 255.0); // convert hsv to bgr cv::cuda::cvtColor(gpu_hsv_8u, gpu_bgr, COLOR_HSV2BGR); // send original frame from GPU back to CPU gpu_frame.download(frame); // send result from GPU back to CPU gpu_bgr.download(bgr); // update previous_frame value gpu_previous = gpu_current; // end post pipeline timer auto end_post_time = high_resolution_clock::now(); // add elapsed iteration time timers["post-process"].push_back(duration_cast<milliseconds>(end_post_time - start_post_time).count() / 1000.0); // end full pipeline timer auto end_full_time = high_resolution_clock::now(); // add elapsed iteration time timers["full pipeline"].push_back(duration_cast<milliseconds>(end_full_time - start_full_time).count() / 1000.0);
可视化、时间和FPS计算与CPU端相同。
结果现在,我们可以在示例视频中比较来自CPU和GPU版本的指标。 我们用于CPU的配置为:Intel Core i7-8700Configuration
- device : cpu
- video file : video/boat.mp4
Number of frames: 320
Elapsed time
- full pipeline : 37.355 seconds
- reading : 3.327 seconds
- pre-process : 0.027 seconds
- optical flow : 32.706 seconds
- post-process : 0.641 seconds
Default video FPS : 29.97
Optical flow FPS : 9.75356
Full pipeline FPS : 8.53969用于GPU的配置为:Nvidia GeForce GTX 1080 TiConfiguration
- device : gpu
- video file : video/boat.mp4
Number of frames: 320
Elapsed time
- full pipeline : 8.665 seconds
- reading : 4.821 seconds
- pre-process : 0.035 seconds
- optical flow : 1.874 seconds
- post-process : 0.631 seconds
Default video FPS : 29.97
Optical flow FPS : 170.224
Full pipeline FPS : 36.8148当我们使用CUDA加速时,这使光流计算的速度提高了约17倍!但是不幸的是,我们生活在现实世界中,并不是所有的流程阶段都可以加速。因此,对于整个流程,我们只能获得约4倍的加速。总结本文我们概述了GPU OpenCV模块并编写了一个简单的演示,以了解如何加速Farneback的Optical Flow算法。我们研究了OpenCV为该模块提供的API,您也可以重用该API来尝试使用CUDA加速OpenCV算法。备注:作者也是我们「3D视觉从入门到精通」特邀嘉宾:一个超干货的3D视觉学习社区本文仅做学术分享,如有侵权,请联系删文。下载1在「3D视觉工坊」公众号后台回复:3D视觉即可下载 3D视觉相关资料干货,涉及相机标定、三维重建、立体视觉、SLAM、深度学习、点云后处理、多视图几何等方向。下载2在「3D视觉工坊」公众号后台回复:3D视觉github资源汇总即可下载包括结构光、标定源码、缺陷检测源码、深度估计与深度补全源码、点云处理相关源码、立体匹配源码、单目、双目3D检测、基于点云的3D检测、6D姿态估计源码汇总等。下载3在「3D视觉工坊」公众号后台回复:相机标定即可下载独家相机标定学习课件与视频网址;后台回复:立体匹配即可下载独家立体匹配学习课件与视频网址。

重磅!3DCVer-学术论文写作投稿 交流群已成立

扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

同时也可申请加入我们的细分方向交流群,目前主要有3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、多传感器融合、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、求职交流、ORB-SLAM系列源码交流、深度估计等微信群。

一定要备注:研究方向+学校/公司+昵称,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。

640?wx_fmt=png▲长按加微信群或投稿

640?wx_fmt=jpeg

▲长按关注公众号

3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近3000星球成员为创造更好的AI世界共同进步,知识星球入口:

学习3D视觉核心技术,扫描查看介绍,3天内无条件退款640?wx_fmt=jpeg 圈里有高质量教程资料、可答疑解惑、助你高效解决问题觉得有用,麻烦给个赞和在看~640?wx_fmt=gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK