Всем привет, с вами я, Наиль Габутдинов, iOS разработчик.
Обычно чтобы создать одну точную и реалистичную 3D-модель предмета реального мира средней сложности требуется несколько часов работы профессионального художника. Тем временем, с развитием AR и VR сильно растет запрос на быстрое и недорогое создание качественных моделей. Для этих целей в macOS Monterey в составе RealityKit был представлен Object Capture API, который позволяет автоматизировать этот процесс. Object Capture использует алгоритмы фотограмметрии и может преобразовать серию фотографий в высококачественную 3D-модель оптимизированную для дополненной реальности всего за несколько минут. API появился год назад, но про него мало писали, поэтому я предлагаю рассмотреть данный инструмент подробнее.
Для начала нужно сфотографировать объект со всех сторон. Фото можно сделать на iPhone или iPad, цифровую зеркальную камеру или даже дрон. Нужно убедиться, что мы получаем четкие фотографии объекта со всех сторон. Позже рассмотрим best practices по съемке объекта.
Если съемка происходит на iPhone или iPad, API может использовать данные с устройств оснащеных LiDAR датчиком, чтобы установить фактический размер объекта, а также направление земной гравитации, чтобы модель автоматически ставилась с правильной стороны.
API поддерживается на последних Mac с процессором Intel, но должен работать быстрее на компьютерах с новейшими процессорами Apple, поскольку в них используется Apple Neural Engine для ускорения алгоритмов компьютерного зрения.
Basic workflow
Давайте рассмотрим схему базового рабочего процесса Object Capture. В этом процессе есть два основных этапа: создание и настройка сеанса, где мы передаем исходный набор фотографий объекта, и затем обработка, где мы устанавливаем некоторые параметры 3D-моделей, которые хотим получить.
Создание и настройка сеанса
Первый этап состоит из двух подэтапов: создание сеанса PhotogrammetrySession и подключение связанного с ним выходного потока данных.
PhotogrammetrySession — это основной класс в API и главная точка управления процессом. Сеанс также можно рассматривать как контейнер для фиксированного набора изображений, к которым будут применяться алгоритмы фотограмметрии для создания итоговой 3D-модели.
Есть два способа передать набор изображений для дальнейшего использования.
Первый и самый простой — это URL-адрес директории с фотографиями. PhotogrammetrySession будет принимать их один за другим и сообщать о возможных проблемах. Если в изображениях формата HEIC есть данные о глубине, они будут автоматически применяться для восстановления реального размера и расположения объекта.
Второй вариант – последовательный набор сущностей PhotogrammetrySample, которые помимо самого изображения содержат конфигурируемые данные о глубине, направлении гравитации, метаданные, данные о сегментации.
Давайте посмотрим, как создать сеанс с помощью API.
import RealityKit
let inputFolderURL = URL(fileURLWithPath: "/resources/pics/ObjectPhotos", isDirectory: true)
let session = try! PhotogrammetrySession(input: inputFolderURL,
configuration: PhotogrammetrySession.Configuration())
Object Capture API является частью RealityKit, поэтому надо импортировать данный фреймворк. В данном примере рассмотрим наиболее частый вариант, когда исходные изображения берутся из папки на локальном диске, и указываем эту папку в формате URL-адреса. Затем создаем сеанс PhotogrammetrySession, передавая URL-адрес в качестве источника. Инициализатор выдаст ошибку, если путь к папке не существует или не может быть прочитан. При необходимости можем указать расширенные параметры конфигурации PhotogrammetrySession.Configuration.
После того, как сеанс PhotogrammetrySession создан, мы можем делать запрос построения 3D-модели. Сеанс будет выдавать итоговую модель, а также сообщения о состоянии в своем output-потоке. Нужно реализовать обработку этих сообщений по мере их поступления.
Давайте рассмотрим пример кода, который обрабатывает поток сообщений от PhotogrammetrySession:
Task {
for try await output in session.outputs {
switch output {
case .requestProgress(let request, let fraction):
print("Progress: \(fraction)")
case .requestComplete(let request, let result):
if case .modelFile(let url) = result {
print("Result output at \(url)")
}
case .requestError(let request, let error):
"Request \(request) get error \(error)"
case .processingComplete:
print("Completed!")
default:
//Обработка других сообщений
break
}
}
}
Для реализации асинхронности обработки сообщений используется AsyncSequence. Output-сообщения включают в себя результаты запросов, а также сообщения о состоянии процесса, например, прогресс выполнения. Как только будет сделан первый запрос на создание модели, сообщения начнут поступать в поток выходных сообщений. Последовательность выходных сообщений не завершится, пока сеанс жив. Он будет продолжать отправлять сообщения до тех пор, пока PhotogrammetrySession не будет деинициализирована или не возникнет fatal error.
Теперь давайте рассмотрим подробнее типы сообщений, которые мы можем получать:
после начала выполнения запроса периодически будет приходить сообщение requestProgress со значением прогресса, его можно использовать для показа статуса обработки в своем приложении;
после завершения обработки запроса мы получим сообщение requestComplete, содержащее результат, например, модель или ограничивающую рамку;
если во время обработки что-то пошло не так, получим ошибку requestError;
warning, например, это может быть предупреждение о файлах в папке, которые не удалось загрузить;
после завершения обработки всех запросов в очереди получим сообщение processingComplete.
Обработка результата
Давайте рассмотрим различные типы результата сканирования, которые мы можем запросить. Существует три разных типа данных, которые можем получить в итоге:
Файл модели (USDZ или OBJ с текстурами);
ModelEntity – сущность RealityKit, которая содержит в себе структуру всех данных о модели;
BoundingBox – ограничивающий параллелепипед, который задает геометрические рамки объекта.
Каждый раз запрашивая результат мы указываем уровень детализации. Уровень Preview предназначен только для предварительного показа перед генерацией более точной модели. Основные уровни детализации в порядке возрастания качества и размера файла: Reduced, Medium и Full. Все эти уровни готовы к использованию из коробки. Кроме того, есть Raw, который предназначен для профессионального использования, и для него потребуется процесс постобработки.
Давайте посмотрим, как получить результат в коде:
try! session.process(requests: [
.modelFile("/outputs/model-reduced.usdz", detail: .reduced)
.modelFile("/outputs/model-full.usdz", detail: .full)
])
Как видим, можно одновременно сгенерировать две модели с разным уровнем детализации за один вызов. Мы запросили одну модель с пониженным уровнем детализации и одну со средним уровнем, каждая из них будет сохранена в отдельный файл USDZ. Одновременный запрос всех желаемых уровней детализации в одном вызове позволяет движку Object Capture выполнять вычисления и создавать все необходимые модели быстрее, так как делает это совместно.
Время обработки каждой из моделей будет зависеть от количества исходных изображений и выбранного уровня детализации.
Interactive workflow
Теперь давайте рассмотрим расширенный (интерактивный) рабочий процесс Object Capture.
Интерактивный рабочий процесс даёт возможность вносить корректировки в предварительную модель перед ее окончательной сборкой, таким образом устраняется необходимость пост-редактирования модели и оптимизируется использование памяти.
Обратите внимание, что этапы настройки и обработки такие же как и в базовом процессе. Добавился блок посередине, который представляет собой 3D интерфейс для интерактивного редактирования предварительной (preview) модели.
Сначала запрашиваем предварительную модель, указав в запросе уровень детализации. Эта превью-модель будет иметь низкое качество, зато будет создана максимально быстро. Получив ее, мы можем настроить границы области захвата, чтобы удалить любую нежелательную геометрию в модели, например, посторонний предмет, который попал в кадр вместе с основным объектом. Также можем сделать геометрическое преобразование для масштабирования, перемещения и поворота модели.
Этот процесс повторяется до тех пор, пока мы не будем довольны результатом. Затем строим окончательную модель, как в базовом сценарии, и можем ее экспортировать в файл USDZ или запросить объект ModelEntity для отображения.
Рекомендации по использованию
Давайте рассмотрим best practices, которые помогут нам добиться наилучших результатов.
Выбор объекта
Первое, что нужно учитывать при сканировании, — это выбрать объект с подходящими характеристиками. Apple приводит следующие рекомендации:
Объект должен обладать достаточной детализацией текстуры. Если объект содержит безтекстурные или прозрачные области, на результирующем скане может отсутствовать детализация этих частей.
Лучше избегать объектов с сильно отражающими поверхностями. Если же они есть, то получить наилучший результат можно рассеивая свет при сканировании.
Необходимо убедиться, что объект жёсткий и не меняет форму в процессе съемки с разных сторон.
Если сканируем объект с мелкими деталями, нам потребуется использовать камеру с высоким разрешением в дополнение к большому количеству фотографий поверхностей крупным планом.
Как снимать объект
Теперь пройдемся по рекомендациям к процессу сканирования.
Во-первых, нужно поместить объект на незагроможденный фон, чтобы объект четко выделялся и в кадр не попадали лишние предметы.
Процесс сканирования представляет собой медленное перемещение вокруг объекта с обязательным захватом всех его сторон. Если необходимо, можно перевернуть объект и продолжать съемку.
При съемке нужно максимально заполнить поле зрения камеры нашим объектом. Один из способов сделать это — использовать портретный или ландшафтный режим в зависимости от размеров и ориентации объекта.
Нужно поддерживать высокую степень перекрытия между фотографиями.
В зависимости от объекта, от 20 до 200 фото крупным планом должно быть достаточно, чтобы получить хороший результат.
Выбор уровня детализации
Нам доступно несколько вариантов детализации итоговой модели, среди которых которых нужно выбрать подходящий.
Reduced и Medium оптимизированы для быстрой передачи по сети, например, для просмотра 3D-контента в AR Quick Look или для загрузки в AR приложениях. Full и Raw предназначены для особых нужд, где требуется супер-детализация и возможность пост-обработки, например, это максимально реалистичные игры или случаи, когда наша модель служит исходным материалом для дальнейшей доработки профессиональным художником.
Вывод
Сегодня мы видим растущий спрос на создание 3D-контента, в том числе дублирующего предметы реального мира. В этой статье я хотел обратить внимание на классный API от Apple, который позволяет получать качественные модели, по сути просто снимая фото нужного предмета с разных сторон. Как мы видим, это довольно простая в использовании технология, которая легко интегрируется в рабочий процесс, вызов Object Capture API можно добавить в своё приложение на mac или просто вызывать генерацию моделей в простой программе из командной строки, предварительно указав директорию с фото. В то же время, мы можем гибко подстраивать инструмент под свои нужды, настраивая конфигурацию сеанса и выбирая тип (файл или Entity), уровень детализации и запрашивать одновременно несколько моделей.