Неверное масштабирование кумулятивной гомографии

Я должен построить панорамное изображение земли, покрытой направленной вниз камерой (на фиксированной высоте, около 1 метра над землей). Потенциально это может работать до тысяч кадров, поэтому встроенный в класс Stitcher метод panorama не очень подходит - он слишком медленный и потребляет много памяти.

Вместо этого я предполагаю, что пол и движение являются плоскими (здесь это не лишено смысла) и пытаюсь построить кумулятивную гомографию по мере того, как я вижу каждый кадр. То есть для каждого кадра я вычисляю гомографию от предыдущего к новому. Затем я получаю кумулятивную омографию, умножая ее на произведение всех предыдущих омографий.

Допустим, я получаю H01 между кадрами 0 и 1, затем H12 между кадрами 1 и 2. Чтобы получить преобразование для размещения кадра 2 на мозаике, мне нужно получить H01*H12. Это продолжается по мере увеличения количества кадров, так что я получаю H01*H12*H23*H34*H45*....

В коде это что-то вроде:

cv::Mat previous, current;

// Init cumulative homography
cv::Mat cumulative_homography = cv::Mat::eye(3);

video_stream >> previous;
for(;;) {

        video_stream >> current;
        // Here I do some checking of the frame, etc

        // Get the homography using my DenseMosaic class (using Farneback to get OF)
        cv::Mat tmp_H = DenseMosaic::get_homography(previous,current);

        // Now normalise the homography by its bottom right corner
        tmp_H /= tmp_H.at<double>(2, 2);

        cumulative_homography *= tmp_H;

        previous = current.clone( );
}

Это работает довольно хорошо, за исключением того, что когда камера перемещается «вверх» в точке обзора, масштаб гомографии уменьшается. При движении вниз шкала снова увеличивается. Это дает моим панорамам эффект типа перспективы, который мне действительно не нужен.

Например, это делается на несколько секунд видео, которое движется вперед, а затем назад. Первый кадр выглядит нормально: Кадр 2 имплантирован в кадр 1

Проблема возникает, когда мы продвигаемся вперед на несколько кадров: Гомография уменьшается, в результате чего кадр становится меньше на панораме

Затем, когда мы вернемся снова, вы увидите, что рамка снова становится больше: введите здесь описание изображения

Я в недоумении, откуда это.

Я использую плотный оптический поток Farneback для расчета соответствий пикселей, как показано ниже (сопоставление разреженных признаков плохо работает с этими данными), и я проверил свои векторы потока — они в целом очень хорошие, так что это не отслеживание. проблема. Я также попытался изменить порядок входных данных, чтобы найти гомографию (на случай, если я перепутал номера кадров), но все равно не лучше.

cv::calcOpticalFlowFarneback(grey_1, grey_2, flow_mat, 0.5, 6,50, 5, 7, 1.5, flags);

// Using the flow_mat optical flow map, populate grid point correspondences between images
std::vector<cv::Point2f> points_1, points_2;
median_motion = DenseMosaic::dense_flow_to_corresp(flow_mat, points_1, points_2);
cv::Mat H = cv::findHomography(cv::Mat(points_2), cv::Mat(points_1), CV_RANSAC, 1);

Еще я подумал, что это может быть перевод, который я включаю в трансформацию, чтобы убедиться, что моя панорама находится в центре сцены:

cv::warpPerspective(init.clone(), warped, translation*homography, init.size());

Но после проверки значений в гомографии до применения перевода проблема масштабирования, о которой я упоминал, все еще присутствует.

Любые подсказки принимаются с благодарностью. Я мог бы вставить много кода, но он кажется неуместным, пожалуйста, дайте мне знать, если чего-то не хватает

ОБНОВЛЕНИЕ Я попытался отключить оператор *= для полного умножения и попытался изменить порядок умножения гомографий на обратный, но безуспешно. Ниже приведен мой код для вычисления гомографии:

/**
\brief Calculates the homography between the current and previous frames


*/
cv::Mat DenseMosaic::get_homography()
{

    cv::Mat grey_1, grey_2; // Grayscale versions of frames


    cv::cvtColor(prev, grey_1, CV_BGR2GRAY);
    cv::cvtColor(cur, grey_2, CV_BGR2GRAY);

    // Calculate the dense flow
    int flags = cv::OPTFLOW_FARNEBACK_GAUSSIAN;
    if (frame_number > 2) {
        flags = flags | cv::OPTFLOW_USE_INITIAL_FLOW;
    }
    cv::calcOpticalFlowFarneback(grey_1, grey_2, flow_mat, 0.5, 6,50, 5, 7, 1.5, flags);

    // Convert the flow map to point correspondences
    std::vector<cv::Point2f> points_1, points_2;
    median_motion = DenseMosaic::dense_flow_to_corresp(flow_mat, points_1, points_2);

    // Use the correspondences to get the homography
    cv::Mat H = cv::findHomography(cv::Mat(points_2), cv::Mat(points_1), CV_RANSAC, 1);

    return H;
}

И это функция, которую я использую для поиска соответствий на карте потока:

/**
\brief Calculate pixel->pixel correspondences given a map of the optical flow across the image
\param[in]  flow_mat Map of the optical flow across the image
\param[out] points_1 The set of points from #cur
\param[out] points_2 The set of points from #prev
\param[in]  step_size The size of spaces between the grid lines
\return The median motion as a point

Uses a dense flow map (such as that created by cv::calcOpticalFlowFarneback) to obtain a set of point correspondences across a grid.
*/
cv::Point2f DenseMosaic::dense_flow_to_corresp(const cv::Mat &flow_mat, std::vector<cv::Point2f> &points_1, std::vector<cv::Point2f> &points_2, int step_size)
{

    std::vector<double> tx, ty;
    for (int y = 0; y < flow_mat.rows; y += step_size) {
        for (int x = 0; x < flow_mat.cols; x += step_size) {
            /* Flow is basically the delta between left and right points */
            cv::Point2f flow = flow_mat.at<cv::Point2f>(y, x);
            tx.push_back(flow.x);
            ty.push_back(flow.y);


            /*  There's no need to calculate for every single point,
            if there's not much change, just ignore it
            */
            if (fabs(flow.x) < 0.1 && fabs(flow.y) < 0.1)
                continue;

            points_1.push_back(cv::Point2f(x, y));
            points_2.push_back(cv::Point2f(x + flow.x, y + flow.y));
        }
    }

    // I know this should be median, not mean, but it's only used for plotting the 
    // general motion direction so it's unimportant.
    cv::Point2f t_median;
    cv::Scalar mtx = cv::mean(tx);
    t_median.x = mtx[0];
    cv::Scalar mty = cv::mean(ty);
    t_median.y = mty[0];

    return t_median;
}

person n00dle    schedule 16.07.2014    source источник
comment
Что-то, что вы обязательно должны проверить, это исходная и целевая системы координат всех ваших преобразований. Например, H01 искажает кадр №1 в кадр №0 или наоборот? В зависимости от этого определите, должна ли кумулятивная гомография быть H01*H12*H23*... или ...*H23*H12*H01. Кроме того, вы можете избегать оператора *=, который более неоднозначен, чем явное присваивание и умножение (например, H = H*tmp_H). Если вышеуказанное не решает вашу проблему, было бы полезно увидеть код вашей функции get_homography.   -  person BConic    schedule 16.07.2014
comment
Спасибо @AldurDisciple, я пытался поиграть с направлением накопления, но безрезультатно - наоборот, реконструкция неверна, и сжатие все еще происходит. Я собираюсь обновить свой ответ кодом для расчета гомографии.   -  person n00dle    schedule 17.07.2014
comment
Если вычисление гомографии хорошее, это может быть связано с проблемой RANSAC. Сколько соответствующих точек вы используете? Каковы размеры изображения и значение step_size?   -  person BConic    schedule 18.07.2014
comment
Кроме того, вы пытались запустить свою программу с другим входным видео, чтобы проверить, наблюдаете ли вы такое же поведение или другое?   -  person BConic    schedule 18.07.2014
comment
Последний совет: знаете ли вы, что существует функция calcOpticalFlowPyrLK для разреженного оптического потока, которая должна быть более точной и эффективной, чем calcOpticalFlowFarneback?   -  person BConic    schedule 19.07.2014
comment
Привет @AldurDisciple, спасибо за помощь. Размер шага установлен на 5 пикселей на изображении .3MP. Я проверю, сколько осталось после RANSAC. У меня такая же проблема на других видео. Да, у меня есть альтернативная версия программного обеспечения, работающая с разреженным сопоставлением признаков, используя сопоставление дескрипторов SURF в ключевых кадрах, а затем отслеживая признаки KLT между ними, чтобы сбалансировать скорость и точность. Как ни странно, точно такая же проблема.   -  person n00dle    schedule 21.07.2014


Ответы (1)


Оказывается, это произошло потому, что моя точка зрения была близка к объектам, а это означало, что неплоскостность отслеживаемых объектов вызывала перекос в гомографии. Мне удалось предотвратить это (это скорее хак, чем метод...), используя estimateRigidTransform вместо findHomography, так как это не оценивает вариации перспективы.

В данном конкретном случае это имеет смысл, так как представление подвергается только жестким преобразованиям.

person n00dle    schedule 30.07.2014