Я должен построить панорамное изображение земли, покрытой камерой, обращенной вниз (на фиксированной высоте, около 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( );
}
Это работает довольно хорошо, за исключением того, что когда камера перемещается «вверх» в точке обзора, масштаб гомографии уменьшается. По мере того как он движется вниз, масштаб снова увеличивается. Это дает моим панорам эффект типа перспективы, который я действительно не хочу.
Например, это делается на несколько секунд видео, движущихся вперед, а затем назад. Первый кадр выглядит нормально:
Проблема возникает, когда мы продвигаемся на несколько кадров вперед:
Затем, когда мы вернемся снова, вы увидите, что рамка снова становится больше:
Я в растерянности относительно того, откуда это исходит.
Я использую плотный оптический поток 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 framescv::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;
}
Оказывается, это произошло потому, что моя точка зрения была близка к особенностям, а это означает, что неплоскостность отслеживаемых элементов приводила к перекосу в гомографии. Мне удалось предотвратить это (это скорее взлом, чем метод …) с помощью estimateRigidTransform
вместо findHomography
, поскольку это не оценивает для перспективных изменений.
В этом конкретном случае имеет смысл сделать это, поскольку представление только когда-либо претерпевает жесткие преобразования.