Архив рубрики ~Лента новостей~

Как управлять роботом с помощью Python

Как управлять роботом с помощью Python

3D-симуляция и управление движением с помощью PyBullet

Делиться

7829400ab37c16368d155c214e47b295

Вступление

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)

a5103d7baa6ef40cd3650ca39865834c

Давайте рассмотрим код выше:

  • когда вы сможете подключиться к физическому движку, вам необходимо указать, хотите ли вы открыть графический интерфейс (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)

08cef0675833e598039d16520ee7500e

Первое, что нужно сделать, это проанализировать связи (твердые части) и сочленения (соединения между двумя жёсткими частями) робота. Для этого можно просто использовать 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)

b6aa26ad56f639c88c5c07b91119839d

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

93bf89d88ce40c32279bc2ee2cecea44

Управление движением

Чтобы заставить робота что-то сделать, нужно двигать его суставы. Существует три основных типа управления, каждый из которых основан на законах движения Ньютона:

  • Управление положением : по сути, вы говорите роботу «иди сюда». Технически, вы задаёте целевое положение , а затем прикладываете силу, чтобы постепенно перемещать сочленение из текущего положения к цели. По мере приближения к цели прилагаемая сила уменьшается, чтобы избежать перекоса, и в конечном итоге идеально уравновешивает силы сопротивления (например, трение и гравитацию), удерживая сочленение на месте.
  • Управление скоростью : по сути, вы говорите роботу: «Двигайся с этой скоростью». Технически, вы задаёте целевую скорость и прикладываете силу, чтобы постепенно довести её от нуля до целевой, а затем поддерживаете её, уравновешивая приложенную силу и силы сопротивления (например, трение, гравитацию).
  • Управление силой/крутящим моментом : по сути, вы «толкаете робота и смотрите, что происходит». Технически, вы непосредственно задаёте силу, прикладываемую к сочленению, а результирующее движение зависит исключительно от массы объекта, его инерции и окружающей среды. Кстати, в физике слово « сила » используется для обозначения линейного движения (толкание/тяга), а « крутящий момент » — для обозначения вращательного движения (поворот/скручивание).

Мы можем использовать функцию 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)

d63ff4005e41aec50994decc6283ff08

Настоящий вопрос заключается в следующем: « Каково правильное целевое положение для каждого сочленения? ». Ответ кроется в обратной кинематике — математическом процессе расчёта параметров, необходимых для размещения робота в заданном положении и ориентации относительно начальной точки. Каждое сочленение определяет угол, и вам не хочется вручную угадывать углы нескольких сочленений. Поэтому мы попросим 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))

b90063acf8d0c478db905faf32814c8e

Обратите внимание, что я использовал функцию p.getQuaternionFromEuler() для преобразования трёхмерных углов (которые легко понять человеку) в четырёхмерные, поскольку кватернионы проще вычислять физическим движкам. Если говорить технически, то в кватернионе (x, y, z, w) первые три компонента описывают ось вращения, а четвёртое измерение кодирует величину поворота (cos/sin).

Приведённый выше код даёт роботу команду переместить руку в определённое положение над объектом, используя инверсную кинематику. Мы получаем координаты маленького красного кубика в мире с помощью p.getBasePositionAndOrientation() и используем эту информацию для расчёта целевого положения сочленений.

Взаимодействие с объектами

Роботизированная рука оснащена кистью («захватом»), поэтому её можно сжимать и разжимать для захвата объектов. Из предыдущего анализа мы знаем, что «пальцы» могут двигаться в диапазоне от 0 до 0,04 секунды. Таким образом, можно задать целевое положение как нижний предел ( кисть сжата ) или верхний предел ( кисть открыта ).

3dc7ed5b2cc2f1d52243cfe8ba88b61c

Более того, я хочу убедиться, что рука удерживает маленький красный кубик во время движения. Вы можете использовать функцию 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] )

После этого мы можем переместить руку к столу, используя ту же стратегию, что и раньше: Обратная кинематика -> целевое положение -> управление положением.

9837b2b2315e77c25df431d75548b63b

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

## закрыть руку 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)

b5e6a76b577f4878d3bffa2701d15aa1

Полное ручное управление

В 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()

de57e8e3ada20dc663381cc7d1b315a5

Заключение

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

Полный код для этой статьи: GitHub

Надеюсь, вам понравилось! Обращайтесь ко мне с вопросами и отзывами, а также просто делитесь своими интересными проектами.

👉 Давайте общаться 👈

5635afe4be0fe5eba1873a1c653a7ccd

(Все изображения принадлежат автору, если не указано иное)

Источник: towardsdatascience.com

❌ Нет тегов для этой статьи

Оцените материал:

Поделиться
Понравилась статья? Расскажите другим
ВКонтакте
Читайте также
Новости робототехники Компания Arm запускает платформу Total Design for Physical AI and Robotics. Новости робототехники Система Cybercab от Tesla запущена, и уже проводится расследование. Новости робототехники Почему искусственный интеллект является безопасностью рабочей площадки Новости робототехники Сегодня ночью украинские БЭКи атаковали район Геленджика, рядом с Новороссийском Архив рубрики ~Обо всем~ Лидеры в области качества предостерегают от оцифровки неэффективных процессов. Архив рубрики ~Обо всем~ Огромный декагон сформировался вокруг южного полюса Сатурна. Атмосферная волна появилась недавно Новости робототехники Датчики давления могут повысить точность захвата роботов. Новости робототехники Компания Poseidon Aerospace привлекла 60 миллионов долларов перед первым испытательным полетом без пилота. Архив рубрики ~Коротко из Telegram~ Телевизоры LG могут следить за тем, что происходит у вас… Архив рубрики ~Коротко из Telegram~ Крошечная open‑source‑модель MiniCPM5-2B обошла конкурентов до 4B параметров ⚡ Канал… Архив рубрики ~Коротко из Telegram~ Бесплатная обработка голоса в аудио Если записываешь голос на встроенный… Архив рубрики ~Обо всем~ Конец универсального диагноза болезни Паркинсона: почему 2026 год станет годом, когда неврология перейдет на высокоточное исследование. Архив рубрики ~Коротко из Telegram~ Дата-центр Stargate в Абилине назвали «местом рождения AGI» Глава Crusoe… Архив рубрики ~Коротко из Telegram~ Небольшой дайджест — коротко о найденных новостях 1️⃣ Российский облачный… Новости робототехники Компания Arm запускает платформу Total Design for Physical AI and Robotics. Новости робототехники Система Cybercab от Tesla запущена, и уже проводится расследование. Новости робототехники Почему искусственный интеллект является безопасностью рабочей площадки Новости робототехники Сегодня ночью украинские БЭКи атаковали район Геленджика, рядом с Новороссийском Архив рубрики ~Обо всем~ Лидеры в области качества предостерегают от оцифровки неэффективных процессов. Архив рубрики ~Обо всем~ Огромный декагон сформировался вокруг южного полюса Сатурна. Атмосферная волна появилась недавно Новости робототехники Датчики давления могут повысить точность захвата роботов. Новости робототехники Компания Poseidon Aerospace привлекла 60 миллионов долларов перед первым испытательным полетом без пилота. Архив рубрики ~Коротко из Telegram~ Телевизоры LG могут следить за тем, что происходит у вас… Архив рубрики ~Коротко из Telegram~ Крошечная open‑source‑модель MiniCPM5-2B обошла конкурентов до 4B параметров ⚡ Канал… Архив рубрики ~Коротко из Telegram~ Бесплатная обработка голоса в аудио Если записываешь голос на встроенный… Архив рубрики ~Обо всем~ Конец универсального диагноза болезни Паркинсона: почему 2026 год станет годом, когда неврология перейдет на высокоточное исследование. Архив рубрики ~Коротко из Telegram~ Дата-центр Stargate в Абилине назвали «местом рождения AGI» Глава Crusoe… Архив рубрики ~Коротко из Telegram~ Небольшой дайджест — коротко о найденных новостях 1️⃣ Российский облачный…