Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
mapping_integration_web [2018/02/13 14:45]
mgp3202018 v0.1
mapping_integration_web [2020/07/03 17:38] (current)
mai Tag0 Added: pepper
Line 2: Line 2:
  
 Cette documentation explique comment créer une carte à partir d'un robot pour l'​intégrer ensuite dans une interface Web et afficher la localisation du robot. Cette documentation explique comment créer une carte à partir d'un robot pour l'​intégrer ensuite dans une interface Web et afficher la localisation du robot.
 +Ce travail se base sur cet article : [[https://​arxiv.org/​pdf/​1704.04797.pdf|Voir l'​article]]
 +
 +Vidéo de démonstration de la solution : [[https://​youtu.be/​xF7-tTI0zrE|Voir la vidéo YouTube]]
  
 ===== Liste des packages utilisés ===== ===== Liste des packages utilisés =====
  
-  * http://​wiki.ros.org/​rosbag +  * http://​wiki.ros.org/​rosbag ​: Un ensemble d'​outils pour l'​enregistrement et la lecture de topics ROS. 
-  * http://​wiki.ros.org/​gmapping +  * http://​wiki.ros.org/​gmapping ​: Création d'une carte 2D à partir des lasers d'un robot. 
-  * http://​wiki.ros.org/​map_server +  * http://​wiki.ros.org/​map_server ​: Permet de publier et d'​enregistrer des cartes. 
-  * http://​wiki.ros.org/​rosbridge_server +  * http://​wiki.ros.org/​rosbridge_server ​: Publie les topics ROS dans un WebSocket au format JSON. 
-  * http://​wiki.ros.org/​roslibjs/ +  * http://​wiki.ros.org/​roslibjs ​: Librairie JS, permet la lecture et l'​écriture de topics en interaction avec le WebSocket. 
-  * http://​wiki.ros.org/​ros2djs +  * http://​wiki.ros.org/​ros2djs ​: Librairie JS, permet d'​afficher les cartes dans une interface web. 
-  * http://​wiki.ros.org/​amcl +  * http://​wiki.ros.org/​amcl ​: Un système de localisation probabiliste pour un robot se déplaçant en 2D. 
-  * http://​wiki.ros.org/​depth_image_proc +  * http://​wiki.ros.org/​nodelet : Fournit un moyen d'​exécuter plusieurs algorithmes dans le même processus. 
-  * http://​wiki.ros.org/​pointcloud_to_laserscan +  * http://​wiki.ros.org/​image_proc : Rectification d'​image et traitement de couleurs. 
-  * http://​wiki.ros.org/​rb1_base_common+  * http://​wiki.ros.org/​depth_image_proc ​: Traitement des "depth images"​ (transformation en "point cloud" notamment). 
 +  * http://​wiki.ros.org/​pointcloud_to_laserscan ​: Transforme un "point cloud" en "laser scan". 
 +  * http://​wiki.ros.org/​rb1_base_common ​: Contrôle de la base de RB-1. 
 +  * http://​wiki.ros.org/​pepper_bringup : Connexion à Pepper. 
 + 
 +Tous ces packages peuvent être installés en utilisant la commande : 
 +  sudo apt-get install ros-<​ros_distribution>​-<​ros_package>​ 
 +La liste des packages disponibles sur une distribution ROS peut être affichée comme ceci : 
 +  apt-cache search ros-<​ros_distribution>​ 
 + 
 +Pour Pepper, il faut utiliser ROS Kinetic (installable sur Ubuntu 16.04).\\ 
 +Pour RB-1, il faut utiliser ROS Indigo (installable sur Ubuntu 14.04).
  
 ===== Création d'un rosbag ===== ===== Création d'un rosbag =====
Line 23: Line 37:
 On se connecte au robot. Pour Pepper : On se connecte au robot. Pour Pepper :
   roslaunch pepper_bringup pepper_full_py.launch nao_ip:​=<​ip_de_pepper>​ roscore_ip:​=127.0.0.1   roslaunch pepper_bringup pepper_full_py.launch nao_ip:​=<​ip_de_pepper>​ roscore_ip:​=127.0.0.1
-Pour RB1 :+Pour RB1 (il faut au préalable être connecté au WiFi du robot) ​:
   export ROS_MASTER_URI=http://​rb1:​11311   export ROS_MASTER_URI=http://​rb1:​11311
 On lance l'​enregistrement des topics fournis par le robot :\\ On lance l'​enregistrement des topics fournis par le robot :\\
Line 29: Line 43:
   rosbag record -O </​chemin/​et/​nomDuBag>​.bag -a   rosbag record -O </​chemin/​et/​nomDuBag>​.bag -a
   rosbag record -O </​chemin/​et/​nomDuBag>​.bag /<​topic_1>​ /<​topic_2>​   rosbag record -O </​chemin/​et/​nomDuBag>​.bag /<​topic_1>​ /<​topic_2>​
-On fait maintenant se déplacer le robot comme on le souhaite.\\ On arrête ensuite l'​enregistrement avec ''​CTRL''​ + ''​C''​.\\+On fait maintenant se déplacer le robot comme on le souhaite. On arrête ensuite l'​enregistrement avec ''​CTRL''​ + ''​C''​.\\
 On peut inspecter le contenu du rosbag avec la commande : On peut inspecter le contenu du rosbag avec la commande :
   rosbag info <​nom_du_rosbag>​.bag   rosbag info <​nom_du_rosbag>​.bag
Line 40: Line 54:
 NB : il faut **impérativement** lancer cette commande avant de lancer tous les nodes. NB : il faut **impérativement** lancer cette commande avant de lancer tous les nodes.
   rosparam set use_sim_time true   rosparam set use_sim_time true
-On génère ensuite une carte à l'aide de //slam_gmapping/​/. Il faut passer en argument le topic du robot correspondant au type //​sensor_msgs/​LaserScan//​ (i.e. les lasers du robot). Par défaut, les cartes générées sont trop "​dé-zoomées",​ il faut donc jouer sur la résolution (paramètre //delta//) et éventuellement sur les dimensions. Par exemple, pour RB1, la commande suivante génère une carte exploitable :+On génère ensuite une carte à l'aide de [[http://wiki.ros.org/gmapping#​slam_gmapping|slam_gmapping]]. Il faut passer en argument le topic du robot correspondant au type //​sensor_msgs/​LaserScan//​ (i.e. les lasers du robot). Par défaut, les cartes générées sont trop "​dé-zoomées",​ il faut donc jouer sur la résolution (paramètre //delta//) et éventuellement sur les dimensions.\\ 
 +Par exemple, pour RB1, la commande suivante génère une carte exploitable :
   rosrun gmapping slam_gmapping scan:​=/​front_laser/​scan _xmin:=-1 _xmax:=1 _ymin:=-1 _ymax:=1 _delta:​=0.035 _maxUrange:​=10   rosrun gmapping slam_gmapping scan:​=/​front_laser/​scan _xmin:=-1 _xmax:=1 _ymin:=-1 _ymax:=1 _delta:​=0.035 _maxUrange:​=10
 On rejoue le bag : On rejoue le bag :
-  rosbag play --clock </​chemin/​et/​nomDuBag+  rosbag play --clock <nom_du_bag>.bag 
-Avant d'​annuler le gmapping, on sauve la carte :+Avant d'​annuler le gmapping, on enregistre ​la carte :
   rosrun map_server map_saver -f <​map_name>​   rosrun map_server map_saver -f <​map_name>​
 +Exemple de carte obtenue avec les lasers de RB-1 :\\
 +{{:​screenshot_19.png?​200|}}\\
 +Les traits que l'on voit sont des aberrations dues aux lasers, on peut réduire leur impact en jouant sur les paramètres lors de la génération de la carte (paramètre //​Urange//​).
  
 ==== Spécificités de Pepper ==== ==== Spécificités de Pepper ====
  
- +Les lasers de Pepper n'​étant pas assez puissants, on ne peut pas utiliser son topic //​sensor_msgs/​LaserScan//​. Il faut donc générer la carte à partir de la caméra 3D, ce qui demande plusieurs transformations des données récupérées. 
-==== Spécificités ​de RB1 ====+On lance un nodelet manager : 
 +  ​rosrun nodelet nodelet manager __name:=nodelet_manager 
 +On lance notre nodelet depth_image_proc/​convert_metric pour convertir au bon format :\\ 
 +**Erreur bloquante ici, à revoir.** 
 +  rosrun nodelet nodelet load depth_image_proc/​convert_metric nodelet_manager image_raw:=/​pepper_robot/​camera/​depth/​camera/​image_raw 
 +On lance image_proc qui retire la distorsion des images : 
 +  rosrun image_proc image_proc image_raw:=image camera_info:​=/​pepper_robot/​camera/​depth/​camera/​camera_info 
 +On lance notre nodelet depth_image_proc/​point_cloud_xyz pour faire un nuage de points à partir de l'​image raw de la caméra : 
 +  rosrun nodelet nodelet load depth_image_proc/​point_cloud_xyz nodelet_manager image_rect:=/image_rect camera_info:​=/​camera/​depth/​camera/​camera_info 
 +On lance le package //​pointcloud_to_laserscan//​ qui génère un //​sensor_msgs/​LaserScan//​ à partir du nuage de points : 
 +  rosrun pointcloud_to_laserscan pointcloud_to_laserscan_node cloud_in:=/points 
 +Puis on génère la carte comme vu dans la partie 3.
  
 ===== Intégration de la carte dans une interface Web ===== ===== Intégration de la carte dans une interface Web =====
 +
 +Le principe est le suivant : //​rosbridge_server//​ publie un WebSocket contenant les informations des topics au format JSON. //​roslibjs//​ vient ensuite souscrire aux topics et permet la récupération des données dans une page Web (voir [[http://​wiki.ros.org/​roslibjs/​Tutorials/​BasicRosFunctionality|le tutoriel roslibjs]]). Enfin, //ros2djs// permet l’affichage d’un élément de type //​nav_msgs/​OccupancyGrid//​ (le format ROS des cartes) dans l’interface (voir [[http://​wiki.ros.org/​ros2djs/​Tutorials/​VisualizingAMap|le tutoriel ros2djs]]).\\
 +\\
 +Pour cela, on publie un topic de map à partir du fichier de map enregistré.\\
 +NB : il faut bien utiliser le fichier .yaml et non le fichier .pgm de la carte.
 +  rosrun map_server map_server <​yaml_file>​.yaml
 +On lance le WebSocket :
 +  roslaunch rosbridge_server rosbridge_websocket.launch
 +
 +Voici le code HTML de la page que nous avons utilisé. Attention à bien avoir les fichiers des librairies css et javascript positionnées au bon endroit.
 +<​hidden>​
 +<code html>
 +
 +<​!doctype html>
 +<html lang="​fr">​
 + <​head>​
 + <!-- Required meta tags -->
 + <meta charset="​utf-8">​
 + <meta name="​viewport"​ content="​width=device-width,​ initial-scale=1,​ shrink-to-fit=no">​
 + <link rel="​apple-touch-icon"​ sizes="​180x180"​ href="​apple-touch-icon.png">​
 + <link rel="​icon"​ type="​image/​png"​ sizes="​32x32"​ href="​favicon-32x32.png">​
 + <link rel="​icon"​ type="​image/​png"​ sizes="​16x16"​ href="​favicon-16x16.png">​
 + <link rel="​manifest"​ href="​manifest.json">​
 + <link rel="​mask-icon"​ href="​safari-pinned-tab.svg"​ color="#​5bbad5">​
 + <meta name="​theme-color"​ content="#​ffffff">​
 + <link rel="​icon"​ href="​favicon.ico">​
 +
 + <!-- jQuery first, then Popper.js, then Bootstrap JS -->
 + <​script src="​js/​jquery-3.3.1.min.js"></​script>​
 + <​script src="​js/​popper.min.js"></​script>​
 + <​script src="​js/​bootstrap.min.js"></​script>​
 + <​script src="​https://​static.robotwebtools.org/​EaselJS/​current/​easeljs.js"></​script>​
 + <​script src="​https://​static.robotwebtools.org/​EventEmitter2/​current/​eventemitter2.min.js"></​script>​
 + <​script src="​https://​static.robotwebtools.org/​roslibjs/​current/​roslib.js"></​script>​
 + <​script src="​js/​ros2d.min.js"></​script>​
 +
 + <!-- Bootstrap CSS -->
 + <link rel="​stylesheet"​ href="​css/​bootstrap.min.css">​
 + <link rel="​stylesheet"​ href="​css/​style.css">​
 +
 + <​title>​Interface de navigation</​title>​
 + </​head>​
 +
 + <​body>​
 + <nav class="​navbar navbar-expand-md bg-dark navbar-dark">​
 + <div class="​container">​
 +
 + <!-- Brand -->
 + <a class="​navbar-brand"​ href="#">​
 + <​img src="​android-chrome-192x192.png"​ alt="​Logo"​ style="​width:​25px;">&​nbsp;&​nbsp;​Interface de navigation
 + </​a>​
 + </​div> ​
 + </​nav>​
 +
 + <div class="​container">​
 + <div class="​row mt-3">​
 + <div class="​col">​
 + <​div id="​map"></​div>​
 + </​div>​
 + <div class="​col">​
 + <​h2>​Contrôle du robot</​h2>​
 + <​p>​Pour contrôler le robot, cliquez simplement sur la map.</​p>​
 + <​p>​Le point bleu représente la localisation actuelle du robot. Le point rouge représente la destination choisie.</​p>​
 + </​div>​
 + </​div>​
 + </​div>​
 +
 + <​footer class="​footer mt-4">​
 + <div class="​container">​
 + <​span class="​text-muted">​© IMT Atlantique</​span>​
 + </​div>​
 + </​footer>​
 +
 + <​script>​
 +
 + $(document).ready(function() {
 +
 + // Variables sur la map
 + var posImageX = 0;
 + var posImageY = 0;
 + var widthMap = 0;
 + var heightMap = 0;
 + var resolutionMap = 0;
 + var svg;
 +
 + // Position d'​origine du robot
 + var posOriginX = 0;
 + var posOriginY = 0;
 +
 + // Position du clic
 + var posClicX, posClicY;
 +
 + // Connexion à ROS
 + var ros = new ROSLIB.Ros({
 + url : '​ws://​localhost:​9090'​
 + });
 +
 + ros.on('​connection',​ function() {
 + console.log("​Connecté au Web Socket"​);​
 + });
 +
 + // On souscrit au topic /​map_metadata
 + // ----------------------------------
 + var listenerMetaData = new ROSLIB.Topic({
 + ros : ros,
 + name : '/​map_metadata',​
 + messageType : '​nav_msgs/​MapMetaData'​
 + });
 +
 + listenerMetaData.subscribe(function(message) {
 +
 + console.log("​Abonnement au topic /​map_metadata"​);​
 +
 + // On récupère les métadonnées de la map
 + posOriginX = message.origin.position.x;​
 + posOriginY = message.origin.position.y;​
 + widthMap = message.width;​
 + heightMap = message.height;​
 + resolutionMap = message.resolution;​
 + listenerMetaData.unsubscribe();​
 +
 + console.log('​OriginX = ' + posOriginX + ', OriginY = ' + posOriginY);​
 +
 + // On créé le viewer
 + var viewer = new ROS2D.Viewer({
 + divID : '​map',​
 + width : widthMap,
 + height : heightMap
 + });
 +
 + // Préparation de la map
 + var gridClient = new ROS2D.OccupancyGridClient({
 + ros : ros,
 + rootObject : viewer.scene
 + });
 +
 + // Scale the canvas to fit to the map
 + gridClient.on('​change',​ function() {
 + viewer.scaleToDimensions(gridClient.currentGrid.width,​ gridClient.currentGrid.height);​
 + viewer.shift(gridClient.currentGrid.pose.position.x,​ gridClient.currentGrid.pose.position.y);​
 + });
 +
 + var image = $('#​map canvas'​);​
 + posImageX = image.offset().left;​
 + posImageY = image.offset().top;​
 +
 + // On créé le svg
 + svg = document.createElementNS("​http://​www.w3.org/​2000/​svg",​ "​svg"​);​
 + svg.setAttribute('​id',​ '​svg-canvas'​);​
 + svg.setAttribute('​style',​ '​position:​ absolute; top: ' + posImageY + 'px; left: ' + posImageX + '​px;'​);​
 + svg.setAttribute('​width',​ widthMap);
 + svg.setAttribute('​height',​ heightMap);
 + document.body.appendChild(svg);​
 +
 + $('#​svg-canvas'​).on("​click",​ function(e){
 + posClicX = e.pageX - posImageX;
 + posClicY = e.pageY - posImageY;
 +
 + console.log('​Clic X = ' + posClicX + ' / ' + widthMap + ', clic Y = ' + posClicY + ' / ' + heightMap);
 +
 + $('​circle.destination'​).remove();​
 + var shape = document.createElementNS("​http://​www.w3.org/​2000/​svg",​ "​circle"​);​
 + shape.setAttributeNS(null,​ "​cx",​ posClicX);
 + shape.setAttributeNS(null,​ "​cy",​ posClicY);
 + shape.setAttributeNS(null,​ "​r",​ 3);​
 + shape.setAttributeNS(null,​ "​fill",​ '​red'​);​
 + shape.setAttributeNS(null,​ "​class",​ '​destination'​);​
 + svg.appendChild(shape);​
 + });
 +
 + // On souscrit au topic /amcl_pose
 + // ----------------------------------
 + var listener = new ROSLIB.Topic({
 + ros : ros,
 + name : '/​amcl_pose',​
 + messageType : '​geometry_msgs/​PoseWithCovarianceStamped'​
 + });
 +
 + console.log("​Abonnement au topic /​amcl_pose"​);​
 +
 + listener.subscribe(function(message) {
 +
 + var posX = (0 + (message.pose.pose.position.x - posOriginX)/​resolutionMap);​
 + var posY = (heightMap + -1*(message.pose.pose.position.y - posOriginY)/​resolutionMap);​
 +
 + console.log('​X = ' + posX + ', Y = ' + posY);
 + $('​circle.source'​).remove();​
 + var shape = document.createElementNS("​http://​www.w3.org/​2000/​svg",​ "​circle"​);​
 + shape.setAttributeNS(null,​ "​cx",​ posX);
 + shape.setAttributeNS(null,​ "​cy",​ posY);
 + shape.setAttributeNS(null,​ "​r",​ 3);​
 + shape.setAttributeNS(null,​ "​fill",​ '​blue'​);​
 + shape.setAttributeNS(null,​ "​class",​ '​source'​);​
 + svg.appendChild(shape);​
 + //​listener.unsubscribe();​
 +
 + });
 + });
 +
 + });
 +
 + </​script>​
 + </​body>​
 +</​html>​
 +</​code>​
 +</​hidden>​
  
 ===== Affichage de la localisation du robot ===== ===== Affichage de la localisation du robot =====
 +
 +Pour la localisation du robot, on utilise le package //amcl//.\\
 +Si ce n'est pas déjà fait, on publie la carte :
 +  rosrun map_server map_server <​yaml_file>​.yaml
 +On lance la lecture du bag :
 +  rosbag play --clock <​nom_du_bag>​.bag
 +On lance la détection AMCL à partir des lasers (pour RB1, ///​front_laser/​scan//​)
 +  rosrun amcl amcl scan:​=<​nom_du_topic_laser_scan>​
 +La page Web s'​abonne au topic de positionnement et affiche dynamiquement la position du robot sur la carte.
 +
 +===== Travaux futurs =====
 +==== Pour Pepper ====
 +
 +  * Troubleshooting de l'​erreur avec le package //​depth_image_proc/////​convert_metric//​ pour pouvoir générer une carte à partir de la camera de Pepper
 +  * Normalement,​ la suite devrait fonctionner comme avec RB-1
 +
 +==== Pour le déplacement de RB-1 à la localisation spécifiée ====
 +
 +  * Publier les coordonnées de destination depuis l'​interface web vers ROS en utilisant //​roslibjs//​
 +  * Utiliser ces coordonnées pour déplacer RB-1 (probablement grâce au package //​rb1_base_common//​ / //​navigation//​ ou //​control//​)
 +{{tag> pepper ros }}
  • mapping_integration_web.1518533155.txt.gz
  • Last modified: 2019/04/25 14:08
  • (external edit)