3d реконструкция точки из 2d изображений - PullRequest
0 голосов
/ 26 октября 2018

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

Для реконструкции 3D-точки (карты глубины) нам нужно 2 изображения одного и того же объекта с двух разных видов, учитывая, что для такой пары изображений нам также нужна матрица камеры (скажем, P1, P2)

  • Мы находим соответствующие точки на двух изображениях, используя такие методы, как SIFT или SURF и т. д.

  • После получения соответствующего ключаТочка, мы находим найти необходимую матрицу (скажем, K), используя минимум 8 ключевых точек (используется в 8-точечном алгоритме)

  • Учитывая, что мы находимся в камере 1, рассчитать параметры для камеры 2Использование основной матрицы возвращает 4 возможных параметра камеры

  • В конечном итоге мы используем соответствующие точки и оба параметра камеры для оценки 3D-точки с использованием метода триангуляции.

Пройдя теоретический раздел, я в качестве первого эксперимента попытался запустить доступный код здесь , который сработал, как и ожидалось.С некоторыми изменениями в коде example.py я попытался запустить этот пример на всех последовательных парах изображений и объединить трехмерные облака точек для трехмерной реконструкции объекта (dino), как показано ниже:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import cv2

from camera import Camera
import structure
import processor
import features

def dino():
    # Dino
    img1 = cv2.imread('imgs/dinos/viff.003.ppm')
    img2 = cv2.imread('imgs/dinos/viff.001.ppm')
    pts1, pts2 = features.find_correspondence_points(img1, img2)
    points1 = processor.cart2hom(pts1)
    points2 = processor.cart2hom(pts2)

    fig, ax = plt.subplots(1, 2)
    ax[0].autoscale_view('tight')
    ax[0].imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB))
    ax[0].plot(points1[0], points1[1], 'r.')
    ax[1].autoscale_view('tight')
    ax[1].imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))
    ax[1].plot(points2[0], points2[1], 'r.')
    fig.show()

    height, width, ch = img1.shape
    intrinsic = np.array([  # for dino
        [2360, 0, width / 2],
        [0, 2360, height / 2],
        [0, 0, 1]])

    return points1, points2, intrinsic


points3d = np.empty((0,0))
files = glob.glob("imgs/dinos/*.ppm")
len = len(files)

for item in range(len-1):
    print(files[item], files[(item+1)%len])
    #dino() function takes 2 images as input
    #and outputs the keypoint point matches(corresponding points in two different views) along the camera intrinsic parameters.
    points1, points2, intrinsic = dino(files[item], files[(item+1)%len])
    #print(('Length', len(points1))
    # Calculate essential matrix with 2d points.
    # Result will be up to a scale
    # First, normalize points
    points1n = np.dot(np.linalg.inv(intrinsic), points1)
    points2n = np.dot(np.linalg.inv(intrinsic), points2)
    E = structure.compute_essential_normalized(points1n, points2n)
    print('Computed essential matrix:', (-E / E[0][1]))

    # Given we are at camera 1, calculate the parameters for camera 2
    # Using the essential matrix returns 4 possible camera paramters
    P1 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
    P2s = structure.compute_P_from_essential(E)

    ind = -1
    for i, P2 in enumerate(P2s):
        # Find the correct camera parameters
        d1 = structure.reconstruct_one_point(
            points1n[:, 0], points2n[:, 0], P1, P2)

        # Convert P2 from camera view to world view
        P2_homogenous = np.linalg.inv(np.vstack([P2, [0, 0, 0, 1]]))
        d2 = np.dot(P2_homogenous[:3, :4], d1)

        if d1[2] > 0 and d2[2] > 0:
            ind = i

    P2 = np.linalg.inv(np.vstack([P2s[ind], [0, 0, 0, 1]]))[:3, :4]
    #tripoints3d = structure.reconstruct_points(points1n, points2n, P1, P2)
    tripoints3d = structure.linear_triangulation(points1n, points2n, P1, P2)

    if not points3d.size:
        points3d = tripoints3d
    else:
        points3d = np.concatenate((points3d, tripoints3d), 1)


fig = plt.figure()
fig.suptitle('3D reconstructed', fontsize=16)
ax = fig.gca(projection='3d')
ax.plot(points3d[0], points3d[1], points3d[2], 'b.')
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
ax.view_init(elev=135, azim=90)
plt.show()

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

Ответы [ 3 ]

0 голосов
/ 21 ноября 2018

TL; DR

Возможно, вы не сможете получить полную трехмерную реконструкцию, которую хотите, просто комбинируя все 2 реконструкции изображения. Я пытался сделать это разными способами, и ни один из них не сработал. По сути, все сбои, похоже, сводятся к шуму в алгоритме оценки позы 2 изображений, который часто приводит к необоснованным результатам. Любая попытка отследить абсолютную позу простым объединением всех двух поз изображения просто распространяет шум во время реконструкции.

Код в репо , с которым работает ОП, основан на учебнике Геометрия с несколькими представлениями в Computer Vision . В главе 19 цитируется статья , в которой обсуждается успешная трехмерная реконструкция последовательности динозавров , и их подход несколько более сложен. В дополнение к 2 реконструкциям изображений, они также используют 3 реконструкции изображений и (возможно, самое главное) шаг подгонки в конце, который помогает гарантировать, что ни один побочный результат не разрушит реконструкцию.

код

... в процессе

0 голосов
/ 21 ноября 2018

Другой возможный путь понимания для вас - взглянуть на реализацию структуры с открытым исходным кодом из движения или SLAM. Обратите внимание, что эти системы могут стать довольно сложными. Тем не менее, OpenSfM написан на Python, и я думаю, что это легко ориентироваться и понимать. Я часто использую его как справочник для своей собственной работы.

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

Шаги OpenSfM на высоком уровне:

  1. Считайте изображение exif для любой предварительной информации, которую вы можете использовать (например, фокус длина)

  2. Извлечение характерных точек (например, SIFT)

  3. Соответствие характерным точкам

  4. Превратите эти совпадения характерных точек в дорожки (например, если вы видели характерную точку на изображениях 1,2 и 3, то вы можете подключить ее к дорожка вместо совпадения (1,2), совпадения (2,3) и т. д.)

  5. Инкрементная реконструкция (обратите внимание, что существует также глобальный подход). Этот процесс будет использовать треки, чтобы постепенно добавлять изображения для реконструкции, триангуляции новых точек и уточнения позы / точечные позиции с использованием процесса, называемого Bundle Adjustment.

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

0 голосов
/ 20 ноября 2018

Общая идея заключается в следующем.

На каждой итерации вашего кода вы вычисляете относительную позу правой камеры относительно левой.Затем вы триангулируете 2D-точки и объединяете результирующие 3D-точки в большой массив.Но объединенные точки не находятся в одном и том же координатном фрейме.

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

Вот как это сделать.

Сначала, перед циклом, инициализируйте матрицу накопления absolute_P1:

points3d = np.empty((0,0))
files = glob.glob("imgs/dinos/*.ppm")
len = len(files)
absolute_P1 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])

for item in range(len-1):
    # ...

Затем, после триангуляции объекта, сопоставьте 3D-точки с координатной рамкой первой камеры и обновите накопленную позу:

# ...
P2 = np.linalg.inv(np.vstack([P2s[ind], [0, 0, 0, 1]]))
tripoints3d = structure.linear_triangulation(points1n, points2n, P1, P2[:3, :4])

abs_tripoints3d = np.matmul(absolute_P1, np.vstack([tripoints3d, np.ones(np.shape(tripoints3d)[1])]))
absolute_P1 = np.matmul(absolute_P1, np.linalg.inv(P2)) # P2 needs to be 4x4 here!

if not points3d.size:
    points3d = abs_tripoints3d
else:
    points3d = np.concatenate((points3d, abs_tripoints3d), 1)

# ...
...