Wiki
pointcloud_to_laserscan(三维地形点云转二维扫描)
Existing English translations were reused. 6 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.pointcloud_to_laserscan(三维地形点云转二维扫描)
1.1.实车先看:哪些三维点会被画进二维地图
The current mapping chain is:
terrain_map_ext(odom系地形点)
→ pointcloud_to_laserscan(转到底盘系,筛选并投影)
→ obstacle_scan(base_footprint系二维扫描)
→ slam_toolbox(二维建图)
This node is used by the complete slam:=True launch. Existing-map navigation slam:=False does not use this entry point. Current local and global costmaps directly use terrain point clouds, rather than relying on this two-dimensional scan to update obstacles.
Scan with base_footprint as the origin, with directions expressed in the chassis frame. Although the input comes from MID360, it is not the raw LiDAR point cloud, but terrain_map_ext; where intensity has been changed to the height difference relative to the estimated ground, in m, not the LiDAR reflection intensity.
| 实车上看到的现象 | 先看哪些参数或条件 | 怎样理解 |
|---|---|---|
| 正常坡面被画成障碍 | 地形估计、min_intensity |
当前10 cm高差门槛可能接纳被误估成凸起/凹陷的坡点;降低门槛也会接纳更多小障碍和噪声 |
| 下坡较远处的点消失 | min_height、target_frame方向 |
这里重新要求底盘系z不低于-0.5 m;上游按距离放宽高度范围,并不保证能通过这里 |
| 高处横梁、悬空结构被画成墙 | max_height、max_intensity、上游地形筛选 |
通过筛选的点会压到二维平面;目前z上限6 m很宽,是否妨碍车辆通行需按实车包络判断 |
| 车旁小障碍没进入地图 | range_min、min_intensity 和上游输出 |
0.3 m内的点或高差不足10 cm的点会被删;本节点也不能恢复上游丢掉的点 |
| 小杆或障碍边缘漏掉 | angle_increment、点云密度 |
当前约0.5°一个角度格,每格只留最近合格点;分格更细不能补出原本没有的回波 |
| 以为扫描固定20 Hz,实际不是 | scan_time、输入点云周期 |
0.05 s只写入消息,发布由点云回调驱动,没有20 Hz发布定时器 |
| 有点云却没有扫描 | 下游订阅、坐标TF、字段、tf2版本 | 没人订阅扫描时不接点云;当前需要有效TF以及XYZ、intensity字段;Humble的tf2/tf2_ros 0.25.23还可能因内部死锁断流,见点云和TF正常但二维地图不更新 |
1.2.参数
1.2.1.ros__parameters (ROS node parameters)
1.2.1.1.Parameter table
The first 13 items correspond to the current physical YAML; simulation only use_sim_time=True. In addition, list the 3 default items in the source code, clarifying the conditions for TF waiting and no-echo encoding.
pointcloud_to_laserscan:
ros__parameters:
use_sim_time: False
target_frame: base_footprint
min_height: -0.5
max_height: 6.0
min_intensity: 0.1
max_intensity: 2.0
angle_min: -3.1415926 # 约 -π,-180°
angle_max: 3.1415926 # 约 +π,+180°
angle_increment: 0.00872665 # 约 π/360,0.5°
scan_time: 0.05
range_min: 0.3
range_max: 10.0
use_inf: true
The original configuration wrote the comment for angle_min/max as ±π/2 which does not match the value; the current value corresponds to approximately ±π, and the scan angular range is approximately 360°, not 180°.
| Parameter | Current value | Units | Hardware guidance | Purpose | Effect of increasing | Effect of decreasing | Activation conditions / notes |
|---|---|---|---|---|---|---|---|
use_sim_time |
False; in simulation, True |
Not applicable; boolean | Verify and configure correctly before running on hardware | Control ROS time for the node and TF buffer | Not applicable | Not applicable | Output retains input point cloud timestamps instead of using the current time. It should share the upstream and mechanical TF time domain |
target_frame |
base_footprint |
Not applicable; frame name | Verify and configure correctly before running on hardware | Transform point coordinates into this frame before projecting using its origin and axes | Not applicable | Not applicable | Currently a virtual scan in the chassis frame; does more than rename frame_id. Conversion fails without TF; an empty value retains the input frame |
min_height |
-0.5 |
m; target-frame lower z bound | Verify and configure correctly before running on hardware | Remove points more than 0.5 m below the chassis origin | Raise the lower bound, removing more low and downhill points | Lower the lower bound, retaining lower points and potentially unrelated lower levels | Fixed height window, without distance expansion; not height difference relative to local ground |
max_height |
6.0 |
m; target-frame upper z bound | Verify and configure correctly before running on hardware | Remove points more than 6 m above the chassis origin | Retain more high points, potentially including suspended structures in the two-dimensional map | Remove more high points, potentially losing visible obstacle portions | Not robot height. Points must still satisfy height-difference and upstream rules; increasing it does not guarantee more points |
min_intensity |
0.1 |
m; lower terrain-point height-difference bound | Observe closely on hardware; adjust only for a specific problem | Accept only points at least 10 cm above or below the estimated ground | Ignore more small differences, potentially reducing slope misclassification but missing low obstacles | Accept more low obstacles, but also more ground noise or slopes as obstacles | Current input is not reflectivity. Inaccurate terrain estimation cannot be fixed solely with this parameter |
max_intensity |
2.0 |
m; upper terrain-point height-difference bound | Usually keep the current value; not a priority for tuning | Remove points with height differences above 2 m | Allow more large-height-difference points | Remove more large-height-difference points | Normal current near-field differences are below 0.5 m and far-field differences below 1 m, so 2 m usually adds no clipping; the condition still executes |
angle_min |
-3.1415926 |
rad; target-frame planar azimuth | Usually keep the current value; not a priority for tuning | Specify the lower angular bound, currently approximately -180° | Raise the lower bound, narrowing angular coverage | Lower the lower bound, widening angular coverage | Defines azimuth coverage together with the upper bound; not LiDAR vertical field of view or a gimbal mechanical angle |
angle_max |
3.1415926 |
rad; target-frame planar azimuth | Usually keep the current value; not a priority for tuning | Specify the upper angular bound, currently approximately +180° | Raise the upper bound, widening angular coverage | Lower the upper bound, narrowing angular coverage | Currently approximately a full circle; verify together with the lower bound, increment and array boundaries |
angle_increment |
0.00872665 |
rad per angular cell; approximately 0.5° | Observe closely on hardware; adjust only for a specific problem | Divide azimuth into discrete cells | Coarser cells and fewer entries; nearby directions merge more readily | Finer cells and more entries; more empty cells with sparse input | Currently generates 720 entries; does not change MID360 sampling density or point cloud downsampling. Must be positive |
scan_time |
0.05 |
s; scan interval declared in the message | Verify and configure correctly before running on hardware | Set the LaserScan.scan_time field | Declare a longer interval, without changing publication timing | Declare a shorter interval, without changing publication timing | Should match the actual input and output scan period; not a fixed 20 Hz switch. time_increment is separately hardcoded to 0 |
range_min |
0.3 |
m; target-frame horizontal distance | Verify and configure correctly before running on hardware | Remove points within 0.3 m of the virtual center | Remove more nearby points, potentially missing obstacles beside the robot | Retain more nearby points, potentially including self returns | Distance is sqrt(x²+y²), centered on base_footprint; not three-dimensional LiDAR slant range or a complete self mask |
range_max |
10.0 |
m; target-frame horizontal distance | Observe closely on hardware; adjust only for a specific problem | Remove points beyond 10 m from the virtual center | Accept more existing distant points, with greater sparse-point and noise influence | Truncate more distant outlines | Does not increase hardware range; also limited by terrain-output coverage and downstream scan limits |
use_inf |
true |
Not applicable; boolean | Enable or configure as needed | Select a no-return representation for cells without qualifying points | Not applicable | Not applicable | True writes +∞; False writes range_max+inf_epsilon. No qualifying return does not establish clear ground |
transform_tolerance |
0.01; source-code default, absent from YAML |
s; transform-query waiting time | Usually keep the current value; not a priority for tuning | Wait for the required TF during point cloud conversion | Wait longer, potentially delaying processing | Abandon missing transforms sooner | Currently passed as TF query timeout; not a timestamp offset or point cloud synchronization-error tolerance. Unused when target_frame is empty |
queue_size |
Hardware concurrency; source-code default, absent from YAML | Messages; count | Usually keep the current value; not a priority for tuning | Set point cloud subscription and TF message-filter queue depths | Buffer more messages, potentially increasing backlog | Buffer fewer messages; more readily drop messages while TF is unavailable | Default varies by machine; not CPU worker count, publication frequency or scan point count |
inf_epsilon |
1.0; source-code default, absent from YAML |
m; additional out-of-range distance | Unused in the current execution path; leave unchanged for now | Generate a no-return sentinel when use_inf=False | When enabled: place the sentinel farther beyond range_max | When enabled: place the sentinel closer to range_max | Currently use_inf=True, so unused. False defaults to a no-return distance of 10+1=11 m. Keep the additional distance positive when enabled to avoid placing it within the valid range |
1.2.1.2.Parameter Details
1.2.1.2.1.target_frame: The center and direction of the scan are the chassis, not the gimbal or radar.
The point coordinates of the current input terrain_map_ext are in odom; the transform uses the base_footprint←odom corresponding to the input message timestamp, then filtering is performed using the transformed x, y, z. The output obstacle_scan.header.frame_id is base_footprint, and the timestamp follows the input.
In this frame, the planar angle is 0 along +x, and is positive counterclockwise about +z; distances are calculated from the origin of this frame. It and fake_vel_transform Finally, the velocity commands expressed in the gimbal frame are two different interfaces: here the ranging contour is processed, and no motion commands are given to the chassis or gimbal.
Therefore, this is avirtual 2D scan at the chassis originnot a real laser beam at the MID360 origin. The MID360 has a mounting offset from the chassis, so range_min cannot be understood as "0.3 m from the radar housing." The input is an accumulated terrain result, containing points at different times and different heights; for the same angular bin, only the nearest qualifying point is retained, and there is no re-verification of whether a real optical path or occlusion exists from the virtual center of the chassis to that point.
Correct upstream registration and mechanical TF should ensure that the static environment, after the independent gimbal yaw rotation, is still transformed into the chassis frame; you must not manually add another gimbal angle to this point cloud again. The MID360 is mounted at gimbal_yaw and rotates with yaw but not with pitch; the mechanical convention of the two fixed odom joints continues to apply.
base_footprint This name does not automatically zero z, roll or pitch in TF. Current sensor_scan_generation retains the complete three-dimensional pose, so projection uses that frame’s x-y plane. Whether it matches the ground projection or intended horizontal plane uphill requires checking the frame definition and upstream orientation; the name alone is insufficient.
If target_frame is left empty, the source code does not transform points and directly computes in the input frame. The current input is odom, which then becomes a virtual center based on the odom origin and orientation; after the robot moves, it is no longer equivalent to still projecting with the chassis as the center.
1.2.1.2.2.min_height, max_height and two intensity: two different measures
Altitude window view z_base_footprint , i.e., the coordinate of the point relative to the chassis origin along the chassis z-axis; height difference window view intensity , i.e., "how far from the estimated ground" given by the upstream terrain analysis. Their units are both m, but the reference frames are different.
Assuming the upstream has retained these points and other conditions are also satisfied:
| z of the point in the chassis frame | terrain elevation difference provided by the upstream | result of this node |
|---|---|---|
| +0.2 m | 0.15 m | Both windows pass; participate in 2D projection |
| +0.2 m | 0.06 m | Elevation difference less than 0.1 m; deleted |
| -0.7 m | 0.2 m | Below the lower height bound of -0.5 m; deleted even though the elevation difference is sufficient |
Therefore, even if a distant downhill point passes the window that is relaxed with distance through extended terrain nodes, it may still be removed here by the fixed lower bound. Whether the uphill and downhill are continuously visible must be determined by considering both the direction of target_frame and the height window;disRatioZ will not relax the range for this node.
min_intensity=0.1Using 10 cm as the lower limit for height difference when entering a two-dimensional obstacle contour does not mean that the vehicle can climb over a 10 cm step. Lowering it may reveal lower obstacles, but may also map normal ground or slope errors into the map; raising it will reduce contours with small height differences, but may miss low obstacles that are dangerous to the real vehicle. For far-field terrain, the absolute value of the height difference is taken, so the lower side of a slope may also cross the threshold; the near field currently ignores negative height differences, and the difference at the 4 m handover will still propagate into the scan.
max_height=6.0The window of is very wide. If points such as high crossbeams pass the upstream and height-difference filtering, they will be projected onto the 2D map, and even if the vehicle could originally pass underneath, they may still form obstacle outlines. The height to be retained should be checked against the chassis, the gimbal motion envelope, and suspended structures; it must not be treated as a model dimension. At the same time, one must not merely lower the upper limit and thereby miss the only visible part of the obstacle.
Currently, the normal terrain output has a near-field elevation difference below 0.5 m and a far-field difference below 1 m; therefore, max_intensity = 2 m usually does not remove additional points; this does not mean the parameter was not read. If the input is changed, upstream is adjusted, or other marker points appear, the effect will change. The height and elevation difference thresholds use "delete if less than the lower bound / greater than the upper bound," and boundary values pass.
The local implementation requires float XYZ and intensity fields. A cloud without intensity cannot be used directly. With raw LiDAR points, intensity may again mean reflectivity, in which case 0.1 and 2.0 no longer mean 10 cm and 2 m. Do not simply switch topics while retaining the current interpretation.
1.2.1.2.3.angle_min, angle_max and angle_increment: the full circle is divided into 720 divisions
The current angular domain is approximately -180° to +180°, with an increment of about 0.5°, and the source code generates 720 range bins. The angle is in the target coordinate system, atan2(y,x)not the radar vertical field of view, installation tilt, or gimbal mechanical yaw range.
When there are multiple qualified points within the same angular cell, only the one with the shortest horizontal distance is retained. For example, if there are points at 2 m and 5 m in the same direction, the scan retains only the 2 m one; a two-dimensional scan will not simultaneously represent their heights or multilayer structure.
A 0.5° increment corresponds to a lateral extent of about 8.7 cm at 10 m and about 8.7 mm at 1 m; this only describes the geometric scale of the angular grid, not positioning accuracy, and does not guarantee that obstacles this fine can be seen. The input point cloud has undergone upstream filtering and downsampling, so reducing the angular increment will not increase the actual returns.
The source code first builds an array with ceil((angle_max-angle_min)/angle_increment), then computes the index. The current implementation does not check whether the index is out of bounds, while the upper bound of the angle is allowed to pass; when the angular domain and the increment exactly form an integer partition, the upper bound point has an out-of-bounds path. The current approximate full-circle setting yields 720 entries, and this cannot be used to prove the boundary safety of other angle combinations; this gap has no runtime trigger record yet.
1.2.1.2.4.range_min and range_max: Horizontal ring around the chassis
Currently retains points at horizontal distances from 0.3 to 10.0 m, calculated using sqrt(x²+y²) without z. Points with identical x and y but different heights therefore have the same range value, provided they pass the height and height-difference windows.
Increasing range_min can remove more near-field echoes, but it may also miss obstacles beside the vehicle; decreasing it may bring in the vehicle body, gimbal, or other ego-vehicle points. The 0.3 m circular threshold is not the complete vehicle outline; ego-vehicle echoes farther from the chassis origin may still remain, so it must be combined with the installation position and actual vehicle body observations.
Increasing range_max only allows more already-existing far points; it cannot let the radar see farther, nor can it restore points deleted upstream. Currently slam_toolbox is separately configured with min_laser_range=0.3 and max_laser_range=10.0; the two-stage ranges should be checked together; the downstream parameter usage belongs to the mapping parameter block.
1.2.1.2.5.scan_time: Time field, not a publish frequency switch
In the LaserScan definition, scan_time represents the time interval between scans. Here, the actual interval is not measured; only the parameter 0.05 s is written into the message. The output is driven by the input point cloud, and the actual period depends on the terrain point cloud, TF, and computation process.
time_incrementHard-coded to 0, indicating there is no per-beam time difference;header.stampFollows the input point cloud time. Terrain input contains historical points; this result must not be treated as all rays actually being acquired at the same instant by a physical 2D radar, nor may scan_time be used in place of motion compensation or to fix the upstream point time unit.
The actual output cycle should be verified after measurement according to the corresponding startup mode. Modifying scan_time only changes the field; it will not turn a 10 Hz input into a 20 Hz scan, nor will it fill in real sampling times for each angle cell.
1.2.1.2.6.use_inf and inf_epsilon: No echo does not equal confirmed air-to-ground
For True, first set each grid distance to +∞, then use the nearest qualified point to cover it; if a grid cell has no qualified point, keep ∞. For False, first set it to range_max+inf_epsilon; the current default combination is 11 m, which still exceeds the 10 m effective range.
No qualifying point may mean a genuine absence of returns, missing data, occlusion, upstream removal, height or height-difference filtering, or no sample in the angular cell. ∞ alone does not establish that a direction is safe. Check how invalid and out-of-range values are handled in the implementation of each downstream scan consumer.
For the output, fill in only ranges, not LaserScan.intensities. The two intensity parameters here are used to select input points and do not continue to publish terrain elevation differences as scan intensity.
1.2.1.2.7.Default TF parameters and subscription conditions
transform_tolerance=0.01 s serves as the conversion-query wait timeout in the current code. It does not compensate for a 0.01-second point cloud and TF offset or shift message timestamps. The TF message filter waits for the target transform at input time; queue_size controls its queue and point cloud subscription depth. A failed TF conversion returns directly instead of continuing to generate a scan with an identity transform.
The node subscribes cloud_in only when there is a subscriber to the scan output; after the last scan subscriber disappears, it unsubscribes from the point cloud. Both input and output use SensorDataQoS; compatible subscription QoS and a valid TF are prerequisites for receiving scans. Even when all points are filtered out, a scan frame consisting entirely of no-return values can still be published; therefore, the presence of a scan message must not be taken as an indication that the obstacle contour is correct.
By default, queue_size takes the number of hardware concurrent threads, which is suitable to continue using at first; if message queuing or TF waiting causes dropped points, then verify against the actual data rate and load, and do not treat the queue length as the number of worker threads.
1.2.1.2.8.Startup Source and Verification Status
| Launch method | Configuration and input and output sources |
|---|---|
| Complete reality launch, slam=True | Hardware nav2_params.yaml is passed through slam_launch; cloud_in is remapped to terrain_map_ext, scan to obstacle_scan; target_frame=base_footprint |
| Complete simulation launch, slam=True | Simulation nav2_params.yaml; among the 13 parameters, only use_sim_time=True differs. Same inputs, outputs and target frame |
| Complete launch, slam=False | Enters existing-map localization and does not start this conversion node |
| Package’s sample_pointcloud_to_laserscan_launch.py | Example point cloud publisher and static TF; target frame cloud, heights 0–1 m, angles approximately ±90°, scan_time=0.3333, distances 0.45–4 m. Does not read the complete YAML |
Fifteen custom parameters are read at construction time, with no dynamic parameter update callbacks; runtime changes to parameters with the same names do not automatically update these members or rebuild the TF filter. The ROS standard use_sim_time is handled by the node clock. Currently use_inf=True causes inf_epsilon not to participate in the output, while other thresholds still execute according to their respective conditions.
- Source-code references:
pointcloud_to_laserscan/src/pointcloud_to_laserscan_node.cpp; launch wiring is inpb2025_nav_bringup/launch/slam_launch.py, and LaserScan field definitions are in ROS2 sensor_msgs. - Existing verification scope: Standalone Point-LIO output while stationary and during short back-and-forth motion is recorded. This converter’s two-dimensional scan, mapping outlines, continuous gimbal yaw and slope projection remain unverified.
- Pending hardware checks: Chassis origin and projection plane; IMU, LiDAR and chassis transformations; uphill and downhill clipping by fixed height windows; retained low obstacles and suspended structures; self returns; angular coverage; actual scan period; and downstream treatment of no-return regions.