Image Processing: Flatten a 3D Ball There are many descriptions of how to turn a 2D image into a 3D one, however I want to do the opposite, in particular to a ball. As an example, consider the following ball:



If only the (relatively) slow change in intensity due to the light could be removed, the ball would look 2D. 

My first approach was to try get a low frequency version of the original, and divide through by that image. A simple way to do that is to blur the image. Here is a sample result using that approach:



This does the job of flattening the light, but there are some problems:


There is a light 'halo' around sharp edges.
Large flat patches (e.g. the black patches) lose their uniformity in the middle.
The edges of the ball are affected by the background during the blurring (there's a dark ring around the ball, particularly noticeable at the bottom right corner).


I've tried mucking about with filters (e.g. bilinear) but haven't really found anything that works. I've also tried to use wavelets (a frequency based approach makes sense to me), but those approaches tended to distort the fine detail, which is very important.

Any ideas on how to do this are greatly appreciated!
My best attempt at this so far is kind of inspired by the difference of Gaussians. I'm putting it as an answer, even though I'm not totally happy with it.

Basically I made a blurred version of the image using two different Gaussian kernels. One captures the fine detail (small kernel) and the other captures the broad detail (large kernel). These two were then merged. This new blurred image is then subtracted from the original.

UPDATE: Made the process iterative, added contrast equalisation and changed blur divide to blur subtract.



output_image = get_input_image()

for i in xrange(4):
    # Magic numbers
    sigma_broad = 301
    sigma_fine  = 5
    merge_ratio = 0.5

    # Get blur
    broad_detail = cv2.GaussianBlur(output_image, (sigma_broad, sigma_broad), 0)
    fine_detail  = cv2.GaussianBlur(output_image, (sigma_fine, sigma_fine), 0)
    blurred = merge_ratio*broad_detail + (1-merge_ratio)*fine_detail

    # Subtract blur from original (and correct for average drop in intensity)
    output_image = (output_image + np.mean(blurred)) - blurred

# Contrast equalisation
clahe = cv2.createCLAHE(clipLimit=1.0, tileGridSize=(4,4))
output_image = clahe.apply(output_image.astype(np.uint8))


Result:



The reasons that I'm not totally satisfied with this result:


The information I want is altered. The objects in the ball (e.g. the dark patches) are disproportionally reduced.
There are some artefacts.
There are lots of magic numbers and parameters to fiddle with. Ideally the algorithm should be robust for a variety of balls.


ANOTHER UPDATE:
I had a go at a Laplacian Pyramid filter, which I adapted from here. I got decent results, posting mainly for interest. This strategy allows for targeting frequency bands, but it's very fiddly.