OpenCV не проектирует 2D точки в 3D с известной глубиной `Z`

Постановка задачи

Я пытаюсь повторно спроецировать 2D-точки на их исходные 3D-координаты, предполагая, что знаю расстояние, на котором находится каждая точка. Следуя документации OpenCV, мне удалось заставить его работать без искажений. Однако, когда есть искажения, результат не является правильным.

Текущий подход

Итак, идея состоит в том, чтобы изменить следующее:

https://docs.opencv.org/2.4/_images/math/331ebcd980b851f25de1979ebb67a2fed1c8477e.png

в следующее:

От:

  1. Избавляемся от любых искажений, используя cv::undistortPoints
  2. Используйте встроенные функции, чтобы вернуться к нормализованным координатам камеры, обращая второе уравнение выше
  3. Умножение на z отменить нормализацию.

Вопросы

  1. Зачем мне нужно вычитать f_x а также f_y вернуться к нормализованным координатам камеры (найденным эмпирически при тестировании)? В приведенном ниже коде, на шаге 2, если я не вычтю - даже неискаженный результат выключен. Это была моя ошибка - я испортил индексы.
  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 матрица.

Мораль истории Пиши юнит тесты!!!

Другие вопросы по тегам