PyBullet ist eine von Fb entwickelte Open-Supply-Simulationsplattform, die für das Coaching physischer Agenten (z. B. Roboter) in einer 3D-Umgebung entwickelt wurde. Es bietet eine Physik-Engine für starre und weiche Körper. Es wird häufig für Robotik, künstliche Intelligenz und Spieleentwicklung verwendet.

Roboterarme sind aufgrund ihrer Geschwindigkeit, Präzision und Fähigkeit, in gefährlichen Umgebungen zu arbeiten, sehr beliebt. Sie werden für Aufgaben wie Schweißen, Montage und Materialhandhabung verwendet, insbesondere in industriellen Umgebungen (wie der Fertigung).

Für einen Roboter gibt es zwei Möglichkeiten, eine Aufgabe auszuführen:

  • Manuelle Steuerung – erfordert, dass ein Mensch jede Aktion explizit programmiert. Besser geeignet für feste Aufgaben, hat jedoch mit Unsicherheiten zu kämpfen und erfordert eine langwierige Parameteranpassung für jedes neue Szenario.
  • Künstliche Intelligenz – ermöglicht es einem Roboter, durch Versuch und Irrtum die besten Aktionen zu erlernen, um ein Ziel zu erreichen. So kann es sich an veränderte Umgebungen anpassen, indem es ohne einen vordefinierten Plan aus Belohnungen und Strafen lernt (Verstärkungslernen).

In diesem Artikel werde ich zeigen, wie das geht Erstellen Sie eine 3D-Umgebung mit PyBullet zur manuellen Steuerung eines Roboterarms. Ich werde einige nützliche Python-Codes vorstellen, die leicht in anderen ähnlichen Fällen angewendet werden können (einfach kopieren, einfügen, ausführen) und jede Codezeile mit Kommentaren durchgehen, damit Sie dieses Beispiel reproduzieren können.

Aufstellen

PyBullet kann einfach mit einem der folgenden Befehle installiert werden (falls Pip scheitert, conda sollte auf jeden Fall funktionieren):

pip set up pybullet

conda set up -c conda-forge pybullet

PyBullet kommt mit einer Sammlung von Voreinstellungen URDF-Dateien (Unified Robotic Description Format), dabei handelt es sich um XML-Schemata, die die physische Struktur von Objekten in der 3D-Umgebung (z. B. Würfel, Kugel, Roboter) beschreiben.

import pybullet as p
import pybullet_data
import time

## setup
p.join(p.GUI)
p.resetSimulation()
p.setGravity(gravX=0, gravY=0, gravZ=-9.8)
p.setAdditionalSearchPath(path=pybullet_data.getDataPath())

## load URDF
airplane = p.loadURDF("airplane.urdf")

dice = p.loadURDF("dice.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)) #purple

whereas p.isConnected():
    p.setRealTimeSimulation(True)

Gehen wir den obigen Code durch:

  • Wenn Sie eine Verbindung zur Physik-Engine herstellen können, müssen Sie angeben, ob Sie diese öffnen möchten grafische Oberfläche (p.GUI) oder nicht (p.DIRECT).
  • Der kartesische Raum hat 3 Dimensionen: x-Achse (vorwärts/rückwärts), y-Achse (hyperlinks/rechts), z-Achse (oben/unten).
  • Es ist üblich, das festzulegen Schwerkraft Zu (x=0, y=0, z=-9.8) simulieren Die Schwerkraft der Erdeaber man kann es je nach Ziel der Simulation ändern.
  • Normalerweise müssen Sie das tun Ein Flugzeug importieren um einen Boden zu schaffen, sonst würden Gegenstände einfach auf unbestimmte Zeit fallen. Wenn Sie möchten, dass ein Objekt am Boden befestigt wird, dann stellen Sie es ein useFixedBase=True (es ist FALSCH standardmäßig). Ich habe die Objekte mit importiert basePosition=(0,0,0.1) Das bedeutet, dass sie sich 10 cm über dem Boden befinden.
  • Die Simulation kann gerendert werden Echtzeit mit p.setRealTimeSimulation(True) oder schneller (CPU-Zeit) mit p.stepSimulation(). Eine andere Möglichkeit, Echtzeit einzustellen, ist:
import time

for _ in vary(240):   #240 timestep generally utilized in videogame improvement
    p.stepSimulation() #add a physics step (CPU pace = 0.1 second)
    time.sleep(1/240)  #decelerate to real-time (240 steps × 1/240 second sleep = 1 second)

Roboter

Derzeit besteht unsere 3D-Umgebung aus einem kleinen Objekt (winziger roter Würfel) und einem Tisch (großer Würfel), der am Boden (Ebene) befestigt ist. Ich werde ein hinzufügen Roboterarm mit der Aufgabe, den kleineren Gegenstand aufzuheben und auf den Tisch zu legen.

PyBullet verfügt über einen Commonplace-Roboterarm, der dem nachempfunden ist Franka Panda Roboter.

robo = p.loadURDF("franka_panda/panda.urdf", 
                   basePosition=(1,0,0.1), useFixedBase=True)

Das erste, was zu tun ist, ist die Analyse Hyperlinks (die festen Teile) Und Gelenke (Verbindungen zwischen zwei starren Teilen) des Roboters. Zu diesem Zweck können Sie einfach verwenden p.DIRECT da die grafische Oberfläche nicht erforderlich ist.

import pybullet as p
import pybullet_data

## setup
p.join(p.DIRECT)
p.resetSimulation()
p.setGravity(gravX=0, gravY=0, gravZ=-9.8)
p.setAdditionalSearchPath(path=pybullet_data.getDataPath())

## load URDF
robo = p.loadURDF("franka_panda/panda.urdf", 
                  basePosition=(1,0,0.1), useFixedBase=True)

## joints
dic_info = {
    0:"joint Index",  #begins at 0
    1:"joint Title",
    2:"joint Kind",  #0=revolute (rotational), 1=prismatic (sliding), 4=mounted
    3:"state vectorIndex",
    4:"velocity vectorIndex",
    5:"flags",  #nvm at all times 0
    6:"joint Damping",  
    7:"joint Friction",  #coefficient
    8:"joint lowerLimit",  #min angle
    9:"joint upperLimit",  #max angle
    10:"joint maxForce",  #max drive allowed
    11:"joint maxVelocity",  #max pace
    12:"hyperlink Title",  #baby hyperlink linked to this joint
    13:"joint Axis",
    14:"dad or mum FramePos",  #place
    15:"dad or mum FrameOrn",  #orientation
    16:"dad or mum Index"  #−1 = base
}
for i in vary(p.getNumJoints(bodyUniqueId=robo)):
    joint_info = p.getJointInfo(bodyUniqueId=robo, jointIndex=i)
    print(f"--- JOINT {i} ---")
    print({dic_info(ok):joint_info(ok) for ok in dic_info.keys()})

## hyperlinks
for i in vary(p.getNumJoints(robo)):
    link_name = p.getJointInfo(robo, i)(12).decode('utf-8')  #area 12="hyperlink Title"
    dyn = p.getDynamicsInfo(robo, i)
    pos, orn, *_ = p.getLinkState(robo, i)
    dic_info = {"Mass":dyn(0), "Friction":dyn(1), "Place":pos, "Orientation":orn}
    print(f"--- LINK {i}: {link_name} ---")
    print(dic_info)

Jeder Roboter hat ein „Base„, der Wurzelteil seines Körpers, der alles verbindet (wie unsere Skelett-Wirbelsäule). Wenn wir uns die Ausgabe des obigen Codes ansehen, wissen wir, dass der Roboterarm 12 Gelenke und 12 Glieder hat. Sie sind wie folgt verbunden:

Bewegungskontrolle

Um einen Roboter dazu zu bringen, etwas zu tun, muss man seine Gelenke bewegen. Es gibt drei Hauptarten der Steuerung, die alle Anwendung finden Newtons Bewegungsgesetze:

  • Positionskontrolle: Im Grunde sagen Sie dem Roboter: „Gehen Sie hierher“. Technisch gesehen legen Sie a fest Zielpositionund wenden Sie dann Kraft an, um das Gelenk schrittweise von seiner aktuellen Place in Richtung des Ziels zu bewegen. Je näher es kommt, desto geringer wird die ausgeübte Kraft, um ein Überschwingen zu vermeiden, und gleicht sich schließlich den Widerstandskräften (z. B. Reibung, Schwerkraft) perfekt aus, um das Gelenk stabil an Ort und Stelle zu halten.
  • Geschwindigkeit Kontrolle: Im Grunde sagen Sie dem Roboter „Bewegen Sie sich mit dieser Geschwindigkeit“. Technisch gesehen legen Sie a fest Zielgeschwindigkeitund wenden Sie Kraft an, um die Geschwindigkeit allmählich von Null auf das Ziel zu bringen, und halten Sie sie dann aufrecht, indem Sie die aufgebrachte Kraft und die Widerstandskräfte (z. B. Reibung, Schwerkraft) ausgleichen.
  • Kraft/Drehmoment Kontrolle: Im Grunde „schubst du den Roboter an und siehst, was passiert“. Technisch gesehen stellen Sie die am Gelenk ausgeübte Kraft direkt ein und die resultierende Bewegung hängt ausschließlich von der Masse, der Trägheit und der Umgebung des Objekts ab. Nebenbei bemerkt: In der Physik ist das Wort „Gewalt„wird für lineare Bewegungen (Druck/Zug) verwendet, während „Drehmoment„zeigt eine Drehbewegung (Drehung/Drehung) an.

Wir können verwenden p.setJointMotorControl2() ein einzelnes Gelenk bewegen und p.setJointMotorControlArray() Kraft auf mehrere Gelenke gleichzeitig auszuüben. Zum Beispiel werde ich eine Positionskontrolle durchführen, indem ich für jedes Armgelenk ein zufälliges Ziel vorgebe.

## setup
p.join(p.GUI)
p.resetSimulation()
p.setGravity(gravX=0, gravY=0, gravZ=-9.8)
p.setAdditionalSearchPath(path=pybullet_data.getDataPath())

## load URDF
airplane = p.loadURDF("airplane.urdf")
dice = p.loadURDF("dice.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)) #purple

## transfer arm
joints = (0,1,2,3,4,5,6)
target_positions = (1,1,1,1,1,1,1) #<--random numbers
p.setJointMotorControlArray(bodyUniqueId=robo, jointIndices=joints,
                            controlMode=p.POSITION_CONTROL,
                            targetPositions=target_positions,
                            forces=(50)*len(joints))
for _ in vary(240*3):
    p.stepSimulation()
    time.sleep(1/240)

Die eigentliche Frage ist: „Was ist die richtige Zielposition für jedes Gelenk?„Die Antwort ist Inverse Kinematikder mathematische Prozess zur Berechnung der Parameter, die erforderlich sind, um einen Roboter in eine bestimmte Place und Ausrichtung relativ zu einem Startpunkt zu bringen. Jedes Gelenk definiert einen Winkel, und Sie möchten nicht mehrere Gelenkwinkel von Hand erraten. Additionally fragen wir PyBullet um die Zielpositionen im kartesischen Raum zu ermitteln p.calculateInverseKinematics().

obj_position, _ = p.getBasePositionAndOrientation(obj)
obj_position = checklist(obj_position)

target_positions = p.calculateInverseKinematics(
    bodyUniqueId=robo,
    endEffectorLinkIndex=11, #grasp-target hyperlink
    targetPosition=(obj_position(0), obj_position(1), obj_position(2)+0.25), #25cm above object
    targetOrientation=p.getQuaternionFromEuler((0,-3.14,0)) #(roll,pitch,yaw)=(0,-π,0) -> hand pointing down
)

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

Bitte beachten Sie, dass ich verwendet habe p.getQuaternionFromEuler() Zu Wandeln Sie die 3D-Winkel (für Menschen leicht verständlich) in 4D umals „Quaternionen„sind für Physik-Engines einfacher zu berechnen. Wenn Sie technisch werden möchten, in a Quaternion (x, y, z, w)die ersten drei Komponenten beschreiben die Rotationsachse, während die vierte Dimension das Ausmaß der Rotation kodiert (cos/Sünde).

Der obige Code weist den Roboter an, seine Hand mithilfe der umgekehrten Kinematik an eine bestimmte Place über einem Objekt zu bewegen. Wir greifen dorthin, wo der kleine rote Würfel auf der Welt steht p.getBasePositionAndOrientation()und anhand dieser Informationen berechnen wir die Zielposition der Gelenke.

Interagiere mit Objekten

Der Roboterarm verfügt über eine Hand („Greifer“), sodass er zum Greifen von Gegenständen geöffnet und geschlossen werden kann. Aus der vorherigen Analyse wissen wir, dass sich die „Finger“ innerhalb von (0-0,04) bewegen können. Sie können additionally die Zielposition als Untergrenze festlegen (Hand geschlossen) oder die Obergrenze (Hand offen).

Außerdem möchte ich sicherstellen, dass der Arm den kleinen roten Würfel beim Bewegen festhält. Sie können verwenden p.createConstraint() eine machen vorübergehende physikalische Bindung zwischen dem Greifer des Roboters und dem Objekt, so dass es sich zusammen mit der Roboterhand bewegt. In der realen Welt würde der Greifer durch Reibung und Kontakt Kraft ausüben, um das Objekt zusammenzudrücken, damit es nicht herunterfällt. Aber seitdem PyBullet ist ein sehr einfacher Simulator, ich nehme einfach diese Abkürzung.

## shut hand
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=9, #finger_joint1
                        targetPosition=0, #decrease restrict for finger_joint1
                        drive=drive)
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=10, #finger_joint2
                        targetPosition=0, #decrease restrict for finger_joint2
                        drive=drive)

## maintain the item
constraint = 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)
)

Danach können wir den Arm mit der gleichen Strategie wie zuvor in Richtung Tisch bewegen: Inverse Kinematik -> Zielposition -> Positionskontrolle.

Wenn der Roboter schließlich die Zielposition im kartesischen Raum erreicht, können wir die Hand öffnen und die Einschränkung zwischen dem Objekt und dem Arm aufheben.

## shut hand
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=9, #finger_joint1
                        targetPosition=0.04, #higher restrict for finger_joint1
                        drive=drive)
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=10, #finger_joint2
                        targetPosition=0.04, #higher restrict for finger_joint2
                        drive=drive)

## drop the obj
p.removeConstraint(constraint)

Vollständige manuelle Kontrolle

In PyBulletmüssen Sie die Simulation für jede Aktion des Roboters rendern. Daher ist es praktisch, eine Utils-Funktion zu haben, die beschleunigen kann (d. h Sek=0,1) oder verlangsamen (d. h Sek=2) die Echtzeit (Sek. = 1).

import pybullet as p
import time

def render(sec=1):
    for _ in vary(int(240*sec)):
        p.stepSimulation()
        time.sleep(1/240)

Hier ist der vollständige Code für die Simulation, die wir in diesem Artikel durchgeführt haben.

import pybullet_data

## setup
p.join(p.GUI)
p.resetSimulation()
p.setGravity(gravX=0, gravY=0, gravZ=-9.8)
p.setAdditionalSearchPath(path=pybullet_data.getDataPath())

airplane = p.loadURDF("airplane.urdf")
dice = p.loadURDF("dice.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)
drive = 100


## open hand
print("### open hand ###")
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=9, #finger_joint1
                        targetPosition=0.04, #higher restrict for finger_joint1
                        drive=drive)
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=10, #finger_joint2
                        targetPosition=0.04, #higher restrict for finger_joint2
                        drive=drive)
render(0.1)
print(" ")


## transfer arm
print("### transfer arm ### ")
obj_position, _ = p.getBasePositionAndOrientation(obj)
obj_position = checklist(obj_position)

target_positions = p.calculateInverseKinematics(
    bodyUniqueId=robo,
    endEffectorLinkIndex=11, #grasp-target hyperlink
    targetPosition=(obj_position(0), obj_position(1), obj_position(2)+0.25), #25cm above object
    targetOrientation=p.getQuaternionFromEuler((0,-3.14,0)) #(roll,pitch,yaw)=(0,-π,0) -> hand pointing down
)
print("goal place:", 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=(drive/3)*len(arm_joints))

render(0.5)
print(" ")


## shut hand
print("### shut hand ###")
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=9, #finger_joint1
                        targetPosition=0, #decrease restrict for finger_joint1
                        drive=drive)
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=10, #finger_joint2
                        targetPosition=0, #decrease restrict for finger_joint2
                        drive=drive)
render(0.1)
print(" ")


## maintain the item
print("### maintain the item ###")
constraint = 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(" ")


## transfer in the direction of the desk
print("### transfer in the direction of the desk ###")
cube_position, _ = p.getBasePositionAndOrientation(dice)
cube_position = checklist(cube_position)

target_positions = p.calculateInverseKinematics(
    bodyUniqueId=robo,
    endEffectorLinkIndex=11, #grasp-target hyperlink
    targetPosition=(cube_position(0), cube_position(1), cube_position(2)+0.80), #80cm above the desk
    targetOrientation=p.getQuaternionFromEuler((0,-3.14,0)) #(roll,pitch,yaw)=(0,-π,0) -> hand pointing down
)
print("goal place:", 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=(drive*3)*len(arm_joints))
render()
print(" ")


## open hand and drop the obj
print("### open hand and drop the obj ###")
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=9, #finger_joint1
                        targetPosition=0.04, #higher restrict for finger_joint1
                        drive=drive)
p.setJointMotorControl2(bodyUniqueId=robo, controlMode=p.POSITION_CONTROL,
                        jointIndex=10, #finger_joint2
                        targetPosition=0.04, #higher restrict for finger_joint2
                        drive=drive)
p.removeConstraint(constraint)
render()

Abschluss

Dieser Artikel battle ein Tutorial dazu Einen Roboterarm manuell steuern. Wir lernten etwas über Bewegungssteuerung, inverse Kinematik und das Greifen und Bewegen von Objekten. Es werden neue Tutorials mit fortgeschritteneren Robotern folgen.

Vollständiger Code für diesen Artikel: GitHub

Ich hoffe, es hat Ihnen gefallen! Für Fragen und Suggestions oder einfach nur, um mir Ihre interessanten Projekte mitzuteilen, können Sie mich gerne kontaktieren.

👉 Lassen Sie uns verbinden 👈

(Alle Bilder stammen vom Autor, sofern nicht anders angegeben)

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert