Привет, Хабр! Меня зовут Глеб. Я работаю в компании Friflex над проектами по оцифровке спорта. Работая над idChess (приложением для распознавания и аналитики шахматных партий), мы расширяем наш датасет синтетическими данными. В качестве движка используем Blender. В этой статье рассмотрим основы взаимодействия с объектами, получение доступа через API, перемещение, масштабирование и вращение.

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

Далее в статье мы рассмотрим подробнее работу над объектами в Blender.

Интерфейс

Чтобы начать работу с API блендера, откроем python-консоль внутри приложения. Через нее мы будем запускать наши скрипты и наблюдать изменения на сцене.

Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

Помимо консоли, в blender есть текстовый редактор, который позволяет запускать код прямо в приложении. Более подробно про редактор можно посмотреть тут

Если планируете импортировать или создавать свои библиотеки, то имейте в виду, что blender использует свое виртуальное окружение. Доступ к python-библиотекам можно получить по следующему пути: /blender/3.0/python/lib/python3.9

Объекты и коллекции

Список объектов и коллекций, с которыми мы можем работать, представлен в этом блоке:

Все объекты в списке наследуются от класса bpy.types.Object. Разберемся, как получить доступ к экземплярам.

cube = bpy.data.objects['Cube']

bpy.data.objects ведет себя, как словарь: доступны методы values, keys, items, get и т.д. А ключами являются названия объектов.

А что если мы хотим получить объекты, содержащиеся в какой-нибудь коллекции? Просто используем другой атрибут модуля bpy.data.

bpy.data.collections['Collection'].all_objects.values()
# [bpy.data.objects['Cube'], bpy.data.objects['Light'], bpy.data.objects['Camera']]

Заметим, что коллекции похожи на обычные папки за одним исключением: содержимым коллекции являются все вложенные в неё объекты — даже те, которые содержатся в дочерних коллекциях.

Важно: свойства, которые мы рассмотрим далее, являются дескрипторами. Другими словами, чтобы трансформировать объект достаточно изменить его свойство.

location

По умолчанию это свойство отвечает за смещение относительно центра сцены. Попробуем переместить объект «Cube».

print(cube.location)
# <Vector (0.0000, 0.0000, 0.0000)>
cube.location = (1, 0, 2)
# Эквивалентно
cube.location.xz = (1, 2)
# Эквивалентно
cube.location.x = 1
cube.location.z = 2
# Эквивалентно
cube.location[0] = 1
cube.location[2] = 2
print(cube.location)
# <Vector (1.0000, 0.0000, 2.0000)>
Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

dimensions

Это свойство позволяет растягивать и сжимать объект вдоль осей. Dimensions возвращает значения в единицах измерения (по умолчанию это метры).

print(cube.dimensions)
# <Vector (2.0000, 2.0000, 2.0000)>
cube.dimensions = (4, 2, 2)
# Эквивалентно
cube.dimensions.x = 4
# Эквивалентно
cube.dimensions[0] = 4
print(cube.dimensions)
# <Vector (4.0000, 2.0000, 2.0000)>

Однако заметим, что для некоторых объектов dimension работать не будет. Об этом мы поговорим далее, а пока посмотрим на простой пример.

camera = bpy.data.objects['Camera']
print(camera.dimensions)
# <Vector (0.0000, 0.0000, 0.0000)>
camera.dimensions.x = 2
print(camera.dimensions)
# <Vector (0.0000, 0.0000, 0.0000)>
Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

scale

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

print(cube.scale)
# <Vector (1.0000, 1.0000, 1.0000)>
cube.scale = Vector((2, 1, 1)) # Используется mathutils.Vector, встроенный в blender
# Эквиалентно
cube.scale.x = 2
Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

rotation_euler

Для вращения объекта используется свойство rotation_euler. Оно немного отличается от location, dimensions и scale.

import math
print(cube.rotation_euler)
# <Euler (x=0.0000, y=0.0000, z=0.0000), order='XYZ'>
cube.rotation_euler = (
    math.radians(60),
    math.radians(45),
    math.radians(30)
    )
# Эквивалентно
cube.rotation_euler.x = math.radians(60)
cube.rotation_euler.y = math.radians(45)
cube.rotation_euler.z = math.radians(30)
print(cube.rotation_euler)
# <Euler (x=1.0472, y=0.7854, z=0.5236), order='XYZ'>

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

rotation = Euler((0, 0, math.radians(10))) # Используется mathutils.Euler, встроенный в blender
cube.rotation_euler.rotate(rotation)

При вращении объекта с помощью углов Эйлера, нужно помнить об одной их особенности — gimble lock. Про это можно прочитать тут.

Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

bound_box

Это свойство доступно только для чтения. Оно возвращает 8 точек, описывающих границы объекта. В отличии от location, dimensions и scale, bound_box не использует mathutils.Vector.

print(type(cube.bound_box))
# <class 'bpy_prop_array'>
print(type(cube.bound_box[0]))
# <class 'bpy_prop_array'>

Для удобства преобразуем точки в mathutils.Vector:

def bounding_box(obj):
    return [Vector(point) for point in obj.bound_box]

У этого свойства есть еще одна небольшая особенность: по умолчанию координаты граничных точек рассчитываются относительно центра объекта, а не сцены. Чтобы привести координаты к общему центру, можно использовать следующую функцию.

def bounding_box_world(obj):
    return [obj.matrix_world @ point for point in bounding_box(obj)]

data

Ранее мы увидели, что dimensions применяется не ко всем объектам. Но что это значит? Если мы посмотрим на список объектов в нашем проекте, то заметим, что у нас есть три разные по своей природе сущности. Логично, что у камеры будет набор уникальных методов, которого не будет, например, у источника света. Но как это реализуется, если объекты Camera, Cube, Light являются экземплярами общего класса bpy.types.Object.

camera = bpy.data.objects['Camera']
camera.location = (10, 0, 0) # Меняем положения (Свойство объекта)
camera.data.lens = 5 # Меняем фокусное расстояние (Свойство камеры)

В свойстве data находится экземпляр другого класса, предоставляющий доступ к специализированным свойствам и методам. В примере выше, мы переместили камеру (свойство bpy.types.Object) и изменили фокусное расстояние (свойство bpy.types.Camera).

hide_viewport & hide_render

Эти свойства позволяют скрыть объект. hide_viewport отвечает за отображение объекта на сцене, а hide_render — за отображение на конечном изображении.

cube.hide_render = True
cube.hide_viewport = True
Это гифка. Чтобы посмотреть, нажмите на изображение
Это гифка. Чтобы посмотреть, нажмите на изображение

The end

Вы работали с blender и хотите обсудить прочитанное? Или поделиться своим опытом и знаниями, чтобы сделать статью более полной и полезной для новичков? Жду вас в комментариях!

Комментарии (4)


  1. ArXen42
    27.05.2022 19:32

    Как у blender со скоростью рендеринга подобных датасетов и работой с большими сценами?

    Для своей задачи сегментации тоже пробую синтетический подход, но пока сделал выбор в сторону Unreal Engine из-за доступности готовых ассетов и даже целых сцен (мне нужно много "фото" объектов в максимально разнообразных окружениях с реалистичной отрисовкой теней, отражений и т.д.), но столкнулся с несколькими проблемами и неудобствами:

    1. Если каждый кадр из скрипта менять позицию камеры, освещение, сегментируемый объект и прочие элементы окружения случайным образом, в реалтайм рендере c Lumen часто появляются артефакты. Впрочем, большую часть этих проблем удалось решить рендером в MovieRenderQueue(рендер фильмов), большим количеством spatial samples и выключением temporal samples в его настройках, а также отключением "выдержки" у виртуальной камеры. Тем не менее, чувствуется, что движок на такое издевательство над ним не очень рассчитан.

    2. Не самая лучшая работа с прозрачными материалами. В первую очередь относится к lumen - например, в первые же дни работы с движком попался на крупный баг с отражением неба от полупрозрачных объектов вроде стекла даже сквозь стены. Пример рендеров. Думаю Cycles Render в блендере с таким намного лучше справляется. В моём случае хотелось предсказывать прозрачность объекта для его точного вырезания с фона, но в unreal очень ограниченная отрисовка полупрозрачных объектов с альфа каналом, вероятно придется в чем-то другом это делать.

    3. Path Tracer отрисовывает всё еще быстро по сравнению с blender (пара тысяч рендеров за ночь на моей RTX2070) и красиво, но у меня почему-то имеет свойство падать при запуске или в процессе рендера если сцена достаточно тяжелая. Например, он в принципе не хочет работать если добавить к стандартному archviz примеру ландшафт для создания простого пейзажа за окном, падает с ошибкой в VertexFactory.

    4. Комбинировать C++ и blueprints бывает довольно муторно, особенно если хочется из C++ получить какие-то данные от blueprint'а или вызвать его метод. Все проблемы конечно удалось решить, но возможно в каком-нибудь unity было бы проще.

    Тем не менее, в остальном UE очень радует. По сравнению с blender привел бы такие плюсы:

    1. Большой маркетплейс с готовыми ассетами. Бесплатных не очень много, правда, но есть много платных по цене обычных игр. В сам движок встроено много готового, например за получаса настраивается небо с облаками. Из скрипта потом можно менять время суток/геопозицию и получать разное освещение в т.ч. с красивыми закатами, что позволяет легко разнообразить датасет.

    2. Достаточно простой и удобный редактор/компоновщик сцены с реалтайм предпросмотром. Blender, если я правильно понимаю, всё таки больше ориентирован на разработку отдельных 3D моделей, поэтому с нормальной отрисовкой во viewport и быстрым прототипированием сцен там сложнее. В моём случае важно иметь возможность быстро добавлять новые окружения/объекты для увеличения датасета без необходимости часами всё настраивать, даже если результат будет иметь косметические дефекты (не особо влияющие на качество ML модели).

    3. Для UE4 есть плагины вроде UnrealCV. Я, правда, не использовал, но возможно может помочь сэкономить время для разработки генерации сложных датасетов.


    1. domix32
      28.05.2022 17:05
      +1

      Большой маркетплейс с готовыми ассетами

      Блендер вроде не держит собственных маркетплейсов, да им особо и не надо, т.к. модели экспортируются/импортируются в разные форматы. Поэтому можно легко найти больше моделей, например маркетплейсы раз, два . Ещё на каком-нибудь ArtStation можно посмотреть/поискать авторов.

       Из скрипта потом можно менять время суток/геопозицию 

      В игровом еженедельнике периодически появляются различные генеративные штуки, в основном всякое окружение, типа домов, деревьев и т.п. Вероятно можно найти что-то аналогичное.

       Blender, если я правильно понимаю, всё таки больше ориентирован на разработку отдельных 3D моделей, поэтому с нормальной отрисовкой во viewport и быстрым прототипированием сцен там сложнее. 

      Ну, не, гляньте те же ролики от Blender Foundation. Там полный пайплайн видеопроизводства вроде поддерживается, некоторые используют его для прототипирования сцен, но там действительно не слишком много деталей.


  1. kovserg
    28.05.2022 11:49

    В blender есть визуальный редактор для генерации геометрии
    www.youtube.com/watch?v=52UYqe3zdxQ
    www.youtube.com/watch?v=kw9d7piY-Ys
    ps: конечно что на питоне, что с geometry nodes — удобство работы с ними сомнительное.


  1. MonkeyWatchingYou
    30.05.2022 14:28

    Спасибо за статью!

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

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

    Темболее многое можно прототипировать прямо в блендере и дополнять кодом.