加速 OpticalFlow 算法 - OpenCV
Accelerating OpticalFlow Algorithm - OpenCV
我正在从事一个使用光流算法估计无人机位置的项目。为此,我目前正在使用 cv::calcOpticalFlowFarneback
。
我的硬件是一个Odroid U3,最终会连接到无人机飞行控制器。
问题是这个方法对于这个硬件来说真的很重,我正在寻找其他一些方法来优化/加速它。
我已经尝试过的事情:
- 将分辨率降低到 320x240 甚至 160x120。
- 使用 OpenCV TBB(使用
WITH_TBB=ON BUILD_TBB=ON
编译并添加 -ltbb
)。
- 按照建议更改光流参数here
添加我的代码的相关部分:
int opticalFlow(){
// capture from camera
VideoCapture cap(0);
if( !cap.isOpened() )
return -1;
// Set Resolution - The Default Resolution Is 640 x 480
cap.set(CV_CAP_PROP_FRAME_WIDTH,WIDTH_RES);
cap.set(CV_CAP_PROP_FRAME_HEIGHT,HEIGHT_RES);
Mat flow, cflow, undistortFrame, processedFrame, origFrame, croppedFrame;
UMat gray, prevgray, uflow;
currLocation.x = 0;
currLocation.y = 0;
// for each frame calculate optical flow
for(;;)
{
// take out frame- still distorted
cap >> origFrame;
// Convert to gray
cvtColor(origFrame, processedFrame, COLOR_BGR2GRAY);
// rotate image - perspective transformation
rotateImage(processedFrame, gray, eulerFromSensors.roll, eulerFromSensors.pitch, 0, 0, 0, 1, cameraMatrix.at<double>(0,0),
cameraMatrix.at<double>(0,2),cameraMatrix.at<double>(1,2));
if( !prevgray.empty() )
{
// calculate flow
calcOpticalFlowFarneback(prevgray, gray, uflow, 0.5, 3, 10, 3, 3, 1.2, 0);
uflow.copyTo(flow);
// get average
calcAvgOpticalFlow(flow, 16, corners);
/*
Some other calculations
.
.
.
Updating currLocation struct
*/
}
//break conditions
if(waitKey(1)>=0)
break;
if(end_run)
break;
std::swap(prevgray, gray);
}
return 0;
}
备注:
- 我已经 运行
callgrind
并且瓶颈正如预期的那样是 calcOpticalFlowFarneback
函数。
- 我在 运行 程序时检查了 CPU 核心负载,它并没有大量使用所有 4 个核心,在给定时间只有一个核心处于 100%(即使有 TBB) :
光流估计通常是一个安静的耗时操作。我建议更改光流法。
DualTVL1OpticalFlow
是您可以使用的 OpenCV 中性能更高的方法。如果此方法仍然很慢,则应使用 calcOpticalFlowPyrLK
。然而这种方法是一种稀疏运动估计方法,并不直接return一个密集的运动场。
为此:在框架的网格上初始化一组点(例如网格步长 = 10),使用这些点通过 calcOpticalFlowPyrLK
跟踪它们。跟踪点和初始点之间的差异为您提供了每个网格位置的光流。最后,您必须在网格点之间进行插值。例如。使用最近邻或线性插值。
首先,我想对下面的 回答表示感谢,我使用它来构建我的最终解决方案,我将尽可能详细地解释该解决方案。
我的解决方案分为两部分:
多线程 - 将每个帧拆分为 4 个矩阵,每个季度在不同的矩阵中。创建 4 个线程并 运行ning 每个季度在不同的线程中处理。我创建了 4 个四分之一矩阵,这样它们之间会有一些 (5%) 重叠,这样我就不会失去它们之间的连接(见下图 - 黄色部分是宽度的 55% 和高度的 55%)。
Q1 = cv::UMat(gray, Range(0, HEIGHT_RES*0.55), Range(0, WIDTH_RES*0.55));
Q2 = cv::UMat(gray, Range(0, HEIGHT_RES*0.55), Range(WIDTH_RES*0.45, WIDTH_RES));
Q3 = cv::UMat(gray, Range(0.45*HEIGHT_RES, HEIGHT_RES), Range(0, WIDTH_RES*0.55));
Q4 = cv::UMat(gray, Range(0.45*HEIGHT_RES, HEIGHT_RES), Range(WIDTH_RES*0.45, WIDTH_RES));
每个线程都在四分之一处进行光流处理(下面的第 2 部分),主循环正在等待所有线程完成以收集结果并取平均值。
使用稀疏方法 - 在选定的 ROI 网格中使用 calcOpticalFlowPyrLK
方法而不是使用 calcOpticalFlowFarneback
。使用 Lucas-Kanade 稀疏方法而不是 Farneback 密集方法消耗的 CPU 时间要少得多。在我的例子中,我创建了一个带有 gridstep=10
的网格。这是创建网格的简单函数:
void createGrid(vector<cv::Point2f> &grid, int16_t wRes, int16_t hRes, int step){
for (int i= 0; i < wRes ; i+=step)
for (int j= 0; j < hRes; j+=step)
grid.push_back(cv::Point2f(i,j));
}
注意如果grid在整个运行过程中是不变的,最好在进入主循环之前只创建一次。
执行这两个部分后,运行运行程序时,Odroid U3 的所有 4 个核心都在 60%-80% 上不断工作,性能得到加速。
我正在从事一个使用光流算法估计无人机位置的项目。为此,我目前正在使用 cv::calcOpticalFlowFarneback
。
我的硬件是一个Odroid U3,最终会连接到无人机飞行控制器。
问题是这个方法对于这个硬件来说真的很重,我正在寻找其他一些方法来优化/加速它。
我已经尝试过的事情:
- 将分辨率降低到 320x240 甚至 160x120。
- 使用 OpenCV TBB(使用
WITH_TBB=ON BUILD_TBB=ON
编译并添加-ltbb
)。 - 按照建议更改光流参数here
添加我的代码的相关部分:
int opticalFlow(){
// capture from camera
VideoCapture cap(0);
if( !cap.isOpened() )
return -1;
// Set Resolution - The Default Resolution Is 640 x 480
cap.set(CV_CAP_PROP_FRAME_WIDTH,WIDTH_RES);
cap.set(CV_CAP_PROP_FRAME_HEIGHT,HEIGHT_RES);
Mat flow, cflow, undistortFrame, processedFrame, origFrame, croppedFrame;
UMat gray, prevgray, uflow;
currLocation.x = 0;
currLocation.y = 0;
// for each frame calculate optical flow
for(;;)
{
// take out frame- still distorted
cap >> origFrame;
// Convert to gray
cvtColor(origFrame, processedFrame, COLOR_BGR2GRAY);
// rotate image - perspective transformation
rotateImage(processedFrame, gray, eulerFromSensors.roll, eulerFromSensors.pitch, 0, 0, 0, 1, cameraMatrix.at<double>(0,0),
cameraMatrix.at<double>(0,2),cameraMatrix.at<double>(1,2));
if( !prevgray.empty() )
{
// calculate flow
calcOpticalFlowFarneback(prevgray, gray, uflow, 0.5, 3, 10, 3, 3, 1.2, 0);
uflow.copyTo(flow);
// get average
calcAvgOpticalFlow(flow, 16, corners);
/*
Some other calculations
.
.
.
Updating currLocation struct
*/
}
//break conditions
if(waitKey(1)>=0)
break;
if(end_run)
break;
std::swap(prevgray, gray);
}
return 0;
}
备注:
- 我已经 运行
callgrind
并且瓶颈正如预期的那样是calcOpticalFlowFarneback
函数。 - 我在 运行 程序时检查了 CPU 核心负载,它并没有大量使用所有 4 个核心,在给定时间只有一个核心处于 100%(即使有 TBB) :
光流估计通常是一个安静的耗时操作。我建议更改光流法。
DualTVL1OpticalFlow
是您可以使用的 OpenCV 中性能更高的方法。如果此方法仍然很慢,则应使用 calcOpticalFlowPyrLK
。然而这种方法是一种稀疏运动估计方法,并不直接return一个密集的运动场。
为此:在框架的网格上初始化一组点(例如网格步长 = 10),使用这些点通过 calcOpticalFlowPyrLK
跟踪它们。跟踪点和初始点之间的差异为您提供了每个网格位置的光流。最后,您必须在网格点之间进行插值。例如。使用最近邻或线性插值。
首先,我想对下面的
我的解决方案分为两部分:
多线程 - 将每个帧拆分为 4 个矩阵,每个季度在不同的矩阵中。创建 4 个线程并 运行ning 每个季度在不同的线程中处理。我创建了 4 个四分之一矩阵,这样它们之间会有一些 (5%) 重叠,这样我就不会失去它们之间的连接(见下图 - 黄色部分是宽度的 55% 和高度的 55%)。
Q1 = cv::UMat(gray, Range(0, HEIGHT_RES*0.55), Range(0, WIDTH_RES*0.55)); Q2 = cv::UMat(gray, Range(0, HEIGHT_RES*0.55), Range(WIDTH_RES*0.45, WIDTH_RES)); Q3 = cv::UMat(gray, Range(0.45*HEIGHT_RES, HEIGHT_RES), Range(0, WIDTH_RES*0.55)); Q4 = cv::UMat(gray, Range(0.45*HEIGHT_RES, HEIGHT_RES), Range(WIDTH_RES*0.45, WIDTH_RES));
每个线程都在四分之一处进行光流处理(下面的第 2 部分),主循环正在等待所有线程完成以收集结果并取平均值。
使用稀疏方法 - 在选定的 ROI 网格中使用
calcOpticalFlowPyrLK
方法而不是使用calcOpticalFlowFarneback
。使用 Lucas-Kanade 稀疏方法而不是 Farneback 密集方法消耗的 CPU 时间要少得多。在我的例子中,我创建了一个带有gridstep=10
的网格。这是创建网格的简单函数:void createGrid(vector<cv::Point2f> &grid, int16_t wRes, int16_t hRes, int step){ for (int i= 0; i < wRes ; i+=step) for (int j= 0; j < hRes; j+=step) grid.push_back(cv::Point2f(i,j)); }
注意如果grid在整个运行过程中是不变的,最好在进入主循环之前只创建一次。
执行这两个部分后,运行运行程序时,Odroid U3 的所有 4 个核心都在 60%-80% 上不断工作,性能得到加速。