为什么 JPEG 压缩未压缩图像与其原始图像不同FFmpeg、NvJPEG 等

问题描述

我目前正在努力理解为什么重新压缩未压缩的 JPEG 图像与原始图像不同。

很明显,JPEG 是一种有损压缩,但如果要压缩的图像已经未压缩,这意味着所有采样损失都已包含在内怎么办?换句话说:此时下采样和 DCT 应该是可逆的,不会丢失数据。

JPEG algorithm

为确保色彩空间转换不会影响损失,跳过此步骤并使用 YUV 图像。

  1. 将 YUV 图像压缩为 JPEG (image.yuv --> image.yuv.jpg)
  2. 将JPEG图像解压为YUV图像(image.yuv.jpg --> image.yuv.jpg.yuv)
  3. 将 YUV 图像压缩为 JPEG (image.yuv.jpg.yuv --> image.yuv.jpg.yuv.jpg)
  4. 将JPEG图像解压为YUV图像(image.yuv.jpg.yuv.jpg --> image.yuv.jpg.yuv.jpg.yuv)立>

第 1 步包含有损压缩,因此我们将不再处理此步骤。对我来说,有趣的是之后发生的事情:

如果再次压缩(步骤 3),将 JPEG 图像解压缩回 YUV(步骤 2)会导致图像完全适合所有采样步骤。所以步骤 3 之后的 JPEG 图像(根据我的理解)应该与步骤 1 之后的完全相同。此外,步骤 4 和步骤 2 之后的 YUV 图像应该彼此相等。

查看一个 8x8 块的步骤,以下简化序列应该说明我想要描述的内容。让我们从原始的 YUV 图像开始,该图像只能解压缩丢失所有小数位:

[ 1.123,2.345,3.456,... ]    (YUV)
    DTC + Quantization
[ -26,-3,-6,... ]            (Quantized frequency space)
    Inverse DTC + Quantization
[ 1,2,3,... ]                (YUV)

使用已经匹配所有步骤的输入执行此操作,这可能会导致之后丢失数据(在我的示例中使用整数),解压后的图像应与其原始图像匹配:

[ 1,... ]                (YUV)
    DTC + Quantization
[ -26,... ]                (YUV)

还有一些来源和讨论,证实了我的想法:

理论到此为止。在实践中,我使用 ffmpeg 和 Nvidias jpeg samples(使用 NvJPEGEncoder)运行了这些步骤。

ffmpeg:

#Create YUV image
ffmpeg -y -i image.jpg -s 1920x1080 -pix_fmt yuv420p image.yuv
#YUV to JPEG
ffmpeg -y -s 1920x1080 -pix_fmt yuv420p -i image.yuv image.yuv.jpg
#JPEG TO YUV
ffmpeg -y -i image.yuv.jpg -s 1920x1080 -pix_fmt yuv420p image.yuv.jpg.yuv
#YUV to JPEG
ffmpeg -y -s 1920x1080 -pix_fmt yuv420p -i image.yuv.jpg.yuv image.yuv.jpg.yuv.jpg
#JPEG TO YUV
ffmpeg -y -i image.yuv.jpg.yuv.jpg -s 1920x1080 -pix_fmt yuv420p image.yuv.jpg.yuv.jpg.yuv
#YUV to JPEG
ffmpeg -y -s 1920x1080 -pix_fmt yuv420p -i image.yuv.jpg.yuv.jpg.yuv image.yuv.jpg.yuv.jpg.yuv.jpg

英伟达:

#Create YUV image
./jpeg_decode num_files 1 image.jpg image.yuv
#YUV to JPEG
./jpeg_encode image.yuv 1920 1080 image.yuv.jpg
#JPEG TO YUV
./jpeg_decode num_files 1 image.yuv.jpg image.yuv.jpg.yuv
#YUV to JPEG
./jpeg_encode image.yuv.jpg.yuv 1920 1080 image.yuv.jpg.yuv.jpg
#JPEG TO YUV
./jpeg_decode num_files 1 image.yuv.jpg.yuv.jpg image.yuv.jpg.yuv.jpg.yuv
#YUV to JPEG
./jpeg_encode image.yuv.jpg.yuv.jpg.yuv 1920 1080 image.yuv.jpg.yuv.jpg.yuv.jpg

图片对比

  • image.yuv.jpg.yuvimage.yuv.jpg.yuv.jpg.yuv
  • image.yuv.jpg.yuv.jpgimage.yuv.jpg.yuv.jpg.yuv.jpg

显示文件中的差异。这让我想到了我的问题差异发生的原因和地点,因为根据我的理解,文件应该是相等的。

解决方法

这是generation loss

FFmpeg 会将输入解码为未压缩的原始帧(除非您启用 stream copy 模式)。它对所有格式都这样做。来自 ffmpeg Documentation 的图表:

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
  • 不考虑编码数据,因为图像变成了一个简单的原始帧。
  • 编码伪影被视为原始帧的一部分。有损编码器的每一代都会损坏图像以尝试压缩并添加更多编码伪像。
  • 由于 ffmpeg 会在重新编码为 JPEG 之前自动将 JPEG 转换为原始 YUV,因此您可以跳过在实验中制作 YUV 文件。
  • 解决方案是避免使用有损编码器重新编码。