Создание 3D-сетки из изображения с помощью Python

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Несколько лет назад генерация 3D-сетки из единственного двумерного изображения была сложной задачей. Но сегодня благодаря продвижению глубокого обучения разработано множество монокулярных моделей оценки глубины, дающих точную оценку карты глубины изображения. С помощью этой карты, выполнив реконструкцию поверхности, можно создать сетку. Подробности — к старту нашего курса по Fullstack-разработке на Python.


Введение


Монокулярная оценка глубины — это задача оценки значения глубины (расстояния относительно камеры) каждого пикселя для одного (монокулярного) изображения RGB. Результат такой оценки — карта глубины, которая в основном представляет собой матрицу, где каждый элемент соответствует спрогнозированной глубине соответствующего пикселя входного изображения:



Карта глубины


Точки на карте глубины можно рассматривать как множество точек с координатами по трём осям. Карта — это матрица, а значит, каждый её элемент имеет компоненты x, y (столбец и строка соответственно), а z — значение спрогнозированной глубины в точке (x, y). Список точек (x, y, z) в области обработки трёхмерных данных называется облаком точек.



Облако точек. Оригинальный файл Open3D


Можно начать с неструктурированного облака точек и получить сетку, то есть трёхмерное представление объекта из множества вершин и многоугольников [полигонов]. Самый распространённый тип сетки — треугольная сетка, состоящая из множества соединённых общими рёбрами или вершинами трёхмерных треугольников. В литературе вы найдёте несколько методов получения треугольной сетки из облака точек; самые популярные — альфа-форма¹, шаровое вращение² и реконструкция поверхности Пуассона³. Эти методы называют алгоритмами реконструкции поверхности.



Треугольная сетка. Оригинальный файл Open3d


Процедура создания сетки из изображения в этом руководстве состоит из трёх этапов:


  1. Оценка глубины: с использованием монокулярной модели оценки глубины создаётся карта глубины входного изображения.
  2. Построение облака точек: карта глубины преобразуется в облако точек.
  3. Генерация сетки: с помощью алгоритма реконструкции поверхности из облака точек создаётся сетка.

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



Спальня. Изображение из NYU-Depth V2


1. Оценка глубины


Выбранная для этого руководства модель монокулярной оценки глубины — GLPN⁴. Получить её можно в Hugging Face Model Hub с помощью библиотеки Transformers от Hugging Face.


Для этого установите последнюю версию Transformers из PyPI:


pip install transformers

Приведённый ниже код оценивает глубину входного изображения:


import matplotlib
matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
from PIL import Image
import torch
from transformers import GLPNFeatureExtractor, GLPNForDepthEstimation

feature_extractor = GLPNFeatureExtractor.from_pretrained("vinvino02/glpn-nyu")
model = GLPNForDepthEstimation.from_pretrained("vinvino02/glpn-nyu")

# load and resize the input image
image = Image.open("image.jpg")
new_height = 480 if image.height > 480 else image.height
new_height -= (new_height % 32)
new_width = int(new_height * image.width / image.height)
diff = new_width % 32
new_width = new_width - diff if diff < 16 else new_width + 32 - diff
new_size = (new_width, new_height)
image = image.resize(new_size)

# prepare image for the model
inputs = feature_extractor(images=image, return_tensors="pt")

# get the prediction from the model
with torch.no_grad():
    outputs = model(**inputs)
    predicted_depth = outputs.predicted_depth

# remove borders
pad = 16
output = predicted_depth.squeeze().cpu().numpy() * 1000.0
output = output[pad:-pad, pad:-pad]
image = image.crop((pad, pad, image.width - pad, image.height - pad))

# visualize the prediction
fig, ax = plt.subplots(1, 2)
ax[0].imshow(image)
ax[0].tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
ax[1].imshow(output, cmap='plasma')
ax[1].tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
plt.tight_layout()
plt.pause(5)

Для работы с GLPN библиотека Transformers предоставляет два класса: GLPNFeatureExtractor — для предварительной обработки входных данных, и класс модели — GLPNForDepthEstimation.


Из-за архитектуры выходной размер модели составляет:



Выходной размер. Изображение сгенерировано с помощью CodeCogs



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


Монокулярные модели оценки глубины пытаются получить прогнозы высокого качества вблизи границ, поэтому выходные данные (output) обрезаются по центру (строка 33). Чтобы сохранить одинаковые размеры, также обрезается по центру image (строка 34).


Вот некоторые прогнозы:



Прогноз глубины спальни. На входе изображение из NYU-Depth V2



Прогноз глубины игровой комнаты. На входе изображение из NYU-Depth V2



Прогноз глубины офиса. На входе изображение из NYU-Depth V2


2. Построение облака точек


В части 3D-обработки будет использоваться Open3d⁵. Наверное, это лучшая библиотека Python для задач такого рода.


Установите последнюю версию Open3d из PyPI:


pip install open3d

Код ниже преобразует предполагаемую карту глубины в объект облака точек Open3D:


import numpy as np
import open3d as o3d

width, height = image.size

depth_image = (output * 255 / np.max(output)).astype('uint8')
image = np.array(image)

# create rgbd image
depth_o3d = o3d.geometry.Image(depth_image)
image_o3d = o3d.geometry.Image(image)
rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(image_o3d, depth_o3d, convert_rgb_to_intensity=False)

# camera settings
camera_intrinsic = o3d.camera.PinholeCameraIntrinsic()
camera_intrinsic.set_intrinsics(width, height, 500, 500, width/2, height/2)

# create point cloud
pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, camera_intrinsic)

Изображение RGBD — это просто комбинация RGB-изображения и соответствующего изображения глубины. Класс PinholeCameraIntrinsic хранит так называемую внутреннюю матрицу камеры. С этой матрицей Open3D может создать облако точек из изображения RGBD с правильным расстоянием между точками. Внутренние параметры оставьте как есть. Дополнительные сведения смотрите в дополнительных ресурсах в конце руководства.


Для визуализации выполните эту строку:


o3d.visualization.draw_geometries([pcd])

3. Генерация сетки


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


С помощью алгоритма из полученного на последнем шаге облака точек Пуассона этот код генерирует сетку:


# outliers removal
cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=20.0)
pcd = pcd.select_by_index(ind)

# estimate normals
pcd.estimate_normals()
pcd.orient_normals_to_align_with_direction()

# surface reconstruction
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=10, n_threads=1)[0]

# rotate the mesh
rotation = mesh.get_rotation_matrix_from_xyz((np.pi, 0, 0))
mesh.rotate(rotation, center=(0, 0, 0))

# save the mesh
o3d.io.write_triangle_mesh(f'./mesh.obj', mesh)

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


Следующий шаг — оценка нормали. Нормаль — это вектор (естественным образом обладающий величиной и направлением), перпендикулярный поверхности или объекту, и для обработки алгоритмом Пуассона их необходимо оценить. Дополнительные сведения об этих векторах смотрите в дополнительных ресурсах в конце руководства.


Наконец, алгоритм выполняется. Уровень детализации сетки определяется значением depth. Помимо повышения качества сетки более высокое значение глубины увеличивает размеры вывода.


Для визуализации сетки советую скачать MeshLab, потому что есть программы 3D-визуализации только в ч/б.


Вот окончательный результат:



Сгенерированная сетка



Сетка с другого ракурса


Поскольку окончательный результат изменяется в зависимости от значения depth, это сравнение его различных значений:



Сравнение различных значений глубины


Алгоритм с depth=5 привёл к сетке в 375 КБ, depth=6 — к 1,2 МБ, depth=7 — к 5 МБ, depth=8 — к 19 МБ, depth=9 — к 70, а depth=10 — к 86 МБ.


Вывод


Несмотря на использование одного изображения, итог достаточно хороший. Подправив 3D, можно достичь результатов ещё лучше. Это руководство не может полностью охватить все детали обработки 3D-данных, а потому я советую вам прочитать другие ресурсы (они перечислены ниже), чтобы лучше разобраться со всеми аспектами.


Дополнительные ресурсы:


  • Модель камеры-обскуры;
  • Внутренние и внешние матрицы;
  • Нормали;
  • Официальная документация Open3D.

Спасибо, что прочитали. Надеюсь, вы нашли материал полезным.


Литература

[1] H. Edelsbrunner, and E. P. Mücke, Three-dimensional Alpha Shapes (1994)


[2] F. Bernardini, J. Mittleman, H. Rushmeier, C. Silva, and G. Taubin, [The ball-pivoting algorithm for surface reconstruction](http://The ball-pivoting algorithm for surface reconstruction) (1999)


[3] M. Kazhdan, M. Bolitho and H. Hoppe, Poisson Surface Reconstruction (2006)


[4] D. Kim, W. Ga, P. Ahn, D. Joo, S. Chun, and J. Kim, Global-Local Path Networks for Monocular Depth Estimation with Vertical CutDepth (2022)


[5] Q. Zhou, J. Park, and V. Koltun, Open3D: A Modern Library for 3D Data Processing (2018)


[6] N. Silberman, D. Hoiem, P. Kohli, and Rob Fergus, Indoor Segmentation and Support Inference from RGBD Images (2012)


А мы научим работать с Python, чтобы вы прокачали карьеру или стали востребованным IT-специалистом:


  • Профессия Fullstack-разработчик на Python (16 месяцев)
  • Профессия Data Scientist (24 месяца)

Чтобы посмотреть все курсы, кликните по баннеру:



Краткий каталог курсов

Data Science и Machine Learning


  • Профессия Data Scientist
  • Профессия Data Analyst
  • Курс «Математика для Data Science»
  • Курс «Математика и Machine Learning для Data Science»
  • Курс по Data Engineering
  • Курс «Machine Learning и Deep Learning»
  • Курс по Machine Learning

Python, веб-разработка


  • Профессия Fullstack-разработчик на Python
  • Курс «Python для веб-разработки»
  • Профессия Frontend-разработчик
  • Профессия Веб-разработчик

Мобильная разработка


  • Профессия iOS-разработчик
  • Профессия Android-разработчик

Java и C#


  • Профессия Java-разработчик
  • Профессия QA-инженер на JAVA
  • Профессия C#-разработчик
  • Профессия Разработчик игр на Unity

От основ — в глубину


  • Курс «Алгоритмы и структуры данных»
  • Профессия C++ разработчик
  • Профессия «Белый хакер»

А также


  • Курс по DevOps
  • Все курсы
Источник: https://habr.com/ru/company/skillfactory/blog/693338/


Интересные статьи

Интересные статьи

Привет, Хабр!Сегодня я рассмотрю основные возможности BI-инструмента с открытым исходным кодом Metabase.
У нас возникла задача добавить препроцессинг для параметров активити бизнес-процесса Битрикс24. Когда разбирались в задаче не смогли найти ни одного примера и решили выложить свой - может быть кто-ниб...
Привет! Меня зовут Алексей Дёмин, я Android-разработчик в Prequel - мобильном редакторе для фото и видео. Даунскейл изображений встречается в приложениях повсеместно, но из-за высокого уровн...
Признаю: мне очень нравится та невероятная скорость, с которой загружаются домашние компьютеры 1980-х годов. Я какое-то время пытался оптимизировать время загрузки Raspberry Pi, но особен...
Большинство тех, кто начинает изучать программирование, начали это из-за желания сделать свою игру. Нууу… я не исключение, но судьба меня занесла в веб разработку. Ч...