该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 }
最后将原图和计算的光流图下载到内存中并显示。
标签:chrono,demo,frame,opencv,hsv,cuda,time,gpu From: https://www.cnblogs.com/uuvv/p/18289138