This commit is contained in:
Nils Schulte 2024-05-26 16:26:59 +02:00
commit bccf38e984
5 changed files with 259 additions and 0 deletions

57
CMakeLists.txt Normal file
View File

@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.8)
project(simple_slam)
set(CMAKE_CXX_STANDARD 23)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2_sensor_msgs REQUIRED)
find_package(std_msgs REQUIRED)# uncomment the following section in order to fill in
find_package(nav_msgs REQUIRED)
find_package(builtin_interfaces REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(Ceres REQUIRED)
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(Eigen3 REQUIRED NO_MODULE)
add_executable(simpleslam src/simpleslam_node.cpp )
ament_target_dependencies(simpleslam rclcpp sensor_msgs nav_msgs)
target_link_libraries(simpleslam Ceres::ceres Eigen3::Eigen)
# include_directories(include lib/karto_sdk/include
# ${EIGEN3_INCLUDE_DIRS}
# ${CERES_INCLUDES}
# )
install(TARGETS
simpleslam
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()

34
package.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>simple_slam</name>
<version>0.0.0</version>
<description>slam that is hopefully simple</description>
<maintainer email="git@nilsschulte.de">Nils Schulte</maintainer>
<license>AGPL+</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>builtin_interfaces</build_depend>
<build_depend>rosidl_default_generators</build_depend>
<depend>eigen</depend>
<depend>rclcpp</depend>
<depend>sensor_msgs</depend>
<depend>nav_msgs</depend>
<depend>tf2</depend>
<depend>tf2_ros</depend>
<depend>tf2_sensor_msgs</depend>
<build_depend>liblapack-dev</build_depend>
<build_depend>libceres-dev</build_depend>
<build_depend>tbb</build_depend>b
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>

32
src/simpleslam.hpp Normal file
View File

@ -0,0 +1,32 @@
#include <Eigen/Dense>
#include <vector>
class SimpleSlam {
public:
struct ScanPoint {
Eigen::Vector2f point;
Eigen::Vector2f normal;
float normal_distance;
};
void calculate_normals(const std::vector<Eigen::Vector2f> &scan,
std::vector<ScanPoint> &normals) {
for (unsigned int i = 0; i < scan.size(); i += 1) {
/* TODO comapre not only prev and next but based on distance */
const auto &prev = scan[(i - 1) % scan.size()];
const auto &next = scan[(i + 1) % scan.size()];
ScanPoint p;
float dist_prev = (scan[i] - prev).squaredNorm();
float dist_next = (scan[i] - next).squaredNorm();
auto n = dist_next < dist_prev ? (next - scan[i]) : (prev - scan[i]);
const Eigen::Matrix2f r{{0,1},{-1,0}};
p.normal = r*n;
normals.push_back(p);
}
};
void process_scan(const std::vector<Eigen::Vector2f> &scan) {
std::vector<ScanPoint> points;
calculate_normals(scan, points);
};
};

95
src/simpleslam_node.cpp Normal file
View File

@ -0,0 +1,95 @@
#include <memory>
#include "nav_msgs/msg/map_meta_data.hpp"
#include "nav_msgs/msg/occupancy_grid.hpp"
#include "nav_msgs/srv/get_map.hpp"
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/laser_scan.hpp"
#include <Eigen/Dense>
#include <algorithm>
#include <limits>
#include <vector>
#include "simpleslam.hpp"
#include "utils.hpp"
class MinimalSubscriber : public rclcpp::Node {
public:
SimpleSlam slam;
MinimalSubscriber() : Node("minimal_subscriber") {
pub_map_ = this->create_publisher<nav_msgs::msg::OccupancyGrid>("/map", 10);
auto topic_callback =
[this](sensor_msgs::msg::LaserScan::UniquePtr msg) -> void {
// RCLCPP_INFO(this->get_logger(), "angle min '%f'", msg->angle_min);
// RCLCPP_INFO(this->get_logger(), "angle max '%f'", msg->angle_max);
// RCLCPP_INFO(this->get_logger(), "time_increment'%f'",
// msg->time_increment);
// RCLCPP_INFO(this->get_logger(), "angle_increment '%f'",
// msg->angle_increment);
std::vector<Eigen::Vector2f> scan;
Eigen::Vector2f min = Eigen::Vector2f(std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
Eigen::Vector2f max = Eigen::Vector2f(std::numeric_limits<float>::min(),
std::numeric_limits<float>::min());
for (size_t i = 0; i < msg->ranges.size(); i += 1) {
auto radius = msg->ranges[i];
Eigen::Rotation2D<float> rot2(msg->angle_min +
i * msg->angle_increment);
auto p = rot2 * Eigen::Vector2f(radius, 0);
if (p.array().isInf().any())
continue;
min = min.cwiseMin(p);
max = max.cwiseMax(p);
scan.push_back(p);
// RCLCPP_INFO(this->get_logger(), "p(%f,%f)",p[0],p[1]);
}
// std::transform(std::views::enumerate(msg->ranges).cbegin(),
// std::views::enumerate(msg->ranges).cend(), scan.begin(),
// [this](const auto &pair) {
// const auto &[index, radius] = pair;
// Eigen::Rotation2D<float> rot2(
// msg->angle_min + index * msg->angle_increment);
// return rot2 * Eigen::Vector2f(radius, 0);
// });
nav_msgs::msg::OccupancyGrid grid;
grid.header.frame_id = "map";
grid.info.resolution = 50.0f / 1000.0f; /* [m/cell] */
grid.info.width = (max - min)[0] / grid.info.resolution + 1;
grid.info.height = (max - min)[1] / grid.info.resolution + 1;
grid.info.origin.position.x = min[0];
grid.info.origin.position.y = min[1];
grid.data.resize(grid.info.width * grid.info.height);
std::fill(grid.data.begin(), grid.data.end(), -1);
auto put_p = [&](const Eigen::Vector2i &pi, const int o) {
int idx = pi.x() + pi.y() * grid.info.width;
// if(idx < grid.data.size()) {
if (grid.data[idx] <= 0)
grid.data[idx] = o;
};
for (const auto &v : scan) {
Eigen::Vector2i vi = ((v - min) / grid.info.resolution).cast<int>();
bresenham((-min / grid.info.resolution).cast<int>(), vi,
[&](const auto &p) { put_p(p, 0); });
put_p(vi, 100);
}
pub_map_->publish(grid);
};
subscription_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
"scan", 10, topic_callback);
}
private:
rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr subscription_;
rclcpp::Publisher<nav_msgs::msg::OccupancyGrid>::SharedPtr pub_map_;
};
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}

41
src/utils.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <ranges>
#include <algorithm> // For std::transform
#include <utility> // For std::pair
#include <Eigen/Dense>
#include <functional>
// Helper function to create an enumerator
// auto enumerate(const auto& container) {
// auto indices = std::views::iota(0, static_cast<int>(container.size()));
// return std::views::zip(indices, container);
// }
void bresenham(const Eigen::Vector2i& start, const Eigen::Vector2i& end, std::function<void(const Eigen::Vector2i&)> f) {
std::vector<Eigen::Vector2i> linePoints;
int x0 = start.x(), y0 = start.y();
int x1 = end.x(), y1 = end.y();
int dx = std::abs(x1 - x0);
int dy = -std::abs(y1 - y0);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx + dy;
while (true) {
f(Eigen::Vector2i(x0, y0));
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
}
if (e2 <= dx) {
err += dx;
y0 += sy;
}
}
}