Opencv: исказить обратно

У меня есть cameraMatrix и distCoeff, необходимые для неискажения изображения или вектора точек. Теперь я хотел бы исказить их обратно.

Возможно ли это с Opencv? Я помню, что читал что-то об этом в stackoverflow, но сейчас не могу найти.

РЕДАКТИРОВАТЬ: я нашел способ сделать это в этом ответе. Он также находится в зоне разработчиков opencv (в этом выпуске)

Но мои результаты не совсем корректны. Есть некоторая погрешность в 2-4 пикселя больше или меньше. Вероятно, в моем коде что-то не так, потому что в ответе, который я связал, в модульном тесте все выглядит хорошо. Может быть, приведение типа от float к double или что-то еще, чего я не вижу.

вот мой тестовый пример:

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>

using namespace cv;
using namespace std;

void distortPoints(const std::vector<cv::Point2d> & src, std::vector<cv::Point2d> & dst,
                         const cv::Mat & cameraMatrix, const cv::Mat & distorsionMatrix)
{

  dst.clear();
  double fx = cameraMatrix.at<double>(0,0);
  double fy = cameraMatrix.at<double>(1,1);
  double ux = cameraMatrix.at<double>(0,2);
  double uy = cameraMatrix.at<double>(1,2);

  double k1 = distorsionMatrix.at<double>(0, 0);
  double k2 = distorsionMatrix.at<double>(0, 1);
  double p1 = distorsionMatrix.at<double>(0, 2);
  double p2 = distorsionMatrix.at<double>(0, 3);
  double k3 = distorsionMatrix.at<double>(0, 4);

  for (unsigned int i = 0; i < src.size(); i++)
  {
    const cv::Point2d & p = src[i];
    double x = p.x;
    double y = p.y;
    double xCorrected, yCorrected;
    //Step 1 : correct distorsion
    {
      double r2 = x*x + y*y;
      //radial distorsion
      xCorrected = x * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2);
      yCorrected = y * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2);

      //tangential distorsion
      //The "Learning OpenCV" book is wrong here !!!
      //False equations from the "Learning OpenCv" book below :
      //xCorrected = xCorrected + (2. * p1 * y + p2 * (r2 + 2. * x * x));
      //yCorrected = yCorrected + (p1 * (r2 + 2. * y * y) + 2. * p2 * x);
      //Correct formulae found at : http://www.vision.caltech.edu/bouguetj/calib_doc/htmls/parameters.html
      xCorrected = xCorrected + (2. * p1 * x * y + p2 * (r2 + 2. * x * x));
      yCorrected = yCorrected + (p1 * (r2 + 2. * y * y) + 2. * p2 * x * y);
    }
    //Step 2 : ideal coordinates => actual coordinates
    {
      xCorrected = xCorrected * fx + ux;
      yCorrected = yCorrected * fy + uy;
    }
    dst.push_back(cv::Point2d(xCorrected, yCorrected));
  }

}

int main(int /*argc*/, char** /*argv*/) {

    cout << "OpenCV version: " << CV_MAJOR_VERSION << " " << CV_MINOR_VERSION << endl; // 2 4

    Mat cameraMatrix = (Mat_<double>(3,3) << 1600, 0, 789, 0, 1600, 650, 0, 0, 1);
    Mat distorsion   = (Mat_<double>(5,1) << -0.48, 0, 0, 0, 0);

    cout << "camera matrix: " << cameraMatrix << endl;
    cout << "distorsion coefficent: " << distorsion << endl;

    // the starting points
    std::vector<Point2f> original_pts;
    original_pts.push_back( Point2f(23, 358) );
    original_pts.push_back( Point2f(8,  357) );
    original_pts.push_back( Point2f(12, 342) );
    original_pts.push_back( Point2f(27, 343) );
    original_pts.push_back( Point2f(7,  350) );
    original_pts.push_back( Point2f(-8, 349) );
    original_pts.push_back( Point2f(-4, 333) );
    original_pts.push_back( Point2f(12, 334) );
    Mat original_m = Mat(original_pts);

    // undistort
    Mat undistorted_m;
    undistortPoints(original_m, undistorted_m, 
                    cameraMatrix, distorsion);

    cout << "undistort points" << undistorted_m << endl;

    // back to array
    vector< cv::Point2d > undistorted_points;
    for(int i=0; i<original_pts.size(); ++i) {
        Point2d p;
        p.x = undistorted_m.at<float>(i, 0);
        p.y = undistorted_m.at<float>(i, 1);
        undistorted_points.push_back( p );

        // NOTE THAT HERE THERE IS AN APPROXIMATION
        // WHAT IS IT? STD::COUT? CASTING TO FLOAT?
        cout << undistorted_points[i] << endl;
    }

    vector< cv::Point2d > redistorted_points;
    distortPoints(undistorted_points, redistorted_points, cameraMatrix, distorsion);

    cout << redistorted_points << endl;

    for(int i=0; i<original_pts.size(); ++i) {
        cout << original_pts[i] << endl;
        cout << redistorted_points[i] << endl;

        Point2d o;
        o.x = original_pts[i].x;
        o.y = original_pts[i].y;
        Point2d dist = redistorted_points[i] - o;

        double norm = sqrt(dist.dot(dist));
        std::cout << "distance = " << norm << std::endl;

        cout << endl;
    }

    return 0;
}

И вот мой вывод:

    OpenCV version: 2 4
camera matrix: [1600, 0, 789;
  0, 1600, 650;
  0, 0, 1]
distorsion coefficent: [-0.48; 0; 0; 0; 0]
undistort points[-0.59175861, -0.22557901; -0.61276215, -0.22988389; -0.61078846, -0.24211435; -0.58972651, -0.23759322; -0.61597037, -0.23630577; -0.63910204, -0.24136727; -0.63765121, -0.25489968; -0.61291695, -0.24926868]
[-0.591759, -0.225579]
[-0.612762, -0.229884]
[-0.610788, -0.242114]
[-0.589727, -0.237593]
[-0.61597, -0.236306]
[-0.639102, -0.241367]
[-0.637651, -0.2549]
[-0.612917, -0.249269]
[24.45809095301274, 358.5558144841519; 10.15042938413364, 357.806737955385; 14.23419751024494, 342.8856229036298; 28.51642501095819, 343.610956960508; 9.353743900129871, 350.9029663678638; -4.488033489615646, 350.326357275197; -0.3050714463695385, 334.477016554487; 14.41516474594289, 334.9822130217053]
[23, 358]
[24.4581, 358.556]
distance = 1.56044

[8, 357]
[10.1504, 357.807]
distance = 2.29677

[12, 342]
[14.2342, 342.886]
distance = 2.40332

[27, 343]
[28.5164, 343.611]
distance = 1.63487

[7, 350]
[9.35374, 350.903]
distance = 2.521

[-8, 349]
[-4.48803, 350.326]
distance = 3.75408

[-4, 333]
[-0.305071, 334.477]
distance = 3.97921

[12, 334]
[14.4152, 334.982]
distance = 2.60725

person nkint    schedule 06.02.2014    source источник


Ответы (9)


initUndistortRectifyMap, связанный с одним из ответов на вопрос, который вы упомянули, действительно делает то, что вы хотите. Поскольку он используется в Remap для построения полного неискаженного изображения, он дает для каждого места в целевом изображении (неискаженном) местонахождение соответствующего пикселя в искаженном изображении, чтобы они могли использовать его цвет. Так что это действительно карта f(undistorted) = distorted.

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

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

public PointF Distort(PointF point)
{
    // To relative coordinates <- this is the step you are missing.
    double x = (point.X - cx) / fx;
    double y = (point.Y - cy) / fy;

    double r2 = x*x + y*y;

    // Radial distorsion
    double xDistort = x * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2);
    double yDistort = y * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2);

    // Tangential distorsion
    xDistort = xDistort + (2 * p1 * x * y + p2 * (r2 + 2 * x * x));
    yDistort = yDistort + (p1 * (r2 + 2 * y * y) + 2 * p2 * x * y);

    // Back to absolute coordinates.
    xDistort = xDistort * fx + cx;
    yDistort = yDistort * fy + cy;

    return new PointF((float)xDistort, (float)yDistort);
}
person Joan Charmant    schedule 15.06.2014
comment
Я думаю, что это решение не исказило бы точки и не исказило бы их обратно, как просил ОП. У меня такая же ситуация, как у ОП, и умножение коэффициентов на -1 дает ожидаемый результат. - person François Pilote; 09.02.2017
comment
@FrançoisPilote: ваши коэффициенты исходят от OpenCV? Существуют и другие модели искажения, которые используют обратное отображение и производят коэффициенты, действующие в обратном направлении. Я успешно использовал вышеизложенное в своих собственных программах. - person Joan Charmant; 10.02.2017
comment
да, коэффициенты, которые я использую, взяты из opencv, а точнее из функции калибровки камеры. - person François Pilote; 17.02.2017
comment
будет ли это похоже на облака точек? потому что у меня есть неискаженное облако точек, коэффициенты искажения, и я хотел бы их применить - person mereth; 11.10.2019
comment
Спасибо. Он работает с исправленным изображением, созданным с использованием cv::undistort(originalDistortedImg, rectifiedImg, K, D, K); - person lingjiankong; 28.01.2020
comment
Кто-нибудь знает, как заставить это работать, если вы указываете newCameraMatrix, который не является оригиналом? Я пытаюсь делать то, что делает OP, только я использую getOptimalNewCameraMatrix, чтобы неискаженное изображение не обрезалось. Уравнения не определяют, как используется A ', поэтому я не могу понять, как это изменить. - person MrZander; 19.09.2020

Вы можете легко исказить свои точки, используя ProjectPoints.

cv::Mat rVec(3, 1, cv::DataType<double>::type); // Rotation vector
rVec.at<double>(0) = 0;
rVec.at<double>(1) = 0;
rVec.at<double>(2) =0;
cv::Mat tVec(3, 1, cv::DataType<double>::type); // Translation vector
tVec.at<double>(0) =0;
tVec.at<double>(1) = 0;
tVec.at<double>(2) = 0;

cv::projectPoints(points,rVec,tVec, cameraMatrix, distCoeffs,result);

PS: в opencv 3 добавили функцию для искажения.

person mefmef    schedule 09.06.2015
comment
Эта функция искажения предназначена для модели камеры «рыбий глаз», а не для модели камеры-обскуры, которую использует cv::undistortPoints. - person user202729; 06.07.2019

Если вы умножите все коэффициенты искажения на -1, вы можете затем передать их в undistort или undistortPoints, и в основном вы примените обратное искажение, которое вернет искажение.

person Nikos    schedule 05.01.2017
comment
Это только приближение первого порядка, оно хорошо работает только для небольших искажений. - person Hugo Maxwell; 04.04.2018

Модель камеры OCV (см. http://docs.opencv.org/2.4/modules/calib3d/doc/camera_dication_and_3d_reconstruction.html) описывает, как 3D-точка сначала сопоставляется с воображаемой идеальной координатой камеры-обскуры, а затем «искажает» координату, чтобы она моделировала изображение реальной камеры реального мира.

Используя коэффициенты искажения OpenCV (= коэффициенты искажения Брауна), можно легко рассчитать следующие 2 операции:

  • Вычислить координату пикселя в исходном изображении с камеры по заданной координате пикселя в изображении без искажений (т.е. неискаженном). AFAIK для этого нет явной функции OpenCV. Но код в ответе Джоан Чарман делает именно это.
  • Рассчитайте изображение без искажений на основе исходного изображения с камеры. Это можно сделать с помощью cv::undistort(....) или комбинации cv::initUndistortRectifyMap(....) и cv::remap(....).

Однако следующие 2 операции вычислительно намного сложнее:

  • Вычислите координату пикселя в изображении без искажений по координате пикселя в исходном изображении с камеры. Это можно сделать с помощью cv::undistortPoints(....).
  • Рассчитайте исходное изображение камеры из изображения без искажений.

Это может показаться нелогичным. Более подробное объяснение:

Для заданной координаты пикселя в изображении без искажений легко вычислить соответствующую координату в исходном изображении (т.е. «исказить» координату).

x = (u - cx) / fx; // u and v are distortion free
y = (v - cy) / fy;

rr = x*x + y*y
distortion = 1 + rr  * (k1 + rr * (k2 + rr * k3))
# I ommit the tangential parameters for clarity

u_ = fx * distortion * x + cx
v_ = fy * distortion * y + cy
// u_ and v_ are coordinates in the original camera image

Сделать это наоборот гораздо сложнее; в основном нужно было бы объединить все приведенные выше строки кода в одно большое векторное уравнение и решить его для u и v. Я думаю, что для общего случая, когда используются все 5 коэффициентов искажения, это можно сделать только численно. Что (не глядя на код), вероятно, и делает cv::undistortPoints(....).

Однако, используя коэффициенты искажения, мы можем рассчитать карту неискажения (cv::initUndistortRectifyMap(....)), которая отображает координаты изображения без искажений в исходные координаты изображения камеры. Каждая запись в карте неискажения содержит позицию пикселя (с плавающей запятой) в исходном изображении с камеры. Другими словами, карта неискажений указывает от изображения без искажений к исходному изображению с камеры. Таким образом, карта рассчитывается именно по приведенной выше формуле.

Затем карту можно применить для получения нового изображения без искажений из оригинала (cv::remap(....)). cv::undistort() делает это без явного расчета карты неискажения.

person Patrick Dietrich    schedule 06.09.2017

Есть некоторые моменты, которые я обнаружил, когда пытался переискажать точки, используя советы из этой темы:

  1. Код в вопросе почти правильный, но есть ошибка. Он использует r^4 после k3 вместо r^6. Я переписал код и успешно запустился после этого простого исправления.

xCorrected = x * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2); // Multiply r2 after k3 one more time in yCorrected too

  1. Джоан Чарман сказала, что правильные вещи и уравнения позволяют искажать точку назад (не искажать, как упоминалось в комментариях под его ответом). Однако некоторые слова неверны:

// К относительным координатам ‹- это шаг, который вы пропустили

Это неправильно, так как код в этом вопросе уже использует относительные координаты! Это трюк в функциях OpenCV undistortPoints. Он имеет новую внутреннюю матрицу в качестве 6-го аргумента. Если None, то функция возвращает точки в относительных координатах. И именно поэтому исходный код, о котором идет речь, имеет этот шаг:

//Step 2 : ideal coordinates => actual coordinates
      xCorrected = xCorrected * fx + ux;
      yCorrected = yCorrected * fy + uy;
  1. Также я должен сказать о неразберихе в интернет-материалах.

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

Недавно я нашел, почему. Учебник OpenCV и его документация имеют разные названия. В учебнике для уравнений используются переменные «xCorrected» и «yCorrected». Хотя в документе одни и те же вещи называются по-разному: «xDistorted» и «yDistorted».

Итак, позвольте мне разрешить путаницу: операцию искажения можно представить в виде уравнений в различных моделях искажения. Но Undistortion возможен только с помощью численного алгоритма итерации. Не существует аналитических решений для представления неискажений в виде уравнений (из-за части 6-го порядка в радиальной части и нелинейности)

person Ruslan Timchenko    schedule 12.01.2021

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

Однако модель радиальной камеры никоим образом не является особенной или священной, это просто простое правило, которое растягивает пиксели наружу или внутрь к оптическому центру в зависимости от объектива, с которым вы делали снимок. Чем ближе к оптическому центру, тем меньше искажений получает пиксель. Существует множество других способов определения модели радиальной дисторсии, которые могут дать не только аналогичное качество дисторсии, но и обеспечить простой способ определения обратной дисторсии. Но такой путь означает, что оптимальные параметры для такой модели вам придется искать самостоятельно.

Например, в моем конкретном случае я обнаружил, что простая сигмовидная функция (смещенная и масштабированная) способна аппроксимировать существующие параметры моей радиальной модели с интегральной ошибкой MSE, меньшей или равной 1E-06, хотя сравнение между моделями кажется бессмысленным. Я не думаю, что родная радиальная модель дает лучшие значения и не должна рассматриваться как эталонная. Физическая геометрия линзы может различаться таким образом, что обе модели не могут быть представлены, и для более точного приближения геометрии линзы следует использовать подход, подобный сетке. Однако я впечатлен аппроксимированной моделью, потому что она использует только один свободный параметр и обеспечивает особенно точный результат, который заставляет меня задуматься, какая модель на самом деле лучше для работы.

Вот график исходной радиальной модели (красный) и ее сигмовидного приближения (зеленый) вверху, а также их производных (синие линии):

введите здесь описание изображения

Итак, функция искажения/неискажения в моем случае выглядела так:

distort = (r, alpha) -> 2/(1 + exp(-alpha*r)) - 1
undistort = (d, alpha) -> -ln((d + 1)/(d - 1))/alpha

(Обратите внимание, что искажение выполняется в полярных координатах вокруг оптического центра и влияет только на расстояние от оптического центра (т.е. не на сам угол), r - расстояние от оптического центра, альфа - свободный параметр, который необходимо оценить):

Вот как выглядело искажение по сравнению с исходным радиальным искажением (зеленый цвет аппроксимирован, красный — исходное радиальное искажение)

введите здесь описание изображения

А вот как выглядит обратное отображение пикселей, если мы возьмем обычную пиксельную сетку и попытаемся ее не исказить:

введите здесь описание изображения

person Lu4    schedule 03.11.2020

Другой способ - использовать переназначение для проецирования исправленного изображения в искаженное изображение:

img_distored = cv2.remap(img_rect, mapx, mapy, cv2.INTER_LINEAR)

mapx и mapy — это сопоставления от выпрямленных пиксельных местоположений к искаженным пиксельным местоположениям. Его можно получить, выполнив следующие шаги:

X, Y = np.meshgrid(range(w), range(h)
pnts_distorted = np.merge(X, Y).reshape(w*h, 2)
pnts_rectified = cv2.undistortPoints(pnts_distorted, cameraMatrix, distort, R=rotation, P=pose)
mapx = pnts_rectified[:,:,0]
mapy = pnts_rectified[:,:,1]

cameraMatrix, искажение, вращение, поза — это параметры, возвращаемые в функциях калибровки cv и StereoRectify.

person buffalowings    schedule 27.12.2020

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

В приведенном выше случае мне очень помог следующий ответ. (Руководствуясь ответом @Joan Charmant.)

distortBack(point3f undistored, point2f distored_back){
    double temp_x, temp_y;
    temp_x = undistored.x / undistored.z; 
    temp_y = undistored.y / undistored.z;
    
    double r2 = temp_x*temp_x + temp_y*temp_y;

    // Radial distorsion
    double xDistort = temp_x * (1 + dist_k1 * r2 + dist_k2 * r2 * r2 + dist_k3 * r2 * r2 * r2);
    double yDistort = temp_y * (1 + dist_k1 * r2 + dist_k2 * r2 * r2 + dist_k3 * r2 * r2 * r2);

    // Tangential distorsion
    xDistort = xDistort + (2 * dist_p1 * temp_x * temp_y + dist_p2 * (r2 + 2 * temp_x * temp_x));
    yDistort = yDistort + (dist_p1 * (r2 + 2 * temp_y * temp_y) + 2 * dist_p2 * temp_x * temp_y);

    // Back to absolute coordinates.
    distored_back.x = xDistort * camera_fx + camera_cx;
    distored_back.y = yDistort * camera_fy + camera_cy;
}

Дополнительные сведения — cv::UndistorPoints() и в opencv/doc/v4.3.0

Модель камеры-обскуры, дисторсия объектива

person RochaaP    schedule 22.06.2021

Вот реализация, основанная на cv2.projectPoints, источник для interpolate_missing_pixels можно найти здесь.

import numpy as np
import cv2


def distort(
        image: np.ndarray,
        cam_matrix: np.ndarray,
        dist_coefs,
        **kwargs
) -> np.ndarray:
    """
    Applies lens distortion to an image.
    :param image: input image
    :param cam_matrix: camera intrinsic matrix
    :param dist_coefs must be given in opencv format
    :param kwargs passed to `interpolate_missing_pixels`.
    """
    rvec_tvec = np.zeros(3)
    h, w = image.shape[:2]
    rows, cols = np.meshgrid(np.arange(h), np.arange(w), indexing='ij')
    coords_pix_orig = np.array([cols.ravel(), rows.ravel(), np.ones(h*w)])
    coords_cam = np.linalg.inv(cam_matrix) @ coords_pix_orig
    coords_cam = coords_cam.astype(np.float32)
    coords_pix_dist, _ = cv2.projectPoints(
        coords_cam, rvec_tvec, rvec_tvec, cam_matrix, dist_coefs)
    coords_pix_dist = coords_pix_dist.astype(np.int).squeeze(1)
    in_image =\
        (coords_pix_dist[:, 0] >= 0) & (coords_pix_dist[:, 0] < w) & \
        (coords_pix_dist[:, 1] >= 0) & (coords_pix_dist[:, 1] < h)

    orig_r = coords_pix_orig[1][in_image].astype(np.int)
    orig_c = coords_pix_orig[0][in_image].astype(np.int)

    dist_r = coords_pix_dist[:, 1][in_image]
    dist_c = coords_pix_dist[:, 0][in_image]

    dist_image = np.zeros_like(image)
    dist_image[dist_r, dist_c] = image[orig_r, orig_c]

    missing_vals_mask = np.ones((h, w), dtype=np.bool)
    missing_vals_mask[dist_r, dist_c] = False

    interp_dist_image = interpolate_missing_pixels(
        dist_image, missing_vals_mask, **kwargs
    )

    return interp_dist_image
person Sam De Meyer    schedule 28.07.2021