
10

一文详解OpenCV中的CUDA模块
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.

一文详解OpenCV中的CUDA模块
天啦噜
3D视觉工坊
1 week ago
计算机视觉工坊
专注于计算机视觉、VSLAM、目标检测、语义分割、自动驾驶、深度学习、AI芯片、产品落地等技术干货及前沿paper分享。这是一个由多个大厂算法研究人员和知名高校博士创立的平台,我们坚持工坊精神,做最有价值的事~
106篇原创内容
Official Account
- 概述已经支持CUDA的OpenCV模块。
- 看一下cv :: gpu :: GpuMat(cv2.cuda_GpuMat)。
- 了解如何在CPU和GPU之间传输数据。
- 了解如何利用多个GPU。
- 编写一个简单的演示(C ++和Python),以了解OpenCV提供的CUDA API接口并计算我们可以获得的性能提升。
- 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
- 上传,将数据从主机内存复制到设备内存
- 下载,将数据从设备内存复制到主机内存。
四、多个GPU的使用#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();
默认情况下,每种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部分的通用部分:
2.读取第一帧// 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));
由于算法的特殊性,该算法使用两帧进行计算,因此我们需要先读取第一帧,然后再继续。还需要一些预处理,例如调整大小并转换为灰度:
3.读取并预处理其他帧// 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);
在循环读取其余帧之前,我们启动两个计时器:一个计时器将跟踪整个流程的工z作时间,第二个计时器–读取帧时间。由于Farneback的光流法适用于灰度帧,因此我们需要确保将灰度视频作为输入传递。这就是为什么我们首先对其进行预处理以将每帧从BGR格式转换为灰度的原因。另外,由于原始分辨率可能太大,因此我们将其调整为较小的尺寸,就像对第一帧所做的一样。我们再设置一个计时器来计算在预处理阶段花费的时间:
4.计算密集光流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);
我们使用称为calcOpticalFlowFarneback的方法来计算两帧之间的密集光流:
5.后处理// 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);
Farneback的“光流法“输出二维流矢量。我们将这些输出转换为极坐标,以通过色相获得流动的角度(方向),并通过HSV颜色表示的值获得流动的距离(幅度)。对于可视化,我们现在要做的就是将结果转换为BGR空间。之后,我们停止所有剩余的计时器以获取经过的时间:
6.可视化// 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);
我们将尺寸调整为960×540的原始帧可视化,并使用imshow函数显示结果:
这是一个示例“ boat.mp4”视频的内容:// visualization
imshow("original", frame);
imshow("result", bgr);
int keyboard = waitKey(1);
if (keyboard == 27)
break;
GPU端// 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;
该算法在将其移至CUDA时保持不变,但在GPU使用方面存在一些差异。让我们再次遍历整个流程,看看有什么变化:1.视频及其属性此部分在CPU和GPU部分都是通用的,因此保持不变。2.读取第一帧注意,我们使用相同的CPU函数来读取和调整大小,但是将结果上传到cv :: cuda :: GpuMat(cuda_GpuMat)实例:
3.读取和预处理其它帧// 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]);
4.计算密集光流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);
我们首先使用cv :: cuda :: FarnebackOpticalFlow :: create(cv2.cudaFarnebackOpticalFlow.create)创建cudaFarnebackOpticalFlow类的实例,然后调用cv :: cuda:FarnebackOpticalFlow :: calc(cv2.cuda_FarnebackOpticalFlow.calc)计算两个帧之间的光流,而不是使用cv :: calcOpticalFlowFarneback(cv2.calcOpticalFlowFarneback)函数调用。
5.后处理// 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);
对于后处理,我们使用与CPU端使用的功能相同的GPU变体:
可视化、时间和FPS计算与CPU端相同。// 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);
结果现在,我们可以在示例视频中比较来自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视觉 + 上海交大 + 静静“。请按照格式备注,可快速被通过且邀请进群。原创投稿也请联系。
▲长按关注公众号
3D视觉从入门到精通知识星球:针对3D视觉领域的知识点汇总、入门进阶学习路线、最新paper分享、疑问解答四个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近3000星球成员为创造更好的AI世界共同进步,知识星球入口:
学习3D视觉核心技术,扫描查看介绍,3天内无条件退款
圈里有高质量教程资料、可答疑解惑、助你高效解决问题觉得有用,麻烦给个赞和在看~
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK