-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bugfix in transmittance based early stop criteria #541
Conversation
There is a bug in your modification. The |
91c51e8
to
1dc06b3
Compare
@yzslab Sorry for late reply, and thank you for letting me know. I think you're right so I changed the PR accordingly. |
Note that the behaviour you are trying to fix is not a bug, but an intentional feature introduced by the original 3DGS authors to avoid numerical stability issues. With your changes, the final transmittance can be arbitrarily close to zero, causing numerical stability issues in the backward pass. From annex C of the original 3DGS paper: |
@lopeLH Hi, thank you for letting me know, I was not aware if it was intended! However, I've thought about all this and it looks like the only concern in terms of numerical stability with S ra = 1.0f / (1.0f - alpha);
T *= ra; I argue that it's not really a problem in current implementation because of these reasons:
S alpha = min(0.999f, opac * __expf(-sigma));
if (sigma < 0.f || alpha < 1.f / 255.f) {
continue;
}
S temp_T = (1e-4) * (1.0 - 0.999);
temp_T /= (1.0 - 0.999);
for (int l = 0; l < 2400; l++) {
temp_T /= (1.0 - 1.f / 255.f);
}
printf("%.10f\n", temp_T); In the current implementation Therefore, my conclusion is that even in the worst case scenario, the numerical stability regarding I feel like this PR is getting an overkill for such a negligible bug, but again, I still think it's a bug. If something is wrong with my logic, please let me know :) |
I am still not fully convinced about the proposed changes, for a number of reasons:
That said, I am not a maintainer of this library, so these are just my 2cents. |
Hi, I agree on all three points, especially the second point: as you mentioned, I overlooked the I also felt like this is getting deviated too much from the original implementation. As I mentioned earlier, I was not aware if it was intended in the original paper. But after knowing that, I still thought this feature (although intended) put too much constriant on the rendering process than what's required. However, as you pointed out, it was not too much. I still am not happy with the fact that in some cases, despite being very rare, the rendered image can actually be very different than what's expected. However, it turns out the fix for this is not that easy without further changing the current method. But thank you for sharing all these. Knowing this might actually be very helpful on what I was working on, and hopefully to others as well. |
I think there's a bug in terms of when to stop rendering if the transmittance gets near zero.
Intuitively, the stopping criteria checks if the transmittance for rendering "next Gaussian" is very low or not.
Therefore, it should render the "current Gaussian" and then break out of the loop.
However, the current implementation instead checks the criteria and stop immediately, without rendering the current one.
To be more specific, say there are only two Gaussians to be rendered for a pixel, where the first one to be rendered has alpha of 0.5, and the other one has alpha of 1.0. Obviously, because the second Gaussian has alpha of 1.0, the rendered alpha of the pixel should be 1.0.
However, the current implementation only renders the first one because while trying to render the second one, it detects that T will get near zero and doesn't render the second one, leaving the pixel's rendered alpha 0.5.
This can be verified by manually rendering two very bright Gaussians (with the second one being very large):
It renders this weird gray ball, while after applying this bugfix, it correctly renders white image.
If you cut and look at the middle section of the above image, it looks like this:
Here I made randomly initialized Gaussians to learn an all-white image. This behavior compromises stability of training, and it makes the 3DGS never be able to learn a truly saturated image. You can see this from the loss not being able to get reduced below a certain point. The output image has slightly different color because the current implementation struggles to render a truly white image.
If you discard the color values below 250 and map [250, 255] to be [0, 255] to see the effect better, you can clearly see the current implementation struggles because of the weird gray blobs.
Though I think this bug has minimal effect in practice, I still think it needs a fix ;)