Как управлять роботом с помощью Python
3D-симуляция и управление движением с помощью PyBullet
Делиться

Вступление
PyBullet — это платформа моделирования с открытым исходным кодом, созданная Facebook и предназначенная для обучения физических агентов (например, роботов) в трёхмерной среде. Она предоставляет физический движок для моделирования как твёрдых, так и мягких тел. Она широко используется в робототехнике, искусственном интеллекте и разработке игр.
Роботизированные манипуляторы пользуются большой популярностью благодаря своей скорости, точности и способности работать в опасных условиях. Они используются для таких задач, как сварка, сборка и погрузка-разгрузка материалов, особенно в промышленных условиях (например, на производстве).
Робот может выполнить задачу двумя способами:
- Ручное управление — требует явного программирования каждого действия человеком. Лучше подходит для фиксированных задач, но имеет проблемы с неопределенностью и требует утомительной настройки параметров для каждого нового сценария.
- Искусственный интеллект позволяет роботу методом проб и ошибок изучать оптимальные действия для достижения цели. Таким образом, он может адаптироваться к изменяющимся условиям, обучаясь на основе поощрений и штрафов без заранее заданного плана (обучение с подкреплением).
В этой статье я покажу, как создать трёхмерную среду с помощью PyBullet для ручного управления роботизированной рукой . Я представлю несколько полезных фрагментов кода на Python, которые можно легко применить в других подобных случаях (просто скопируйте, вставьте и запустите), и прокомментирую каждую строку кода, чтобы вы могли повторить этот пример.
Настраивать
PyBullet можно легко установить с помощью одной из следующих команд (если pip не работает, conda определенно должен сработать):
pip install pybullet conda install -c conda-forge pybullet
PyBullet поставляется с набором предустановленных файлов URDF (Unified Robot Description Format), которые представляют собой XML-схемы, описывающие физическую структуру объектов в трехмерной среде (например, куба, сферы, робота).
импорт pybullet как p импорт pybullet_data время импорта ## настройка p.connect(p.GUI) p.resetSimulation() p.setGravity(gravX=0, gravY=0, gravZ=-9.8) p.setAdditionalSearchPath(path=pybullet_data.getDataPath()) ## загрузка URDF plane = p.loadURDF(«plane.urdf») cube = p.loadURDF(«cube.urdf», basePosition=[0,0,0.1], globalScaling=0.5, useFixedBase=True) obj = p.loadURDF(«cube_small.urdf», basePosition=[1,1,0.1], globalScaling=1.5) p.changeVisualShape(objectUniqueId=obj, linkIndex=-1, rgbaColor=[1,0,0,1]) #красный пока p.isConnected(): p.setRealTimeSimulation(True)

Давайте рассмотрим код выше:
- когда вы сможете подключиться к физическому движку, вам необходимо указать, хотите ли вы открыть графический интерфейс (p.GUI) или нет (p.DIRECT).
- Декартово пространство имеет 3 измерения : ось x (вперед/назад), ось y (влево/вправо), ось z (вверх/вниз).
- Обычно для имитации гравитации Земли устанавливают значение (x=0, y=0, z=-9,8), но его можно изменить в зависимости от цели моделирования.
- Обычно для создания пола требуется импортировать плоскость , иначе объекты будут падать бесконечно. Если вы хотите, чтобы объект был закреплён на полу, установите useFixedBase=True (по умолчанию False). Я импортировал объекты с basePosition=[0,0,0.1], что означает, что они находятся на высоте 10 см над землёй.
- Симуляция может быть визуализирована в реальном времени с помощью p.setRealTimeSimulation(True) или быстрее (с использованием процессорного времени) с помощью p.stepSimulation(). Другой способ установить режим реального времени:
import time for _ in range(240): #240 временной шаг, обычно используемый при разработке видеоигр p.stepSimulation() #добавление шага физики (скорость ЦП = 0,1 секунды) time.sleep(1/240) #замедление до реального времени (240 шагов × 1/240 секунды sleep = 1 секунда)
Робот
Сейчас наше трёхмерное окружение состоит из маленького объекта (маленького красного кубика) и стола (большого куба), закреплённого на полу (плоскости). Я добавлю роботизированную руку , которая будет поднимать меньший объект и класть его на стол.
В PyBullet по умолчанию используется роботизированная рука, созданная по образцу робота Franka Panda.
robo = p.loadURDF(«franka_panda/panda.urdf», basePosition=[1,0,0.1], useFixedBase=True)

Первое, что нужно сделать, это проанализировать связи (твердые части) и сочленения (соединения между двумя жёсткими частями) робота. Для этого можно просто использовать p.DIRECT, так как графический интерфейс не нужен.
import pybullet as p import pybullet_data ## настройка p.connect(p.DIRECT) p.resetSimulation() p.setGravity(gravX=0, gravY=0, gravZ=-9.8) p.setAdditionalSearchPath(path=pybullet_data.getDataPath()) ## загрузка URDF robo = p.loadURDF(«franka_panda/panda.urdf», basePosition=[1,0,0.1], useFixedBase=True) ## сочленения dic_info = { 0:»индекс сочленения», #начинается с 0 1:»имя сочленения», 2:»тип сочленения», #0=вращательное (вращательное), 1=призматическое (скользящее), 4=фиксированное 3:»индекс вектора состояния», 4:»индекс вектора скорости», 5:»флаги», #nvm всегда 0 6:»демпфирование сочленения», 7:»трение сочленения», #коэффициент 8:»нижнийпредел сочленения», #минимальный угол 9:»верхнийпредел сочленения», #максимальный угол 10:»макссила сочленения», #максимально допустимая сила 11:»максскорость сочленения», #максимальная скорость 12:»имя ссылки», #дочерняя ссылка, подключенная к этому сочленению 13:»ось сочленения», 14:»положение родительского кадра», #позиция 15:»положение родительского кадра», #ориентация 16:»индекс родительского кадра» #−1 = основание } for i in range(p.getNumJoints(bodyUniqueId=robo)): joint_info = p.getJointInfo(bodyUniqueId=robo, jointIndex=i) print(f»— СОЕДИНЕНИЕ {i} —«) print({dic_info[k]:joint_info[k] for k in dic_info.keys()}) ## links for i in range(p.getNumJoints(robo)): link_name = p.getJointInfo(robo, i)[12].decode('utf-8') #field 12=»Имя ссылки» dyn = p.getDynamicsInfo(robo, i) pos, orn, *_ = p.getLinkState(robo, i) dic_info = {«Масса»:dyn[0], «Трение»:dyn[1], «Положение»:pos, «Ориентация»:orn} print(f»— LINK {i}: {link_name} —«) print(dic_info)

У каждого робота есть « база » — корневая часть его тела, которая соединяет всё (например, позвоночник нашего скелета). Судя по результату кода выше, мы знаем, что у роботизированной руки 12 сочленений и 12 звеньев. Они соединены следующим образом:

Управление движением
Чтобы заставить робота что-то сделать, нужно двигать его суставы. Существует три основных типа управления, каждый из которых основан на законах движения Ньютона:
- Управление положением : по сути, вы говорите роботу «иди сюда». Технически, вы задаёте целевое положение , а затем прикладываете силу, чтобы постепенно перемещать сочленение из текущего положения к цели. По мере приближения к цели прилагаемая сила уменьшается, чтобы избежать перекоса, и в конечном итоге идеально уравновешивает силы сопротивления (например, трение и гравитацию), удерживая сочленение на месте.
- Управление скоростью : по сути, вы говорите роботу: «Двигайся с этой скоростью». Технически, вы задаёте целевую скорость и прикладываете силу, чтобы постепенно довести её от нуля до целевой, а затем поддерживаете её, уравновешивая приложенную силу и силы сопротивления (например, трение, гравитацию).
- Управление силой/крутящим моментом : по сути, вы «толкаете робота и смотрите, что происходит». Технически, вы непосредственно задаёте силу, прикладываемую к сочленению, а результирующее движение зависит исключительно от массы объекта, его инерции и окружающей среды. Кстати, в физике слово « сила » используется для обозначения линейного движения (толкание/тяга), а « крутящий момент » — для обозначения вращательного движения (поворот/скручивание).
Мы можем использовать функцию p.setJointMotorControl2() для перемещения одного сочленения и функцию p.setJointMotorControlArray() для приложения силы к нескольким сочленениям одновременно. Например, я буду управлять положением, задавая случайную цель для каждого сочленения руки.
## настройка p.connect(p.GUI) p.resetSimulation() p.setGravity(gravX=0, gravY=0, gravZ=-9.8) p.setAdditionalSearchPath(path=pybullet_data.getDataPath()) ## загрузка URDF plane = p.loadURDF(«plane.urdf») cube = p.loadURDF(«cube.urdf», basePosition=[0,0,0.1], globalScaling=0.5, useFixedBase=True) robo = p.loadURDF(«franka_panda/panda.urdf», basePosition=[1,0,0.1], useFixedBase=True) obj = p.loadURDF(«cube_small.urdf», basePosition=[1,1,0.1], globalScaling=1.5) p.changeVisualShape(objectUniqueId=obj, linkIndex=-1, rgbaColor=[1,0,0,1]) #красный ## перемещение рук joints = [0,1,2,3,4,5,6] target_positions = [1,1,1,1,1,1,1] #<--случайные числа p.setJointMotorControlArray(bodyUniqueId=robo, jointIndices=joints, controlMode=p.POSITION_CONTROL, targetPositions=target_positions, forces=[50]*len(joints)) for _ in range(240*3): p.stepSimulation() time.sleep(1/240)

Настоящий вопрос заключается в следующем: « Каково правильное целевое положение для каждого сочленения? ». Ответ кроется в обратной кинематике — математическом процессе расчёта параметров, необходимых для размещения робота в заданном положении и ориентации относительно начальной точки. Каждое сочленение определяет угол, и вам не хочется вручную угадывать углы нескольких сочленений. Поэтому мы попросим PyBullet определить целевые положения в декартовом пространстве с помощью функции p.calculateInverseKinematics().
obj_position, _ = p.getBasePositionAndOrientation(obj) obj_position = list(obj_position) target_positions = p.calculateInverseKinematics( bodyUniqueId=robo, endEffectorLinkIndex=11, #связь захвата цели targetPosition=[obj_position[0], obj_position[1], obj_position[2]+0.25], #25 см выше объекта targetOrientation=p.getQuaternionFromEuler([0,-3.14,0]) #[крен, тангаж, рыскание]=[0,-π,0] -> рука направлена вниз ) arm_joints = [0,1,2,3,4,5,6] p.setJointMotorControlArray(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndices=arm_joints, targetPositions=target_positions[:len(arm_joints)], forces=[50]*len(arm_joints))

Обратите внимание, что я использовал функцию p.getQuaternionFromEuler() для преобразования трёхмерных углов (которые легко понять человеку) в четырёхмерные, поскольку кватернионы проще вычислять физическим движкам. Если говорить технически, то в кватернионе (x, y, z, w) первые три компонента описывают ось вращения, а четвёртое измерение кодирует величину поворота (cos/sin).
Приведённый выше код даёт роботу команду переместить руку в определённое положение над объектом, используя инверсную кинематику. Мы получаем координаты маленького красного кубика в мире с помощью p.getBasePositionAndOrientation() и используем эту информацию для расчёта целевого положения сочленений.
Взаимодействие с объектами
Роботизированная рука оснащена кистью («захватом»), поэтому её можно сжимать и разжимать для захвата объектов. Из предыдущего анализа мы знаем, что «пальцы» могут двигаться в диапазоне от 0 до 0,04 секунды. Таким образом, можно задать целевое положение как нижний предел ( кисть сжата ) или верхний предел ( кисть открыта ).

Более того, я хочу убедиться, что рука удерживает маленький красный кубик во время движения. Вы можете использовать функцию p.createConstraint(), чтобы создать временную физическую связь между захватом робота и объектом, чтобы он двигался вместе с рукой робота. В реальном мире захват прикладывал бы силу трения и контакта, сжимая объект и не давая ему упасть. Но, поскольку PyBullet — очень простой симулятор, я просто воспользуюсь этим сокращением.
## закрыть руку p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=9, #finger_joint1 targetPosition=0, #нижний предел для finger_joint1 force=force) p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=10, #finger_joint2 targetPosition=0, #нижний предел для finger_joint2 force=force) ## удерживать объект restriction = p.createConstraint( parentBodyUniqueId=robo, parentLinkIndex=11, childBodyUniqueId=obj, childLinkIndex=-1, jointType=p.JOINT_FIXED, jointAxis=[0,0,0], parentFramePosition=[0,0,0], childFramePosition=[0,0,0,1] )
После этого мы можем переместить руку к столу, используя ту же стратегию, что и раньше: Обратная кинематика -> целевое положение -> управление положением.

Наконец, когда робот достигает целевой позиции в декартовом пространстве, мы можем раскрыть кисть и снять ограничение между объектом и рукой.
## закрыть руку p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=9, #finger_joint1 targetPosition=0.04, #верхний предел для finger_joint1 force=force) p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=10, #finger_joint2 targetPosition=0.04, #верхний предел для finger_joint2 force=force) ## отпустить объект p.removeConstraint(constraint)

Полное ручное управление
В PyBullet необходимо визуализировать симуляцию каждого действия робота. Поэтому удобно использовать функцию utils, которая может ускорить (например, sec=0.1) или замедлить (например, sec=2) реальный промежуток времени (sec=1).
импорт pybullet как p импорт времени def render(sec=1): for _ in range(int(240*sec)): p.stepSimulation() time.sleep(1/240)
Вот полный код для моделирования, которое мы выполнили в этой статье.
импорт pybullet_data ## настройка p.connect(p.GUI) p.resetSimulation() p.setGravity(gravX=0, gravY=0, gravZ=-9.8) p.setAdditionalSearchPath(path=pybullet_data.getDataPath()) plane = p.loadURDF(«plane.urdf») cube = p.loadURDF(«cube.urdf», basePosition=[0,0,0.1], globalScaling=0.5, useFixedBase=True) robo = p.loadURDF(«franka_panda/panda.urdf», basePosition=[1,0,0.1], globalScaling=1.3, useFixedBase=True) obj = p.loadURDF(«cube_small.urdf», basePosition=[1,1,0.1], globalScaling=1.5) p.changeVisualShape(objectUniqueId=obj, linkIndex=-1, rgbaColor=[1,0,0,1]) render(0.1) force = 100 ## открытая ладонь print(«### открытая ладонь ###») p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=9, #finger_joint1 targetPosition=0.04, #верхний предел для finger_joint1 force=force) p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=10, #finger_joint2 targetPosition=0.04, #верхний предел для finger_joint2 force=force) render(0.1) print(» «) ## переместить руку print(«### переместить руку ### «) obj_position, _ = p.getBasePositionAndOrientation(obj) obj_position = list(obj_position) target_positions = p.calculateInverseKinematics( bodyUniqueId=robo, endEffectorLinkIndex=11, #связь захвата цели targetPosition=[obj_position[0], obj_position[1], obj_position[2]+0.25], #25 см над объектом targetOrientation=p.getQuaternionFromEuler([0,-3.14,0]) #[крен, тангаж, рыскание]=[0,-π,0] -> рука направлена вниз ) print(«целевая позиция:», target_positions) arm_joints = [0,1,2,3,4,5,6] p.setJointMotorControlArray(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndices=arm_joints, targetPositions=target_positions[:len(arm_joints)], forces=[force/3]*len(arm_joints)) render(0.5) print(» «) ## закрыть руку print(«### закрыть руку ###») p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=9, #finger_joint1 targetPosition=0, #нижний предел для finger_joint1 force=force) p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=10, #finger_joint2 targetPosition=0, #нижний предел для finger_joint2 force=force) render(0.1) print(» «) ## удержание объекта print(«### удержание объекта ###») restriction = p.createConstraint( parentBodyUniqueId=robo, parentLinkIndex=11, childBodyUniqueId=obj, childLinkIndex=-1, jointType=p.JOINT_FIXED, jointAxis=[0,0,0], parentFramePosition=[0,0,0], childFramePosition=[0,0,0,1] ) render(0.1) print(» «) ## движение к столу print(«### движение к столу ###») cube_position, _ = p.getBasePositionAndOrientation(cube) cube_position = list(cube_position) target_positions = p.calculateInverseKinematics( bodyUniqueId=robo, endEffectorLinkIndex=11, #ссылка «взять-цель» targetPosition=[cube_position[0], cube_position[1], cube_position[2]+0.80], #80 см над столом targetOrientation=p.getQuaternionFromEuler([0,-3.14,0]) #[roll,pitch,yaw]=[0,-π,0] -> рука направлена вниз ) print(«target position:», target_positions) arm_joints = [0,1,2,3,4,5,6] p.setJointMotorControlArray(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndices=arm_joints, targetPositions=target_positions[:len(arm_joints)], forces=[force*3]*len(arm_joints)) render() print(» «) ## раскрытая ладонь и отпущенный объект print(«### раскрытая ладонь и отпущенный объект ###») p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=9, #finger_joint1 targetPosition=0.04, #верхний предел для finger_joint1 force=force) p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL, jointIndex=10, #finger_joint2 targetPosition=0.04, #верхний предел для finger_joint2 force=force) p.removeConstraint(constraint) render()

Заключение
Эта статья представляет собой руководство по ручному управлению роботизированной рукой . Мы изучили управление движением, инверсную кинематику, захват и перемещение объектов. В будущем появятся новые руководства с более продвинутыми роботами.
Полный код для этой статьи: GitHub
Надеюсь, вам понравилось! Обращайтесь ко мне с вопросами и отзывами, а также просто делитесь своими интересными проектами.
👉 Давайте общаться 👈

(Все изображения принадлежат автору, если не указано иное)
Источник: towardsdatascience.com
Оцените материал:
Похожие записи
Стартапам не нужно становиться вирусными, чтобы завоевать клиентов.
24.10.2026
Создание сценариев для экстремальных событий без использования экстремальных данных.
24.10.2026
4,9 на 3000 отзывов. Сколько из них написал живой человек?
24.10.2026Присоединяйтесь и подпишитесь на рассылку самых свежих новостей по Email
Получайте свежие новости и идеи на почту. Без спама — только самое интересное.
Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.
