示例#1
0
class Slam(Node):
    """
    SLAM NODE
    This node is in charge of receiving data from the LIDAR and creating a map of the environment
    while locating the robot in the map.

    INPUT
    - Bottle detected by the Neuron (to be removed soon)
    - Lidar detections

    OUTPUT
    - Robot position: /robot_pos
    - Map as a byte array: /world_map
    (this map can be interpred using the map_utils.py package !)
    """
    def __init__(self):
        super().__init__('slam_node')

        # Create second subscription (SLAM)
        self.subscription2 = self.create_subscription(
            LidarData, 'lidar_data', self.listener_callback_lidar, 100)
        self.subscription2  # prevent unused variable warning

        # Create third subscription (Motor speeds to compute robot pose change)
        self.subscription3 = self.create_subscription(
            MotorsSpeed, 'motors_speed', self.listener_callback_motorsspeed,
            1000)
        self.subscription3  # prevent unused variable warning

        # create subscription to control map_quality
        self.slam_control_sub = self.create_subscription(
            String, 'slam_control', self.slam_control_callback, 1000)

        # Create publisher for the position of the robot, and for the map
        self.publisher_position = self.create_publisher(
            Position, 'robot_pos', 10)
        self.publisher_map = self.create_publisher(Map, 'world_map', 10)

        # Initialize parameters for slam
        laser = LaserModel(detection_margin=DETECTION_MARGIN,
                           offset_mm=OFFSET_MM)
        self.slam = RMHC_SLAM(laser,
                              MAP_SIZE_PIXELS,
                              MAP_SIZE_METERS,
                              map_quality=MAP_QUALITY,
                              hole_width_mm=OBSTACLE_WIDTH_MM,
                              x0_mm=X0,
                              y0_mm=Y0,
                              theta0=0,
                              sigma_xy_mm=DEFAULT_SIGMA_XY_MM)
        self.trajectory = []
        self.mapbytes = bytearray(MAP_SIZE_PIXELS * MAP_SIZE_PIXELS)
        self.previous_distances = None
        self.previous_angles = None
        self.robot_pose_change = np.array([0.0, 0.0, 0.0])
        self.is_map_quality_high = True

        # DEBUG parameters
        self.map_index = 0
        self.previous_pos = (0, 0, 0)
        self.s = time.time()
        print("SLAM is starting")

    def slam_control_callback(self, msg):
        """Called to changed the map quality
        """
        if msg.data == "save_state":
            print("Slam is saving current state")
            self.last_valid_map = self.mapbytes.copy()
            self.last_valid_pos = self.slam.getpos()
        elif msg.data == "recover_state":
            # METHOD 1
            self.slam.setmap(self.last_valid_map)
            ##  self.slam.setpos(self.last_valid_pos)
        elif msg.data == "freeze":
            self.last_valid_map = self.mapbytes.copy()
            self.last_valid_pos = self.slam.getpos()
        elif msg.data == "unfreeze":
            self.slam.setmap(self.last_valid_map)
            # self.slam.setpos(self.last_valid_pos)
        return
        ##########################
        print("changing quality")
        if self.is_map_quality_high:
            #self.previous_map = self.mapbytes.copy()
            self.slam.map_quality = 0
            self.slam.sigma_xy_mm = 5 * DEFAULT_SIGMA_XY_MM
        else:
            #self.slam.setmap(self.previous_map)
            self.slam.map_quality = MAP_QUALITY
            self.slam.sigma_xy_mm = DEFAULT_SIGMA_XY_MM
        self.is_map_quality_high = not self.is_map_quality_high

    def listener_callback_motorsspeed(self, msg):
        """
        Must compute the robot pose change since the last time the data was collected from the motor
        The input are the motors speed and the time change.

        Documentation of the SLAM library says :
            "pose_change is a tuple (dxy_mm, dtheta_degrees, dt_seconds) computed from odometry"
        """
        dx_left = msg.RADIUS * msg.left * (0.1047) * msg.time_delta
        dx_right = msg.RADIUS * msg.right * (0.1047) * msg.time_delta
        delta_r = 1 / 2 * (dx_left + dx_right)  # [mm]
        delta_d = -(dx_right - dx_left)
        delta_theta = np.arctan(delta_d / msg.LENGTH) * 57.2958  # [deg]
        self.robot_pose_change += [delta_r, delta_theta, msg.time_delta]

    def listener_callback_lidar(self, msg):
        # transform angles to $FORMAT1
        angles = np.array(msg.angles)
        angles = list(
            (angles + 180) % 360)  # because LIDAR is facing the robot
        distances = list(msg.distances)

        # Update SLAM with current Lidar scan and scan angles if adequate
        if len(distances) > MIN_SAMPLES:
            e = time.time()
            self.s = e
            self.slam.update(distances,
                             scan_angles_degrees=angles,
                             pose_change=tuple(self.robot_pose_change))
            # self.analyse_odometry()
            self.robot_pose_change = np.array([0.0, 0.0, 0.0])
            self.previous_distances = distances.copy()
            self.previous_angles = angles.copy()
        elif self.previous_distances is not None:
            # If not adequate, use previous
            print("Slam NOT updating")
            self.slam.update(self.previous_distances,
                             scan_angles_degrees=self.previous_angles)

        # Get current robot position and current map bytes as grayscale
        x, y, theta = self.slam.getpos()
        self.slam.getmap(self.mapbytes)

        # Send topics
        pos = Position()
        pos.x = float(x)
        pos.y = float(y)
        pos.theta = float(theta)
        self.publisher_position.publish(pos)
        map_message = Map()
        map_message.map_data = self.mapbytes
        map_message.index = self.map_index
        self.publisher_map.publish(map_message)
        print("Map sent: ", self.map_index)
        self.map_index += 1

    # debug functions here (do NOT call them on real runs)

    def analyse_odometry(self):
        """Compare the odometry resuls with the change in position according to SLAM. 
        For this function to work, self.robot_pose_change must be set to something different than zero.
        And the variabe self.previous_pos must exist
        """
        x, y, theta = self.slam.getpos()
        x_old, y_old, theta_old = self.previous_pos
        dr = np.sqrt((x - x_old)**2 + (y - y_old)**2)
        dtheta = theta - theta_old
        print("-------")
        print("SLAM     change of pos: {:.3f}  {:.3f}".format(dr, dtheta))
        print("Odometry change of pos: {:.3f}  {:.3f} ".format(
            self.robot_pose_change[0], self.robot_pose_change[1]))
        self.previous_pos = (x, y, theta)

    def find_lidar_range(self, distances, angles):
        """Finds the critical angles of the LIDAR and prints them. 
        Careful, this function works (currently) only with the previous angles format.
        """
        (i1, i2) = lidar_utils.get_valid_lidar_range(distances=distances,
                                                     angles=angles,
                                                     n_points=10)
        print('indexes : {} : {} with angles {} - {}'.format(
            i1, i2, angles[i1], angles[i2]))
class narlam:
    def __init__(self):
        self.flag = 0
        self.pause = 0
        self.lidar = Lidar(LIDAR_DEVICE)
        self.slam = RMHC_SLAM(LaserModel(), MAP_SIZE_PIXELS, MAP_SIZE_METERS)
        #self.viz = MapVisualizer(MAP_SIZE_PIXELS, MAP_SIZE_METERS, 'SLAM')
        self.trajectory = []
        self.mapbytes = bytearray(MAP_SIZE_PIXELS * MAP_SIZE_PIXELS)
        self.iterator = self.lidar.iter_scans()
        self.previous_distances = None

        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0

    def slam_no_map(self, path_map, map_name_pgm, map_name_png):
        self.flag = 0
        self.pause = 0
        path_map_name = path_map + '/' + map_name_pgm  # map for reusing

        next(self.iterator)

        while True:
            if self.flag == 1:
                break
            if self.pause == 1:
                continue

            items = [item for item in next(self.iterator)]
            distances = [item[2] for item in items]
            angles = [item[1] for item in items]

            if len(distances) > MIN_SAMPLES:
                f = interp1d(angles, distances, fill_value='extrapolate')
                distances = list(f(np.arange(360)))
                self.slam.update(distances)
                self.previous_distances = distances.copy()

            elif self.previous_distances is not None:
                self.slam.update(self.previous_distances)

            self.x, self.y, local_theta = self.slam.getpos()
            local_theta = local_theta % 360
            if local_theta < 0:
                self.theta = 360 + local.theta
            else:
                self.theta = local_theta

            self.slam.getmap(self.mapbytes)

        # On SLAM thread termination, save map image in the map directory
        # PNG format(To see/pathplannig)
        image = Image.frombuffer('L', (MAP_SIZE_PIXELS, MAP_SIZE_PIXELS),
                                 self.mapbytes, 'raw', 'L', 0, 1)
        image.save(path_map + '/' + map_name_png)

        # PGM format(To reuse map)
        pgm_save(path_map_name, self.mapbytes,
                 (MAP_SIZE_PIXELS, MAP_SIZE_PIXELS))

        self.lidar.stop()
        self.lidar.disconnect()

    def slam_yes_map(self, path_map, map_name):
        self.flag = 0
        self.pause = 0
        path_map_name = path_map + '/' + map_name

        map_bytearray, map_size = pgm_load(path_map_name)
        self.slam.setmap(map_bytearray)

        next(self.iterator)

        while True:
            if self.flag == 1:
                break
            if self.pause == 1:
                pass

            items = [item for item in next(self.iterator)]
            distances = [item[2] for item in items]
            angles = [item[1] for item in items]

            if len(distances) > MIN_SAMPLES:
                f = interp1d(angles, distances, fill_value='extrapolate')
                distances = list(f(np.arange(360)))
                self.slam.update(distances, should_update_map=False)
                self.previous_distances = distances.copy()

            elif self.previous_distances is not None:
                self.slam.update(self.previous_distances,
                                 should_update_map=False)

            self.x, local_y, local_theta = self.slam.getpos()
            self.y = MAP_SIZE_METERS * 1000 - local_y
            local_theta = local_theta % 360
            if local_theta < 0:
                local_theta = 360 + local.theta
            else:
                local_theta = local_theta
            #6/11 -> we found that the vehicle's angle was reversed on the map
            self.theta = (local_theta + 180) % 360

            self.slam.getmap(self.mapbytes)

        self.lidar.stop()
        self.lidar.disconnect()
示例#3
0
def run_slam(LIDAR_DEVICE, MAP_SIZE_PIXELS, MAP_SIZE_METERS, mapbytes,
             posbytes, odometry, command, state, STATES):
    MIN_SAMPLES = 200

    # Connect to Lidar unit
    lidar = start_lidar(LIDAR_DEVICE)
    # Create an RMHC SLAM object with a laser model and optional robot model
    slam = RMHC_SLAM(LaserModel(),
                     MAP_SIZE_PIXELS,
                     MAP_SIZE_METERS,
                     map_quality=_DEFAULT_MAP_QUALITY,
                     hole_width_mm=_DEFAULT_HOLE_WIDTH_MM,
                     random_seed=None,
                     sigma_xy_mm=_DEFAULT_SIGMA_XY_MM,
                     sigma_theta_degrees=_DEFAULT_SIGMA_THETA_DEGREES,
                     max_search_iter=_DEFAULT_MAX_SEARCH_ITER)
    # Initialize empty map
    mapbytes_ = bytearray(MAP_SIZE_PIXELS * MAP_SIZE_PIXELS)
    # Send command to start scan
    lidar.start_scan()
    # We will use these to store previous scan in case current scan is inadequate
    previous_distances = None
    previous_angles = None

    pose_change = (0, 0, 0)
    pose_change_find = (0, 0, 0)
    iter_times = 1
    while command[0] != b'qa':

        # read a scan
        items = lidar.scan()

        # Extract distances and angles from triples
        distances = [item[2] for item in items]
        angles = [item[1] for item in items]

        if state[0] == STATES['stop']:
            if command[0] == b'lm':
                mapbytes_find = mapbytes

                pose_change_find = find_slam_pos(lidar, MAP_SIZE_PIXELS,
                                                 MAP_SIZE_METERS,
                                                 mapbytes_find,
                                                 pose_change_find,
                                                 iter_times / 2.0, slam)
                iter_times = iter_times + 1.0
                print(pose_change_find)
                pose_change = pose_change_find
                pose_change_find = (0, 0, 0)
                '''
                items = lidar.scan()

        # Extract distances and angles from triples
                distances = [item[2] for item in items]
                angles    = [item[1] for item in items]

                slam.update(distances, pose_change, scan_angles_degrees=angles)
                '''

                if (iter_times > 10):
                    command[0] = b'w'
                    print(slam)

                    slam.setmap(mapbytes_find)
                    pose_change = pose_change_find
                    pose_change_find = (0, 0, 0)
                    print('ennnnnnnnnnnnnnnnnnd')
                    print(pose_change)
                    iter_times = 1

        else:

            # Update SLAM with current Lidar scan and scan angles if adequate
            if len(distances) > MIN_SAMPLES:
                slam.update(distances, pose_change, scan_angles_degrees=angles)
                previous_distances = distances.copy()
                previous_angles = angles.copy()

            # If not adequate, use previous
            elif previous_distances is not None:
                slam.update(previous_distances,
                            pose_change,
                            scan_angles_degrees=previous_angles)

            #pass the value into point_cal function
            global point_a, point_b
            global current_theta_o
            # Get current robot position
            x, y, theta = slam.getpos()
            current_theta_o = theta
            point_a = x - 5000
            point_b = y - 5000

            # Calculate robot position in the map
            xbytes = int(x / 1000. / MAP_SIZE_METERS * MAP_SIZE_PIXELS)
            ybytes = int(y / 1000. / MAP_SIZE_METERS * MAP_SIZE_PIXELS)
            # Update robot position
            posbytes[xbytes + ybytes * MAP_SIZE_PIXELS] = 255
            #print('LIDAR::', command[0], x,y,theta)
            pose_change = (odometry[0], odometry[1] / np.pi * 180, odometry[2])
            # Get current map bytes as grayscale
            slam.getmap(mapbytes_)
            # Update map
            mapbytes[:] = mapbytes_

    # Shut down the lidar connection
    lidar.stop()
    lidar.disconnect()

    print('LIDAR::thread ends')
示例#4
0
class narlam:
    def __init__(self):
        self.flag = 0
        self.lidar = Lidar(LIDAR_DEVICE)
        self.slam = RMHC_SLAM(LaserModel(), MAP_SIZE_PIXELS, MAP_SIZE_METERS)
        self.viz = MapVisualizer(MAP_SIZE_PIXELS, MAP_SIZE_METERS,
                                 'SLAM MAP')  # no visualizer needed
        self.trajectory = []
        self.mapbytes = bytearray(MAP_SIZE_PIXELS * MAP_SIZE_PIXELS)
        self.iterator = self.lidar.iter_scans()

        self.previous_distances = None
        self.previous_angles = None

        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0

    def slam_no_map(self, path_map_name):
        # doing slam with building maps from zero simultaneously
        next(self.iterator)

        while True:
            if self.flag == 1:
                break

            items = [item for item in next(self.iterator)]

            distances = [item[2] for item in items]
            angles = [item[1] for item in items]

            if len(distances) > MIN_SAMPLES:
                self.slam.update(distances, scan_angles_degrees=angles)
                self.previous_distances = distances.copy()
                self.previous_angles = angles.copy()

            elif self.previous_distances is not None:
                self.slam.update(self.previous_distances,
                                 scan_angles_degrees=self.previous_angles)

            self.x, local_y, local_theta = self.slam.getpos()
            local_theta = local_theta % 360
            if local_theta < 0:
                self.theta = 360 + local.theta
            else:
                self.theta = local_theta

            self.slam.getmap(self.mapbytes)

            # save map generated by slam
            image = Image.frombuffer('L', (MAP_SIZE_PIXELS, MAP_SIZE_PIXELS),
                                     self.mapbytes, 'raw', 'L', 0, 1)
            image.save(path_map_name)

        self.lidar.stop()
        self.lidar.disconnect()

    def slam_yes_map(self, path_map_name):
        # doing localization only, with pre-built map image file.

        with open(path_map_name, "rb") as map_img:
            f = map_img.read()
            b = bytearray(f)
            self.slam.setmap(b)

        next(self.iterator)

        while True:
            if self.flag == 1:
                break

            items = [item for item in next(self.iterator)]

            distances = [item[2] for item in items]
            angles = [item[1] for item in items]

            if len(distances) > MIN_SAMPLES:
                self.slam.update(distances,
                                 scan_angles_degrees=angles,
                                 should_update_map=False)
                self.previous_distances = distances.copy()
                self.previous_angles = angles.copy()

            elif self.previous_distances is not None:
                self.slam.update(self.previous_distances,
                                 scan_angles_degrees=self.previous_angles,
                                 should_update_map=False)

            self.x, self.y, self.theta = self.slam.getpos()

        self.lidar.stop()
        self.lidar.disconnect()