可视化 demo 需额外安装 Open3D(>0.17.0),可用 pip install 'pypatchworkpp[demo]' 或 make cppinstall_with_demo;从源码构建 Python 包需先装 libeigen3-dev、cmake 等依赖;ROS2 需在 colcon 工作空间中单独编译 patchworkpp 包
patchwork-plusplus
url-kaistBSD-2-Clause更新放缓面向 3D 激光雷达点云的快速地面分割算法,能在复杂场景下稳健剔除地面点,为后续检测与建图提供干净的非地面点云。
Patchwork++: Fast and robust ground segmentation method for 3D LiDAR scans. @ IROS'22
pip install pypatchworkpp
make cppinstallREADME 未说明显存或 GPU 要求,算法本身在 CPU 上运行;测试环境为 Ubuntu 20.04/22.04/24.04 与 macOS 14+(Apple Silicon)
从 3D LiDAR 点云中快速稳健地分割地面,自带自适应阈值,适合做感知预处理
面向 3D 激光雷达点云的快速地面分割算法,能在复杂场景下稳健剔除地面点,为后续检测与建图提供干净的非地面点云。
核心能力
- 用自适应高程与平坦度阈值自动调参
- 用 RNR 去除基于强度的反射噪声
- 用 RVPF 抑制垂直结构干扰
- 用 TGR 按概率把误删的地面点恢复回来
- 同时提供 C++、Python 绑定与 ROS2 节点
容易踩的坑
- v1.4.1v1.4.1 — Kill per-patch heap traffic in Patchwork++ (+14.8% Hz)最新正式版2026-05-23 · 2026-05-23 06:46 UTC
Highlights
Patch bump.
pypatchworkpp.patchworkppis now +14.8% Hz on KITTI seq 00 (97.5 → 111.9 Hz, median of 3 runs, i7-12700) thanks to killing short-lived heap allocations in R-VPF + R-GPF. Closes the alloc-driven part of #96.Perf —
pypatchworkpp.patchworkppKITTI seq 00, 2900 timed frames, median of 3 runs:
Stage ms/frame Hz Δ Hz v1.4.0 baseline (JacobiSVD + per-call allocs) 10.26 97.5 — eigh only 9.94 100.6 +3.2% v1.4.1 (eigh + alloc-free) 8.94 111.9 +14.8% The win comes from three changes inside
PatchWorkpp::extract_piecewisegroundandPatchWorkpp::estimate_plane:estimate_plane: dropEigen::MatrixX3f eigen_ground,centered, andcentered.adjoint() * centeredheap allocations. Replace with a single-pass scalar accumulation of mean + 9 cross-products; build the 3×3 cov on the stack.extract_piecewiseground: promotesrc_wo_verticalsandsrc_tmpto reused instance scratch members.vector::clear()keeps capacity, so per-patch malloc pressure on the glibc heap drops away after the first few patches.estimateGroundmain loop:auto& zoneinstead ofauto zoneforConcentricZoneModel_[zone_idx]. Avoids a deep-copy of the full 3-level nested vector per outer iteration.
Plus a smaller cleanup:
JacobiSVD<Matrix3f>→SelfAdjointEigenSolver::computeDirecton the 3×3 PSD covariance in bothcpp/common/src/plane_fit.cppand the in-placePatchWorkpp::estimate_plane.singular_values_is repacked descending so every consumer (linearity_,planarity_,ground_flatness,line_variable,flatness_thrindex(2)) keeps the same convention bit-for-bit.Patchwork (classic)
Unchanged: 4.29 ms / 232.9 Hz (within run-to-run noise of the v1.4.0 baseline). TBB
parallel_foralready amortises allocations across cores and SVD is sub-µs/patch.Numerical equivalence
KITTI seq 00 (4541 frames), v1.4.0 → v1.4.1:
Method (protocol) Before After Δ F1 patchwork(pw)P 92.34, R 94.64, F1 93.41 P 92.34, R 94.64, F1 93.41 0.00 patchworkpp(pp)P 94.88, R 98.47, F1 96.62 P 94.89, R 98.48, F1 96.63 +0.01 Algebraic identity of
JacobiSVDvseighverified on 500 real KITTI patch covariances:normal_(up to sign),singular_values_,linearity_,planarity_,ground_flatness,line_variableall match to FP precision.Refs
- #100 — PR (perf: alloc-free + eigh)
- #96 — Issue (R-VPF / R-GPF allocation profile)
- v1.4.0v1.4.0 — Shared common library + optional TBB on classic Patchwork (+1.73× Hz)2026-05-21 · 2026-05-21 08:26 UTC
查看更新说明
Highlights
- Shared
cpp/common/library forPointXYZ,PCAFeature,PatchStatus,estimate_plane,xy2theta,xy2radius,point_z_cmp. Three previously-drifted copies of the plane-fit math (which had caused real bugs — see Fix 2 in #90) collapse to one canonical implementation. pypatchworkpp.patchworkgainstbb::parallel_for. Measured 1.73× speedup on KITTI seq 00 (i7-12700, 24 logical cores): 8.31 ms → 4.81 ms median per frame, 120 → 208 Hz.- TBB is optional at build time. If
libtbb-dev/brew install tbb/vcpkg install tbbis present, you get the speedup; otherwise the build falls back to a sequential loop with a CMake STATUS message. CI / wheels keep working without TBB installed. pypatchworkpp.patchworkppstays sequential — same TBB pattern was applied and benchmarked at 1 / 2 / 4 / 8 / 16 / 24 threads, every configuration was slower than single-thread (111 Hz → 93 Hz at 2 threads, → 69 Hz at 24 threads). Per-patch work is small (~14 µs avg) and dominated by short-lived allocations inside R-VPF + R-GPF; concurrent malloc serialises on the heap. See #96 for the full measurement and the conditions under which we'd revisit.python/examples/bench_hz.py— per-frame timing harness reporting median / mean / p95 / p99 ms and Hz.
Numerical equivalence
KITTI 00–10 full sweep (23,201 frames), Patchwork++ paper protocol, v1.3.1 → v1.4.0:
Method F1 v1.3.1 F1 v1.4.0 Δ --method patchwork96.0172 96.0172 0 (byte-identical) --method patchworkpp96.2918 96.2919 +0.0001 (float noise) Both within the ±0.05 F1 budget set in the refactor plan.
References
- #94 — PR: extract
cpp/common/library - #95 — PR: TBB on classic Patchwork
- #96 — Issue: why Patchwork++ has no TBB
- #97 — PR: this release
See
CHANGELOG.mdfor the full v1.4.0 entry. - Shared
- v1.3.1v1.3.1 — Fix ringwise_flatness leakage in TGR (#69)2026-05-21 · 2026-05-21 03:37 UTC
查看更新说明
Bug fix
- #69 → #91:
ringwise_flatnessis now cleared at the end of every ring iteration incpp/patchworkpp/src/patchworkpp.cpp, not only when the ring produced revert candidates. The previous placement leaked flatnesses from no-candidate rings into the next ring'stemporal_ground_revertcall, biasing themean ± stdevdecision threshold. Reported by @KennethBlomqvist.
Numerical impact
KITTI 00–10 full sweep (23,201 frames) under the Patchwork++ paper evaluation protocol:
Build P R F1 v1.3.0 95.5494 97.1649 96.2886 v1.3.1 95.5496 97.1710 96.2918 ΔF1 = +0.003 (within run-to-run noise). The bug only triggers when a ring finishes with no revert candidates, which is uncommon on KITTI; macro-average impact is negligible but the fix is correctness.
See
CHANGELOG.md. - #69 → #91:
- v1.3.0v1.3.0 — Performance enhancement for pypatchworkpp.patchwork2026-05-21 · 2026-05-21 03:20 UTC
查看更新说明
Highlights
- +2.29 F1 on KITTI 00–10 for
pypatchworkpp.patchwork(classic Patchwork). Three deviations from the originalurl-kaist/patchworkwere identified and fixed; numbers now match the Patchwork++ paper Table I within run-to-run variance (96.02 vs. 95.88 F1). pypatchworkpp.patchworkpp(the real Patchwork++) is unaffected — all three fixes were incpp/patchwork/, notcpp/patchworkpp/. Patchwork++ continues to match the paper.- New
USAGE.mdexplains the two SemanticKITTI evaluation protocols (Patchwork vs Patchwork++ paper), the parameter-tuning order, and a copy-pasteable command to reproduce paper Table I. - New
python/examples/evaluate_semantickitti.py— paper-faithful KITTI evaluation driver with--method {patchwork, patchworkpp}and--eval_protocol {patchwork, patchworkpp}.
Results — SemanticKITTI 00–10, 23,201 frames, paper-matched params
Configuration Eval protocol P R F1 pypatchworkpp.patchwork, v1.2.0Patchwork-paper 86.94 96.75 91.37 pypatchworkpp.patchwork, v1.2.0Patchwork++ paper 89.70 98.49 93.73 pypatchworkpp.patchwork, v1.3.0Patchwork-paper 92.77 93.66 93.08 pypatchworkpp.patchwork, v1.3.0Patchwork++ paper 94.64 97.58 96.02 url-kaist/patchworkoriginal ROS 2 (reference)Patchwork++ paper 94.38 97.90 96.05 Patchwork++ paper Table I, Patchwork [1] Patchwork++ paper 94.23 97.62 95.88 The three fixes
elevation_thris converted to the sensor frame by subtractingsensor_height. The YAML thresholds in the original repo are documented as ground-frame; the reimpl was using the raw value, so the elevation gate effectively never fired for normal ground.- Plane-distance comparison uses uncentred
normal · pagainstth_dist_d_ = th_dist − d_. The previous centred form shifted the cutoff by an extra−d_(~ 1.6 m on KITTI), absorbing far-from-plane points into ground. - Tier index is the GLOBAL ring index across all zones, so each of the first
elevation_thr.size()rings gets its own threshold. The previous(zone==0) ? ring : zonecollapse destroyed the per-ring tuning.
References
- #87 — How to reproduce the performance on the paper?
- #88 — Explanation about the evaluation protocol
- #89 — Performance enhancement step-by-step ablation report (with subset and full-sweep tables)
- #90 — PR landing the fixes
See
CHANGELOG.mdfor the full changelog. - +2.29 F1 on KITTI 00–10 for
- v1.2.0v1.2.0 — Crash fix for partial (non-360°) point clouds2026-05-10 · 2026-05-10 01:12 UTC
查看更新说明
What's new
Bug-fix release. Fixes a startup crash reported in #62 when processing partial (non-360°) point clouds — for example OS1 128 Ouster scans that don't begin at azimuth 0.
Changes since 1.1.0
- #81 (#62, supersedes #66 #72) — Switch `normal_`, `singular_values_`, and `pc_mean_` from `Eigen::VectorXf` (dynamic) to `Eigen::Vector3f` (fixed-size). The dynamic vectors were being indexed before being sized, which was safe with full 360° clouds but crashed on partial inputs. Thanks @MatteGombia for the original fix; @enricocovili for diagnosing the root cause in #62.
Compatibility
- API unchanged. No migration needed.
- Wheels for CPython 3.8 – 3.13 on Linux x86_64 (manylinux2014, musllinux), Windows 32/64-bit, macOS 14 (arm64).
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Patchwork++, an extension of Patchwork, is a fast, robust, and self-adaptive ground segmentation algorithm on 3D point cloud.
:open_file_folder: What's in this repo
- C++ source code of Patchwork++ (patchworkpp)
- Python binding of Patchwork++ using pybind11 (python_wrapper)
- Examples codes of C++, Python, and ROS2 :thumbsup:
- Full suppor of a reproducible SemanticKITTI evaluation harness for both Patchwork and Patchwork++ (
python/examples/evaluate_semantickitti.py). In the papers of Patchwork and Patchwork++, we use different evaluation protocols — seeUSAGE.md§1 for why and §4 for per-sequence numbers.
If you are familiar with ROS1, you can also visit here and try executing ROS1-based Patchwork++!
:package: Prerequisite packages
What we need are just minimal dependencies.
Ubuntu / Debian:
sudo apt-get install g++ build-essential libeigen3-dev python3-pip python3-dev cmake -y
macOS (Apple Silicon or Intel):
brew install cmake
Eigen is fetched automatically by CMake, so no extra system package is required on macOS. The build works with the bundled AppleClang toolchain.
:gear: How to build & Run
Python
The released library is on PyPI:
pip install pypatchworkpp # core library
pip install 'pypatchworkpp[demo]' # + Open3D for the visual demos
Then import pypatchworkpp in your script — see the Python examples.
make pyinstall # equivalent to `pip install ./python/`
make pyinstall_with_demo # also installs Open3D >= 0.17.0
C++
Pure installation
make cppinstall
Installation with demo
Only Open3D (> 0.17.0) is additionally installed for visualization purposes.
make cppinstall_with_demo
How to run the C++ demos is explained here.
ROS2
You should not need any extra dependency, just clone and build:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone https://github.com/url-kaist/patchwork-plusplus.git
cd ~/ros2_ws
colcon build --packages-select patchworkpp
source ./install/setup.bash
How to launch ROS2 nodes is explained here.
:compass: Choosing an algorithm
This repository ships two ground segmentation algorithms with the same input/output API. Pick the one that fits your data:
- Patchwork++ (default): adaptive elevation/flatness thresholds, RNR (intensity-based reflected noise removal), RVPF (vertical structure suppression), and TGR (probability-based ground revert). Best when the LiDAR has reflection artefacts or you want self-tuning thresholds.
- Patchwork (classic, since 1.1.0): fixed elevation/flatness thresholds with explicit
z < -sensor_height - 2.0mcutoff and few-points reject, plus optional ATAT for unknown sensor heights. Often more aggressive on ground-plane noise in heavily cluttered scenes.
Python:
import pypatchworkpp as p
pp_default = p.patchworkpp(p.Parameters()) # Patchwork++
pp_classic = p.patchwork(p.PatchworkParams()) # Patchwork (classic)
ROS2: Patchwork++ is the default; pass algorithm:=patchwork to switch to the classic Patchwork.
# Default — runs Patchwork++
ros2 launch patchworkpp patchworkpp.launch.py
# Override to the classic Patchwork
ros2 launch patchworkpp patchworkpp.launch.py algorithm:=patchwork
:pencil: Citation
If you use our codes, please cite our paper (arXiv, IEEE Xplore)
@inproceedings{lee2022patchworkpp,
title={{Patchwork++: Fast and robust ground segmentation solving partial under-segmentation using 3D point cloud}},
author={Lee, Seungjae and Lim, Hyungtae and Myung, Hyun},
booktitle={Proc. IEEE/RSJ Int. Conf. Intell. Robots Syst.},
year={2022},
pages={13276-13283}
}
In addition, you can also check the paper of our baseline, Patchwork. (arXiv, IEEE Xplore)
@article{lim2021patchwork,
title={Patchwork: Concentric Zone-based Region-wise Ground Segmentation with Ground Likelihood Estimation Using a 3D LiDAR Sensor},
author={Lim, Hyungtae and Minho, Oh and Myung, Hyun},
journal={IEEE Robotics and Automation Letters},
year={2021}
}
:triangular_flag_on_post: Tested Environment
- Ubuntu
18.04 and20.04, 22.04, and 24.04 - macOS 14+ (Apple Silicon)
- CMake 3.25.1 (>=3.20, min. Required to install Open3D)
- In
scripts/install_open3d.bash, the installation of the higher version of CMake is already implemented.
- In
- Open3D
0.15.20.18.0 - pybind11 v2.2.3
- Eigen 3.3.7
:mailbox: Contact Information
If you have any questions, please do not hesitate to contact us
- Seungjae Lee :envelope: sj98lee
atkaistackr - Hyungtae Lim :envelope: shapelim
atkaistackr
Todo List
- [ ] Support intensity for RNR in
masterbranch - [ ] Support
Patchworkmode for users who use this repository for baseline comparison purposes - [ ] Integrate TBB and optimize the performance