Optimize distance computations for cylinder - #6337
Conversation
44eeaa7 to
4f1d1c9
Compare
4f1d1c9 to
07cbec9
Compare
|
Hi @akretz Looks good - I have ventured a bit further and opened a PR against yours on your fork. See akretz#1. @mvieth it seems that the normals provided for the test is not correctly normalized - see Should we update them to be correctly normalized or should we let them stay as is - there could be removed some further normalizing in the various methods, but it would reduce robustness of the algorithm, with respect to handling ill-formed normals? |
|
I had a look at your proposed logic, and I think there might be an even more straightforward approach: I think it would be fastest to just compute the vector from the cylinder axis to the point via this formula: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation If I did not make a mistake, it would be Also a heads-up: the code with the normals and the angle might not be covered by a unit test because |
Thanks for that! Your suggestion is a much more straightforward way to do that computation. I have modified my PR accordingly.
Good point! I have added checks for these functions in the test to verify that nothing breaks. I've had to change a test normal, because the angles between I have also cherry-picked @larshg's useful benchmark. |
This was also one of the optimizations that ChatGPT found 😄 Maybe it could be updated in: pcl/common/include/pcl/common/distances.h Line 75 in 96e9621 Even though the current implementation works if line_dir is not normalized, which is the case in the test: pcl/test/common/test_common.cpp Line 141 in 96e9621 The sqrPointToLineDistance is used in the cylinder and cone in doSamplesVerifyModel, through an intermediate local method, which seems superfluous? |
|
So a question again arise, should one assume that a direction is normalized? Its seems so in the cylinder model coefficients as it gets normalized, but should or should we not add that assumption in general - and update the documentation accordingly? |
I think there it would not bring any benefit: one cross product needs 6 multiplications and 3 subtractions (plus 3 subtractions for
Not sure if I understand you correctly - the direction of the cylinder axis gets normalized (e.g. in countWithinDistance), so there is no assumption that the user passed in model coefficients with the direction already normalized. I would keep it that way since it is more robust, and normalizing the axis direction once costs very little compared to the rest of the function. However I also do not really see the need to explicitly say in the documentation that the direction does not have to be normalized? I works either way. |
311a967 to
3e99e19
Compare
mvieth
left a comment
There was a problem hiding this comment.
Two minor suggestions, otherwise looks good to me. Thank you!
I have splitted the operations and I get this count - so its less operations? And I think ie. a dot product can fit into a FMA operations, which could result in faster code? But it requires the line_dir to be normalized which would increase the number of operations, unless it is done outside a loop etc. It would also be possible to calculate 1 / dir_len_sq before entering a loop (for many points), as long as the line_dir doesn't change. Then one could remove the div operation and save a squaredNorm calculation, ie 3 mul and 2 adds also? But that would require some more changes to the api - or not use this api, but do the calculations in cylinder file.
I was generally taking about the sqrPointToLineDistance function in common. It doesn't tell in the docs if the line_dir should be normalized. But if we change to the dot vector calculation, it has to be (or it would be required to do it in the method, but that would probably end up being slower than the cross product version. Hope that makes more sense 😄 |
Co-authored-by: Markus Vieth <39675748+mvieth@users.noreply.github.com>
I think you are right and I'm counting the same numbers of operations. You are using Lagrange's identity here. The expression I have eventually implemented in this PR is a different one, because we not only need the distance but also the direction vector. So I have done something like this to get the distance, where we get the direction vector for free inside the norm: All three expressions should be mathematically equivalent. Not sure about numerical stability though. AI told me the cross product is most numerically stable and the expression using Lagrange's identity is the least numerically stable, because when both vectors are almost parallel, both operands of the subtraction almost cancel out. This PR escalated quite a bit. That wasn't my intention, sorry 🫠 |
|
Ahhh. I thought I was using the formula from @mvieth link. Ie. like this: But I think I got ChatGPT to come up with an optimization, instead of writing my self, which ended up in a new way to do it. So I guess there are 3 ways to do it.
Don't be - its nice to have a good look and eventual find some optimizations 👍 And I got some math refreshed! |
mvieth
left a comment
There was a problem hiding this comment.
@akretz Thanks!
@larshg Perhaps the optimization of sqrPointToLineDistance is something we should discuss in a separate PR, since it is not directly related to the changes here? I think we need at least a benchmark for that, and also a deeper look into the numerical stability.
Also just in case you are not aware: there are two sqrPointToLineDistance functions, one takes the squared length of the line direction to avoid one squaredNorm()
Yes, to both. It just seems to only be used in cylinder and cone, so kinda related, but it can be looked at in another PR 😄 |
|
@akretz can you update the timings in top message? |
I have optimized the running time of the cylinder sampling consensus algorithm. The bottleneck is the distance computation which finds inliers.
For the computation of the vector
dirfrom the cylinder axis to the point, we can reduce the number of operations. We computeline_dir.cross3 (pt - line_pt)insidepointToLineDistance ()and then throw it away. But keeping that cross product and then computing the cross product of that vector andline_dirgives us the vector we are looking for.I have benchmarked these modifications on the example and had it run for 100,000 RANSAC iterations on a single core while removing the early return. The benchmarks on two different platforms with gcc and clang yield speedups of up to 2.6 (all built with
-DCMAKE_BUILD_TYPE=MinSizeRel).