Подтвердить что ты не робот

OpenCV 3.0: Калибровка не подходит, как ожидалось

Я получаю результаты, которых я не ожидаю, когда я использую OpenCV 3.0 calibrateCamera. Вот мой алгоритм:

  • Загрузка в 30 точках изображения
  • Загрузить в 30 соответствующих мировых точках (в этом случае копланар)
  • Используйте точки для калибровки камеры, просто для искажения
  • Не искажайте точки изображения, но не используйте внутренние свойства (копланарные точки мира, поэтому внутренности являются изворотливыми)
  • Используйте неискаженные точки, чтобы найти гомографию, трансформируясь в мировые точки (можно сделать это, потому что все они копланарны)
  • Используйте преобразование гомографии и перспективы, чтобы отобразить неискаженные точки в мировое пространство.
  • Сравните исходные точки мира с отображаемыми точками

Точки, которые у меня есть, являются шумными и только небольшим участком изображения. Есть 30 копланарных точек из одного представления, поэтому я не могу получить встроенные функции камеры, но должен иметь возможность получить коэффициенты искажения и гомографию для создания фронтально-параллельного представления.

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

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

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

Любые идеи?

Добавлен

Чтобы быть ясным, я знаю, что результаты будут плохими, и я ожидаю этого. Я также понимаю, что я могу узнать плохие параметры искажения, что приводит к худшим результатам при тестировании точек, которые не были использованы для обучения модели. Я не понимаю, как модель искажения имеет больше ошибок при использовании набора для обучения в качестве тестового набора. То есть, если cv:: calibrateCamera должен выбрать параметры для уменьшения ошибки по сравнению с набором обучающих наборов, но он вызывает больше ошибок, чем если бы он только что выбрал 0 для K!, K2,... K6, P1, P2. Плохие данные или нет, он должен по крайней мере лучше тренироваться. Прежде чем я могу сказать, что данные не подходят для этой модели, я должен быть уверен, что делаю все, что в моих силах, с доступными данными, и я не могу сказать, что на этом этапе.

Здесь пример изображения

Отмечены точки с зелеными контактами. Это, очевидно, просто тестовое изображение. Example image

Вот пример материала

В дальнейшем изображение обрезается с большого, указанного выше. Центр не изменился. Это то, что происходит, когда я не верю только с точками, отмеченными вручную из зеленых контактов и позволяющими K1 (только K1) варьироваться от 0:

Перед Set A image, 1920 by 1080, distorted

После Set A image, 1920 by 1080, undistorted

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

// Load image points
    std::vector<cv::Point2f> im_points;
    im_points.push_back(cv::Point2f(1206, 1454));
    im_points.push_back(cv::Point2f(1245, 1443));
    im_points.push_back(cv::Point2f(1284, 1429));
    im_points.push_back(cv::Point2f(1315, 1456));
    im_points.push_back(cv::Point2f(1352, 1443));
    im_points.push_back(cv::Point2f(1383, 1431));
    im_points.push_back(cv::Point2f(1431, 1458));
    im_points.push_back(cv::Point2f(1463, 1445));
    im_points.push_back(cv::Point2f(1489, 1432));
    im_points.push_back(cv::Point2f(1550, 1461));
    im_points.push_back(cv::Point2f(1574, 1447));
    im_points.push_back(cv::Point2f(1597, 1434));
    im_points.push_back(cv::Point2f(1673, 1463));
    im_points.push_back(cv::Point2f(1691, 1449));
    im_points.push_back(cv::Point2f(1708, 1436));
    im_points.push_back(cv::Point2f(1798, 1464));
    im_points.push_back(cv::Point2f(1809, 1451));
    im_points.push_back(cv::Point2f(1819, 1438));
    im_points.push_back(cv::Point2f(1925, 1467));
    im_points.push_back(cv::Point2f(1929, 1454));
    im_points.push_back(cv::Point2f(1935, 1440));
    im_points.push_back(cv::Point2f(2054, 1470));
    im_points.push_back(cv::Point2f(2052, 1456));
    im_points.push_back(cv::Point2f(2051, 1443));
    im_points.push_back(cv::Point2f(2182, 1474));
    im_points.push_back(cv::Point2f(2171, 1459));
    im_points.push_back(cv::Point2f(2164, 1446));
    im_points.push_back(cv::Point2f(2306, 1474));
    im_points.push_back(cv::Point2f(2292, 1462));
    im_points.push_back(cv::Point2f(2278, 1449));

    // Create corresponding world / object points
    std::vector<cv::Point3f> world_points;
    for (int i = 0; i < 30; i++) {
        world_points.push_back(cv::Point3f(5 * (i / 3), 4 * (i % 3), 0.0f));
    }

    // Perform calibration
    // Flags are set out so they can be commented out and "freed" easily
    int calibration_flags = 0
        | cv::CALIB_FIX_K1
        | cv::CALIB_FIX_K2
        | cv::CALIB_FIX_K3
        | cv::CALIB_FIX_K4
        | cv::CALIB_FIX_K5
        | cv::CALIB_FIX_K6
        | cv::CALIB_ZERO_TANGENT_DIST
        | 0;

    // Initialise matrix
    cv::Mat intrinsic_matrix = cv::Mat(3, 3, CV_64F);
    intrinsic_matrix.ptr<float>(0)[0] = 1;
    intrinsic_matrix.ptr<float>(1)[1] = 1;
    cv::Mat distortion_coeffs = cv::Mat::zeros(5, 1, CV_64F);

    // Rotation and translation vectors
    std::vector<cv::Mat> undistort_rvecs;
    std::vector<cv::Mat> undistort_tvecs;

    // Wrap in an outer vector for calibration
    std::vector<std::vector<cv::Point2f>>im_points_v(1, im_points);
    std::vector<std::vector<cv::Point3f>>w_points_v(1, world_points);

    // Calibrate; only 1 plane, so intrinsics can't be trusted
    cv::Size image_size(4000, 3000);
    calibrateCamera(w_points_v, im_points_v,
        image_size, intrinsic_matrix, distortion_coeffs, 
        undistort_rvecs, undistort_tvecs, calibration_flags);

    // Undistort im_points
    std::vector<cv::Point2f> ud_points;
    cv::undistortPoints(im_points, ud_points, intrinsic_matrix, distortion_coeffs);

    // ud_points have been "unintrinsiced", but we don't know the intrinsics, so reverse that   
    double fx = intrinsic_matrix.at<double>(0, 0);
    double fy = intrinsic_matrix.at<double>(1, 1);
    double cx = intrinsic_matrix.at<double>(0, 2);
    double cy = intrinsic_matrix.at<double>(1, 2);

    for (std::vector<cv::Point2f>::iterator iter = ud_points.begin(); iter != ud_points.end(); iter++) {
        iter->x = iter->x * fx + cx;
        iter->y = iter->y * fy + cy;
    }

    // Find a homography mapping the undistorted points to the known world points, ground plane
    cv::Mat homography = cv::findHomography(ud_points, world_points);

    // Transform the undistorted image points to the world points (2d only, but z is constant)
    std::vector<cv::Point2f> estimated_world_points;    
    std::cout << "homography" << homography << std::endl;
    cv::perspectiveTransform(ud_points, estimated_world_points, homography);

    // Work out error
    double sum_sq_error = 0;
    for (int i = 0; i < 30; i++) {
        double err_x = estimated_world_points.at(i).x - world_points.at(i).x;
        double err_y = estimated_world_points.at(i).y - world_points.at(i).y;

        sum_sq_error += err_x*err_x + err_y*err_y;
    }
    std::cout << "Sum squared error is: " << sum_sq_error << std::endl;
4b9b3361

Ответ 1

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

Кроме того, представляется логичным, что исправление большинства переменных дает вам наименьшие ошибки, поскольку степени свободы в процессе минимизации меньше. Я бы попытался исправить разные, чтобы установить еще один консенсус. По крайней мере, это позволит вам узнать, какие переменные наиболее чувствительны к уровням шума ввода.

Надеюсь, такая маленькая часть изображения будет близка к центру изображения, так как она повлечет за собой наименьшее количество искажений объектива. Используется ли в вашем случае другая модель искажений? Более жизнеспособным способом является адаптация количества параметров искажения с учетом положения шаблона относительно центра изображения.

Не зная ограничений алгоритма, я, возможно, неправильно понял вопрос, это тоже вариант, в таком случае я могу откатиться.

Я хотел бы иметь это как комментарий скорее, но у меня недостаточно очков.

Ответ 2

OpenCV запускает алгоритм Levenberg-Marquardt внутри откалиброванной камеры.

https://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm/

Этот альгортим отлично работает в проблемах с одним минимумом. В случае одиночного изображения точки, расположенные близко друг к другу, и многомерная проблема (n = количество коэффициентов), алгоритм может быть нестабильным (особенно с неправильной начальной оценкой матрицы камеры). Сходимость алгоритма хорошо описана здесь:

https://na.math.kit.edu/download/papers/levenberg.pdf/

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

Калибровка камеры также вычисляет позу камеры, которая будет плохой в моделях с неправильной калибровочной матрицей.

В качестве решения я предлагаю изменить подход. Вам не нужно вычислять матрицу камеры и позировать на этом шаге. Поскольку вы знаете, что точки расположены на плоскости, вы можете использовать уравнение проекции плоскости 3d-2d для определения типа распределения точек. По распределению я имею в виду, что все точки будут расположены одинаково на какой-то трапеции.

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

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

Это не самое простое решение, но я надеюсь, что оно вам поможет.