Нейросети в фотограмметрии
Фотограмметрия — дисциплина, по технологиям которой можно создавать 3D-модели из фотографий. То есть, делаем несколько (но лучше, много) снимков объекта, интерьера или экстерьера, загоняем их в программу и получаем 3D-модель.
Вот так могут выглядеть полученные модели после некоторой обработки:
Гораздо лучшее качество демонстрируют Quixel, они занимаются производством моделей по этой технологии.
Использование фотограмметрии может быть достаточно широким, вот несколько примеров:
- Создание 3D-моделей для кино или компьютерных игр.
Например, для достижения максимальной реалистичности в последней Call of Duty почти все модели сделаны именно таким образом.
Что такое фотограмметрия и как ее использовали при разработке перезапуска Modern Warfare - Составление модели ландшафта для строительства.
Documentation of paintings restoration through photogrammetry and change detection algorithms - Картографирование:
Photogrammetry on the fly - Автопилоты и робототехника:
3D reconstruction, shows depth of information a Tesla can collect from just a few seconds of video from the vehicle’s 8 cameras
Проблема и постановка задачи
Как сказано выше, фотограмметрия — это дисциплина, определяющая общий подход, а в действительности существует множество программ для работы с фотограмметрией. Каждая их них работает по общим для дисциплины алгоритмам, хотя и с некоторыми нюансами.
Вот скриншот стандартного конвейера создания 3D-модели из программы Meshroom:
За каждым из этих блоков кроется еще несколько вспомогательных под-алгоритмов, десятки параметров влияющих на результат и не всегда очевидно каким именно образом они скажутся на результате.
Этим алгоритмам уже десятки лет и поскольку они лежат в основе почти каждой программы, то и проблемы у программ общие.
К таким проблемам можно отнести:
- Низкую производительность.
Создание модели занимает от 30 минут до десятков часов на мощном ПК. - Плохая работа с повторяющимися паттернами.
Текстуры ковров, паркетов, дорожной плитки и т.п. начинают двоиться, а части моделей дублируются и накладываться сами на себя. - Проблемы с глянцевыми и отражающими поверхностями.
В моделях появляются провалы или выпуклости сильно отличающиеся от реальной формы объекта.
Глядя на проблемы существующих подходов, можно прийти к выводу, что это проблемы алгоритмов, ведь человеку по фото понятно каким должен получиться объект в 3D. Подтверждением тому несчетное многообразие туториалов по созданию модели из нескольких снимков.
Например, некоторое время назад я делал для анимации модель машины. Мне хватило пары чертежей и фотографий в качестве референса.
Почему бы не попробовать для решения задачи применить нейросети? Немного погуглив, оказывается, что наработок в этой области много, однако до реального применения они в большинстве своем не дошли.
Подборки с примерами таких решений:
https://github.com/timzhang642/3D-Machine-Learning
https://github.com/natowi/3D-Reconstruction-with-Neural-Network
Для меня это интересная возможность потренироваться и в разработке нейросетей и познакомиться поближе с программной стороной Blender. А знакомиться там есть с чем:
Подход к решению
Процесс создания модели по снимкам состоит из большого числа шагов, как было показано выше. Можно попробовать заменить их все одной нейросетью, в будущем так, наверное, и будет. Но для начала я хочу решить первые этапы — это восстановление позиций камер в сцене, то есть, мест, откуда были сделаны снимки относительно всей сцены.
В общем эта задача решается так: на паре снимков программа ищет общие точки и, определяя их смещение от снимка к снимку, вычисляет как далеко сделан снимок, и какой поворот совершила камера.
Если дать нейросети решать такую задачу, она будет предсказывать расстояние между снимками по трем осям XYZ, и поворот относительно другой камеры градусах. Проблема в том, что она это будет делать даже для снимков, между которыми вообще нет общих точек. Например, когда на снимках запечатлены противоположные стены одного помещения. В таком случае, использовать показания нейросети нельзя. Сначала надо научиться понимать — а есть ли между снимками что-то общее, есть ли смысл определять положение камеры.
Вот пример двух кадров из одной сцены, но с противоположных ракурсов. Общего на снимках слишком мало для понимания, как далеко стоит вторая камера относительно первой.
Итого: наша задача — создать нейросеть, определяющую есть ли пересечение между снимками и на сколько оно сильное.
Сборка датасета
Датасет — набор данных, который делится на две части. Одна для обучения нейросети, вторая для проверки, ее нейросеть во время обучения не видит.
Для обучения нейросети нужно собрать много обучающих примеров.
Каждый из них представлен двумя изображениями и числом, характеризующим степень пересечения этих снимков.
Первые две картинки легко получить в Blender классическим подходом — достаточно запустить анимацию камеры по помещению, а для имитации съемки с рук добавить шум в анимацию движения:
Нажать «Render» и получить набор картинок:
Теперь надо придумать как получить картинку для расчета степени пересечения двух кадров.
По порядку:
- Для упрощения привожу все материалы и текстуры к белому цвету и без отражений:
2. Теперь помещаю камеру в коробку с источником света в центре и с отверстием для камеры. Отверстие достаточно большое, что бы не мешать камере, но не более. В итоге, источник света освещает только ту часть сцены, что камера видит в этом положении:
3. Смотрим что получается из этого положения и из другой точки. Видно, что тени размытые и освещена не только та часть, что видна из исходного положения камеры, но и тени:
4. Уменьшаю размер источника света и тени становятся более резкими. А уменьшение числа переотражений позволяет сделать тени полностью черными:
5. Получается набор ч/б кадров характеризующих степень пересечения кадров:
В динамике выглядит несколько яснее:
В этой работе с подготовкой ч/б кадров много нюансов.
Например, для анимации из 800 кадров надо сделать 640 000 таких ч/б картинок для определения соответствий каждого кадра к любому другому. Это не быстро. Скажем, при рендере кадра за 1 секунду (что считается хорошей скоростью) понадобилось бы больше недели непрерывной работы ПК только на рендер под одну анимацию из 800 кадров, а сцен под такие анимации уже заготовлено около 40. Ждать год, пока всё отрендрится я не собирался, так что оптимизировал рендер одной сцены до нескольких часов (около 5), что уже приемлемо.
Описания всего процесса оптимизаций и подобных нюансов хватит еще на пару статей, но здесь не об этом.
Обработав ч/б картинки парой скриптов получается такой файл с описанием результата:
[
{
"scene": 1,
"root": 100,
"frame": 1,
"value": 0.1138
},
{
"scene": 1,
"root": 100,
"frame": 10,
"value": 0.176
},
{
"scene": 1,
"root": 100,
"frame": 100,
"value": 0.995
},
Здесь:
- scene — номер сцены, идентификатор;
- root — номер исходного кадра;
- frame — номер целевого кадра;
- value — степень пересечения кадра frame с кадром root.
Обучение нейросети
Теперь, когда датасет можно считать готовым, пора переходить к обучению нейросети.
Первая проблема с которой я столкнулся состоит в том, что примеров в датасете с малым value гораздо больше, чем с большим. Если ничего с этим не делать, то нейросети проще всегда предсказывать числа около нуля и иметь точность больше 90 %.
Для решения проблемы такого смещения я разделяю датасет на 10 групп по диапазонам. В каждый момент времени обучения нейросеть получает равное число примеров каждой группы.
То есть, когда нейросеть учится на пачках (батчах) из 50 примеров, она получает 5 примеров из каждой группы.
Из этих же групп я получаю данные для проверки нейросети. Каждый пятый пример откладываем для тестовой выборки — нам же надо понимать, как будет работать сеть на данных, которых прежде не видела.
Чтобы убедиться, что нейросеть будет подстроиться под датасет сначала собираю небольшую обучающую выборку (один батч) из пятидесяти примеров и обучаем нейросеть, пока ее точность не приблизится к астрономическим значениям. Например, я допускаю, что погрешности в 1–5 % процентов будет достаточно. Значит, на уменьшенном датасете надо добиться переобучения меньше этих значений, например 0,1–1 %.
Через несколько экспериментов получается подобрать удачные параметры для старта обучения нейросети.
Теперь этой нейросети можно подать на обучение всю обучающую выборку и добиться погрешности всего около 11–12 %. Это хороший результат за небольшое время. К слову сказать, погрешность около 0, 50 или 100 % была бы плохим знаком. Но в данном случае понятно, что направление верное и можно улучшать полученный результат.
Теперь пора добавить дропауты, батч нормализацию и другие data science штуки для предотвращения переобучения нейросети. Получается красиво — погрешность меньше 9 %!
Прежде, чем описывать что я делал дальше, нужно напомнить про один важный момент: нейросеть учится батчами — набор примеров из обучающей выборки. Количество примеров в батче ограничивается или архитектурой нейросети (иногда бывают нужны батчи из одного или пары примеров) или памятью компьютера, на котором запускается обучение. Я использовал 50 примеров в батче.
К этому моменту мне стало интересно, если средняя ошибка в обучающей выборке около 9 %, то каков разброс. Может быть он в области 5–14 %, а может быть и в диапазоне 0–90 %. По графику погрешности этого не понять, поэтому я запускаю функцию predict() для всех примеров из обучающей выборки.
Результаты оказались интересными, как и последующее решение.
Выяснилось, что сеть ошибается редко. Лишь на 2 % примеров сеть в своих прогнозах ошибалась более, чем на 10 %.
Сначала я сохранил эти примеры с увеличенной погрешностью отдельно как «супер-сложные» и разбавлял ими каждый батч во время обучения. Общая точность нейросети выросла, но теперь погрешность больше 10 % стала проявляться на других примерах, но в целом таких случаев стало втрое меньше (менее 0,7 %). Это временное решение дало мне понять, что направление верное, хотя реализация пока не очень хороша.
Перед каждой сессией обучения батч формируется рандомно. Однако в Python у функции random можно задать веса — число для каждого из элементов, определяющее вероятность выбора этого элемента.
В качестве такого веса я использовал ранее просчитанную сложность примеров. Чем больше нейросеть ошибалась на том или ином примере, тем чаще этот пример попадает в батч.
Итак, у меня есть нейросеть с приемлемой погрешностью в 9 %. Ею размечаю сложность каждого из обучающих примеров и запускаю обучение подсовывая сложные примеры чаще. Тем чаще, чем сильнее была ошибка. Что б училась, а то че эт она!
Теперь основная фишка процесса обучения — пересчет сложности через каждые 1000 батчей одного процента датасета, такая контрольная работа у нейросети. А каждые 100 000 батчей запускаю полный пересчет сложности всех примеров, экзамен типа. Такая методика показалась наиболее сбалансированной, так как пересчет сложности всего датасета занимает существенное время (около 20–30 минут), а пересчет одного процента достаточно быстрый (лишь 15–20 секунд).
Такого подхода хватило что бы уменьшить погрешность с 9 % до 4,5. Для решения поставленной задачи такой точности более чем достаточно. Я доволен.
Заключение
Напомню, что получившаяся нейросеть (я называю ее «Surface Match») лишь промежуточное звено перед созданием нейросети восстанавливающей положение камер в 3D-сцене. Она пригодится и для улучшения датасета и для усиления последующих нейросетей.
Например, с ее помощью можно составить более полный датасет, в котором будут все подходящие кадры для определения положения камер в сцене.
Или можно добавить эту функциональность в новую нейросеть. Известно, что когда нейросеть решает несколько смежных задач, ее точность и обобщающая способность становится выше.
🤪 Кабытанибыла, впереди много интересных находок и достижений, чего и вам желаю!