I almost wasted an afternoon on this. While building the evaluation pipeline for my PSNR research , I had a nagging worry: some benchmark datasets shipped as BMP, but I was storing everything as PNG. If the format introduced even tiny pixel differences, my whole comparison framework was suspect.
So I checked.
import numpy as np
from PIL import Image
img_bmp = np.array(Image.open("reference.bmp"))
img_png = np.array(Image.open("reference.png"))
print(np.array_equal(img_bmp, img_png)) # True
Bit-for-bit identical. PNG uses lossless compression — the key word being lossless — so decoded pixel values are exactly what went in. Not approximately. Exactly.
I ran the full verification anyway (because I’m the kind of person who needs to see the numbers before I’ll stop worrying):
from skimage.metrics import peak_signal_noise_ratio, structural_similarity
noisy = np.clip(
img_bmp.astype(np.float32) + np.random.normal(0, 10, img_bmp.shape),
0, 255
).astype(np.uint8)
psnr_bmp = peak_signal_noise_ratio(img_bmp, noisy)
psnr_png = peak_signal_noise_ratio(img_png, noisy)
ssim_bmp = structural_similarity(img_bmp, noisy, channel_axis=2)
ssim_png = structural_similarity(img_png, noisy, channel_axis=2)
print(f"PSNR — BMP ref: {psnr_bmp:.4f}, PNG ref: {psnr_png:.4f}") # identical
print(f"SSIM — BMP ref: {ssim_bmp:.6f}, PNG ref: {ssim_png:.6f}") # identical
Every decimal place matches. Obviously. The arrays are the same data.
One thing worth flagging: this only holds for lossless formats (BMP, PNG, uncompressed TIFF). The moment you touch JPEG or any lossy codec, all bets are off. JPEG re-encoding introduces quantization artifacts, so saving and reloading the same image changes pixel values. I’ve seen researchers chase “model regressions” for days that turned out to be JPEG re-encoding noise.
Use PNG. Same pixels as BMP, fraction of the file size. And verify your boring assumptions before you build a pipeline on top of them — the five minutes it takes is cheaper than the rabbit hole it prevents.