Im letzten Artikel über den autonomen Heimwagen 2.0 haben wir es geschafft, den Kilometerzähler eines Budgetroboters zu verbessern, eine akzeptable 2D-Raumkarte mit Slam-Algorithmen und einem erschwinglichen Lidar zu erstellen und andere Probleme beim Erstellen des Projekts zu klären. Dieses Mal werden wir sehen, wie die autonome Navigation in rviz funktioniert, wofür wir verantwortlich sind. Wir werden Programme implementieren, mit denen Sie rviz verlassen können. Betrachten Sie auch einige der „Schönheitselemente“ von rviz, die der ROS-Robotik das Leben erleichtern.
Beginnen wir mit dem Üben.
Die Aufgabe des Roboters : unabhängig von Punkt A nach Punkt B oder besser sofort zu mehreren Punkten B zu fahren.Es wird davon ausgegangen, dass die Skripte (Knoten) des Linorobot-Projekts, um das das Projekt erstellt wird, auf dem Roboter gestartet werden und die Visualisierung des Geschehens und die Steuerung durchgeführt werden von einem externen PC, einer virtuellen Maschine (im Folgenden als "PC" bezeichnet).In meinem Fall wird von der Software auf dem Wagen, deren Herzstück das Himbeer-Pi-3b-Single-Board ist, Folgendes installiert: Ubuntu 16.04, ROS Kinetic, auf einem PC: Ubuntu 18.04, ROS Melodic.Bevor Sie irgendwohin gehen, müssen Sie die Abmessungen des Roboters einstellen - Länge und Breite.Dazu müssen Sie, wie auf der Projektseite angegeben, die entsprechenden Werte eingeben:roscd linorobot/param/navigation
nano costmap_common_params.yaml
Richtige Footprint-Parameter:footprint: [[-x, -y], [-x, y], [x, y], [x, -y]]
Dabei ist x = Länge des Roboters / 2 und y = Breite / 2* Idealerweise können Sie für den Roboter ein Urdf-Modell zeichnen, wie dies zuvor im vorherigen Projekt des Roboterwagens getan wurde .Wenn dies nicht getan wird, leidet die Navigation nicht viel, aber anstelle des Modells des Roboters bewegen sich „TF-Achsen“ auf der Karte. Wir werden weiter darauf eingehen, wie Sie die Situation mit der visuellen Darstellung des Roboters auf der Karte ein wenig verbessern und gleichzeitig keine Zeit mit der Erstellung des Urdf-Modells verschwenden können.Wir schaffen eine Umgebung
Führen Sie auf dem Roboter zwei verschiedene Terminals aus:roslaunch linorobot bringup.launch
roslaunch linorobot navigate.launch
Führen Sie auf dem PC die rviz Visual Shell aus:roscd lino_visualize/rviz
rviz -d navigate.rviz
Wenn alles gut gegangen ist, können Sie auf dem PC die Position des Roboters in rviz beobachten:
TF-Achsen sind in rviz sichtbar, sowie die Option Poligon, die im Anzeigefeld hinzugefügt werden kann. Polygon - Dies sind nur die Footprint-Größen des Roboters, die zu Beginn eingeführt wurden, sodass Sie die Abmessungen des Roboters sehen können. * TF-Achsen sind von den Achsen im Bild aus sichtbar: TF-Map - Map und TF-Base-Link - der Roboter selbst, die verbleibenden Achsen werden in Rviz stummgeschaltet, um nicht miteinander zu verschmelzen.Die Daten vom Lidar stimmen geringfügig nicht mit den Wänden auf der Karte überein, der Roboter ist nicht lokalisiert.Wenn Sie von dieser Position aus gehen, versucht der Algorithmus, sich anzupassen. Sie können ihm jedoch auch helfen, indem Sie die Startposition direkt in rviz mit der Schaltfläche „2D-Posenschätzung“ angeben:
Geben Sie als Nächstes im rviz-Visualisierungsfenster auf der Karte die anfängliche Position und Richtung des Roboters mit einer kräftigen grünen Pfeil-Anfangspose an (Sie können dies viele Male tun):
Nach Klärung der Position verschiebt sich base_link relativ zur Karte geringfügig: Die
absolute Genauigkeit der Übereinstimmung der Ränder vom Lidar mit der Karte beim manuellen Anpassen der Anfangsposition nicht benötigt, korrigiert der Algorithmus dann alles selbst.Bevor du trotzdem gehst
Setzen Sie jedes Mal die Startposition ein, wenn der Roboter am Boden von derselben Stelle aus startet. Daher werden wir den Dienst verwenden (denken Sie daran, dass es zusätzlich zu den Themen in ROS Dienste gibt). Beim Zugriff wird der "Standort des Roboters auf der Karte" gelesen und diese Daten werden dann verwendet.Erstellen Sie ein Skript auf dem PC (es spielt keine Rolle, an welcher Stelle)get_pose.py
import rospy
from std_srvs.srv import Empty, EmptyResponse
from geometry_msgs.msg import PoseWithCovarianceStamped, Pose
robot_pose = Pose()
def service_callback(request):
print ("Robot Pose:")
print (robot_pose)
return EmptyResponse()
def sub_callback(msg):
global robot_pose
robot_pose = msg.pose.pose
rospy.init_node('service_server')
my_service = rospy.Service('/get_pose_service', Empty , service_callback)
sub_pose = rospy.Subscriber('/amcl_pose', PoseWithCovarianceStamped, sub_callback)
rospy.spin()
Nach dem Start zeigt das Skript im Terminal nichts an und wartet leise auf den Zugriff. Da wir in rviz über die grafische Oberfläche kürzlich die Anfangsposition des Roboters festgelegt haben, können Sie sich auf unser neues Skript beziehen und diese Position herausfinden. Ohne das Terminal mit dem Skript zu schließen (wie zuvor gestartete Terminals mit Knoten), werden wir in einem anderen Terminal Folgendes ausführen:rosservice call /get_pose_service
Nachdem die Anforderung im Terminal mit dem Skript ausgeführt wurde, zeigt get_pose.py die Position des Roboters an:
Speichern Sie diese Indikatoren und fügen Sie sie zu den Einstellungen des amcl-Knotens auf dem Roboter hinzu, sodass der Roboter jedes Mal an der angegebenen Position startet:nano linorobot/launch/include/amcl.launch
HinzufügenParameter<param name="initial_pose_x" value="0.548767569629"/>
<param name="initial_pose_y" value="0.218281839179"/>
<param name="initial_pose_z" value="0.0"/>
<param name="initial_orientation_z" value="0.128591756735"/>
<param name="initial_orientation_w" value="0.991697615254"/>
Jetzt startet der Roboter beim Start bereits an einer bestimmten Position und muss auch nicht manuell in rviz eingestellt werden:
Endlich geht es los
Starten Sie die zu Beginn angegebenen Befehle für den Roboter und den PC neu und sehen Sie sich den Roboter in rviz an.Wir werden das "2D NAV Goal" -Rviz verwenden, um den Roboter zu senden, wohin die Seele es wünscht:
Wie Sie sehen können, kann der Roboter nicht nur die Entfernung der Reise, sondern auch die Bewegungsrichtung anzeigen.Im obigen Beispiel fuhren wir ein wenig vorwärts, drehten uns um und gingen zurück.In rviz sind auch die bekannten Amcl-Partikelschwärme „grüne Pfeile“ sichtbar, die zeigen, wo sich der Roboter laut Programm befindet. Nach der Fahrt nähert sich ihre Konzentration dem realen Standort des Roboters, die Gesamtzahl nimmt ab. Der Roboter fährt schnell genug - 0,3 m / s, sodass das Programm mehr Zeit benötigt, um den Roboter zu lokalisieren.Das Navigieren in rviz mithilfe des „2D-NAV-Ziels“, um zu verstehen, wie weit und in welche Richtung sich der Roboter bewegt, ist praktisch. Noch bequemer ist jedoch eine Reise ohne Beteiligung des Betreibers an rviz.Daher markieren wir auf der Karte die Punkte von Interesse, an die wir gehen werden. Bewegen Sie dazu den Roboter mit "2D NAV Goal" zu den gewünschten Punkten auf der Karte. An diesen Stellen wird auf den Dienst get_pose.py zugegriffen. Als nächstes werden die Koordinaten aller Punkte in das Reiseprogramm übertragen.Warum nicht einfach den Roboter in Ihren Armen zu Sehenswürdigkeiten bringen? Die Sache ist "Amcl Partikelschwarm", der Algorithmus, der sie erstellt. Selbst wenn sich der Roboter am interessierenden Punkt befindet und seine Position manuell festgelegt wird, wird eine Variante seiner Position über den Bereich um den interessierenden Punkt selbst "gesprüht". Deshalb musst du noch gehen.Beginnen wir mit dem Codenamen „Korridor“:
Wir speichern die Pose, wenn sich der Roboter am gewünschten Punkt befindet, indem wir einen zuvor erstellten Dienst anrufen:rosservice call /get_pose_service
Weiter - der "Eingang":
Und der letzte Punkt - "die Küche":
Jetzt ist es Zeit, ein Reiseprogramm zu erstellen -MoveTBtoGoalPoints.py
import rospy
import actionlib
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
"""
pose x,y,x; orientation: x,y,z,w
"""
GoalPoints = [ [(5.31514576482, 1.36578380326, 0.0), (0.0, 0.0, -0.498454937648, 0.866915610157)],
[(5.77521810772, -1.2464049907, 0.0), (0.0, 0.0, -0.959487358308, 0.281751680114)],
[(2.14926455346, -2.7055208156, 0.0), (0.0, 0.0, -0.99147032254, 0.130332649487)]]
def assign_goal(pose):
goal_pose = MoveBaseGoal()
goal_pose.target_pose.header.frame_id = 'map'
goal_pose.target_pose.pose.position.x = pose[0][0]
goal_pose.target_pose.pose.position.y = pose[0][1]
goal_pose.target_pose.pose.position.z = pose[0][2]
goal_pose.target_pose.pose.orientation.x = pose[1][0]
goal_pose.target_pose.pose.orientation.y = pose[1][1]
goal_pose.target_pose.pose.orientation.z = pose[1][2]
goal_pose.target_pose.pose.orientation.w = pose[1][3]
return goal_pose
if __name__ == '__main__':
rospy.init_node('MoveTBtoGoalPoints')
client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
client.wait_for_server()
for TBpose in GoalPoints:
TBgoal = assign_goal(TBpose)
client.send_goal(TBgoal)
client.wait_for_result()
* In GoalPoints fügen wir die zuvor gespeicherten Koordinaten der Trip Points hinzu. Der Code enthält eine Erklärung, in welcher Reihenfolge die Parameter ablaufen: Pose x, y, x; Ausrichtung: x, y, z, w - [(5.31514576482, 1.36578380326, 0.0), (0.0, 0.0, -0.498454937648, 0.866915610157)]Führen Sie das Skript MoveTBtoGoalPoints.py parallel zu den zuvor gestarteten Knoten auf dem Roboter und dem PC aus. Siehe das Ergebnis:
Wie Sie sehen können Der Roboter fuhr erfolgreich zum Flur und sah sich auf dem Weg in die Küche im Spiegel und blieb stecken. Gut mit Lidars beim Treffen mit Spiegeln, leider so.Was kann getan werden?Es ist möglich, Zwischenpunkte vor und nach den Spiegeln hinzuzufügen, die Geschwindigkeit des Roboters zu verringern und entlang der Route ein undurchsichtiges Klebeband auf den Spiegel zu kleben.Dies sind jedoch die nächsten Schritte, aber wir werden zunächst die Aufgabe in Betracht ziehen, mit und ohne die Hilfe von rviz von Punkt A nach Punkt B zu reisen. Im Prinzip brauchen wir jetzt keinen externen PC, weil Das Reiseskript führt uns zu den gewünschten Punkten auf der Karte.Aber lassen Sie uns nicht eilen, um den PC zusammen mit rviz fallen zu lassen, da das Linorobot-Projekt "out of the box" nicht richtig funktioniert und Sie "mit einer Datei" auf dem Navigationsstapel arbeiten müssen.Hier ist die Zeit zum Einspruch - lassen Sie mich sagen, wo ist die autonome Navigation, der Roboter fuhr n Schritte vorwärts, seitwärts, rückwärts und sogar in den Spiegel?Nun, das ist alles, bis auf einige Details, die ROS auszeichnen.Schauen wir uns diese Details an.Angenommen, ein verantwortungsloser Bürger hat ein lebloses Objekt auf dem Weg des Roboters zurückgelassen. Dieser Gegenstand befindet sich nicht auf der Karte, die dem Roboter zur Navigation übergeben wurde:
Was wird passieren?Mal sehen:
Wie Sie sehen, hat der Lidar ein Hindernis gesehen, der Algorithmus hat es auf der Karte markiert und einen Umweg angeordnet.Fügen Sie schließlich etwas Schönheit zu rviz hinzu
Der visuelle Editor von rviz verfügt über interessante Plugins, die nicht standardmäßig installiert sind. Ihre Verwendung kann bei der Arbeit mit rviz viele interessante Punkte hinzufügen.Das Repository mit Plugins ist hier .Installieren Sie Plugins auf unserem PC in unserer Umgebung:roscd; cd ../src;
catkin_create_pkg my_rviz_markers rospy
cd my_rviz_markers/
cd src
git clone https://github.com/jsk-ros-pkg/jsk_visualization
git clone https://github.com/ClementLeBihan/rviz_layer
cd ..
rosdep install --from-paths -y -r src
cd ~/linorobot_ws/
catkin_make
Nach der Installation in rviz sollten zusätzliche Plugins angezeigt werden. Fügenwir nun beispielsweise die Symbole hinzu:
Erstellen Sie im Ordner my_rviz_markers / samples ein Skript:pictogram_array_objects_demo.py
import rospy
import math
from jsk_rviz_plugins.msg import Pictogram, PictogramArray
from random import random, choice
rospy.init_node("pictogram_object_demo_node")
p = rospy.Publisher("/pictogram_array", PictogramArray, queue_size=1)
r = rospy.Rate(1)
actions = [Pictogram.JUMP, Pictogram.JUMP_ONCE, Pictogram.ADD,
Pictogram.ROTATE_X, Pictogram.ROTATE_Y, Pictogram.ROTATE_Z]
pictograms = ["home","fa-robot"]
object_frames = ["map","base_link"]
while not rospy.is_shutdown():
arr = PictogramArray()
arr.header.frame_id = "/map"
arr.header.stamp = rospy.Time.now()
for index, character in enumerate(pictograms):
msg = Pictogram()
msg.header.frame_id = object_frames[index]
msg.action = actions[2]
msg.header.stamp = rospy.Time.now()
msg.pose.position.x = 0
msg.pose.position.y = 0
msg.pose.position.z = 0.5
msg.pose.orientation.w = 0.7
msg.pose.orientation.x = 0
msg.pose.orientation.y = -0.7
msg.pose.orientation.z = 0
msg.size = 0.5
msg.color.r = 25 / 255.0
msg.color.g = 255 / 255.0
msg.color.b = 240 / 255.0
msg.color.a = 1.0
msg.character = character
arr.pictograms.append(msg)
p.publish(arr)
r.sleep()
Machen wir es ausführbar:sudo chmod +x pictogram_array_objects_demo.py
Erstellen Sie im Ordner my_rviz_markers / launch einen Start zum Starten:<launch>
<node pkg="my_rviz_markers" type="pictogram_array_objects_demo.py" name="pictogram_array_objects_demo" </node>
</launch>
Es beginnt wie gewohnt (alle anderen Knoten zu diesem Zeitpunkt sollten ebenfalls funktionieren):roslaunch my_rviz_markers pictogram_array_objects_demo.launch
Setzen Sie den Marker auf die Karte
Markierungen können hilfreich sein, um zu verstehen, wo sich der Punkt auf der Karte befindet.Erstellen Sie wie im vorherigen Beispiel ein Skript im Ordner my_rviz_markers / samples:basic_marker.py
import rospy
from visualization_msgs.msg import Marker
from geometry_msgs.msg import Point
class MarkerBasics(object):
def __init__(self):
self.marker_objectlisher = rospy.Publisher('/marker_basic', Marker, queue_size=1)
self.rate = rospy.Rate(1)
self.init_marker(index=0,z_val=0)
def init_marker(self,index=0, z_val=0):
self.marker_object = Marker()
self.marker_object.header.frame_id = "/map"
self.marker_object.header.stamp = rospy.get_rostime()
self.marker_object.ns = "start"
self.marker_object.id = index
self.marker_object.type = Marker.SPHERE
self.marker_object.action = Marker.ADD
my_point = Point()
my_point.z = z_val
self.marker_object.pose.position.x = 5.31514576482
self.marker_object.pose.position.y = 1.36578380326
self.marker_object.pose.orientation.x = 0
self.marker_object.pose.orientation.y = 0
self.marker_object.pose.orientation.z = -0.498454937648
self.marker_object.pose.orientation.w = 0.866915610157
self.marker_object.scale.x = 0.2
self.marker_object.scale.y = 0.2
self.marker_object.scale.z = 0.2
self.marker_object.color.r = 0.0
self.marker_object.color.g = 0.0
self.marker_object.color.b = 1.0
self.marker_object.color.a = 1.0
self.marker_object.lifetime = rospy.Duration(0)
def start(self):
while not rospy.is_shutdown():
self.marker_objectlisher.publish(self.marker_object)
self.rate.sleep()
if __name__ == '__main__':
rospy.init_node('marker_basic_node', anonymous=True)
markerbasics_object = MarkerBasics()
try:
markerbasics_object.start()
except rospy.ROSInterruptException:
pass
Erstellen Sie im Ordner my_rviz_markers / launch einen Start zum Starten:basic_marker.launch<launch>
<node pkg="my_rviz_markers" type="basic_marker.py" name="basic_marker" />
</launch>
Lauf:roslaunch my_rviz_markers basic_marker.launch
Zu rviz hinzufügen:
Fügen Sie Grafiken hinzu
Hier werden der Abstand und der Winkel zwischen den beiden Frames
angezeigt : Erstellen Sie im Ordner my_rviz_markers / launch einen Start zum Starten:overlay_meny_my.launch overlay_menu_my.launch<launch>
<node pkg="my_rviz_markers" type="haro_overlay_complete_demo.py" name="overlay_menu" />
</launch>
Erstellen Sie im Ordner my_rviz_markers / samples die folgenden Skripte:haro_overlay_complete_demo.py
from jsk_rviz_plugins.msg import OverlayText, OverlayMenu
from std_msgs.msg import ColorRGBA, Float32
import rospy
import math
import random
from geometry_msgs.msg import Twist
class HaroOverlay(object):
def __init__(self):
self.text_pub = rospy.Publisher("/text_sample", OverlayText, queue_size=1)
self.plot_value_pub = rospy.Publisher("/plot_value_sample", Float32, queue_size=1)
self.piechart_value_pub = rospy.Publisher("/piechart_value_sample", Float32, queue_size=1)
self.menu_publisher = rospy.Publisher("/test_menu", OverlayMenu, queue_size=1)
self.plot_value = 0
self.piechart_value = 0
self.max_distance_from_object = 10.0
self.subs = rospy.Subscriber("/haro_base_link_to_person_standing_tf_translation", Twist, self.twist_callback)
self.counter = 0
self.rate = rospy.Rate(100)
self.overlaytext = self.update_overlaytext()
self.menu = self.update_overlay_menu()
def twist_callback(self, msg):
self.plot_value = msg.linear.x
self.piechart_value = msg.angular.z
def update_overlaytext(self, number=1.23, number2=20):
text = OverlayText()
text.width = 200
text.height = 400
text.left = 10
text.top = 10
text.text_size = 12
text.line_width = 2
text.font = "DejaVu Sans Mono"
text.text = """Distance= %s.
Angle=%s.
Counter = <span style="color: green;">%d.</span>
""" % (str(number), str(number2) ,self.counter)
text.fg_color = ColorRGBA(25 / 255.0, 1.0, 240.0 / 255.0, 1.0)
text.bg_color = ColorRGBA(0.0, 0.0, 0.0, 0.2)
return text
def update_overlay_textonly(self, new_text):
self.overlaytext.text = new_text
def update_overlay_menu(self):
menu = OverlayMenu()
menu.title = "HaroSystemMode"
menu.menus = ["Sleep", "Searching", "MovingInCircles","Waiting"]
menu.current_index = self.counter % len(menu.menus)
return menu
def update_overlay_menu_haro_tf(self):
menu = OverlayMenu()
menu.title = "HaroDistanceFromPerson"
menu.menus = ["FarAway", "CloseBy", "Target", "OtherWayRound"]
fraction = 10.0
if self.piechart_value < (math.pi/fraction):
if self.plot_value >= self.max_distance_from_object:
index = 0
elif self.plot_value >= (self.max_distance_from_object/ fraction) and self.plot_value < self.max_distance_from_object:
index = 1
elif self.plot_value < (self.max_distance_from_object/fraction):
index = 2
else:
index = 3
menu.current_index = index
return menu
def start_dummy_demo(self):
while not rospy.is_shutdown():
self.overlaytext = self.update_overlaytext()
self.menu = self.update_overlay_menu()
if self.counter % 100 == 0:
self.menu.action = OverlayMenu.ACTION_CLOSE
self.text_pub.publish(self.overlaytext)
self.value_pub.publish(math.cos(self.counter * math.pi * 2 / 1000))
self.menu_publisher.publish(self.menu)
self.rate.sleep()
self.counter += 1
def start_harodistance_demo(self):
while not rospy.is_shutdown():
self.overlaytext = self.update_overlaytext(number=self.plot_value, number2=self.piechart_value)
self.menu = self.update_overlay_menu_haro_tf()
self.text_pub.publish(self.overlaytext)
self.plot_value_pub.publish(self.plot_value)
self.piechart_value_pub.publish(self.piechart_value)
self.menu_publisher.publish(self.menu)
self.rate.sleep()
self.counter += 1
def dummy_overlay_demo():
rospy.init_node('distance_overlay_demo_node', anonymous=True)
haro_overlay_object = HaroOverlay()
try:
haro_overlay_object.start_harodistance_demo()
except rospy.ROSInterruptException:
pass
if __name__ == '__main__':
dummy_overlay_demo()
Das zweite Skript isttf_haro_to_object_listener.py
import sys
import rospy
import math
import tf
from geometry_msgs.msg import Twist, Vector3
if __name__ == '__main__':
rospy.init_node('tf_listener_haro_to_person')
listener = tf.TransformListener()
if len(sys.argv) < 3:
print("usage: tf_haro_to_object_listener.py parent child")
else:
follower_model_name = sys.argv[1]
model_to_be_followed_name = sys.argv[2]
topic_to_publish_name = "/"+str(follower_model_name)+"_to_"+str(model_to_be_followed_name)+"_tf_translation"
tf_translation = rospy.Publisher(topic_to_publish_name, Twist ,queue_size=1)
rate = rospy.Rate(10.0)
ctrl_c = False
follower_model_frame = "/"+follower_model_name
model_to_be_followed_frame = "/"+model_to_be_followed_name
def shutdownhook():
global ctrl_c
ctrl_c = True
rospy.on_shutdown(shutdownhook)
while not ctrl_c:
try:
(trans,rot) = listener.lookupTransform(follower_model_frame, model_to_be_followed_frame, rospy.Time(0))
translation_object = Vector3()
translation_object.x = trans[0]
translation_object.y = trans[1]
translation_object.z = trans[2]
angular = math.atan2(translation_object.y, translation_object.x)
linear = math.sqrt(translation_object.x ** 2 + translation_object.y ** 2)
cmd = Twist()
cmd.linear.x = linear
cmd.angular.z = angular
tf_translation.publish(cmd)
except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
continue
rate.sleep()
Es beginnt so (es wird angenommen, dass alle anderen Hauptknoten und rviz bereits ausgeführt werden):roslaunch linorobot overlay_meny_my.launch
python tf_haro_to_object_listener.py base_link map
Wir werden den Winkel und die Entfernung des base_link-Frames relativ zur Karte sehen.Fortsetzung folgt.