为什么ffmpeg';s转换到YUV420这么差?

为什么ffmpeg';s转换到YUV420这么差?,ffmpeg,Ffmpeg,我一直在使用ffmpeg和其他压缩工具来比较YUV420重采样视频的率失真曲线。 在这些比较中,来自ffmpeg的结果总是更差,峰值信噪比值低0.5-1.0 dB 我跟踪了问题到ffmpeg在RGB和YUV420之间的转换。 为了简化,让我们假设“无损压缩”,因此只考虑RGB-> YUV420-> RGB。 此外,我们对单个PNG图像帧进行操作 # Use some default options. ffmpeg="ffmpeg -nostdin -hide_banner -v err

我一直在使用
ffmpeg
和其他压缩工具来比较YUV420重采样视频的率失真曲线。 在这些比较中,来自
ffmpeg
的结果总是更差,峰值信噪比值低0.5-1.0 dB

我跟踪了问题到
ffmpeg
在RGB和YUV420之间的转换。 为了简化,让我们假设“无损压缩”,因此只考虑RGB-> YUV420-> RGB。 此外,我们对单个PNG图像帧进行操作

# Use some default options.
ffmpeg="ffmpeg -nostdin -hide_banner -v error"

# Obtain a source image.
wget -nv -O original.png https://i.stack.imgur.com/8J1qY.png
size="256x256"

# Compare it with itself to verify that we get an infinite average PSNR.
$ffmpeg -v info -i original.png -i original.png -lavfi psnr -f null - |& grep PSNR
# average:inf

# Convert the image to YUV420, and convert back to RGB.
$ffmpeg -i original.png -pix_fmt yuv420p -f rawvideo -y temp1.yuv420
$ffmpeg -f rawvideo -s $size -pix_fmt yuv420p -i temp1.yuv420 -y result1.png

# Compare it with the original image to measure the PSNR (in dB).
$ffmpeg -v info -i result1.png -i original.png -lavfi psnr -f null - |& grep PSNR
# average:36.894551
现在,作为替代方案,我们手动执行RGB YUV420色度重采样:

yuv444_to_yuv420="extractplanes=y+u+v[y][u][v];\
  [u]scale=w=iw/2:h=ih/2:flags=area[u];\
  [v]scale=w=iw/2:h=ih/2:flags=area[v];\
  [y][u][v]mergeplanes=0x001020:yuv420p"
yuv420_to_rgb="extractplanes=y+u+v[y][u][v];\
  [u]scale=w=iw*2:h=ih*2:flags=neighbor[u];\
  [v]scale=w=iw*2:h=ih*2:flags=neighbor[v];\
  [y][u][v]mergeplanes=0x001020:yuv444p,format=rgb24"

$ffmpeg -i original.png -pix_fmt yuv444p -f rawvideo - | \
  $ffmpeg -f rawvideo -pix_fmt yuv444p -s $size -i - \
    -lavfi "$yuv444_to_yuv420" -f rawvideo -y temp2.yuv420
$ffmpeg -f rawvideo -pix_fmt yuv420p -s $size -i temp2.yuv420 \
  -lavfi "$yuv420_to_rgb" -y result2.png

# Measure PSNR by comparing with the original image.
$ffmpeg -v info -i result2.png -i original.png -lavfi psnr -f null - |& grep PSNR
# average:37.536444
# This is an improvement of 0.64 dB!
这带来了两个问题:

  • 为什么默认情况下,
    ffmpeg
    不实现与
    yuv420p
    的更好转换
  • 有没有更简单的方法来获得或表达这种改进的转换

  • 经过实验,我确实找到了两个解决办法:

    (1) 参数
    -sws_flags
    设置过滤器图中所有隐式引入的
    swscale
    过滤器的参数。 这些过滤器似乎也负责YUV420转换。 使用精心选择的过滤器标志,它可以:

    $ffmpeg -i original.png -sws_flags 'area+accurate_rnd+full_chroma_int' \
      -pix_fmt yuv420p -f rawvideo -y temp1.yuv420
    
    $ffmpeg -f rawvideo -s $size -pix_fmt yuv420p -i temp1.yuv420 \
      -sws_flags 'neighbor+accurate_rnd+full_chroma_int' -y result1.png
    
    $ffmpeg -v info -i result1.png -i original.png -lavfi psnr -f null - |& grep PSNR
    # average:37.567842
    
    (2) 还可以将过滤选项指定为
    scale
    过滤器(默认为100%比例)的参数,该过滤器根据下一个图形节点请求的输入格式执行
    rgb24->yuv420p
    yuv420p->rgb24
    格式转换:

    $ffmpeg -v info -i original.png \
      -lavfi 'scale=flags=area+accurate_rnd+full_chroma_int,format=yuv420p' \
      -f rawvideo -y temp1.yuv420
    
    $ffmpeg -v info -f rawvideo -s $size -pix_fmt yuv420p -i temp1.yuv420 \
      -lavfi 'scale=flags=neighbor+accurate_rnd+full_chroma_int' -y result1.png
    
    $ffmpeg -v info -i result1.png -i original.png -lavfi psnr -f null - |& grep PSNR
    # average:37.567842
    
    不幸的是,这种行为不是默认的,但至少有一种半方便的方式来访问它