如何解决将 16 位灰度 PNG 转换为 HEVC/x265
我想将 12 位图像信号转换为 HEVC 以进行有效压缩。因为我需要能够重建原始的 12 位信号,所以压缩需要是无损可逆的。目前我的数据是 16 位 PNG 文件。
我的第一次尝试是使用 ffmpeg:
ffmpeg -y -framerate 1 -i input.png -c:v libx265 -x265-params "lossless=1" output.mp4
不幸的是,输出是不可逆的。从mp4中提取图像时,像素值略有偏差。
ffmpeg -i output.mp4 -vframes 1 reconstructed.png
以下答案建议先将输入转换为 YUV444 以避免 ffmpeg 的意外行为:Lossless x264 compression
到目前为止,我未能成功将我的 16 位文件转换为 YUV,将其转换为 x256 并在解码时收到正确的重建。
是否有直接的方法将 16 位图像转换为 HEVC?
解决方法
我找到了一个带有轻微舍入错误的解决方案:
编码:
-
基于以下帖子:How to render png's as h.265 12 bit video?
使用可以使用以下编解码器参数:-x265-params lossless=1 -pix_fmt yuv444p12le
用于有损 12 bpc 编码。 -
通过反复试验,我意识到 12 位数据必须位于每个 16 位元素的高 12 位。 您需要将输入像素放大 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
过滤器:-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
和 reconstructed.png
之间的最大绝对差为 4
级。
产生差异的原因可能是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.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
更新:
使用灰度格式:
在工作灰度时,您不需要将像素格式转换为 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]。
当使用 "full range" [0,255] 表示 8 位或 [0,4095] 表示 12 位时,您需要在流的元数据中指定它。
使用 FFmpeg 设置元数据的方法是使用 bitstream filter。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。