我找到了一个具有较小舍入误差的解决方案:
编码:
-
基于以下帖子:如何将 png 渲染为 h.265 12 位视频? https://video.stackexchange.com/questions/25248/how-to-render-pngs-as-h-265-12-bit-video
使用时可以使用以下编解码器参数:-x265-params lossless=1 -pix_fmt yuv444p12le
用于有损 12 bpc 编码。
-
通过反复试验,我意识到12位数据必须在高 12 位每个 16 位元素。
您需要将输入像素放大 16 倍才能将数据放入高位。
(缩放 16 相当于将 uint16 元素左移 4)。
要放大像素,您可以使用colorlevels
视频过滤器:
-vf colorlevels=rimax=0.0625:gimax=0.0625:bimax=0.0625
以下命令对单个帧进行编码:
ffmpeg -i input.png -vf colorlevels=rimax=0.0625:gimax=0.0625:bimax=0.0625 -c:v libx265 -x265-params lossless=1 -pix_fmt yuv444p12le output.mkv
解码:
- 为了进行解码,您需要将像素除以 16,以便将数据放入较低的 12 位中。
(除以 16 相当于将 uint16 元素右移 4)。
我找不到使用的解决方案colorlevels
,所以我用了curves
filter:
-vf "curves=r='0/0 1.0/0.0625':g='0/0 1.0/0.0625':b='0/0 1.0/0.0625'"
- 16 位 PNG 的合适像素格式是
rgb48be
.
以下命令解码单个帧(并除以 16):
ffmpeg -i output.mkv -vf "curves=r='0/0 1.0/0.0625':g='0/0 1.0/0.0625':b='0/0 1.0/0.0625'" -pix_fmt rgb48be reconstructed.png
差异:
之间的最大绝对差input.png
and reconstructed.png
is 4
levels.
造成差异的原因可能是 RGB 与 YUV 相互转换造成的舍入误差。
我使用以下 MATLAB 代码进行测试:
I = imread('peppers.png');
% Build 10 PNG images (used as input).
for i = 1:10
J = insertText(I, [size(I,2)/2-18, size(I,1)/2-36], num2str(i), 'FontSize', 72);
J = imnoise(im2double(J), 'gaussian', 0, 0.01); % Add some noise
J = uint16(round(J*4095)); % Convert to 12 bits range (range [0, 4095])
imwrite(J, sprintf('input%02d.png', i), 'fmt', 'png', 'BitDepth', 16, 'Mode', 'lossless'); % Write to PNG file
end
%Encode video file using x265 codec, and 12 bits YUV444 format.
[status, cmdout] = system('ffmpeg -y -i input%02d.png -vf colorlevels=rimax=0.0625:gimax=0.0625:bimax=0.0625 -c:v libx265 -x265-params lossless=1 -pix_fmt yuv444p12le output.mkv');
if (status ~= 0), disp(cmdout);end
% Decode output.mkv into 10 PNG image files
[status, cmdout] = system('ffmpeg -y -i output.mkv -vf "curves=r=''0/0 1.0/0.0625'':g=''0/0 1.0/0.0625'':b=''0/0 1.0/0.0625''" -pix_fmt rgb48be reconstructed%02d.png');
if (status ~= 0), disp(cmdout);end
% Compare input and output:
for i = 1:10
I = imread(sprintf('input%02d.png', i));
J = imread(sprintf('reconstructed%02d.png', i));
max_abs_diff = max(max(max(imabsdiff(I, J))));
disp(['max_abs_diff = ', num2str(max_abs_diff)]);
end
Update:
与...一起工作灰度 format:
使用灰度时,不需要将像素格式转换为 YUV。
从灰度转换为 YUV444 会将输入数据的大小乘以 3,因此最好避免转换。
以下命令对单个灰度帧进行编码:
ffmpeg -i input.png -vf "curves=all='0/0 0.0625/1.0'" -c:v libx265 -x265-params lossless=1 -pix_fmt gray12le -bsf:v hevc_metadata=video_full_range_flag=1 output.mkv
以下命令解码单个灰度帧(并除以 16):
ffmpeg -i output.mkv -vf "curves=all='0/0 1.0/0.0625'" -pix_fmt gray16be reconstructed.png
最大绝对差为 2。
使用注意事项-bsf:v hevc_metadata=video_full_range_flag=1
:
在H.265中,Y颜色通道的默认范围是“有限范围”。
对于 8 位,“有限范围”适用 [16, 235]。
对于 12 位,“有限范围”适用 [256, 3760]。
当8位使用“全范围”[0, 255]或12位使用[0, 4095]时,您需要在流的元数据中指定它。
使用 FFmpeg 设置元数据的方法是使用比特流过滤器 https://www.ffmpeg.org/ffmpeg-bitstream-filters.html.