Two little words - rolling shutter.
https://picamera.readthedocs.io/en/late ... osure-time is a pretty good explanation.
You don't say which camera version, resolution, or sensor mode you're using, so I'll take the V2 camera in mode 5 (1640x922) as an example.
The sensor starts exposing line 1 at time t=0.
In that mode each line takes 18.9us to read out, so line 2 starts exposing at t=18.9us.
At t=5ms the sensor has started exposure on line 264, and starts reading out line 1.
At t=10ms (the flicker period), line 529 starts exposing, so it will have the same illumination as for line 0.
(At t=17.4ms the last line has started exposing, so at t=17.4+5 = 22.4ms the sensor has read out the whole frame and will be adding blanking lines until t=40ms to achieve your 25fps.)
So in that example I would expect 1.7 bands to be visible in the output image. The line length (in usecs) and frame length (in lines) varies between modes, so the number/height of the bands will vary.
The intensity variation will depend on the exposure time as a percentage of the flicker period. In your example, the worst you could achieve would be from -2.5ms to +2.5ms around the mains zero crossing point. Assuming a sinusoidal illumination pattern (not true for fluorescent lights), that would have a maximum intenity of sine(45) = 0.707 of the peak intensity. If you shorten the exposure time to 1ms, then maximum would be sine(9) = 0.156 the peak intensity.
That is why I said that if your exposure time is less than the flicker period then there is little you can do.
(Global shutter cameras would at least make each frame uniformly illuminated, but then the frame rate has to be a multiple of the flicker period to avoid variation between frames. They're also generally lower resolution, significantly more expensive, and there isn't one that works with the Pi firmware).
Software Engineer at Raspberry Pi Trading. Views expressed are still personal views.
I'm not interested in doing contracts for bespoke functionality - please don't ask.