OpenCV не проектирует 2D точки в 3D с известной глубиной `Z`
Постановка задачи
Я пытаюсь повторно спроецировать 2D-точки на их исходные 3D-координаты, предполагая, что знаю расстояние, на котором находится каждая точка. Следуя документации OpenCV, мне удалось заставить его работать без искажений. Однако, когда есть искажения, результат не является правильным.
Текущий подход
Итак, идея состоит в том, чтобы изменить следующее:
https://docs.opencv.org/2.4/_images/math/331ebcd980b851f25de1979ebb67a2fed1c8477e.png
в следующее:
От:
- Избавляемся от любых искажений, используя
cv::undistortPoints
- Используйте встроенные функции, чтобы вернуться к нормализованным координатам камеры, обращая второе уравнение выше
- Умножение на
z
отменить нормализацию.
Вопросы
-
Зачем мне нужно вычитатьf_x
а такжеf_y
вернуться к нормализованным координатам камеры (найденным эмпирически при тестировании)?В приведенном ниже коде, на шаге 2, если я не вычтю - даже неискаженный результат выключен.Это была моя ошибка - я испортил индексы. - Если я включу искажение, результат будет неправильным - что я делаю не так?
Пример кода (C++)
#include <iostream>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <vector>
std::vector<cv::Point2d> Project(const std::vector<cv::Point3d>& points,
const cv::Mat& intrinsic,
const cv::Mat& distortion) {
std::vector<cv::Point2d> result;
if (!points.empty()) {
cv::projectPoints(points, cv::Mat(3, 1, CV_64F, cvScalar(0.)),
cv::Mat(3, 1, CV_64F, cvScalar(0.)), intrinsic,
distortion, result);
}
return result;
}
std::vector<cv::Point3d> Unproject(const std::vector<cv::Point2d>& points,
const std::vector<double>& Z,
const cv::Mat& intrinsic,
const cv::Mat& distortion) {
double f_x = intrinsic.at<double>(0, 0);
double f_y = intrinsic.at<double>(1, 1);
double c_x = intrinsic.at<double>(0, 2);
double c_y = intrinsic.at<double>(1, 2);
// This was an error before:
// double c_x = intrinsic.at<double>(0, 3);
// double c_y = intrinsic.at<double>(1, 3);
// Step 1. Undistort
std::vector<cv::Point2d> points_undistorted;
assert(Z.size() == 1 || Z.size() == points.size());
if (!points.empty()) {
cv::undistortPoints(points, points_undistorted, intrinsic,
distortion, cv::noArray(), intrinsic);
}
// Step 2. Reproject
std::vector<cv::Point3d> result;
result.reserve(points.size());
for (size_t idx = 0; idx < points_undistorted.size(); ++idx) {
const double z = Z.size() == 1 ? Z[0] : Z[idx];
result.push_back(
cv::Point3d((points_undistorted[idx].x - c_x) / f_x * z,
(points_undistorted[idx].y - c_y) / f_y * z, z));
}
return result;
}
int main() {
const double f_x = 1000.0;
const double f_y = 1000.0;
const double c_x = 1000.0;
const double c_y = 1000.0;
const cv::Mat intrinsic =
(cv::Mat_<double>(3, 3) << f_x, 0.0, c_x, 0.0, f_y, c_y, 0.0, 0.0, 1.0);
const cv::Mat distortion =
// (cv::Mat_<double>(5, 1) << 0.0, 0.0, 0.0, 0.0); // This works!
(cv::Mat_<double>(5, 1) << -0.32, 1.24, 0.0013, 0.0013); // This doesn't!
// Single point test.
const cv::Point3d point_single(-10.0, 2.0, 12.0);
const cv::Point2d point_single_projected = Project({point_single}, intrinsic,
distortion)[0];
const cv::Point3d point_single_unprojected = Unproject({point_single_projected},
{point_single.z}, intrinsic, distortion)[0];
std::cout << "Expected Point: " << point_single.x;
std::cout << " " << point_single.y;
std::cout << " " << point_single.z << std::endl;
std::cout << "Computed Point: " << point_single_unprojected.x;
std::cout << " " << point_single_unprojected.y;
std::cout << " " << point_single_unprojected.z << std::endl;
}
Тот же код (Python)
import cv2
import numpy as np
def Project(points, intrinsic, distortion):
result = []
rvec = tvec = np.array([0.0, 0.0, 0.0])
if len(points) > 0:
result, _ = cv2.projectPoints(points, rvec, tvec,
intrinsic, distortion)
return np.squeeze(result, axis=1)
def Unproject(points, Z, intrinsic, distortion):
f_x = intrinsic[0, 0]
f_y = intrinsic[1, 1]
c_x = intrinsic[0, 2]
c_y = intrinsic[1, 2]
# This was an error before
# c_x = intrinsic[0, 3]
# c_y = intrinsic[1, 3]
# Step 1. Undistort.
points_undistorted = np.array([])
if len(points) > 0:
points_undistorted = cv2.undistortPoints(np.expand_dims(points, axis=1), intrinsic, distortion, P=intrinsic)
points_undistorted = np.squeeze(points_undistorted, axis=1)
# Step 2. Reproject.
result = []
for idx in range(points_undistorted.shape[0]):
z = Z[0] if len(Z) == 1 else Z[idx]
x = (points_undistorted[idx, 0] - c_x) / f_x * z
y = (points_undistorted[idx, 1] - c_y) / f_y * z
result.append([x, y, z])
return result
f_x = 1000.
f_y = 1000.
c_x = 1000.
c_y = 1000.
intrinsic = np.array([
[f_x, 0.0, c_x],
[0.0, f_y, c_y],
[0.0, 0.0, 1.0]
])
distortion = np.array([0.0, 0.0, 0.0, 0.0]) # This works!
distortion = np.array([-0.32, 1.24, 0.0013, 0.0013]) # This doesn't!
point_single = np.array([[-10.0, 2.0, 12.0],])
point_single_projected = Project(point_single, intrinsic, distortion)
Z = np.array([point[2] for point in point_single])
point_single_unprojected = Unproject(point_single_projected,
Z,
intrinsic, distortion)
print "Expected point:", point_single[0]
print "Computed point:", point_single_unprojected[0]
Результаты для нулевого искажения (как упомянуто) правильны:
Expected Point: -10 2 12
Computed Point: -10 2 12
Но когда искажения включены, результат выключен:
Expected Point: -10 2 12
Computed Point: -4.26634 0.848872 12
Обновление 1. Разъяснение
Это проекция с камеры на изображение - я предполагаю, что 3D-точки находятся в координатах кадра камеры.
Обновление 2. Разобрался с первым вопросом
ОК, я вычисляю вычитание f_x
а также f_y
- Я был достаточно глуп, чтобы испортить индексы. Обновлен код для исправления. Другой вопрос остается в силе.
Обновление 3. Добавлен эквивалентный Python-код
Чтобы увеличить видимость, добавьте коды Python, потому что он имеет ту же ошибку.
1 ответ
Ответ на вопрос 2
Я обнаружил, в чем проблема - 3D координаты точки имеют значение! Я предполагал, что независимо от того, какие 3D координатные точки я выберу, реконструкция позаботится об этом. Однако я заметил кое-что странное: при использовании диапазона трехмерных точек только подмножество этих точек было восстановлено правильно. После дальнейшего расследования я обнаружил, что только изображения, которые находятся в поле зрения камеры, будут правильно восстановлены. Поле зрения является функцией внутренних параметров (и наоборот).
Чтобы вышеприведенные коды работали, попробуйте установить параметры следующим образом (встроенные в мою камеру):
...
const double f_x = 2746.;
const double f_y = 2748.;
const double c_x = 991.;
const double c_y = 619.;
...
const cv::Point3d point_single(10.0, -2.0, 30.0);
...
Также не забывайте, что в камере отрицательные координаты y
координаты UP
:)
Ответ на вопрос 1:
Была ошибка, когда я пытался получить доступ к встроенным функциям, используя
...
double f_x = intrinsic.at<double>(0, 0);
double f_y = intrinsic.at<double>(1, 1);
double c_x = intrinsic.at<double>(0, 3);
double c_y = intrinsic.at<double>(1, 3);
...
Но intrinsic
был 3x3
матрица.
Мораль истории Пиши юнит тесты!!!