Skip to content

registration_ransac_based_on_feature_matching cannot be deterministic #7475

@leon-nn

Description

@leon-nn

Checklist

Describe the issue

The result in this Python demo for the global registration step cannot be deterministically reproduced:
https://www.open3d.org/docs/latest/tutorial/pipelines/global_registration.html

This is after setting the global random seed, as shown in PR #5247

o3d.utility.random.seed(0)

A user in these issues suggests that setting this global random seed works, but it does not for me.

The example script provided runs the global registration with RANSAC function twice, setting the global seed before each call. However, the results are different. Even when running the script twice, the first result is different each time. It doesn't seem to matter if I set the global random seed at the top of the file or right before the global registration function is called.

Steps to reproduce the bug

import numpy as np
import open3d as o3d


def preprocess_point_cloud(pcd, voxel_size):
    print(":: Downsample with a voxel size %.3f." % voxel_size)
    pcd_down = pcd.voxel_down_sample(voxel_size)

    radius_normal = voxel_size * 2
    print(":: Estimate normal with search radius %.3f." % radius_normal)
    pcd_down.estimate_normals(
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30)
    )

    radius_feature = voxel_size * 5
    print(":: Compute FPFH feature with search radius %.3f." % radius_feature)
    pcd_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
        pcd_down,
        o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100),
    )
    return pcd_down, pcd_fpfh


def prepare_dataset(voxel_size):
    print(":: Load two point clouds and disturb initial pose.")

    demo_icp_pcds = o3d.data.DemoICPPointClouds()
    source = o3d.io.read_point_cloud(demo_icp_pcds.paths[0])
    target = o3d.io.read_point_cloud(demo_icp_pcds.paths[1])
    trans_init = np.asarray(
        [
            [0.0, 0.0, 1.0, 0.0],
            [1.0, 0.0, 0.0, 0.0],
            [0.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 0.0, 1.0],
        ]
    )
    source.transform(trans_init)

    source_down, source_fpfh = preprocess_point_cloud(source, voxel_size)
    target_down, target_fpfh = preprocess_point_cloud(target, voxel_size)
    return source, target, source_down, target_down, source_fpfh, target_fpfh


def execute_global_registration(
    source_down, target_down, source_fpfh, target_fpfh, voxel_size
):
    distance_threshold = voxel_size * 1.5
    print(":: RANSAC registration on downsampled point clouds.")
    print("   Since the downsampling voxel size is %.3f," % voxel_size)
    print("   we use a liberal distance threshold %.3f." % distance_threshold)
    result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
        source_down,
        target_down,
        source_fpfh,
        target_fpfh,
        True,
        distance_threshold,
        o3d.pipelines.registration.TransformationEstimationPointToPoint(False),
        3,
        [
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9),
            o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(
                distance_threshold
            ),
        ],
        o3d.pipelines.registration.RANSACConvergenceCriteria(100000, 0.999),
    )
    return result


if __name__ == "__main__":
    voxel_size = 0.05  # means 5cm for this dataset
    source, target, source_down, target_down, source_fpfh, target_fpfh = (
        prepare_dataset(voxel_size)
    )
    o3d.utility.random.seed(0)
    result_ransac = execute_global_registration(
        source_down, target_down, source_fpfh, target_fpfh, voxel_size
    )
    print(result_ransac)
    print(result_ransac.transformation)
    o3d.utility.random.seed(0)
    result_ransac = execute_global_registration(
        source_down, target_down, source_fpfh, target_fpfh, voxel_size
    )
    print(result_ransac)
    print(result_ransac.transformation)

Error message

:: Load two point clouds and disturb initial pose.
:: Downsample with a voxel size 0.050.
:: Estimate normal with search radius 0.100.
:: Compute FPFH feature with search radius 0.250.
:: Downsample with a voxel size 0.050.
:: Estimate normal with search radius 0.100.
:: Compute FPFH feature with search radius 0.250.
:: RANSAC registration on downsampled point clouds.
   Since the downsampling voxel size is 0.050,
   we use a liberal distance threshold 0.075.
RegistrationResult with fitness=6.771008e-01, inlier_rmse=3.694170e-02, and correspondence_set size of 3223
Access transformation to get result.
[[-0.52908008  0.84837802  0.01813838  0.60695513]
 [-0.22360642 -0.16000528  0.96145644  0.86199177]
 [ 0.81858075  0.50463159  0.27435838 -1.42571538]
 [ 0.          0.          0.          1.        ]]
:: RANSAC registration on downsampled point clouds.
   Since the downsampling voxel size is 0.050,
   we use a liberal distance threshold 0.075.
RegistrationResult with fitness=6.747899e-01, inlier_rmse=3.047766e-02, and correspondence_set size of 3212
Access transformation to get result.
[[-0.53022708  0.84741436  0.02735227  0.5848452 ]
 [-0.19899316 -0.15573983  0.96754681  0.77459227]
 [ 0.8241729   0.50757661  0.25120714 -1.44945346]
 [ 0.          0.          0.          1.        ]]

Expected behavior

Running the global registration RANSAC code when setting the same global seed should have the same result

Open3D, Python and System information

- Operating system: Ubuntu 24.04
- Python version: Python 3.10.19
- Open3D version: 0.19.0
- System architecture: x86
- Is this a remote workstation?: yes
- How did you install Open3D?: pip
- Compiler version (if built from source): N/A

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugNot a build issue, this is likely a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions