В предыдущей заметке мы рассказали о том, как мы решали задачу из области промышленной дефектоскопии методами современного машинного зрения. В частности, мы упомянули, что одним из подходов к обогащению данных обучающей выборки является генератор синтетических данных. В этой заметке мы расскажем:
- как сделали такой генератор на основе Blender и Python,
- какие типы масок для задач компьютерного зрения вообще можно получить в Blender.
Заметка от партнера IT-центра МАИ и организатора магистерской программы “VR/AR & AI” — компании PHYGITALISM.
Введение
Напомним, что генератор фотореалистичных 3D моделей промышленных труб и их дефектов нужен был для того, чтобы обогатить набор обучающих данных для нейросетевых алгоритмов детекции дефектов на изображениях (дефекты труб на ТЭЦ, снятые при помощи беспилотников). Беспилотники и автоматическая детекция дефектов на практике должна минимизировать время и расходы на проведение сервисного обслуживания станции.
Использование синтетических данных потенциально может улучшить качество работы нейронных сетей и различных алгоритмов машинного обучения (больше данных для обучения — лучше качество). Однако, недостаточно добиться от генератора только количественного выигрыша по данным, немаловажно получить их в хорошем качестве. В случае, если распределение данных генератора будет отличаться от реальных, использование смешанного датасета приведет к ухудшению качества работы алгоритма.
Если данные сгенерированы корректно, то генератор позволяет:
- сократить объем необходимых реальных фотографий труб,
- получить данные, которые в реальности встречаются крайне редко и сделать набор данных более сбалансированным,
- получить большее число признаков (в данном примере, помимо изображения дефекта, мы получаем его 3D модель).
Поскольку генератор позволяет получать 3D объекты, он способен стать источником новых данных не только для алгоритмов классического компьютерного зрения (CV), но и для целого ряда задач геометрического глубокого обучения (3D ML, GDL).
Применение 3D ML подходов может дать преимущество при решении задач дефектоскопии, так как пространственные сканеры / камеры глубины (RGB-D, Lidar и пр.) позволяют находить менее очевидные человеческому глазу дефекты и реконструировать изучаемые объекты (например, вздутие трубы не всегда можно обнаружить, не потрогав трубу руками или чувствительным щупом).
Часть 1: Реализация генератора данных
Рис.1 Примеры данных, сгенерированных в проекте. Слева направо: меш труб, отрендеренные изображения с текстурами, битовые маски дефектов (авторазметка), ограничивающие прямоугольники дефектов (авторазметка).
Вся работа по созданию искусственного набора данных была осуществлена в Blender, с использованием скриптов на языке Python. Исключение составила лишь программа преобразования растровой разметки в формат Yolo, написанная на языке Rust.
В нашем проекте Python генерировал змеевики, а Rust получал разметку ржавчины.
Рис.2 Рабочее окно Blender с плагином генератора труб.
На начальных этапах работы тестировалось построение сцены (и трубы и сами дефекты) средствами полигонального моделирования. При таком подходе к генерации данных встает вопрос о том, как внести разнообразия в процесс генерации и о том, как удобнее создавать разметку, ведь отмечать поврежденные участки в меше группами вершин не самый удобный способ. Из этих соображений было принято решение объединить создание дефектов и разметки с помощью шейдеров.
Рис. 3 Пример сцены, из которой рендерились наборы изображений с разметкой поперечных трещин.
Задача создания генератора синтетических данных была разделена на следующие этапы:
- Настройка цифрового двойника камеры БПЛА с накамерным светом (рендеринг итоговых изображений должен позволять добиться реалистичности в синтетических данных).
- Создание инструмента для быстрого моделирования базовой геометрии труб.
- Настройка процедурных материалов для различных поверхностей труб (металлический блеск, ржавчина и пр.).
- Настройка процедурных материалов для различных дефектов труб (различные трещины, дыры, изгибы и пр.).
- Настройка процедурной анимации позиции камеры, освещения и материалов для создания разнообразных изображений из одной сцены.
- Настройка масок дефектов (битовые маски и ограничивающие прямоугольники для разных классов дефектов).
- Рендер итоговых сцен.
- Приведение разметки дефектов к форматам YOLO и MS COCO.
Настройка цифрового двойника камеры БПЛА с накамерным светом
Рис.4 Камера с “осветителем” на сцене в Blender.
Объект с самосветящимся материалом, имитирующий кольцевой осветитель, и всенаправленная лампа назначены дочерними объектами камеры. Размер сенсора, угол обзора, относительное отверстие объектива и разрешение получаемого изображения настроены в соответствии с реальными характеристиками камеры DJI Mavic 2 Zoom.
Инструмент для быстрого моделирования базовой геометрии труб
Рис.5 Сцена, наполненная змеевиками труб, полученными с помощью скрипта из этого раздела.
В первую очередь нужно отметить, что вся геометрия труб строилась по NURB’s без прямой конвертации в полигональные модели (Про использование NURBS в 3D моделировании на хабре писали в этой заметке). Все дефекты, в том числе, геометрические создавались посредством материалов на этапе рендера.
Не было никакой необходимости выстраивать совершенно новую сцену в отношении геометрии для каждого кадра, достаточно было сделать несколько заготовок и менять ракурсы, освещение и материалы. Выяснилось, что наиболее распространённая схема размещения труб — простой массив. Создать массив параллельных труб — не представляло никакой сложности, для этого есть модификатор Array. Другой распространенной схемой оказались змеевики, в том числе облегающие цилиндрические поверхности. Для быстрого создания таких труб был написан скрипт на языке Python, в котором можно настроить радиус цилиндрической поверхности, шаг змеевика, его направление и количество повторений.
import bpy
import time
from math import sin, cos, pi, radians
# Генерация змеевиков в плоскости внутри прямоугольника со сторонами
# sizeX, sizeY
def create_flat_curve(sizeX, sizeY):
points = []
for y in range(sizeY):
for x in range(sizeX):
if y % 2 == 0:
point = [x, y]
else:
point = [sizeX - x - 1, y]
points.append(point)
curve_name = "Pipe_Flat_" + str(time.time_ns())
curveData = bpy.data.curves.new(curve_name, type='CURVE')
curveData.dimensions = '3D'
curveData.resolution_u = 2
polyline = curveData.splines.new('NURBS')
polyline.points.add(len(points)-1)
for i, point in enumerate(points):
x,y = point
polyline.points[i].co = (x, y, 0, 1)
curveData.bevel_depth = 0.4
#polyline.use_endpoint_u = True
curveOB = bpy.data.objects.new(curve_name, curveData)
bpy.context.collection.objects.link(curveOB)
# Генерация змеевиков в пространстве
def create_cyl_curve(radius, angle, height, density, horizontal):
phi = radians(angle)
steps = int(density * phi / (pi*2))
print("Steps:", steps)
points = []
if horizontal:
for z in range(height):
for step in range(steps):
if z % 2 == 0:
x = radius * cos(step * phi / steps)
y = radius * sin(step * phi / steps)
else:
x = radius * cos(phi - (step+1) * phi / steps)
y = radius * sin(phi - (step+1) * phi / steps)
point = [x, y, z]
points.append(point)
if not horizontal:
for step in range(steps):
for z in range(height):
x = radius * cos(step * phi / steps)
y = radius * sin(step * phi / steps)
if step % 2 == 0:
point = [x, y, height-z-1]
else:
point = [x, y, z]
points.append(point)
print("Points:", len(points))
curve_name = "Pipe_Cylinder_" + str(time.time_ns())
curveData = bpy.data.curves.new(curve_name, type='CURVE')
curveData.dimensions = '3D'
curveData.resolution_u = 2
polyline = curveData.splines.new('NURBS')
polyline.points.add(len(points)-1)
for i, point in enumerate(points):
x,y,z = point
polyline.points[i].co = (x, y, z, 1)
curveData.bevel_depth = 0.3
#polyline.use_endpoint_u = True
curveOB = bpy.data.objects.new(curve_name, curveData)
bpy.context.collection.objects.link(curveOB)
Настройка материалов поверхностей труб
От использования готовых наборов текстур из изображений пришлось отказаться по нескольким причинам:
- необходима корректная проекция изображений на геометрию (что само по себе нетривиальная задача),
- необходима повторяемость рисунка (здесь мы ограничены информацией из изображений),
- такие текстуры имеют предел детализации из-за разрешения исходных изображений.
Запекание сгенерированных текстур, например из Substance Painter, было исключено чтобы не множить инструменты и сущности (такая вот бритва Оккама у нас вышла).
Рис.6 Группа нод (назовем ее “супернодой”) для настройки материалов, объединенные в одну большую ноду.
Все ноды базовых материалов были объединены в группу с выведенными в интерфейс основными параметрами. В зависимости от конкретной сцены к тем или иным параметрам материала подключались генераторы псевдо-случайных чисел, собранные из ноды белого шума, произвольного индекса объекта, анимированного значения и математических нод.
Здесь и далее для конструирования шейдеров используется интерфейс с нодами, поскольку разработчик данного решения CG художник, и этот подход был для него предпочтительным =)
Рис. 7 Содержание суперноды из рис.6: материалы внутри группы собирались преимущественно из шумов и градиентов.
Настройка материалов дефектов труб
Рис.8 Пример сгенерированных труб с дефектами коррозии (слева) и цветами побежалости (справа).
Такие дефекты, как коррозия и цвета побежалости, создавались непосредственно из шума Перлина, градиентов и смещения (Displacement) геометрии модели по нормали к поверхности (само смещение производилась в шейдере, при этом геометрия неподвижна).
Рис.9 Создание дефекта “Разрыв трубы” в Blender.
В основе каждой трещины лежит процедурная текстура — сферический градиент (трещины имеют форму эллипса, подверженного многочисленным деформациям через изменение его UV координат). Границы трещин подвержены, как и в случае с коррозией — смещениями по нормали. Повреждённая часть визуализируется шейдером прозрачности, поэтому в зависимости от освещения через трещины иногда можно разглядеть тыльную поверхность трубы.
Рис.10 Создание дефекта “Выход трубы из ряда” в Blender.
Для таких дефектов, как “выход трубы из ряда” и “разрыв”, использовалось векторное смещение по выбранной оси в системе координат объекта. Создание такого чисто геометрического результата средствами шейдеров обусловлено удобством вывода данных для разметки как значения материала (примеры таких разметок смотри во второй части заметки).
Анимация камеры, освещение и материалы
Для получения набора разнообразных изображений из одной сцены мы анимировали позицию и поворот камеры, яркость источников света и параметры материалов в одном ключе с использованием анимационного модификатора Noise с заданными пороговыми значениями. Таким образом, можно было не беспокоиться о количестве кадров последующего рендера, ведь сколько бы их не оказалось, каждый был уникальным безо всяких закономерностей.
Рис.11 Применение шума на анимационных кривых позиции, поворота камеры, интенсивности и позиции источника света для процедурной съемки сцены.
Настройка масок дефектов
Для вывода черно-белых масок разметки дефектов использовался канал Arbitrary Output Value (AOV), в ноду которого подавался коэффициент смешивания базового материала и материала дефекта. Иногда использовалась бинарная математическая нода Greater Than (на выходе 0, если входное значение меньше порогового, иначе 1).
Рендер
В композиторе было настроено две выводящих ноды: одна сохраняла изображение, вторая маску. Сцены рендерились как анимированные, то есть на каждый кадр в заданном диапазоне сохранялось два файла. Изображения отправлялись в директорию с данными согласно соглашениям разметки Yolo, одноименные маски сохранялись во временной директории для последующего преобразования в разметку.
Рис.12 Директории с сохраненными изображениями (слева) и разметкой (справа).
Приведение разметки к формату YOLO
Формат разметки YOLO предполагает обозначение участков изображения ограничивающими прямоугольниками. Текстовый файл должен содержать нормированные координаты центров ограничивающих прямоугольников и их габариты. Для получения такого вида разметки была написана программа, рекурсивно проходящая по соседним пикселям маски, значения которых отличны от нуля, и сохраняющая минимальные и максимальные координаты связанных пикселей, после чего абсолютные координаты вершин прямоугольников нормализовались. Выбор языка Rust для написания этой программы был обусловлен скоростью выполнения и возможностью с лёгкостью реализовать одновременную обработку нескольких изображений на разных потоках процессора. Ниже приведен код на Python для поиска группы пикселей изображения, относящейся к одному дефекту.
Рис.13 Сгенерированные трубы с дефектом трещины (слева) и соответствующая маска для данного изображения (справа).
import bpy
import colorsys
image = bpy.data.images["two_cubes.png"]
sizeX = 64
sizeY = 64
image.scale(sizeX, sizeY)
pixels = image.pixels
size = [sizeX, sizeY]
print(len(pixels))
grid = [[ [0] for y in range(size[1])] for x in range(size[0])]
print("LEN:", len(grid))
print(len(pixels)/4, " == ", sizeX*sizeY)
def rgb_to_hex(rgb):
hex_string = ""
for c in rgb:
hex_string += str(hex(max(min(int(c * 255 + 0.5), 255), 0)))[2::]
return hex_string.upper()
def search_neighbours(grid, x, y, color, l,b,r,t):
grid[x][y] = 0
#print("FROM", x, y)
if x < l:
l = x
if x > r:
r = x
if y < b:
b = y
if y > t:
t = y
if x < size[0]-1:
if grid[x+1][y] == color:
#print("RIGHT")
l,b,r,t = search_neighbours(grid, x+1, y, color, l, b, r, t)
if y < size[1]:
if grid[x][y+1] == color:
#print("UP")
l,b,r,t = search_neighbours(grid, x, y+1, color, l, b, r, t)
if x > 0:
if grid[x-1][y] == color:
#print("LEFT")
l,b,r,t = search_neighbours(grid, x-1, y, color, l, b, r, t)
if y > 0:
if grid[x][y-1] == color:
#print("DOWN")
l,b,r,t = search_neighbours(grid, x, y-1, color, l, b, r, t)
return (l,b,r,t)
for i in range(0, int(len(pixels) / 4)):
if pixels[i*4] > 0:#sum(pixels[i*4:i*4+3]) > 0:
x = (i) % size[1]
y = int(i / size[1])
#color = pixels[i*4:i*4+2]
#hex_col = rgb_to_hex(pixels[i*4:i*4+3])
grid[x][y] = 1
#print(x,y)
print("GRID FINISHED")
color = 1
islands = []
for y in range(size[1]):
for x in range(size[0]):
if grid[x][y] == color:
"LOOKING FOR NEW ISLAND"
print(search_neighbours(grid, x, y, color, x, y, x, y))
Часть 2: Создание масок и разметки в Blender
Рис.14 Исходная тестовая сцена в Blender.
В этой части мы постараемся больше показывать, нежели рассказывать. На основе одной тестовой сцены в Blender (рис. 14) покажем, какие возможно сгенерировать полезные для задач компьютерного зрения изображения из трехмерной сцены.
Итоговое изображение в Blender получается как сумма различных “пассов” (проход лучей в сцене до момента попадания в пиксель итогового изображения): то есть для каждого пикселя мы складываем его интенсивность из нескольких компонентов (недавно вышло вот такое хорошее образовательное видео, которое может помочь разобраться в азах компьютерной графики тем, кто только начинает познавать эту науку).
Рис. 15 Combined Pass: итоговый рендер сцены с учетом всех компонент.
Рис.16 Depth Pass: карта глубины для данной сцены.
Канал глубины позволяет передать информацию о позициях пикселей в пространстве через их удалённость от камеры. Используя пару изображений Combined+Depth можно тренировать нейросети, восстанавливающие трёхмерные сцены из изображений.
Рис. 17 Normal Pass: карта нормалей сцены.
Канал нормалей снабжает изображение информацией о нормалях поверхностей, что дает возможность не только менять освещение при постобработке, но и понимать форму объектов (хотя информации об их расположении в пространстве относительно друг друга отсутствует).
Пассы различных типов лучей рендер-движка Cycles могут предоставить разметку для разных типов материалов, что тоже может быть полезным при анализе изображений.
Рис. 18 Diffuse Color Pass: рассеивающая составляющая материалов (например высокое значение у зеркальных металлических поверхностей и низкое значение у шероховатых диэлектриков).
Рис. 19 Glossy Color Pass: отражающая способность материала (блики).
Рис. 20 Emission Pass: самосвечение материалов.
Рис. 21 Ambient Occlusion Pass: суммарная интенсивность света в каждой точке.
Рис. 22 Shadow Pass: тени (для каждой точки пространства просчитываются относительно источников света на сцене).
Одной из распространенных задач в генерации синтетических данных является создание фото-реалистичных изображений, сопровождающихся разметкой определенных объектов или их частей. Blender предлагает несколько способов создания масок, которые могут быть использованы в качестве разметки.
Маски можно создавать на индивидуальные объекты и их группы, материалы, а также на произвольные параметры материалов.
Рис. 23 Cryptomatte Object Pass: разметка различных объектов случайным цветом.
Рис. 24 Cryptomatte Material Pass: разметка различных материалов случайными цветами.
В пассах Cryptomatte всем объектам и материалам присваиваются уникальные цвета.
Допустим, мы хотим создать две маски: на одной будут отмечены все обезьянки, на другой геометрические примитивы. Всем объектам нужно назначить Object ID (он же Pass Index), для обезьянок это будет 1, для примитивов 2, 0 останется для пола. Для удобства объекты разных классов можно распределить по коллекциям и написать скрипт, который присваивает всем объектам коллекции свой Object ID.
Чтобы получить необходимую маску, нужно использовать ноду ID Mask в композиторе.
Также в композиторе можно настроить одновременный вывод пассов и масок в отдельные файлы (см. рисунок ниже).
Рис. 25 monkeys mask: маскирование объектов класса “обезьяна”.
Рис. 26 primitives mask: маскирование объектов класса “геометрические примитивы”.
Если мы хотим отметить каждый интересующий нас объект по отдельности, им нужно присвоить свои уникальные Object ID.
Blender позволяет выводить в изображение любые параметры материалов. Для этого в настройках пассов нужно создать слой AOV, в который будут сохраняться значения из шейдеров в виде RGB или числа с плавающей точкой. В соответствующем материале нужно создать ноду Output/AOV и присвоить ей имя слоя.
Разберём этот материал:
Рис. 27 Combined Pass, умноженный на маску материала.
Здесь текстура шума подана на параметр Scale шейдера подповерхностного рассеивания (см. рис. выше). Допустим, мы хотим получить маску на те области поверхности, в которых параметр Scale больше 1.8.
В результате получим маску:
Проделаем теперь подобное с другим материалом и выделим красные области, подав в AOV фактор смешивания синего и красного цветов:
AOV даёт более широкие возможности для разметки, этот подход можно использовать для обозначения областей объектов, подверженных смещению (Displacement). На объектах на изображении ниже использовалось смещение по нормали поверхности для имитации повреждений:
Нужно отметить, что на этих объектах разные материалы, но для каждого из них включен вывод значения смещения в один канал AOV, значения при этом складываются.
Отдельным примером может служить использование AOV для разметки “повреждённых” областей объектов, на которых основной материал заменяется на прозрачный. На этой обезьянке применено трёхмерное смещение (Vector Displacement), то есть каждый участок, подверженный такому эффекту смещается не по нормали к исходной поверхности, а по трём осям согласно значениям из цвета, подаваемого на вход (R,G и B соответствуют X, Y и Z).
Если же мы хотим обозначить в разметке только поврежденные участки видимой поверхности, иными словами края, нужно выбрать области разметки, значение в которых не превышает пороговое.
Используя драйверы, можно передавать в шейдер параметры любых других объектов и, как следствие, создавать разметку не только для видимых элементов и настроек материалов, но и для чего угодно в сцене, например для скорости одного объекта относительно другого или имитировать карты температур.
Заключение
Blender обладает богатым набором инструментов для создания изображений из трёхмерных сцен, которые также можно использовать для генерации и визуализации многомерных данных. Каналы и пассы позволяют создавать маски для участков изображения, представляющих интерес для разметки. Выгодной особенностью Blender также является возможность расширения его функционала за счет скриптов на языке Python.
В будущем мы постараемся рассказать и про другие наши эксперименты связанные с 3D ML вобще и с Blender в частности, а пока можете подписаться на наш канал в Telegram — 3D ML, где мы рассказываем несколько раз в неделю о новостях и достижениях в этой науке.