https://stackoverflow.com/questions/63251089/how-to-do-a-loc...
https://stackoverflow.com/questions/65666507/local-contrast-...
One possible preprocessing step could be to do a high pass filter on it, if the shadows vary slowly over the image.
There are also more specialized techniques specifically for removing shadows from documents, like these:
http://civc.ucsb.edu/graphics/Papers/ACCV2016_DocShadow/
https://faculty.iiit.ac.in/~vgandhi/papers/shadow_removal_ca...
I also found this, an image editor based approach if you just want to do a few images manually:
https://janithl.github.io/2021/12/remove-shadows-and-uneven-...
btw: The iOS Notes app has quite a capable document scanning tool. It's cleverly hidden though.
From the Wikipedia article, "Otsu's method performs badly in case of heavy noise, small objects size, inhomogeneous lighting and larger intra-class than inter-class variance." (Emphasis mine.)
Right now my solution is at the stage of local thresholds with a configurable block size.
Thanks to your pointer, I know now that my next steps will be to review the Niblack or the Bernsen algorithms. (Or just integrate ImageJ.)
If you want to preserve aliasing and also color generally, I'm sadly not aware of any open source solution for that. Various scanner apps seem to do it with varying degrees of success; I'd be curious if there's a standard algorithm for it. It feels related to the de-curving algorithms that take a book page and make it flat. So you'd be modeling both the page curvature and black/white values simultaneously. Seems possible for general lighting/shadow, but wouldn't work for reflectivity from camera flash.
Glad I caught this post, I hope the solution can contribute to my problem (although I do not have a way to obtain a fixed ground truth — lighting will change for each picture.)