ROS2 Tutorial: Creating map from a Floor Plan

Creating a map from a PNG

The code for this section is located in navigation_bot_06 part of an archive. Note that archive includes some additional folders, like maps and worlds, that are used by all projects and therefore are located outside of them. Also note that we use ROS2 Galactic now.

Introduction

In this tutorial, I am going to follow automaticaddison.com, while he is following https://www.youtube.com/watch?v=ySlU5CIXUKE&ab_channel=CaraSperbeck, and there is a chance that is not the end of a line. Also, they use ROS 1, while I use ROS2. Anyway, both sources are greatly recommended.

That said, I have to mention that "you can not simply take a floor plan and convert it to a map", which is something authors of the sources (above) do not address. See, a normal floor plan has doors, and they can be opened, closed or half-opened. The approach we use doesn't take it into consideration. Also, furniture can create an obstacle for a robot, or maybe not. For example, a vacuum cleaner can move under the table, except for the spots where table's legs are. That last problem can be solved by drawing "obstacles" (table's legs) with one color and non-obstacles (table's top) with another. Then we can take it into consideration when converting... Again, in this article the issue is ignored.

Creating a Blueprint

A floor plan we are going to use is a simple .png file. In "maps" folder, add a B&W image you want to convert:

Converting the image

First of all, we need to make sure the image is in binary format. Here is a Python file, credits to automaticaddison.com; we place it in utils folder that is located in the src folder of our workspace, outside navigation_bot_06 folder. This way any project (like futire navigation_bot_XXX) can use it:

convert_png_to_binary.py:

                        import cv2 # Import OpenCV
    
                        # read the image file
                        img = cv2.imread('../maps/floor_plans/maze_01.png')
                        
                        ret, bw_img = cv2.threshold(img, 220, 255, cv2.THRESH_BINARY)
                        
                        # converting to its binary form
                        bw = cv2.threshold(img, 240, 255, cv2.THRESH_BINARY)
                        
                        cv2.imwrite("../maps/floor_plans/maze_01_binary.png", bw_img)
                

Note that maps folder should have floor_plans subfolder:

                    $ cd $HOME/SnowCron/ros_projects/harsh/src/maps
                    $ mkdir floor_plans
                

To run this script, we need to:

                    $ cd $HOME/SnowCron/ros_projects/harsh/src/utils
                    $ python3 convert_png_to_binary.py
                

Generating the map

In utils folder, create convert_png_to_map.py file

                        import numpy as np
                        import cv2
                        import math
                        import os.path
                        
                        # The name of your floor plan you want to convert to a ROS map: 
                        strSrcFileName = "../maps/floor_plans/maze_01_binary.png"
                        image = cv2.imread(strSrcFileName)
                        
                        strDstFileName = "maze_01_binary"
                        strDstLocation = "../maps/"
                        
                        # We scale the map, so it is 40 meters wide
                        img_width_meters = 40
                        
                        width, height, _ = image.shape
                        
                        x1 = [0,width,  0,0]
                        y1 = [0,0,      0,height]
                        
                        deltax = img_width_meters
                        dx = math.sqrt((x1[1]-x1[0])**2 + (y1[1]-y1[0])**2)*.05
                        sx = deltax / dx
                        
                        deltay = img_width_meters * float(height) / width
                        dy = math.sqrt((x1[3]-x1[2])**2 + (y1[3]-y1[2])**2)*.05
                        sy = deltay/dy 
                        
                        res = cv2.resize(image, None, fx=sx, fy=sy, interpolation = cv2.INTER_CUBIC)
                        
                        # Convert to grey
                        res = cv2.cvtColor(res, cv2.COLOR_BGR2GRAY)
                        #cv2.imwrite("KEC_BuildingCorrected.pgm", res );
                        #      cv2.imshow("Image2", res)
                        
                        completeFileNameMap = os.path.join(strDstLocation, strDstFileName +".pgm")
                        completeFileNameYaml = os.path.join(strDstLocation, strDstFileName +".yaml")
                            
                        cv2.imwrite(completeFileNameMap, res )
                        
                        yaml = open(completeFileNameYaml, "w")
                        
                        yaml.write("image: " + strDstFileName + ".pgm\n")
                        yaml.write("resolution: 0.050000\n")
                        yaml.write("origin: [" + str(-1) + "," +  str(-1) + ", 0.000000]\n")
                        yaml.write("negate: 0\noccupied_thresh: 0.65\nfree_thresh: 0.196")
                        yaml.close()
                

This Python file is based on the https://automaticaddison.com/, the difference is, it calculates size of a map without user input. Ideally, it should take command line parameters for that, but right now everything is hardcoded.

Compiling and Running

                        $ colcon build --packages-select navigation_bot_06
                        $ source install/setup.bash
                        $ ros2 launch navigation_bot_06 main_simulation_launch.py world:=src/worlds/maze.sdf slam:=False
                

(C) snowcron.com, all rights reserved

Please read the disclaimer