Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 51 additions & 45 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,57 +1,63 @@
cmake_minimum_required (VERSION 2.6)
cmake_minimum_required(VERSION 3.16)

project (TOPO_GRAPH_2D)
project(TOPO_GRAPH_2D)

find_package( Boost REQUIRED )
if ( NOT Boost_FOUND )
message(STATUS "This project requires the Boost library, and will not be compiled.")
return()
endif()

find_package(CGAL COMPONENTS Core Boost)
FIND_PACKAGE(Qt4 REQUIRED)


if ( CGAL_FOUND )

include( ${CGAL_USE_FILE} )
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include( CGAL_CreateSingleSourceCGALProgram )

else()

message(STATUS "This program requires the CGAL library, and will not be compiled.")

# --- Dependencies ---
find_package(Boost REQUIRED)
if(NOT Boost_FOUND)
message(FATAL_ERROR "This project requires the Boost library.")
endif()

INCLUDE(${QT_USE_FILE})
ADD_DEFINITIONS(${QT_DEFINITIONS})
find_package(CGAL REQUIRED)

#add_subdirectory (dir)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()

# --- Output directories ---
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

add_library (topo_graph_2d STATIC /
src/VoriConfig.cpp /
src/VoriGraph.cpp /
src/TopoGraph.cpp /
src/RoomDect.cpp /
src/roomGraph.cpp /
src/Denoise.cpp /
src/passageSearch.cpp /
src/cgal/CgalVoronoi.cpp /
src/cgal/AlphaShape.cpp /
src/qt/QImageVoronoi.cpp /
src/cgal/AlphaShapeRemoval.cpp)

include_directories (${TOPO_GRAPH_2D_SOURCE_DIR}/include)

include_directories(${CGAL_INCLUDE_DIR} ${QT_INCLUDE_DIR})

# --- Static library with all core sources ---
add_library(topo_graph_2d STATIC
src/VoriConfig.cpp
src/VoriGraph.cpp
src/TopoGraph.cpp
src/RoomDect.cpp
src/roomGraph.cpp
src/Denoise.cpp
src/passageSearch.cpp
src/cgal/CgalVoronoi.cpp
src/cgal/AlphaShape.cpp
src/qt/QImageVoronoi.cpp
src/cgal/AlphaShapeRemoval.cpp
)

target_include_directories(topo_graph_2d PUBLIC
${TOPO_GRAPH_2D_SOURCE_DIR}/include
)

target_link_libraries(topo_graph_2d PUBLIC
CGAL::CGAL
Qt6::Core
Qt6::Gui
Qt6::Widgets
Boost::boost
)

# --- Main executable ---
add_executable(example_segmentation test/example.cpp)
include_directories (${TOPO_GRAPH_2D_SOURCE_DIR}/include)

message(STATUS " cgal: ${CGAL_LIBRARIES} ")

TARGET_LINK_LIBRARIES(example_segmentation topo_graph_2d ${QT_LIBRARIES} ${CGAL_LIBRARIES} -lboost_unit_test_framework -lboost_filesystem -lboost_system)
target_link_libraries(example_segmentation
topo_graph_2d
CGAL::CGAL
Qt6::Core
Qt6::Gui
Qt6::Widgets
Boost::boost
)

message(STATUS "CGAL version: ${CGAL_VERSION}")
message(STATUS "Qt6 version: ${Qt6_VERSION}")
161 changes: 120 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,142 @@
# Area graph
# Area Graph: Generation of Topological Maps using Voronoi Diagrams

## Paper
The paper describing the method was accepted for publication at ICAR2019. The preprint version is [available on Arxiv](https://arxiv.org/abs/1910.01019).
> [!NOTE]
> **Repository Status**: This repository has been updated and modernized for compatibility with modern C++ compilers, **Qt5**, **CGAL 5.x**, and **macOS (Apple Silicon)** / **Ubuntu 22.04 (ROS 2 Humble)** environments.

Hou, J., Y. Yuan, and S. Schwertfeger, "Area Graph: Generation of Topological Maps using the Voronoi Diagram", 19th International Conference on Advanced Robotics (ICAR): IEEE Press, 2019.
---

```
@conference {hou2019area,
title = {Area Graph: Generation of Topological Maps using the Voronoi Diagram},
booktitle = {19th International Conference on Advanced Robotics (ICAR)},
year = {2019},
publisher = {IEEE Press},
organization = {IEEE Press},
author = {Hou, Jiawei and Yuan, Yijun and Schwertfeger, S{\"o}ren}
}
```
## 1. Executive Summary

### What is it?
The **Area Graph Generator** is a topological mapping tool that converts 2D occupancy grid maps (produced by SLAM algorithms like `slam_toolbox`) into structured **Area Graphs**. An Area Graph decomposes complex indoor floor plans into distinct functional regions (rooms, hallways, doorways) represented as nodes, connected by topological edges.

### Why do we need it?
Standard 2D occupancy grids provide geometric obstacle data but lack high-level semantic structure. Downstream modules—such as LLM task planners (e.g., *"Go to the kitchen and find the cup"*) and global path planners—require topological spatial graphs to perform high-level spatial reasoning and target navigation.

### How does it work?
1. **Denoising**: Filters salt-and-pepper LiDAR scan artifacts and small obstacle noise.
2. **Voronoi Skeletonization**: Computes Generalized Voronoi Diagrams (GVD) using CGAL to trace spatial medial axes.
3. **Alpha-Shape Removal & Room Detection**: Identifies critical points (narrowings/doorways) and partitions the free space into bounded room polygons.
4. **Graph Synthesis**: Merges adjacent sub-areas, prunes redundant dead-end branches, and computes topological centroids and connectivity matrices.

---

## 2. Restorations & Modernization Fixes

The original repository (ICAR 2019) relied on legacy dependencies (`qt4-default`) and deprecated C++ constructs that fail to compile on modern systems. The following key fixes were applied to restore full functionality:

| File | Fix Description |
|---|---|
| `CMakeLists.txt` | Upgraded to modern CMake standards; added support for **Qt5** (`Qt5::Widgets`, `Qt5::Gui`) and **CGAL 5+** targets. |
| `src/cgal/AlphaShape.cpp` | Included missing `<QPainterPath>` headers required for Qt5 path drawing. |
| `src/roomGraph.cpp` | Fixed graph overlay rendering in `draw()` to correctly paint topological nodes (red ellipses) and adjacency edges (black lines) on top of room polygons. |
| `test/example.cpp` | Replaced hardcoded output filenames (`clean.png`, `roomGraph.png`) with dynamic input-based prefixes (`<input_name>_clean.png`, `<input_name>_roomGraph.png`). |
| `include/TopoGeometry.h` | Updated Boost geometry namespace usage and include definitions to prevent template instantiation failures. |

---

## How to compile
### Dependencies
Before running the code, make sure you have installed: cmake, g++, Eigen3, Qt4, CGAL
They can be installed by (Ubuntu):
## 3. Installation & Dependencies

### Prerequisites

#### macOS (Apple Silicon / Intel) via Homebrew
```bash
brew install cmake cgal qt@5 boost
```
sudo apt-get install g++
sudo apt-get install cmake
sudo apt-get install qt4-default
sudo apt-get install libcgal-dev
Make sure Qt5 is discoverable by CMake:
```bash
export Qt5_DIR="$(brew --prefix qt@5)/lib/cmake/Qt5"
```
The code has been test on Ubuntu 14.04 and 16.04.

### How to use
Now, we can build our Area Graph generation code:
#### Ubuntu 22.04 LTS / 20.04 LTS
```bash
sudo apt-get update
sudo apt-get install -y build-essential cmake libcgal-dev qtbase5-dev libboost-all-dev
```
cd /path/to/map-matching/code/
mkdir build
cd build

---

## 4. Building the Project

```bash
# 1. Navigate to the area_graph_repo directory
cd docs/research/osm-AG/area_graph_1/area_graph_repo

# 2. Create build directory
mkdir -p build && cd build

# 3. Configure and build
cmake ..
make example_segmentation
./bin/test_areaMatch Map.png resolution door_width corridor_width noise_percentage
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
```
where the meanings of the arguments are shown belows.
* Map.png: The map you are going to generate the Area Graph for it. Please don't use the maps whose background color is lighter than the sites (obstacle points).
* resolution: resolution of the map (the default resolution is set as 0.05)
* door_width: the widest door's width in the environment
* corridor_width: the narrowest corridor's width in the environment
if you don't know the door width and corridor width of the environment, set it as -1 and we use the fix W = 1.25 to run the Alpha Shape algorithm to detect rooms
* noise_percentage: You can rely on intuition to estimate how much noise is in the map. If you use the map in the directory "afterAlphaRemoval" as input, you can set this argument as 0.

example:
Upon successful build, the binary `example_segmentation` will be generated in `build/bin/`.

---

## 5. Usage & Parameter Guide

### Command Line Interface

```bash
./bin/example_segmentation <map_path> <resolution> <door_width> <corridor_width> <noise_percent>
```
./bin/example_segmentation ../dataset/input/Freiburg79_scan_furnitures_trashbins.png 0.05 -1 -1 1.5

### Parameter Breakdown

| Parameter | Type | Default | Description | How to Measure |
|---|---|---|---|---|
| `<map_path>` | `string` | *Required* | Path to the 2D occupancy grid PNG image. | Path to your map file. |
| `<resolution>` | `float` | `0.05` | Map scale in meters per pixel. | Found in your SLAM `.yaml` file (`resolution: 0.05`). |
| `<door_width>` | `float` | `1.15` | Maximum doorway/passageway width in meters. Set to `-1` for auto default (`1.25m`). | Measure standard doors in meters, or count doorway pixels $\times$ resolution. |
| `<corridor_width>`| `float` | `1.35` | Average corridor/hallway width in meters. Set to `-1` for auto default. | Measure hallway width in meters, or count hallway pixels $\times$ resolution. |
| `<noise_percent>` | `float` | `1.5` | Denoising filter sensitivity threshold ($0.0 - 100.0$). | Use `1.5` for raw SLAM maps; use `0.1`–`0.5` for pre-cleaned maps. |

---

## 6. Step-by-Step Reproduction Guide

### Step 1: Prepare Your Map
Convert your SLAM `.pgm` output (e.g., from `nav2_map_server`) to `.png` using ImageMagick or Python:
```bash
convert my_map.pgm my_map.png
```
or

### Step 2: Run Segmentation & Area Graph Generation

#### Example 1: Standard SLAM Map (`test_map22.png`)
```bash
./bin/example_segmentation ../maps/test_map22.png 0.05 0.63 1.55 1.5
```
./bin/example_segmentation ../dataset/input/Freiburg79_scan_furnitures_trashbins.png 0.05 0.85 2.7 1.5

#### Example 2: Pre-Cleaned Custom Map (`big_map_7_cleaned.png`)
```bash
./bin/example_segmentation ../maps/big_map_7_cleaned.png 0.05 0.85 1.5 0.5
```

---

## 7. Expected Outputs

Running the segmentation script generates five diagnostic and visual output images in your active working directory:

1. **`<base_name>_clean.png`**: Denoised binary image.
2. **`<base_name>_afterAlphaRemoval.png`**: Free-space boundary extraction.
3. **`<base_name>_voronoi_skeleton.png`**: Generalized Voronoi Diagram medial axis overlay.
4. **`<base_name>_<alpha>.png`**: Initial room polygon partitioning before topological merging.
5. **`<base_name>_roomGraph.png`**: **Final Area Graph**. Features distinct color-coded room polygons, red circular nodes at room centroids, and black topological connection edges.

---

## 8. Paper Citation

```bibtex
@conference {hou2019area,
title = {Area Graph: Generation of Topological Maps using the Voronoi Diagram},
booktitle = {19th International Conference on Advanced Robotics (ICAR)},
year = {2019},
publisher = {IEEE Press},
organization = {IEEE Press},
author = {Hou, Jiawei and Yuan, Yijun and Schwertfeger, S{\"o}ren}
}
```
2 changes: 1 addition & 1 deletion include/RoomDect.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ typedef CGAL::Triangulation_hierarchy_vertex_base_2<Avb> Av;
typedef CGAL::Triangulation_face_base_2<K> Tf;
typedef CGAL::Alpha_shape_face_base_2<K, Tf> Af;

typedef CGAL::Triangulation_default_data_structure_2<K, Av, Af> Tds;
typedef CGAL::Triangulation_data_structure_2<Av, Af> Tds;
typedef CGAL::Delaunay_triangulation_2<K, Tds> Dt;
typedef CGAL::Triangulation_hierarchy_2<Dt> Ht;
typedef CGAL::Alpha_shape_2<Ht> Alpha_shape_2;
Expand Down
2 changes: 1 addition & 1 deletion include/TopoGeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static std::string print(const point &p){

// comparison for 2D coordinates - in order to put them in a map
struct Smaller{
bool operator() (const point &one, const point &two){
bool operator() (const point &one, const point &two) const {
if( boost::geometry::get<0>(one) == boost::geometry::get<0>(two)) return boost::geometry::get<1>(one) < boost::geometry::get<1>(two);
return boost::geometry::get<0>(one) < boost::geometry::get<0>(two);
}
Expand Down
8 changes: 4 additions & 4 deletions include/VoriGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class VoriGraphHalfEdge;
struct VoriGraphPolygon {
VoriGraphPolygon() : isRay( false ) {}

bool operator==(const VoriGraphPolygon &other) {
bool operator==(const VoriGraphPolygon &other) const {
return this == &other;
}

Expand All @@ -46,7 +46,7 @@ struct VoriGraphHalfEdge { //path
roomId( -1 ), obstacleAverage( 0. ), obstacleMinimum( 0. ), pathFace( NULL ),
roomPath( false ) {}

bool operator==(const VoriGraphHalfEdge &other) {
bool operator==(const VoriGraphHalfEdge &other) const {
return this == &other;
}

Expand Down Expand Up @@ -108,7 +108,7 @@ struct VoriGraphVertex {
VoriGraphVertex() : groupId( 0 ), roomId( -1 ), obstacleDist( 0. ), roomVertex( false ), borderVertex( false ),
roomCenter( false ),passageVertex(false) {}

bool operator==(const VoriGraphVertex &other) {
bool operator==(const VoriGraphVertex &other) const {
return this == &other;
}

Expand Down Expand Up @@ -181,7 +181,7 @@ struct VoriGroup {
//add by jiawei 2017.10.31
struct VoriGraphArea {
VoriGraphArea() : roomId( -1 ){}
bool operator==(const VoriGraphArea &other) {
bool operator==(const VoriGraphArea &other) const {
return this == &other;
}

Expand Down
2 changes: 1 addition & 1 deletion include/cgal/AlphaShape.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AlphaShapePolygon{
typedef CGAL::Triangulation_face_base_2<K> Tf;
typedef CGAL::Alpha_shape_face_base_2<K,Tf> Af;

typedef CGAL::Triangulation_default_data_structure_2<K,Av,Af> Tds;
typedef CGAL::Triangulation_data_structure_2<Av,Af> Tds;
typedef CGAL::Delaunay_triangulation_2<K,Tds> Dt;
typedef CGAL::Triangulation_hierarchy_2<Dt> Ht;
typedef CGAL::Alpha_shape_2<Ht> Alpha_shape_2;
Expand Down
28 changes: 5 additions & 23 deletions src/Denoise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,33 +93,15 @@ DenoiseImg(const char *input_name, const char *output_name, int &black_threshold
getPoints(rgb, black_threshold, points);

// Removes outliers using erase-remove idiom.
// The Dereference_property_map property map can be omitted here as it is the default value.
// CGAL 6.x uses named parameters API
int sec_percent=percentage>=1?1:0;

// We have to use Identity_property_map for CGAL 4.7 (ubuntu 16)
#if CGAL_VERSION_NR > 1040201000
points.erase(CGAL::remove_outliers(points.begin(), points.end(),
CGAL::Identity_property_map<Point>(),
neighbors, percentage),
points.erase(CGAL::remove_outliers<CGAL::Sequential_tag>(points, neighbors,
CGAL::parameters::threshold_percent(percentage)),
points.end());
points.erase(CGAL::remove_outliers(points.begin(), points.end(),
CGAL::Identity_property_map<Point>(),
neighbors, sec_percent),
points.erase(CGAL::remove_outliers<CGAL::Sequential_tag>(points, neighbors,
CGAL::parameters::threshold_percent(sec_percent)),
points.end());
#else
points.erase(CGAL::remove_outliers(points.begin(), points.end(),
CGAL::Dereference_property_map<Point>(),
neighbors, percentage),
points.end());
points.erase(CGAL::remove_outliers(points.begin(), points.end(),
CGAL::Dereference_property_map<Point>(),
neighbors, sec_percent),
points.end());
#endif
// points.erase(CGAL::remove_outliers(points.begin(), points.end(),
// CGAL::Dereference_property_map<Point>(),
// neighbors, percentage),
// points.end());

// Optional: after erase(), use Scott Meyer's "swap trick" to trim excess capacity
std::vector<Point>(points).swap(points);
Expand Down
Loading