FFmpeg无法识别每个32位的3个通道。

3
我正在使用FFmpeg将游戏的线性深度缓冲区写入openEXR。不幸的是,FFmpeg没有完全遵守openEXR文件规范(例如允许一个通道使用无符号整数),因此我将一个浮点通道写入openEXR,并使用以下命令将其放入绿色通道:-f rawvideo -pix_fmt grayf32be -s %WIDTH%x%HEIGHT% -r %FPS% -i - -vf %DEFVF% -preset ultrafast -tune zerolatency -qp 6 -compression zip1 -pix_fmt gbrpf32le %NAME%_depth_%d.exr
浮点范围为0F到1F,并且是线性的。通过在Blender合成器中测试16位整数(每像素组件),我可以确认计算和线性化是正确的。对于16位整数数据,写入方式如下:short s = (short) (linearzieDepth(depth) * (Math.pow(2,16) - 1))。而对于浮点数,线性化值直接写入OpenEXR而不乘以任何值。
然而,在查看openEXR文件时,它与16位png文件的“渐变”不同...并且在并排查看它们时,似乎接近0的值不是线性的,并且它们不像16位png那样暗。 (是的,我将图像节点设置为线性),并且与游戏的3D跟踪数据进行比较,我无法重现深度并且无法使用深度缓冲区屏蔽物体,但是使用png文件则可以。
在图像中,线性浮点范围如何变得与线性整数范围如此不同呢?
更新:
我现在使用以下代码向FFmpeg写入3个通道。
float f2 = this.linearizeDepth(depth);

buffer.putFloat(f2);
buffer.putFloat(0);
buffer.putFloat(0);

字节缓冲区的大小为width * height * 3 * 4,即每个通道占用4个字节的3个通道。现在的命令是-f rawvideo -pix_fmt gbrpf32be -s %WIDTH%x%HEIGHT% -r %FPS% -i - -vf %DEFVF% -preset ultrafast -tune zerolatency -qp 6 -compression zip1 -pix_fmt gbrpf32le %NAME%_depth_%d.exr,这意味着输入(字节缓冲区)期望使用32位浮点数和3个通道。This is how it turns out FFmpeg似乎正在分离通道或其他操作...可能是一个错误,也可能是我的问题?

你确定linearizeDepth是大端字节序吗?你确定三个颜色通道被写入缓冲区时是按顺序一个接一个地写成三个平面(绿色平面宽度高度4字节,然后蓝色平面宽度高度4字节,最后红色平面宽度高度4字节)?你用什么查看器来显示exr图像? - Rotem
是的,我正要写那个,昨天晚上我设法解决了它,但还没有时间在这里写下来。我发现 openEXR 把 R G B 通道保存为单独的“图像”,可以这样说...我是通过测试找到的,但在 openEXR 的文件规格说明中无法很好地解释。 - Chryfi
1个回答

2
问题在于从 grayf32be 转换到 gbrpf32le 的颜色转换。

假设源像素范围为 [0, 1],我们可以在将像素格式转换为 gbrpf32le 之前添加格式转换过滤器: -vf format=rgb48le

另外,看起来FFmpeg忽略了范围参数,解决方法是添加比例尺过滤器:scale=in_range=full:out_range=full

更新的命令:

ffmpeg -y -f rawvideo -pix_fmt grayf32be -src_range 1 -s 192x108 -i in.raw -vf "scale=in_range=full:out_range=full,format=rgb48le" -vcodec exr -compression zip1 -pix_fmt gbrpf32le -dst_range 1 out.exr

可重现的例子:
  • Create 16 bits Tiff image (used as reference):

     ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -pix_fmt gray16le in.tif
    
  • Convert the Tiff to float (big endian):

     ffmpeg -y -src_range 1 -i in.tif -pix_fmt grayf32be -dst_range 1 -f rawvideo in.raw
    
  • Convert from raw to OpenEXR format:

     ffmpeg -y -f rawvideo -pix_fmt grayf32be -src_range 1 -s 192x108 -i in.raw -vf "scale=in_range=full:out_range=full,format=rgb48le" -vcodec exr -compression zip1 -pix_fmt gbrpf32le -dst_range 1 out.exr
    

用于比较差异的Python代码:

img1 = cv2.imread('in.tif', cv2.IMREAD_UNCHANGED)
img2 = cv2.imread('out.exr', cv2.IMREAD_UNCHANGED)

green_ch = img2[:, :, 1]  # Green channel

max_abs_diff = np.max(np.abs(green_ch*65535 - img1.astype(float)))

最大差异为3(65535级别之一)。
我们可能需要对过滤器参数进行微调...
由于FFmpeg颜色转换和范围转换存在问题(看起来是这样),因此在问题得到解决之前,您可能无法获得所需的结果。

更新:

当输入的像素格式为grayf32be(三个颜色通道平面格式)时,似乎它正在工作。

测试:

  • Create 16 bits Tiff image (used as reference):

     ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -pix_fmt gray16le in.tif
    
  • Convert the Tiff to float (big endian):

     ffmpeg -y -src_range 1 -i in.tif -pix_fmt grayf32be -dst_range 1 -f rawvideo in.raw
    
  • Duplicate the "Grayscale plane" three times for getting 3 identical color planes (using "concat protocol" for avoiding any color conversion issues):

     ffmpeg -y -f rawvideo -pix_fmt grayf32be -s 192x108 -i "concat:in.raw|in.raw|in.raw" -f rawvideo in3.raw
    
  • Convert from 3 color channels raw to OpenEXR format:

     ffmpeg -y -f rawvideo -pix_fmt gbrpf32be -s 192x108 -i in3.raw -vcodec exr -compression zip1 -pix_fmt gbrpf32le out.exr
    

比较差异的Python代码(比较3个颜色通道):

img1 = cv2.imread('in.tif', cv2.IMREAD_UNCHANGED)
img2 = cv2.imread('out.exr', cv2.IMREAD_UNCHANGED)

blue_ch = img2[:, :, 0]  # Blue channel
green_ch = img2[:, :, 1]  # Green channel
red_ch = img2[:, :, 2]  # Red channel

max_red_abs_diff = np.max(np.abs(red_ch*65535 - img1.astype(float)))
max_green_abs_diff = np.max(np.abs(green_ch*65535 - img1.astype(float)))
max_blue_abs_diff = np.max(np.abs(blue_ch*65535 - img1.astype(float)))

最大差异为0.001953125(可以忽略不计)。

但是将格式转换为rgb48le时,会将32位缩小到16位吗?那么我们不会失去信息吗?一旦它被缩小为16位,即使再次扩展到32位,另外的16位也已经丢失了,对吗? - Chryfi
1
我无法修复FFmpeg... 如果16位不够,请尝试创建原始的gbrpf32le输入而不进行像素格式转换。考虑使用OpenCV代替。 - Rotem
我会再尝试传递3个通道,之前的所有测试都导致图像损坏。16位已经很酷了,但保存深度缓冲区的重点是精度,而openGL深度缓冲区已经是32位浮点数了。我还会看看openCV,也许它有所帮助。自从以来,FFmpeg一直在困扰我,特别是因为它没有正确遵守openEXR文件规范。 - Chryfi
我已更新问题并附上了我的新测试结果...现在我通过了3个通道,但是即使声明gbrpf32le作为输入像素格式,ffmpeg仍然决定将它们拆分开来... - Chryfi
我尝试了以下几种方法:1. ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -pix_fmt gray16le in.tif 2. ffmpeg -y -src_range 1 -i in.tif -pix_fmt grayf32le -dst_range 1 -f rawvideo in.raw 3. ffmpeg -y -f rawvideo -pix_fmt grayf32le -s 192x108 -i "concat:in.raw|in.raw|in.raw" -f rawvideo in3.raw 4. ffmpeg -y -f rawvideo -pix_fmt gbrpf32le -s 192x108 -i in3.raw -vcodec exr -compression zip1 -pix_fmt gbrpf32le out.exr FFmpeg存在一个错误——即使没有像素格式转换,浮点值也会被修改(in.raw|in.raw|in.raw重复3次)。 - Rotem
重新检查后,当输入的像素格式为“grayf32be”时,看起来它正在工作。我更新了我的答案。 - Rotem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接