加速 OpticalFlow 算法 - OpenCV

Accelerating OpticalFlow Algorithm - OpenCV

我正在从事一个使用光流算法估计无人机位置的项目。为此,我目前正在使用 cv::calcOpticalFlowFarneback
我的硬件是一个Odroid U3,最终会连接到无人机飞行控制器。

问题是这个方法对于这个硬件来说真的很重,我正在寻找其他一些方法来优化/加速它。

我已经尝试过的事情:

添加我的代码的相关部分:

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;
}

备注:

光流估计通常是一个安静的耗时操作。我建议更改光流法。

DualTVL1OpticalFlow 是您可以使用的 OpenCV 中性能更高的方法。如果此方法仍然很慢,则应使用 calcOpticalFlowPyrLK。然而这种方法是一种稀疏运动估计方法,并不直接return一个密集的运动场。 为此:在框架的网格上初始化一组点(例如网格步长 = 10),使用这些点通过 calcOpticalFlowPyrLK 跟踪它们。跟踪点和初始点之间的差异为您提供了每个网格位置的光流。最后,您必须在网格点之间进行插值。例如。使用最近邻或线性插值。

首先,我想对下面的 回答表示感谢,我使用它来构建我的最终解决方案,我将尽可能详细地解释该解决方案。

我的解决方案分为两部分:

  1. 多线程 - 将每个帧拆分为 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 部分),主循环正在等待所有线程完成以收集结果并取平均值。

  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% 上不断工作,性能得到加速。