基于opencv + GPU cuda的光流算法demo

该demo来自learnopencv.com网站,是作为opencv cuda 模块的启蒙示例。看来这是一个简单的例子,但是由于从未接触过opencv cuda图像处理,我个人仍感觉比较新颖和有趣,特别是运行效果很惊奇,这里和大家一起学习解读以下。想看一手内容可以在网络直接搜索Getting Started With Opencv cuda Module。这里是对其直接搬运、截取、翻译和个人解读。

代码主要应用到的算法是Gunnar Farneback的稠密光流算法(Farneback算法),是一种基于梯度的经典光流算法。具体算法内容比较复杂,可以参考论文和网上解读。OpenCV中提供了cuda::FarnebackOpticalFlow这个实现进行稠密光流计算。

1. 读取视频图像

复制代码
 1 cout << "Hello Cuda!" << endl;
 2 
 3     /** 打开本地视频流  */
 4     VideoCapture capture("/home/workspace/av/testcuvid/input2.mp4");
 5     if(!capture.isOpened())
 6     {
 7         cout << "Failed to open video." << endl;
 8         return 0;
 9     }
10 
11     /** 获取本地视频关键信息  */
12     double fps = capture.get(CAP_PROP_FPS);
13     int num_frames = int(capture.get(CAP_PROP_FRAME_COUNT));
14 
15     cout << "fps:" << fps << "  frames:" << num_frames << endl;
复制代码

上面代码主要是使用opencv的VideoCapture打开本地视频,并获取本地视频的帧率和总帧数。

2. 读取第一帧图像并载入GPU显存中

复制代码
 1     Mat frame, previous_frame;
 2 
 3     /** 读取第一帧图像,并转换大小,转换成灰度图 */
 4     capture >> frame;
 5     resize(frame, frame, Size(960, 540), 0, 0, INTER_LINEAR);
 6     cvtColor(frame, previous_frame, COLOR_BGR2GRAY);
 7 
 8     /** 定义GPU版本的mat,并将视频图像帧上传到GPU显存中  */
 9     cuda::GpuMat gpu_previous;
10     gpu_previous.upload(previous_frame);
复制代码

3. 定义一些运算中间量和统计中间量

复制代码
 1     /** 定义运算中间量  */
 2     Mat hsv[3], angle, bgr;
 3     cuda::GpuMat gpu_magnitude, gpu_normalized_magnitude, gpu_angle;
 4     cuda::GpuMat gpu_hsv[3], gpu_merged_hsv, gpu_hsv_8u, gpu_bgr;
 5 
 6     /** 单通道矩阵,第一列是1,其他是0  */
 7     hsv[1] = Mat::ones(frame.size(), CV_32F);
 8     gpu_hsv[1].upload(hsv[1]);
 9 
10     /** 定义统计信息记录map  */
11     map<string,list<double> > timers;
12     timers.insert(map<string, list<double> >::value_type("reading", list<double>()));
13     timers.insert(map<string, list<double> >::value_type("pre-process", list<double>()));
14     timers.insert(map<string, list<double> >::value_type("optical flow", list<double>()));
15     timers.insert(map<string, list<double> >::value_type("post-process", list<double>()));
16     timers.insert(map<string, list<double> >::value_type("full pipeline", list<double>()));
复制代码

4. 循环读取图像并进行预处理

复制代码
 1     while(true)
 2     {
 3         auto start_full_time = chrono::high_resolution_clock::now();
 4 
 5         auto start_read_time = chrono::high_resolution_clock::now();
 6 
 7         /** 继续读取下一帧视频图像,直到为空  */
 8         capture >> frame;
 9         if(frame.empty())
10             break;
11 
12         /** 将读取到的图像上传到显存  */
13         cuda::GpuMat gpu_frame;
14         gpu_frame.upload(frame);
15 
16         auto end_read_time = chrono::high_resolution_clock::now();
17 
18         timers["reading"].push_back(chrono::duration_cast<chrono::milliseconds>(end_read_time - start_read_time).count() / 1000.0);
19 
20         auto start_pre_time = chrono::high_resolution_clock::now();
21 
22         /** 设置图像大小,并转换为灰度图  */
23         cv::cuda::resize(gpu_frame, gpu_frame, Size(960, 540), 0, 0, INTER_LINEAR);
24         cv::cuda::GpuMat gpu_current;
25         cv::cuda::cvtColor(gpu_frame, gpu_current, COLOR_BGR2GRAY);
复制代码

以上以此读取本地视频中的视频帧,直到结束。读取到的图像上传到GPU显存中,然后调用cuda实现的算法将其缩放,并转为灰度图。这里说明以下,稠密 光流计算的输入为灰度图像。

5. 计算光流图

复制代码
 1 auto end_pre_time = chrono::high_resolution_clock::now();
 2 
 3         timers["pre-process"].push_back(chrono::duration_cast<chrono::milliseconds>(end_pre_time - start_pre_time).count() /1000.0);
 4 
 5         auto start_of_time = chrono::high_resolution_clock::now();
 6 
 7         /** Farneback算子创建  */
 8         Ptr<cuda::FarnebackOpticalFlow> ptr_calc = cuda::FarnebackOpticalFlow::create(5, 0.5, false, 15, 3, 5, 1.2, 0);
 9 
10         /** 计算光流图像  */
11         cuda::GpuMat gpu_flow;
12         ptr_calc->calc(gpu_previous, gpu_current, gpu_flow);
13 
14         auto end_of_time = chrono::high_resolution_clock::now();
15 
16         timers["optical flow"].push_back(chrono::duration_cast<chrono::milliseconds>(end_of_time - start_of_time).count() /1000.0);
复制代码

调用opencv封装好的算法,输入前一帧图像和当前帧图像,计算出光流结果。

6. 光流结果处理,转换成直观的光流图

复制代码
 1 auto start_post_time = chrono::high_resolution_clock::now();
 2         /** 将光流图中xy通道数据分割  */
 3         cv::cuda::GpuMat gpu_flow_xy[2];
 4         cv::cuda::split(gpu_flow, gpu_flow_xy);
 5 
 6         /** 笛卡尔坐标转极坐标(向量坐标)  */
 7         cv::cuda::cartToPolar(gpu_flow_xy[0], gpu_flow_xy[1], gpu_magnitude, gpu_angle, true);
 8         /** 将上面的数值进行标准化  */
 9         cv::cuda::normalize(gpu_magnitude, gpu_normalized_magnitude, 0.0, 1.0, NORM_MINMAX, -1);
10         /** 将光流相位信息下载到内存  */
11         gpu_angle.download(angle);
12         angle *= ((1/360.0)*(180/255.0));   // 角度信息转为h通道信息
13         /** 将转化后的h通道上传到GPU显存中,将归一化后的数值上传到v通道中  */
14         gpu_hsv[0].upload(angle);               // h通道对应角度
15 //        gpu_hsv[1].upload(Mat::ones(frame.size(), CV_32F));   // s通道为1,上面定义了
16         gpu_hsv[2] = gpu_normalized_magnitude;  // 通道对应归一化的位移
17         /** 合并通道  */
18         cv::cuda::merge(gpu_hsv, 3, gpu_merged_hsv);
19         /** 转换数据格式  */
20         gpu_merged_hsv.cv::cuda::GpuMat::convertTo(gpu_hsv_8u, CV_8U, 255.0);
21         /** 转成RBG图像  */
22         cv::cuda::cvtColor(gpu_hsv_8u, gpu_bgr, COLOR_HSV2BGR);
复制代码

5中计算出来的光流结果是像素xy方向的位移。这里将其转成极坐标表示,然后对极坐标的标量(位移量)进行归一化,对极坐标的角度(方向量)进行转化。归一化后的位移量作为光流图的h通道,转化后的方向量作为光流图的通道。加上前面定义的s通道,就形成光流图了。最后将光流图多通道合并,然后转rgb。

7. 显示

复制代码
 1 /** 将原图和光流图下载到内存  */
 2         gpu_frame.download(frame);
 3         gpu_bgr.download(bgr);
 4 
 5         /** 更新前一帧图像,准备下次计算  */
 6         gpu_previous = gpu_current;
 7 
 8         auto end_post_time = chrono::high_resolution_clock::now();
 9 
10         timers["post-process"].push_back(chrono::duration_cast<chrono::milliseconds>(end_post_time - start_post_time).count() / 1000.0);
11 
12         auto end_full_time = chrono::high_resolution_clock::now();
13 
14         timers["full pipeline"].push_back(chrono::duration_cast<chrono::milliseconds>(end_full_time - start_full_time).count() / 1000.0);
15 
16         /** 显示原图和光流图  */
17         imshow("original", frame);
18         imshow("result", bgr);
19         
20         int keyboard = waitKey(1);
21         if(keyboard == 27)
22             break;
23 
24     }
复制代码

最后将原图和计算的光流图下载到内存中并显示。

posted @   远桥  阅读(299)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示