Ah, cool! That’s really useful to know.
As for literature, if you google “Flat Field Correction” you’ll find a ton of articles/blog posts from image manipulation products (Adobe, National Instruments, FLIR) singing its praises, but for its technical application I was just going off the formula found on wikipedia: Flat-field correction - Wikipedia
It’s a technique for correcting vignetting (where an image becomes darker the further from the image center you go). It’s really useful for the wide angle lens.
For the OpenMV boards, the “Dark Frame” subtraction isn’t super necessary since the image formats use integers, so there isn’t much low-level noise to remove. I’m basically just doing C = R * (m/F), where m/F is part of a calibration process that gets saved out for later.
You do need a reference Flat Field (calibration) frame, but I think the likes of Photoshop take in a file path to the image you want to correct and a path to its corresponding calibration frame.
You could add an instance method to the Image class like this:
def Image.flat_fielded(ff: image.Image, overwrite: bool = False) -> image.Image
m = ff.get_statistics().mean()
out = self * m / ff # whatever this is in c
Where ff would be the calibration image. It would return a new image if overwrite = False, but write to the buffer used by the image instance if overwrite = True. The option to overwrite would be useful since it may not be necessary to keep the raw image around (in my use case I don’t care about it).
Though matlab has an implementation that somehow estimates the shading in the given image and does not need a reference flat field image
imflatfield - 2-D image flat-field correction - MATLAB
As for how it’s not garbage, it actually is! If you’re only using integer math. I have experimented with saving out m/F as a grayscale bitmap with quality set to 100, and when multiplying a snapshot by that saved image the product looks terrible. There’s something about using 32bit-float ndarrays that gives much better results even though, when converted to an image, the product is truncated back to uint8.
So this works really well (R and F are images converted to float ndarrays):
int(float(R) * float(m) / float(F))
And this is garbage (R and F are converted to uint8 ndarrays):
R * m / F => int * int / int
You could definitely get a good result using 16-bit floating point numbers internally (32 bit is overkill I think). I see there are float16 declarations throughout the openmv repo, so it may be pretty easy to implement and get decent results out? Furthermore, I think this is equivalent to what I’m doing in the first pseudo-equation up there:
R * int(float(m) / float(F))
I just convert R to a float ndarray so I can have access to in-place multiplication: R *= m/F