ffmpeg – Lossless compression of 16 bit RGB sequences; split into two 8 bit sequences?

I am trying to come up with better ways to losslessly compress 16 bit PNG sequences. I’ve tried the ffv1 codec in ffmpeg and found that it usually ends up producing a file size that is only slightly smaller than the png sequence itself (for example, reducing 6.38 GB down to 6.25 GB).

ffmpeg -i 16bpc_image%04d.png -c:v ffv1 -coder 1 -context 0 -g 1 -level 3 out_ffv1.mkv

I’ve also just tried using 7-zip with ultra compression and the largest dictionary size (3840m) and got something similar (6.23 GB).

I think the issue is the lack of interframe compression. I’ve had good success with libx264rgb on 8 bpc png sequences in the past, but the issue is that it doesn’t support a 16 bit pixel format.

I realized I can split the 16 bpc PNGs into two 8 bpc PNG sequences and try encoding each of them using libx264rgb; and sure enough, that actually gave me a good improvement:

ffmpeg -framerate 60 -i high_byte_image_%04d.png -c:v libx264rgb -qp 0 -preset veryslow highbyte.mp4
ffmpeg -framerate 60 -i low_byte_image_%04d.png -c:v libx264rgb -qp 0 -preset veryslow lowbyte.mp4 

The lowbyte .mp4 file is 3.80 GB and the highbyte is 0.99 GB, for a total of 4.79 GB, a pretty decent reduction compared to the PNG sequence, the ffv1 codec, and the 7-zip archive.

I created the “high_byte” and “low_byte” images using this Python function:

def split_16bpc_to_8bpc(image_16bpc):
    high_byte = (image_16bpc >> 8).astype(np.uint8)
    low_byte = (image_16bpc & 0xFF).astype(np.uint8)
    return high_byte, low_byte

where the argument is from cv2

image_16bpc = cv2.imread(f'16_bpc_input.png', cv2.IMREAD_UNCHANGED)

While the highbyte image looks and compresses similar to the way an 8bpc image would, I noticed the lowbyte image has a lot of rainbow RGB noise. Here’s an example comparison:

enter image description here

Obviously that noise is the reason the low byte compressed file is so much larger than the high byte version.

I suspect there may be a more clever way to approach this. Any ideas on what else I should try?

Read more here: Source link