Point Cloud Processing With PCL and Open3D in ROS 2
Learn how PCL and Open3D convert, filter, and segment point clouds in ROS 2 applications.

A point cloud is what a LiDAR or an RGB-D camera hands back after scanning a scene: point clouds from these sensors are rich but unstructured. Turning that scatter into something a robot can act on, an obstacle map, a segmented object, a patch of floor it can safely drive across, takes algorithms that ROS 2 itself does not include. ROS 2's job is communication: getting messages from one node to another reliably, at scale, across a modular system. What it contributes is a message format, sensor_msgs/PointCloud2, and nothing about what to do with the numbers inside it. Two open-source libraries have grown up to fill that gap, PCL and Open3D, and they take different architectural routes to get there, different primary languages, different paths into a ROS 2 node.
The full pipeline a ROS 2 point cloud node must execute, end to end
Every working ROS 2 point cloud node runs the same four-stage loop once the library-specific details are stripped away. First, it subscribes to a topic where a sensor is publishing PointCloud2 messages in real time, something like /camera/depth/points or /points. Second, it converts that ROS message into a format its processing library actually understands: a pcl::PointCloud object if the pipeline runs on PCL, an open3d::geometry::PointCloud if it runs on Open3D. Third, it runs its algorithms against that native structure, whatever mix of filtering, segmentation, clustering, or registration the task calls for. Fourth, it converts the result back into a PointCloud2 message and publishes it on a new topic, where a navigation stack, a manipulation planner, or a visualization tool picks it up.
Keep that loop in mind as a map, because everything that follows in this piece is a close-up of one of those four stops. Subscribe, convert, process, republish. The conversion step, in particular, deserves more attention than it usually gets, because it is where a surprising amount of a pipeline's total compute budget quietly goes.
The performance cost of the conversion step between ROS and PCL
For PCL, the bridge connecting ROS 2 to the library is the pcl_ros package, part of the broader perception_pcl stack, which is billed as the preferred bridge for 3D and n-D point cloud work in ROS and stays maintained and released across Humble, Jazzy, Kilted, and Rolling as of May 2025. The function doing the actual work on the way in is pcl::fromROSMsg, which takes a sensor_msgs::PointCloud2 and produces a pcl::PointCloud<PointT>. It is not free. Before a single filter or segmentation call has run, fromROSMsg has already spent a meaningful chunk of CPU time just copying data out of the ROS message and into PCL's internal memory layout.
That copy became a known target for optimization. A thread on the Open Robotics Discourse, started by facontidavide, laid out a drop-in replacement for fromROSMsg that skips the intermediate copy entirely, letting PCL's internal structure read the ROS message data directly instead of duplicating it. The result, by the thread's own characterization, runs close to twice as fast as the stock conversion. Reuse the same point cloud object across iterations instead of allocating a fresh one every callback, since repeated heap allocation on a tight loop adds up fast.
Going the other direction carries its own cost. Once processing finishes, pcl::toROSMsg converts the cloud back for republication, but it preserves PCL's internal alignment bytes as it does so. Those bytes pad out the resulting message. A node optimized on the conversion-in side can still end up publishing something heavier than it needs to be on the way out. A network of downstream subscribers can start complaining about message size as a result.
What PCL's algorithm suite provides once conversion is done
The payoff for all that conversion overhead is real. PCL, originally built at Willow Garage and now at stable release 1.15.1 as of August 26, 2025 (Wikipedia's own page still lists 1.15.0), covers filtering, feature estimation, surface reconstruction, 3D registration, model fitting, object recognition, and segmentation, with each capability compiled as its own separate library, libpcl_filters, libpcl_features, libpcl_surface, and so on.
The processing order in real pipelines is repeatedly filter first, segment second, cluster third. On the filtering side, several operations are most relevant to real-time ROS pipelines. Voxel Grid filtering downsamples a dense cloud into something a downstream algorithm can chew through fast, collapsing points within a small volume into one representative point. PassThrough filtering trims the cloud down to a region of interest, throwing out anything outside a defined spatial range. Statistical Outlier Removal cleans up the noise that real sensors always produce.
Once a cloud is filtered down to something manageable, segmentation goes to work on it. RANSAC-based plane fitting, exposed in ROS through something like pcl_ros::SACSegmentation, picks out dominant flat surfaces, a floor, a tabletop, and separates them from whatever else is in the scene. What's left after that gets handed to Euclidean cluster extraction, which groups the remaining points into distinct blobs, each one a candidate object a robot might need to avoid or pick up. Registration, the process of aligning two clouds using Iterative Closest Point (ICP), is useful for map building and object pose estimation.
None of this runs on a single core by default. PCL takes advantage of OpenMP and Intel TBB for multi-core parallelism, and it has CUDA implementations sitting in the pcl::gpu:: namespace, with NVIDIA separately maintaining NVIDIA-AI-IOT/cuPCL to accelerate PCL-type operations on CUDA hardware. The trade-off for all this capability is a steep API.
What this looks like assembled into something production-grade shows up in the ros2_lidar_perception pipeline from Prajwal-Reddy25 on GitHub, built for Ubuntu 24.04 and ROS 2 Jazzy, hardware-independent, and running entirely on PCL and C++17 for its hot path. It takes in raw PointCloud2 data and outputs ground and non-ground clouds, clustered 3D bounding boxes, optional track IDs, and optional velocity estimates, essentially the filter-segment-cluster chain described above, wired into a real perception stack. The library relies on Eigen for linear algebra, FLANN for k-nearest neighbor search, and VTK for visualization.
The current state of the robotics middleware migration
None of that capability comes with a guarantee of full parity with the ROS 1 version of the same tools. The perception_pcl port to ROS 2 is still, as of this writing, an unfinished project, and anyone assuming a ROS 1 PCL workflow will just carry over node-for-node into ROS 2 is going to be disappointed.
The specifics are visible in the repository itself.
That gap has a direct operational consequence. A team that relied on pcl_ros::SACSegmentation as a ready-made ROS 1 node must either wait for the port, contribute it, or implement the equivalent manually in their own ROS 2 node. None of those is catastrophic, but none of them is free either.
The fairest read of the situation, though, cuts the other way. The core of the system, pcl_conversions and the underlying PCL algorithms themselves, already works fine in ROS 2. The missing piece is at the level of pre-built, ready-to-launch nodes, not at the level of the library. Teams comfortable writing their own nodes and calling PCL's algorithms directly are, in practice, barely affected by any of this. As of early 2026, open pull requests in the ros-perception/perception_pcl repository include migration of pcl_ros::SACSegmentation to ROS 2 (PR #510, September 2025) and a PCLNode variadic template refactor (PR #511, February 2026), both community-driven, indicating that some classic ROS 1 nodes are not yet natively available in ROS 2.
How Open3D connects to ROS 2 and its conversion layer
The perception_open3d repository under the ros-perception organization provides the packages needed to interface ROS with Open3D, explicitly positioned as the Open3D analog to perception_pcl.
The conversion work itself lives in the open3d_conversions package, which provides open3dToRos and rosToOpen3d functions supporting XYZ and XYZRGB point clouds. Intensity data (XYZI clouds) doesn't get its own dedicated field; instead, the intensity value gets stored inside the colors field, which can make a cloud's colors look strange during debugging.
There's a build-configuration trap that is easy to miss. Anyone building open3d_conversions from source and leaving it in a default debug configuration will see conversion performance that is noticeably worse than it should be; the fix is building in Release mode. It is the kind of detail that costs a debugging afternoon if nobody mentions it up front.
Alongside the official package, there's at least one well-used community alternative. The open3d_ros_pointcloud_conversion repository from felixchenfy on GitHub provides Python functions, convertCloudFromOpen3dToRos and convertCloudFromRosToOpen3d, covering the same XYZ and XYZRGB cases, with a test case included that round-trips a cloud out through a ROS topic and back.
Open3D's algorithmic offerings and Python access broadening who can use it
Algorithmically, Open3D provides algorithms for filtering (including voxel downsampling), registration (ICP and its variants), surface reconstruction, and 3D semantic understanding, with APIs in both C++ and Python via Pybind11 bindings. On raw speed for at least one common operation, a direct benchmark on identical hardware found Open3D completing voxel downsampling considerably faster than PCL; ICP comparisons are murkier, since the two libraries don't converge by identical criteria, so a straight speed comparison there doesn't mean much. GPU acceleration has been part of Open3D since version 0.12.0, with its tensor-namespace types able to run directly on NVIDIA GPUs, which matters for anyone with high-throughput workloads and the right hardware sitting behind the pipeline.
What actually sets Open3D apart from PCL is who gets to use it easily. It's who gets to use it easily. Open3D ships with Python bindings maintained by its own core team, while PCL has no official Python bindings at all; the community wrapper most people reach for, strawlab/python-pcl, has gone unmaintained. For a robotics team whose data pipelines, model inference, and post-processing all live in Python, that difference is not cosmetic. Open3D lets that team work in the language its stack already runs in, without writing a C++ wrapper layer or depending on a package nobody is patching anymore.
There's a concrete data point for how this plays out in a competitive setting. Povasin/ros2_slam_pipeline, a fully automated visual-inertial SLAM pipeline built on ROS 2 Jazzy and Open3D for the Hilti-Trimble SLAM Challenge 2026, scored 94.1 out of 100 on the challenge's evaluation, a result that puts Open3D's practical standing inside a serious, competitive ROS 2 perception stack beyond much doubt.
Choosing between PCL and Open3D for a given pipeline stage
None of the preceding sections argue for picking one library and running the whole system on it.
PCL, accessed through pcl_ros in C++, tends to be the right call for the classical, real-time operations sitting on the hot path: voxel filtering, PassThrough region-of-interest extraction, RANSAC plane segmentation, Euclidean clustering. It also fits teams with deep existing C++ expertise who need fine control over memory layout and threading, and it fits situations where an established PCL node already exists in the distro, so migration risk stays low.
Open3D earns its place in Python-first workflows, in rapid prototyping, and in any stage that talks directly to an ML model, since Python access there removes an entire wrapper layer from the equation. It's the natural choice too when NVIDIA hardware is present and tensor-namespace GPU-accelerated processing is on the table. And it suits post-processing, visualization, or reconstruction stages that run after the real-time hot path has already done its job, where the Release-mode build requirement is a one-time setup cost rather than an ongoing tax.
Where a team needs a ready-made ROS 2 segmentation node today, plane fitting or Euclidean clustering out of the box, PCL is still the more complete answer, though with the caveat covered earlier: some of those nodes, pcl_ros::SACSegmentation among them, are mid-port and may need to be written by hand in the meantime. That gap in pcl_ros's ROS 2 migration is itself an argument for looking at Open3D when building new, Python-accessible nodes, especially since perception_open3d, despite starting out targeting ROS 1, has since been extended to support ROS 2 directly.
Performance considerations that apply to any ROS 2 point cloud pipeline
Two things hold regardless of which library ends up doing the processing. First, a dense point cloud running through a live pipeline can blow through CPU and memory budgets fast, and the standard defense is the same no matter the library: downsample and trim to a region of interest before anything heavier gets a chance to run.
Conversion cost occurs on every single callback, not just once at startup, so it functions as a fixed tax on the whole pipeline. Cutting that tax down, using the optimized fromROSMsg replacement on the PCL side, or making sure open3d_conversions gets built in Release mode on the Open3D side, buys back real headroom before anyone even starts tuning the algorithms themselves. Get the conversion step right first. Everything downstream of it runs faster for the effort.
Sources
- Point Cloud Library
Provided background on PCL's origins at Willow Garage, its algorithm modules, and the current stable release version referenced in the article.
- GitHub - felixchenfy/open3d_ros_pointcloud_conversion: 2 Python API functions for point cloud conversion between Open3D and ROS. Compatible for XYZ and XYZRGB point type.
Described the community Python conversion functions convertCloudFromOpen3dToRos and convertCloudFromRosToOpen3d and the round-trip test case.
- Optimized ROS->PCL conversion - ROS General - Open Robotics Discourse
Provided the basis for the discussion of the drop-in optimized fromROSMsg replacement that skips an intermediate copy and runs close to twice as fast.
- GitHub - ros-perception/perception_pcl: PCL (Point Cloud Library) ROS interface stack · GitHub
Provided details on open pull requests migrating pcl_ros nodes to ROS 2, including PR #510 and PR #511, and the overall status of the ROS 2 port.
- Open3D vs PCL: Which 3D Processing Library Should You Use?
Provided the benchmark comparison showing Open3D completing voxel downsampling faster than PCL and context on ICP comparison caveats.
- [conversions] toPCLPointCloud2 or toROSMsg<PointCloud2> wastes memory with alignment bytes · Issue #4939 · PointCloudLibrary/pcl
Provided details on how pcl::toROSMsg preserves internal alignment bytes, padding the resulting message beyond what is needed.
- perception_pcl - ROS Package Overview
Confirmed that perception_pcl is maintained and released across Humble, Jazzy, Kilted, and Rolling.
- open3d_conversions - ROS Package Overview
Confirmed the open3d_conversions package providing open3dToRos and rosToOpen3d functions and details on XYZI intensity handling.


