Я получаю результаты, которых я не ожидаю, когда я использую OpenCV 3.0 calibrateCamera. Вот мой алгоритм:
- Загрузка в 30 точках изображения
- Загрузить в 30 соответствующих мировых точках (в этом случае копланар)
- Используйте точки для калибровки камеры, просто для искажения
- Не искажайте точки изображения, но не используйте внутренние свойства (копланарные точки мира, поэтому внутренности являются изворотливыми)
- Используйте неискаженные точки, чтобы найти гомографию, трансформируясь в мировые точки (можно сделать это, потому что все они копланарны)
- Используйте преобразование гомографии и перспективы, чтобы отобразить неискаженные точки в мировое пространство.
- Сравните исходные точки мира с отображаемыми точками
Точки, которые у меня есть, являются шумными и только небольшим участком изображения. Есть 30 копланарных точек из одного представления, поэтому я не могу получить встроенные функции камеры, но должен иметь возможность получить коэффициенты искажения и гомографию для создания фронтально-параллельного представления.
Как и ожидалось, ошибка изменяется в зависимости от флажков калибровки. Однако он отличается от того, что я ожидал. Если я разрешу всем переменным настраиваться, я ожидаю, что ошибка снизится. Я не говорю, что ожидаю лучшей модели; Я действительно ожидаю чрезмерной, но это все равно должно уменьшить ошибку. Однако я вижу, что чем меньше переменных я использую, тем ниже моя ошибка. Лучший результат - с прямой гомографией.
У меня две подозреваемые причины, но они кажутся маловероятными, и я хотел бы услышать неподдельный ответ, прежде чем я их выдушу. Я вытащил код, чтобы просто делать то, о чем я говорю. Это немного длиннее, но включает загрузку точек.
В коде нет ошибок; Я использовал "лучшие" очки, и он отлично работает. Я хочу подчеркнуть, что решение здесь не может состоять в том, чтобы использовать лучшие точки или выполнять лучшую калибровку; вся суть упражнения заключается в том, чтобы увидеть, как различные модели калибровки реагируют на различные качества калибровочных данных.
Любые идеи?
Добавлен
Чтобы быть ясным, я знаю, что результаты будут плохими, и я ожидаю этого. Я также понимаю, что я могу узнать плохие параметры искажения, что приводит к худшим результатам при тестировании точек, которые не были использованы для обучения модели. Я не понимаю, как модель искажения имеет больше ошибок при использовании набора для обучения в качестве тестового набора. То есть, если cv:: calibrateCamera должен выбрать параметры для уменьшения ошибки по сравнению с набором обучающих наборов, но он вызывает больше ошибок, чем если бы он только что выбрал 0 для K!, K2,... K6, P1, P2. Плохие данные или нет, он должен по крайней мере лучше тренироваться. Прежде чем я могу сказать, что данные не подходят для этой модели, я должен быть уверен, что делаю все, что в моих силах, с доступными данными, и я не могу сказать, что на этом этапе.
Здесь пример изображения
Отмечены точки с зелеными контактами. Это, очевидно, просто тестовое изображение.
Вот пример материала
В дальнейшем изображение обрезается с большого, указанного выше. Центр не изменился. Это то, что происходит, когда я не верю только с точками, отмеченными вручную из зеленых контактов и позволяющими K1 (только K1) варьироваться от 0:
Перед
После
Я бы поставил его на ошибку, но когда я использую больший набор точек, которые покрывают больше экрана, даже из одной плоскости, он работает достаточно хорошо. Это выглядит ужасно. Однако ошибка не так плоха, как вы могли бы подумать, глядя на изображение.
// 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;