I want to convert a 12bit image signal to HEVC for effective compression. Because I need to be able to reconstruct the original 12bit signal, the compression needs to be losslessly reversible. At the moment I have the data as 16-bit PNG files.
My first try was using ffmpeg:
ffmpeg -y -framerate 1 -i input.png -c:v libx265 -x265-params "lossless=1" output.mp4
Unfortunately the output is not reversible. When extracting the image from the mp4, the pixel values are slightly off.
ffmpeg -i output.mp4 -vframes 1 reconstructed.png
Following Answer suggest converting the input to YUV444 first to avoid unexpected behavior by ffmpeg: Lossless x264 compression
I have failed so far to successfully convert my 16bit file to YUV, convert it to x256 and receive a correct reconstruction when decoding.
Is there a straight forward way to convert 16bit images to HEVC?
I found a solution with minor rounding errors:
Encoding:
Based on the following post: How to render png's as h.265 12 bit video?
Use can use the following codec parameters:
-x265-params lossless=1 -pix_fmt yuv444p12le
for lossy 12 bpc encoding.By trial and error, I realized that the 12 bits data must be in the upper 12 bits of each 16 bits element. You need to scale up the input pixels by 16 for placing the data in the upper bits.
(Scaling by 16 is equivalent to left shifting the uint16 elements by 4).
For scaling pixels up you can use
colorlevels
video filter:-vf colorlevels=rimax=0.0625:gimax=0.0625:bimax=0.0625
The following command encodes a single frame:
Decoding:
(Dividing by 16 is equivalent to right shifting the uint16 elements by 4).
I couldn't find a solution using
colorlevels
, so I usedcurves
filter:-vf "curves=r='0/0 1.0/0.0625':g='0/0 1.0/0.0625':b='0/0 1.0/0.0625'"
rgb48be
.The following command decodes a single frame (and divide by 16):
Differences:
The maximum absolute difference between
input.png
andreconstructed.png
is4
levels.The reason for the difference is probably rounding errors caused by converting RGB to YUV and back.
I used the following MATLAB code for testing:
Update:
Working with Grayscale format:
When working Grayscale, you don't need to convert the pixel format to YUV.
Converting from Grayscale to YUV444 multiplies the size of input data by 3, so it's better to avoid the conversion.
The following command encodes a single Grayscale frame:
The following command decodes a single Grayscale frame (and divide by 16):
The maximum absolute difference is 2.
Note about using
-bsf:v hevc_metadata=video_full_range_flag=1
:In H.265, the default range of Y color channel is "limited range".
For 8 bits the "limited range" applies [16, 235].
For 12 bits the "limited range" applies [256, 3760].
When using "full range" [0, 255] for 8 bits or [0, 4095] for 12 bits, you need to specify it in the stream's Metadata.
The way do set the Metadata with FFmpeg is using a bitstream filter.