Normals

Surface normals are unit vectors perpendicular to locally estimated surfaces in a lidar point cloud. The SDK computes them from neighboring lidar returns and estimates one normal per valid return from destaggered XYZ and range images.

Users can add normals to frames for visualization, save them for later processing, or pass them to algorithms such as point cloud registration, surface analysis, and filtering. Use the normals CLI command to process a stream of frames, or call the normals API when integrating normal estimation into an application.

When used with viz, the visualizer can color the point cloud by the computed normals while also showing the corresponding image view:

A screenshot of Ouster Viz displaying normals values in the point cloud and image panels

Ouster Viz displaying normals values after running the normals command.

Using the CLI

The chainable normals command computes normals for every frame. It adds a NORMALS field for the first return and a NORMALS2 field when the second return is available.

ouster-cli source {OSF_PATH} normals viz

By default, the command dewarps points using the frame poses and expresses the normals in the global coordinate frame. Use --sensor-coord to compute them in the sensor coordinate frame:

ouster-cli source {OSF_PATH} normals --sensor-coord viz

Using the API

The normals function expects destaggered XYZ and range arrays. XYZ points and sensor origins must use the same coordinate frame. The examples below use the sensor coordinate frame, so every sensor origin is (0, 0, 0). See XYZLut & Destaggering for creating XYZ data and destaggering lidar fields. LidarFrame fields are stored in staggered layout, so the examples stagger the computed normals before writing the NORMALS field.

sensor_origins = np.zeros(
    (range_destaggered.shape[1], 3), dtype=np.float64)

normal_image = normals(
    xyz_destaggered,
    range_destaggered,
    sensor_origins_xyz=sensor_origins)
auto sensor_origins = core::MatrixX3dR::Zero(
    range_destaggered.cols(), 3);

auto normal_vectors = algorithm::normals(
    xyz_destaggered,
    range_destaggered,
    sensor_origins);

Invalid returns, or returns for which a normal cannot be estimated, contain a zero vector.