【问题标题】:Rotating JPEGs in .NET with minimal loss of quality在 .NET 中以最小的质量损失旋转 JPEG
【发布时间】:2021-07-05 18:33:09
【问题描述】:

我正在尝试支持从 ASP.NET MVC 旋转 JPEG 图像(以 90 度为增量)。我正在尝试使用System.Drawing (GDI+),但是我遇到了问题。

我尝试使用Image.RotateFlip,它可以旋转图像但会导致质量下降。即使编码器质量为 100,旋转后的图像上仍然存在可见的伪影,这些伪影不在原始图像上,当我使用其他程序(Gimp 等)旋转它时也不会出现。

using (Image image = Image.FromFile("C:\\source.jpg")) {
    ImageFormat sourceFormat = image.RawFormat;
    image.RotateFlip(RotateFlipType.Rotate90FlipNone);
    EncoderParameters encoderParams = null;
    try {
        if (sourceFormat == ImageFormat.Jpeg) {
            encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
        }
        image.Save("C:\\target.jpg", GetEncoder(sourceFormat), encoderParams);
    } finally {
        if (encoderParams != null)
            encoderParams.Dispose();
    }
}

我在transforming a JPEG without loss of information 上找到了一篇文章。使用 Encoder.Transformation 似乎是 .NET 的一个选项 - 但是我无法让它导致我的任何 JPEG 测试图像旋转,无论尺寸是否是 16 的倍数。

using (Image image = Image.FromFile("C:\\source.jpg")) {
    ImageFormat sourceFormat = image.RawFormat;
    EncoderParameters encoderParams = null;
    try {
        if (sourceFormat == ImageFormat.Jpeg) {
            encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Transformation, 
                (long)EncoderValue.TransformRotate90);
        }
        image.Save("C:\\target.jpg", GetEncoder(sourceFormat), encoderParams);
    } finally {
        if (encoderParams != null)
            encoderParams.Dispose();
    }
}

有谁知道如何使用上述方法或其他方法成功地将 .NET 中的 JPEG 以 90 度增量旋转,而质量损失最小或没有损失?谢谢。

另外,这是我对GetEncoder 的实现:

private ImageCodecInfo GetEncoder(ImageFormat format) {
    foreach (var info in ImageCodecInfo.GetImageEncoders())
        if (info.FormatID == format.Guid)
            return info;
    return null;
}

编辑:

我更新了上面的代码以更好地匹配我的实际代码。该错误在以下行中:

if (sourceFormat == ImageFormat.Jpeg) {

应该是:

if (sourceFormat.Guid == ImageFormat.Jpeg.Guid) {

【问题讨论】:

  • 您的代码对我有效。您确定为您的数据返回 ImageCodecInfo 吗?
  • 谢谢@pb,我得到的是 ImageCodecInfo 但不是 encoderParams 因为我的实际代码在设置它之前有一个额外的检查,它有一个错误。

标签: .net image-processing jpeg


【解决方案1】:

感谢您确认我发布的代码有效。这帮助我隔离了我的问题。我现在觉得很傻。我的实际代码在设置 encoderParams 之前检查了图像格式 - 但它有一个错误:

if (sourceFormat == ImageFormat.Jpeg) {
    // set encoderParams here

我发现上面的条件总是假的,所以 encoderParams 没有被设置。修复很简单:

if (sourceFormat.Guid == ImageFormat.Jpeg.Guid) {

【讨论】:

    【解决方案2】:

    使用任何对有损压缩图像进行解压缩、旋转并再次有损压缩的方法(即使使用相同的参数),您都会损失质量。在有损解压缩过程中,您只能得到有损压缩图像的近似值(请记住,压缩是根据 PSNR 定义的,因此另一个实现可能会稍微不同地解压相同的图像)。重新压缩也是如此,除非您采用完全相同的有损压缩器和相同的参数,否则不可能重新压缩图像,从而得到相同的量化 DCT 值。

    JPEG 格式通过获取代表所有四个像素的平均颜色来以 2x2 像素的正方形压缩颜色信息,因此,如果您的图像宽度和高度可被 2 整除,您将损失更少的质量,因为在压缩中删除了大部分信息是在解压中插入的信息。

    同样,亮度信息被压缩为 8x8 像素的正方形,因此如果您的宽度和高度可被 8 整除,则网格将在旋转图像后对齐,您将丢失更少的实际信息。

    要进行无损旋转,您必须使用完全不同的方法,读取 JPEG 文件并重新排列和旋转每个信息(量化的 DCT 值),以便在不解压缩和重新压缩的情况下形成旋转图像(熵编码是一种可以安全往返的无损机制。

    【讨论】:

    • OP 特别要求无损变换和 90° 增量应用于 JPEG。所以很清楚如何做才能准确地保持输入质量。
    • @malat:是的,我同意这很清楚。这就是我在这个答案中写的。你有什么不同意的吗?
    • 这是我的写法,我相信这仍然不清楚使用 4:2:0 色度二次采样时何时可以实现旋转,用户可能会对您对 8x8 块参考的引用感到困惑.
    【解决方案3】:

    这是我对 Mike Henry 的回答的改编,适合我的需要,任何人都可能觉得它有用。

    public MemoryStream RotateImage(Stream stream, RotateFlipType rotationFlipType)
    {
        try
        {
            if (stream != null)
            {
                using (Image image = Image.FromStream(stream))
                {
                    ImageFormat sourceFormat = image.RawFormat;
                    image.RotateFlip(rotationFlipType);
                    EncoderParameters encoderParams = null;
                    try
                    {
                        if (sourceFormat.Guid == ImageFormat.Jpeg.Guid)
                        {
                            encoderParams = new EncoderParameters(1);
                            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
                        }
                        var ms = new MemoryStream();
                        image.Save(ms,
                            ImageCodecInfo.GetImageEncoders().FirstOrDefault(e => e.FormatID == sourceFormat.Guid),
                            encoderParams);
                        ms.Position = 0;
                        return ms;
                    }
                    finally
                    {
                        if (encoderParams != null)
                            encoderParams.Dispose();
                    }
                }
    
            }
        }
        finally
        {
            if (stream != null)
            {
                stream.Dispose();
            }
        }
        return null;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      • 2011-11-12
      • 2020-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多