Как я могу реверсировать 2D точки в 3D? - PullRequest
54 голосов
/ 16 сентября 2008

У меня есть 4 2D точки в пространстве экрана, и мне нужно спроецировать их обратно в 3D пространство. Я знаю, что каждая из 4 точек является углом жесткого прямоугольника, повернутого в 3D, и знаю размер прямоугольника. Как я могу получить 3D-координаты из этого?

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

Это может подпадать под зонтик фотограмметрии, но поиск в Google по этому поводу не привел меня к какой-либо полезной информации.

Ответы [ 14 ]

62 голосов
/ 29 ноября 2015

Хорошо, я пришел сюда в поисках ответа и не нашел ничего простого и понятного, поэтому я пошел дальше и сделал глупую, но эффективную (и относительно простую) вещь: оптимизацию Монте-Карло.

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

Вот еще фото от двигателя танка Томаса:

Thomas the Tank Engine

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

With an outline of the square

Я получаю четыре очка на 2D-изображении: (318, 247), (326, 312), (418, 241) и (452, 303).

По договоренности мы говорим, что эти точки должны соответствовать трехмерным точкам: (0, 0, 0), (0, 0, 1), (1, 0, 0) и (1, 0, 1). Другими словами, единичный квадрат в плоскости y = 0.

Проецирование каждой из этих трехмерных координат в 2D выполняется путем умножения вектора 4D [x, y, z, 1] на матрицу проекции 4x4, а затем деления компонентов x и y на z, чтобы фактически получить коррекцию перспективы. Это более или менее то, что делает gluProject () , за исключением того, что gluProject() также учитывает текущий видовой экран и учитывает отдельную матрицу вида модели (мы можем просто предположить, что матрица вида модели - это единичная матрица). Очень удобно смотреть на документацию gluProject(), потому что я на самом деле хочу решение, которое работает для OpenGL, но учтите, что в документации отсутствует деление на z в формуле.

Помните, алгоритм должен начинаться с некоторой матрицы проекций и случайным образом возмущать ее, пока она не даст желаемую проекцию. Итак, что мы собираемся сделать, это спроектировать каждую из четырех трехмерных точек и посмотреть, насколько мы приблизились к желаемым 2D точкам. Если наши случайные возмущения приводят к тому, что проецируемые двумерные точки становятся ближе к тем, которые мы отмечали выше, мы сохраняем эту матрицу как улучшение по сравнению с нашим первоначальным (или предыдущим) предположением.

Давайте определим наши точки:

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

Нам нужно начать с некоторой матрицы, единичная матрица кажется естественным выбором:

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

Нам нужно на самом деле реализовать проекцию (которая в основном является умножением матрицы):

def project(p, mat):
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

Это в основном то, что gluProject() делает, 720 и 576 - это ширина и высота изображения соответственно (то есть область просмотра), и мы вычитаем из 576, чтобы считать тот факт, что мы посчитали координаты y сверху, в то время как OpenGL обычно считает их снизу. Вы заметите, что мы не вычисляем z, это потому, что нам это здесь не нужно (хотя это может быть удобно, чтобы убедиться, что оно попадает в диапазон, который OpenGL использует для буфера глубины).

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

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)

Чтобы возмущать матрицу, мы просто выбираем элемент для возмущения на случайную величину в некотором диапазоне:

def perturb(amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)

(Стоит отметить, что наша функция project() на самом деле вообще не использует mat[2], поскольку мы не вычисляем z, а все наши координаты y равны 0, значения mat[*][1] также не имеют значения Мы могли бы использовать этот факт и никогда не пытаться нарушать те значения, которые могли бы привести к небольшому ускорению, но это оставлено в качестве упражнения ...)

Для удобства давайте добавим функцию, которая выполняет основную часть аппроксимации, вызывая perturb() снова и снова для определения наилучшей из найденных нами матриц:

def approximate(mat, amount, n=100000):
    est = evaluate(mat)

    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

Теперь осталось только запустить ...:

for i in xrange(100):
    mat = approximate(mat, 1)
    mat = approximate(mat, .1)

Я считаю, что это уже дает довольно точный ответ. Через некоторое время я нашел следующую матрицу:

[
    [1.0836000765696232,  0,  0.16272110011060575, -0.44811064935115597],
    [0.09339193527789781, 1, -0.7990570384334473,   0.539087345090207  ],
    [0,                   0,  1,                    0                  ],
    [0.06700844759602216, 0, -0.8333379578853196,   3.875290562060915  ],
]

с ошибкой около 2.6e-5. (Обратите внимание, что элементы, которые мы сказали, не использовались в вычислениях, на самом деле не были изменены по сравнению с нашей исходной матрицей; это потому, что изменение этих записей не приведет к изменению результата оценки, и поэтому изменение никогда не будет перенесено.)

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

def transpose(m):
    return [
        [m[0][0], m[1][0], m[2][0], m[3][0]],
        [m[0][1], m[1][1], m[2][1], m[3][1]],
        [m[0][2], m[1][2], m[2][2], m[3][2]],
        [m[0][3], m[1][3], m[2][3], m[3][3]],
    ]

glLoadMatrixf(transpose(mat))

Теперь мы можем, например, перевести вдоль оси z, чтобы получить разные позиции вдоль дорожек:

glTranslate(0, 0, frame)
frame = frame + 1

glBegin(GL_QUADS)
glVertex3f(0, 0, 0)
glVertex3f(0, 0, 1)
glVertex3f(1, 0, 1)
glVertex3f(1, 0, 0)
glEnd()

With 3D translation

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

Choo choo!

7 голосов
/ 18 декабря 2012

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

У вас есть квадратный маркер (2D штрих-код), и вы хотите найти его позу (перемещение и вращение относительно камеры) после нахождения четырех краев маркера. Обзор-Picture

Мне не известны последние вклады в этой области, но, по крайней мере, до определенного момента (2009 г.) RPP должен был опередить ПОЗИТ, упомянутый выше (и это действительно классический подход для этого) Пожалуйста, смотрите ссылки, они также предоставляют источник.

(PS - я знаю, что это немного старая тема, но в любом случае, пост может быть кому-то полезен)

5 голосов
/ 24 августа 2010

D. DeMenthon разработал алгоритм для вычисления позы объекта (его положения и ориентации в пространстве) из характерных точек на двумерном изображении при знании модели объекта - это ваша Точная проблема :

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

Алгоритм известен как Posit и описан в классической статье «Поза объекта на основе модели в 25 строках кода» (доступна на на его веб-сайте , раздел 4).

Прямая ссылка на статью: http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/Pose25Lines.pdf Реализация OpenCV: http://opencv.willowgarage.com/wiki/Posit

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

4 голосов
/ 17 сентября 2008

Из двумерного пространства будет 2 допустимых прямоугольника, которые можно построить. Не зная исходную матричную проекцию, вы не узнаете, какая из них правильная. Это то же самое, что и проблема «коробки»: вы видите два квадрата, один внутри другого, с 4 внутренними вершинами, соединенными с 4 соответствующими внешними вершинами. Вы смотрите на коробку сверху вниз или снизу вверх?

При этом вы ищете матричное преобразование T, где ...

{{x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4}} x T = {{x1, y1}, {x2, y2 }, {x3, y3}, {x4, y4}}

(4 x 3) x T = (4 x 2)

Так что T должна быть (3 x 2) матрица. Итак, у нас есть 6 неизвестных.

Теперь создайте систему ограничений на T и решите ее с помощью Simplex. Чтобы построить ограничения, вы знаете, что линия, проходящая через первые две точки, должна быть параллельна линии, проходящей ко вторым двум точкам. Вы знаете, что линия, проходящая через точки 1 и 3, должна быть параллельной линиям, проходящим через точки 2 и 4. Вы знаете, что линия, проходящая через точки 1 и 2, должна быть ортогональной линии, проходящей через точки 2 и 3. Вы знаете, что длина линии от 1 и 2 должны равняться длине строки от 3 и 4. Вы знаете, что длина строки от 1 и 3 должна равняться длине строки от 2 и 4.

Чтобы сделать это еще проще, вы знаете о прямоугольнике, поэтому вы знаете длину всех сторон.

Это должно дать вам множество ограничений для решения этой проблемы.

Конечно, чтобы вернуться, вы можете найти Т-инверс.

@ Роб: Да, существует бесконечное количество проекций, но не бесконечное количество проектов, в которых точки должны удовлетворять требованиям прямоугольника.

@ nlucaroni: Да, это разрешимо, только если у вас есть четыре точки в проекции. Если прямоугольник проецируется только на 2 точки (т.е. плоскость прямоугольника ортогональна поверхности проекции), то это не может быть решено.

Хммм ... Я должен пойти домой и написать этот маленький драгоценный камень. Это звучит как веселье.

Обновление:

  1. Существует бесконечное количество проекций, если вы не исправите одну из точек. Если вы зафиксируете одну из точек исходного прямоугольника, то есть два возможных исходных прямоугольника.
4 голосов
/ 17 сентября 2008

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

/*   FUNCTION:        YCamera :: CalculateWorldCoordinates
     ARGUMENTS:       x         mouse x coordinate
                      y         mouse y coordinate
                      vec       where to store coordinates
     RETURN:          n/a
     DESCRIPTION:     Convert mouse coordinates into world coordinates
*/

void YCamera :: CalculateWorldCoordinates(float x, float y, YVector3 *vec) { // START GLint viewport[4]; GLdouble mvmatrix[16], projmatrix[16];</p> <pre><code>GLint real_y; GLdouble mx, my, mz; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix); glGetDoublev(GL_PROJECTION_MATRIX, projmatrix); real_y = viewport[3] - (GLint) y - 1; // viewport[3] is height of window in pixels gluUnProject((GLdouble) x, (GLdouble) real_y, 1.0, mvmatrix, projmatrix, viewport, &mx, &my, &mz); /* 'mouse' is the point where mouse projection reaches FAR_PLANE. World coordinates is intersection of line(camera->mouse) with plane(z=0) (see LaMothe 306) Equation of line in 3D: (x-x0)/a = (y-y0)/b = (z-z0)/c Intersection of line with plane: z = 0 x-x0 = a(z-z0)/c <=> x = x0+a(0-z0)/c <=> x = x0 -a*z0/c y = y0 - b*z0/c */ double lx = fPosition.x - mx; double ly = fPosition.y - my; double lz = fPosition.z - mz; double sum = lx*lx + ly*ly + lz*lz; double normal = sqrt(sum); double z0_c = fPosition.z / (lz/normal); vec->x = (float) (fPosition.x - (lx/normal)*z0_c); vec->y = (float) (fPosition.y - (ly/normal)*z0_c); vec->z = 0.0f;

}

2 голосов
/ 18 сентября 2008

Чтобы продолжить подход Ронса: вы можете найти свои z-значения, если знаете, как вы повернули прямоугольник.

Хитрость в том, чтобы найти проективную матрицу, которая сделала проекцию. К счастью, это возможно и даже дешево. Соответствующая математика может быть найдена в статье Пола Хекберта «Проективные отображения для деформации изображения».

http://pages.cs.wisc.edu/~dyer/cs766/readings/heckbert-proj.pdf

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

Теперь у вас все еще остаются четыре линии вместо точек (как объяснил Рон). Так как вы знаете размер вашего оригинального прямоугольника, ничего не потеряно. Теперь вы можете подключить данные из метода Рона и из двумерного подхода в решатель линейных уравнений и решить для z. Таким образом вы получите точные значения z каждой вершины.

Примечание. Это работает только потому, что:

  1. Первоначальная форма была прямоугольником
  2. Вы знаете точный размер прямоугольника в трехмерном пространстве.

Это действительно особый случай.

Надеюсь, это поможет, Nils

2 голосов
/ 16 сентября 2008

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

Найдите две точки с максимальным расстоянием между ними: они, скорее всего, определяют диагональ (исключение: особые случаи, когда прямоугольник почти параллелен плоскости YZ, оставленный для студента) Назовите их A, C. Рассчитайте BAD, BCD углы. Они, по сравнению с прямыми углами, дают вам ориентацию в трехмерном пространстве. Чтобы узнать расстояние z, необходимо сопоставить проекционные стороны с известными сторонами, а затем, основываясь на методе трехмерного проецирования (это 1 / z?), Вы на правильном пути, чтобы узнать расстояния.

1 голос
/ 05 мая 2017

Да, Монте-Карло работает, но я нашел лучшее решение для этой проблемы. Этот код отлично работает (и использует OpenCV):

Cv2.CalibrateCamera(new List<List<Point3f>>() { points3d }, new List<List<Point2f>>() { points2d }, new Size(height, width), cameraMatrix, distCoefs, out rvecs, out tvecs, CalibrationFlags.ZeroTangentDist | CalibrationFlags.FixK1 | CalibrationFlags.FixK2 | CalibrationFlags.FixK3);

Эта функция принимает известные 3d и 2d точки, размер экрана и возвращает вращение (rvecs [0]), перемещение (tvecs [0]) и матрицу внутренних значений камеры. Это все, что вам нужно.

1 голос
/ 05 октября 2016

Спасибо @Vegard за отличный ответ. Я немного очистил код:

import pandas as pd
import numpy as np

class Point2:
    def __init__(self,x,y):
        self.x = x
        self.y = y

class Point3:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y
        self.z = z

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

def project(p, mat):
    #print mat
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point2(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)    

def perturb(mat, amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)
    return mat2

def approximate(mat, amount, n=1000):
    est = evaluate(mat)
    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

for i in xrange(1000):
    mat,est = approximate(mat, 1)
    print mat
    print est

Примерный звонок с .1 у меня не сработал, поэтому я его вынул. Я тоже запускал его некоторое время, и в последний раз я проверял его на

[[0.7576315397559887, 0, 0.11439449272592839, -0.314856490473439], 
[0.06440497208710227, 1, -0.5607502645413118, 0.38338196981556827], 
[0, 0, 1, 0], 
[0.05421620936883742, 0, -0.5673977598434641, 2.693116299312736]]

с ошибкой около 0,02.

1 голос
/ 17 сентября 2008

http://library.wolfram.com/infocenter/Articles/2794/

http://campar.in.tum.de/Students/SepPoseEstimation

http://opencvlibrary.sourceforge.net/Posit будет работать, но это может расходиться или зацикливаться.

...