[FFmpeg-user] Lossless compression of 10bit grayscale images (stored as 16bit pngs)
Paul B Mahol
onemda at gmail.com
Thu Nov 4 09:10:28 EET 2021
On Wed, Nov 3, 2021 at 11:57 PM Tom Vercauteren <tom.vercauteren at gmail.com>
wrote:
> Hello,
>
> Apologies for cross-posting a question I initially posted on stackoverflow
> (
> https://stackoverflow.com/q/69739665/17261462) but having had no response
> there I thought this mailing list may be a better place for it.
>
> I am trying to encode 10 bit images losslessly in a video format. The
> images are stored as 16 bit png files (but only use 10 bit - currently the
> least significant ones) and I have been working with ffmpeg to create and
> read back the video files.
>
> My best attempt so far is based on
> https://stackoverflow.com/a/66180140/17261462 but as mentioned there, I
> get
> some pixel intensity differences which may be due to rounding when
> converting between 10 and 16 bit representation. I tried a few different
> means (bit shifting, left bit replication, floating point based scaling)
> but haven't yet figured out how to get a trully lossless reconstruction.
>
> Below is a small piece of python code to replicate my issue. I probably am
> doing something wrong there so feedback would be appreciated.
>
>
Upload input png somewhere?
I guess that png files use only first 10bits from least significant bit.
> ```python
>
> import subprocessimport numpy as npimport matplotlib.pyplot as
> pltimport tempfileimport imageio
> # Create simple image
> bitdepth = 10
> hbd = int(bitdepth/2)
> im0 = np.zeros((1<<hbd,1<<hbd),dtype=np.uint16)
> im0[:] =
> np.arange(0,1<<bitdepth).reshape(im0.shape)print('im0',np.min(im0),np.max(im0),im0.shape,im0.dtype)#
> tile it to be at least 64 pix
> im0 = np.tile(im0, (2,
> 2))print('im0',np.min(im0),np.max(im0),im0.shape,im0.dtype)
> im0ref = im0# bitshift it or rescale intensities#im0 = (im0<<6)#im0 =
> (im0<<6) + (im0>>4)
> im0 = np.uint16(np.round(im0 *
>
> np.float64((1<<16)-1)/np.float64((1<<10)-1)))print('im0',np.min(im0),np.max(im0),im0.shape,im0.dtype)
> # Save it as png
> tmp0 = tempfile.NamedTemporaryFile(suffix='.png',
> delete=False)print(f'Using tmp file: {tmp0.name}')
> imageio.imwrite(tmp0.name,im0)
> # Encode with ffmpeg
> tmp1 = tempfile.NamedTemporaryFile(suffix='.mkv', delete=False)# note
> that adding the following doesn't seem to impact the results # + '
> -bsf:v hevc_metadata=video_full_range_flag=1' \
> mycmd = f'ffmpeg -y -i {tmp0.name}' \
> + ' -c:v libx265 -x265-params lossless=1' \
> + ' -pix_fmt gray10be' \
> + f' {tmp1.name}'print(mycmd)
> p = subprocess.run(mycmd.split(), capture_output=True)print(
> 'stdout:', p.stdout.decode() )print( 'stderr:', p.stderr.decode() )
>
> tmp2 = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
> mycmd = f'ffmpeg -y -i {tmp1.name}' \
> + ' -pix_fmt gray16be' \
> + f' {tmp2.name}'print(mycmd)
> p = subprocess.run(mycmd.split(), capture_output=True)print(
> 'stdout:', p.stdout.decode() )print( 'stderr:', p.stderr.decode() )
> # Read back with ffmpeg
> im1 = imageio.imread(tmp2.name
> )print('im1',np.min(im1),np.max(im1),im1.shape,im1.dtype)
> # Bitshift or scale back
> im1pre = im1#im1 = (im1>>6)
> im1 = np.uint16(np.round(im1 *
> np.float64((1<<10)-1)/np.float64((1<<16)-1)))
> # check the result
> plt.figure()
> plt.imshow(im0ref)
> plt.colorbar()
>
> plt.figure()
> plt.imshow(im1)
> plt.colorbar()
>
> plt.figure()
> plt.imshow(np.int32(im1)-np.int32(im0ref))
> plt.colorbar()
> print('err: ',np.linalg.norm((np.float32(im1)-np.float32(im0ref)).ravel()))
>
> plt.show()
> ```
>
> Many thanks,
>
> Tom
> _______________________________________________
> ffmpeg-user mailing list
> ffmpeg-user at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-user
>
> To unsubscribe, visit link above, or email
> ffmpeg-user-request at ffmpeg.org with subject "unsubscribe".
>
More information about the ffmpeg-user
mailing list