Skip to content

Nacho/robust kernels#2425

Merged
germanros1987 merged 31 commits intoisl-org:masterfrom
nachovizzo:nacho/robust_kernels
Oct 13, 2020
Merged

Nacho/robust kernels#2425
germanros1987 merged 31 commits intoisl-org:masterfrom
nachovizzo:nacho/robust_kernels

Conversation

@nachovizzo
Copy link
Copy Markdown
Contributor

@nachovizzo nachovizzo commented Oct 4, 2020

Robust Kernels for Registration in Open3D

This PR brings the power of statistical robust kernels to aid the registration pipelines built-in Open3D. For the time being, it only changes the PointToPlane ICP algorithm but could be easily adapted for other optimization problems.

Results

The full explanation on this it's hosted on the tutorial robust_kernels. The results are shown below:

Noisy Input Data

noise

Vanilla ICP

vanilla_icp

Vanilla ICP + higher distance threshold

vanilla_large_icp

Robust ICP using TukeyLoss

robust_icp

As we show, using a robust kernel(o robust loss function) in the PointToPlane optimization, leads to much better results in the context of noisy data.

Kernels Implemented so far

RobustKernel (base class)

Adding a new robust kernel is as easy as inheriting from open3d::pipelines::registration::RobustKernel and implemented the virtual Weight(double residual) method.

image

L2

Is the Vanilla ICP loss function, by default, this is the selected kernel(thus, no real robust optimization). Although this guarantee backward compatibility will all the existing code that relies on Open3D registration ICP pipelines.
image

L1

image

Huber

image

Cauchy

image

German-McClure

image

Tukey

image

Documentation

I spent extra time and effort trying to make the documentation the most extensible possible. Please check the tutorial and docs generated by this PR for more information.

More info

For bibliography, you could check out https://arxiv.org/pdf/1810.01474.pdf or https://arxiv.org/pdf/2004.14938.pdf (this adaptive kernel I plane to add soon)


This change is Reviewable

@update-docs
Copy link
Copy Markdown

update-docs Bot commented Oct 4, 2020

Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes.

@nachovizzo nachovizzo marked this pull request as draft October 4, 2020 17:32
@griegler griegler requested review from theNded and yxlao October 5, 2020 13:29
@theNded
Copy link
Copy Markdown
Contributor

theNded commented Oct 5, 2020

This PR looks great, thanks for the contribution! Several initial considerations:

  1. We are pushing the Tensor branch, would you consider developing a version for Tensors? i.e., instead of taking a double as an argument, we may take a flattened 1D Tensor as input.
  2. [Optional] would it be possible that you benchmark the performance for different kernels on some small datasets (like merely the point clouds in test_data)?
    Thanks again!

@nachovizzo
Copy link
Copy Markdown
Contributor Author

@theNded Thanks for the feedback.

1.) Yes I can totally do it, although I more in favor of atomic PR, so I would probably happen in the next PR
2.) I will try to add a small benchmark. Even so, I don't think this adds any overhead in the optimization problem.

I will clean up what I've done, add some experiments on the tutorials showing the use case of robust kernels, and move forward. If you have any other concerns respect to the design It would be awesome to know before I open the cleaned version of these changes.

Thanks!

@theNded
Copy link
Copy Markdown
Contributor

theNded commented Oct 6, 2020

@nachovizzo
1 totally makes sense for me. For 2 I was only want to make sure that the registration can converge around to the correct result, the runtime would not be critical.

Thanks and looking forward to the PR!

@nachovizzo
Copy link
Copy Markdown
Contributor Author

@theNded this PR is ready to review. I did not add any sort of benchmark right now because I ran out of time. Even so, the API is backward compatible, so, it will not change any running code. In the future, we should add benchmarks and also unit tests that assert that robust kernels work with noisy data.

I hope this contribution is helpful for all the Open3D users!

@nachovizzo nachovizzo marked this pull request as ready for review October 8, 2020 14:24
Copy link
Copy Markdown
Contributor

@theNded theNded left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5 of 11 files at r1, 1 of 2 files at r2, 7 of 7 files at r3, 1 of 1 files at r4.
Reviewable status: all files reviewed, 4 unresolved discussions (waiting on @nachovizzo and @yxlao)


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 39 at r4 (raw file):

namespace registration {

double L2Loss::Weight(double /*residual*/) const { return 1.0; }

Overall I'm thinking about making Weights inline member functions, since they are mostly one-liners.


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 46 at r4 (raw file):

double HuberLoss::Weight(double residual) const {
    const double e = std::abs(residual);

return k_ / std::max(e, k_);


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 54 at r4 (raw file):

double CauchyLoss::Weight(double residual) const {
    return 1.0 / (1 + std::pow(residual / k_, 2.0));

Would you consider writing a short inline function/Macro square(x) for these weights? std::pow is slower than x*x. It is a micro optimization though, you may ignore it.


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 63 at r4 (raw file):

double TukeyLoss::Weight(double residual) const {
    const double e = std::abs(residual);
    if (e > k_) {

return std::pow((1.0 - std::pow(std::min(1, e / k_), 2.0)), 2.0);

@theNded
Copy link
Copy Markdown
Contributor

theNded commented Oct 11, 2020

Thanks for the great work! I added some suggestions for minor changes.
Several potential next steps can be

Copy link
Copy Markdown
Contributor Author

@nachovizzo nachovizzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: all files reviewed, 4 unresolved discussions (waiting on @theNded and @yxlao)


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 39 at r4 (raw file):

Previously, theNded (Wei Dong) wrote…

Overall I'm thinking about making Weights inline member functions, since they are mostly one-liners.

I totally agree. Even so, the fact that C++ does not have proper support for polymorphism without virtual disable the compiler to inline these methods. I spent a few hours, there are 3/4 cppcon talks trying to address this problem. Most of them end up writing a header-only library that it's usually only supported in c++17. With this I want to say, I didn't find any "elegant" solution for this. I didn't want to use pointers, nor smartpointers, I don't see why we need to allocate one RobustKernel object in the heap... the problem is that it's the only way to make this polymorphic. For the time being, I didn't detect any real overhead.

Here are 2 examples to investigate further, even the trivial L2Loss::Weight function can not be inlined by the compiler(due to memory allocations in the heap and so on...)

PS: I''m open to suggestions if you find a better way to implement this strategy pattern


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 46 at r4 (raw file):

Previously, theNded (Wei Dong) wrote…

return k_ / std::max(e, k_);

Great catch!


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 54 at r4 (raw file):

Previously, theNded (Wei Dong) wrote…

Would you consider writing a short inline function/Macro square(x) for these weights? std::pow is slower than x*x. It is a micro optimization though, you may ignore it.

Done


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 63 at r4 (raw file):

Previously, theNded (Wei Dong) wrote…

return std::pow((1.0 - std::pow(std::min(1, e / k_), 2.0)), 2.0);

Got it, much better

Address comments from PR#2425
@nachovizzo
Copy link
Copy Markdown
Contributor Author

Thanks for the great work! I added some suggestions for minor changes.
Several potential next steps can be

Should we attempt to address this or in other PR?

Copy link
Copy Markdown
Contributor Author

@nachovizzo nachovizzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 13 of 14 files reviewed, 4 unresolved discussions (waiting on @theNded and @yxlao)


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 39 at r4 (raw file):

Previously, nachovizzo (Ignacio Vizzo) wrote…

I totally agree. Even so, the fact that C++ does not have proper support for polymorphism without virtual disable the compiler to inline these methods. I spent a few hours, there are 3/4 cppcon talks trying to address this problem. Most of them end up writing a header-only library that it's usually only supported in c++17. With this I want to say, I didn't find any "elegant" solution for this. I didn't want to use pointers, nor smartpointers, I don't see why we need to allocate one RobustKernel object in the heap... the problem is that it's the only way to make this polymorphic. For the time being, I didn't detect any real overhead.

Here are 2 examples to investigate further, even the trivial L2Loss::Weight function can not be inlined by the compiler(due to memory allocations in the heap and so on...)

PS: I''m open to suggestions if you find a better way to implement this strategy pattern

Done.


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 46 at r4 (raw file):

Previously, nachovizzo (Ignacio Vizzo) wrote…

Great catch!

Done.


cpp/open3d/pipelines/registration/RobustKernel.cpp, line 63 at r4 (raw file):

Previously, nachovizzo (Ignacio Vizzo) wrote…

Got it, much better

Done.

@theNded
Copy link
Copy Markdown
Contributor

theNded commented Oct 12, 2020

Thanks for the great work! I added some suggestions for minor changes.
Several potential next steps can be

Should we attempt to address this or in other PR?

Yeah of course, "next steps" here definitely mean other PRs :-)

Copy link
Copy Markdown
Contributor

@theNded theNded left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 1 of 1 files at r5.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @yxlao)

@germanros1987
Copy link
Copy Markdown
Contributor

Codacy Here is an overview of what got changed by this pull request:

Clones added
============
- cpp/open3d/pipelines/registration/TransformationEstimation.h  2
         

See the complete overview on Codacy

@germanros1987 germanros1987 merged commit cf45d38 into isl-org:master Oct 13, 2020
nachovizzo added a commit to nachovizzo/Open3D that referenced this pull request Oct 16, 2020
nachovizzo added a commit to nachovizzo/Open3D that referenced this pull request Oct 16, 2020
Ideally one could also add a robust kernel in this step.
This extends PR isl-org#2425
theNded pushed a commit that referenced this pull request Oct 20, 2020
* Add weighting function for the per-row version of ComputeJTJandJTr.

This somehow extends PR #2425

* Add weight vector to match ComputeJTJandJr new API.

Ideally one could also add a robust kernel in this step.
This extends PR #2425

* Expose weight parameter for ColorMapOptimizationJacobian

* Finish ColorizeICP re-binding in python

* Move private function to unnamed namespace

* Update python examples

* Fix generic->geomtric

* Fix tests.

After changing how we compute the Jacbonians API

* Fix unit tests
nachovizzo added a commit to nachovizzo/Open3D that referenced this pull request Oct 20, 2020
theNded added a commit that referenced this pull request Jul 22, 2021
* Add Generalized ICP draft

* Add weighting function for the per-row version of ComputeJTJandJTr.

This somehow extends PR #2425

* wip

* Add weighting function for the per-row version of ComputeJTJandJTr.

This somehow extends PR #2425

* Add weight vector to match ComputeJTJandJr new API.

Ideally one could also add a robust kernel in this step.
This extends PR #2425

* Expose weight parameter for ColorMapOptimizationJacobian

* Finish ColorizeICP re-binding in python

* Move private function to unnamed namespace

* Update python examples

* Fix generic->geomtric

* Finish draft of the optimization pipeline

* Change naming

* Fix tests.

After changing how we compute the Jacbonians API

* Add Python bindings for Genrealized ICP

* Add draft for ComputeRMSE

* Fix unit tests

* Fix GenerelaizedICP draft

* Log also correspondence size for each ICP iteration

* Fix optimizaiton calculation

* Attempt to fix optimization problem

* Fix source/target swap.

This finally works!

* One step closer to gicp

* Complete copy-paste from Registartion.cpp

No time to deal with API changes, will fix this later

* Finish gicp implentation (hopefully)

* Turn gicp into an ugly polymorphic code

* Use existing normal in the PointCloud instead of re-compute

* Fix stupid bug

* Add small chec

* fix

* Another huge bugfix. M -> M-1

* last bugfix of the night

* Another bugfix. I can now obtain the same results as the original
implemenation

* Don't apply incremental rotation to Source Covariance Matrix.

On my experiments this only changed a bit the results. And given
the fact that we should change the complete API, I guess it makes
no sense

* Compute source covariance for each iteration.

Altough this might look reasonable, it doesn't make sense to re-compute
the covariances matrix at each ICP iteration. Although the source cloud
is moving... so "it might" change...

* Add comments and fix mintor details

* Fix style

* Update CHANGELOG.md

* Fix missing sign. Epsilon must be 0.001

* Add covariance-point information to geometry::PointCloud type

This should be replaced in the future with the dictionary-based class.
For the moment, this allow us to cleanly update the covariance
information, both from the GeneralizedICP method and from the outside
world.

* Improve the GeneralizedICP initialization step

* Unused variables cleanup

* Cleanup a bit

* Add covariance data member to PointCloud class

* Add Variance propagation methods

* Propagate Covariance information to VoxelDownSample methods

* Update PointCloud pybindings

* Reuse CovarianceEstimate code for Normal Computation

* Add matrix3dvector pybind type to expose PointCloud::covariances_

* Expose HasCovariances() method to Python API

* Expose the PointCloud::EstimateCovariances() method to Python API

* Opaque the covariance type

* Sort things out

* Fix matrix3dvector pybind.

Eigen::Matrix3d is not a 16bits aligned type(like Matrix4d). Thus, we
can't use Eigen::aligned_allocator<T>, and therefore we have to override
the default template parameters. This fix the issue with
PointCloud::covariances_ python bindings. Now it's fully working

* Start migrating towards the new PointCloud API

* Quick implementation of the SVD

* Initialize with zeros not with identity

* Initialize with zeros not with identity

* Remove unused variable

* Add unit tests for new covariance member

* Covariance Matrix always positive-semi definitive

* Covariance Matrix always positive-semi definitive

* Simplify

* Clear temp covariances buffer for normal estimation.

If the user doesn't explicitly request to estimate the covariance
member shouldn't be populated

* Fix bug

* Still not working as expected

* Add GeneralizedICP back to build

* Add static method to compute covariances.

This allows a cleaner EstimateNormals implementation

* Make method static

* Revert log change

* Already checked insided RegistrationICP

* Fix some comments

* Bring branch closer to master

* Happy new year

* Expose epsilon parameter for GeneralizedICP

* Two changes in this branch

* Make it more similar to ColorizedICP

* Improve some comments

* Fix some docs:

* Add some tests

* Add missing unit-test (disabled for now)

* Improve a bit the comments

* Check for covariances instead of normals

* Simplify wrapper

* Prevent GeneralizedICP for running if no covariance are there

* Fix EstimateCovariance logic

This static method always requires to resize the output vector of
matrices

* OpenMP static schedule

* It's faster to use the fast-normal-computation rather than SVD

* Put back comment

* Add simple tutorial

* Improve a bit the tutorial

* Change titles

* Fix notebook style

* Fix unit tests

This actually works in gcc-9

* remove jupytor example temporarily

* add generalized ICP cpp example

* support/test generalized icp in reconstruction system

Co-authored-by: Wei Dong <weidong@andrew.cmu.edu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants