【问题标题】:Graphics.DrawImage generates some 'noise', a keyline near image edgeGraphics.DrawImage 会产生一些“噪声”,即图像边缘附近的关键线
【发布时间】:2017-01-20 22:54:51
【问题描述】:

我有一项服务可以根据需要调整/裁剪图像的大小,方法是加载完整大小的文件并对其进行裁剪,将其大小调整为要求的尺寸和要求的质量。

public static byte[] Resize(Image sourceImage, int? targetWidth, int? targetHeight, int quality);

但是,我面临的问题是,对于某些尺寸,输出图像包含一些“噪声”,其形式为沿边缘的一些微弱的关键线。 您可以在此处查看图像边缘应如何显示的示例

这里实际上是如何返回

它只为某些维度添加一个keyline,误差是一致的,并且与通过的质量(1-100)无关。

以下是调整大小的代码,这里也是一个简单的游乐场https://github.com/gromag/ImageResizeTest

有什么建议吗?

using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace grom.lib.graphics
{
    public class ImageResizer
    {
    /// <summary>
    /// A quick lookup for getting image encoders
    /// </summary>
    private static Dictionary<string, ImageCodecInfo> encoders = null;

    /// <summary>
    /// A quick lookup for getting image encoders
    /// </summary>
    public static Dictionary<string, ImageCodecInfo> Encoders
    {
        //get accessor that creates the dictionary on demand
        get
        {
            //if the quick lookup isn't initialised, initialise it
            if (encoders == null)
            {
                encoders = new Dictionary<string, ImageCodecInfo>();
            }

            //if there are no codecs, try loading them
            if (encoders.Count == 0)
            {
                //get all the codecs
                foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
                {
                    //add each codec to the quick lookup
                    encoders.Add(codec.MimeType.ToLower(), codec);
                }
            }

            //return the lookup
            return encoders;
        }
    }

    /// <summary>
    /// *************************************************************
    /// Resizes or crops an images to the requested width and height.
    /// *************************************************************
    /// If any dimension is not passed, the function will calculate the missed dimensions
    /// If both dimensions are not passed, the function will return a *copy* of the original image
    /// If any of the requested dimensions exceeds the original one's, this function will return null
    /// If dimensions' ratio does not match the original ratio, clipping will occur.
    /// </summary>
    /// <param name="sourceImage"></param>
    /// <param name="targetWidth"></param>
    /// <param name="targetHeight"></param>
    /// <returns>A bitmap, you will **need** to dispose of such image</returns>
    public static byte[] Resize(Image sourceImage, int? targetWidth, int? targetHeight, int quality)
    {
        //w, h source width and height
        int w = sourceImage.Size.Width;
        int h = sourceImage.Size.Height;
        //wt, ht requested width and height
        int wt = 1;
        int ht = 1;

        //The new image would exceed the max boundary of the source image
        if (targetWidth > w || targetHeight > h) return null;

        if (targetWidth == null && targetHeight == null)
        {
            wt = w;
            ht = h;
        }

        var sourceRatio = (double)w / (double)h;

        //if no target width expressed then
        //if w = sourceRatio * h
        //then
        //wt = sourceRatio * ht
        wt = (int)(targetWidth ?? sourceRatio * ht);

        //if no target height expressed then
        //if h = w/sourceRatio
        //then
        //ht = wt/sourceRatio
        ht = (int)(targetHeight ?? wt / sourceRatio);

        var targetRatio = (double)wt / (double)ht;

        #region ***Clipping explaination in visual terms
        //Clip applied to original image before scaling
        // If proportions are as follow:
        //
        //          target 2:1               source 1:1
        //       ___________             _______________
        //      |           |           |               |
        //      |___________|           |               |
        //                              |               |
        //                              |               |
        //                              |_______________|
        // Then we will clip as follows:
        //
        //                clip to source
        //               _______________
        //              |_ _ _ _ _ _ _ _|
        //              |               |
        //              |               |
        //              |_ _ _ _ _ _ _ _|
        //              |_______________|

        // or vertical clip instead if proportions are as follow:
        //
        //          target 1:2               source 1:1
        //       ___                     _______________
        //      |   |                   |               |
        //      |   |                   |               |
        //      |___|                   |               |
        //                              |               |
        //                              |_______________|
        // Then we will clip as follows:
        //
        //                clip to source
        //               _______________
        //              |    !     !    |
        //              |    !     !    |
        //              |    !     !    |
        //              |    !     !    |
        //              |____!_____!____|
        #endregion
        Rectangle clip;

        if (targetRatio >= sourceRatio)
        {
            //The image requested is more elungated than the original  one 
            //therefore we clip the height
            //targetRatio = wt/ht
            //ht = wt/targetRatio
            //hClip = w/targetRatio
            var hClip = (int)Math.Ceiling((double) w / (double)targetRatio);

            //Rectangle pars are: x, y, width, height
            clip = new Rectangle(0, (h - hClip) / 2, w, hClip);
        }
        else
        {
            //The image requested is more stretched in height than the original  one 
            //therefore we clip the width
            //targetRatio = wt/ht
            //wt = targetRatio * ht
            //hClip = targetRatio * h
            var wClip = (int)Math.Ceiling((double)h * (double)targetRatio);

            //Rectangle pars are: x, y, width, height
            clip = new Rectangle((w - wClip) / 2, 0, wClip, h);
        }


        var targetImage = new Bitmap(wt, ht);

        using (var g = Graphics.FromImage(targetImage))
        {
            g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;


            var targetRectangle = new Rectangle(0, 0, wt, ht);

            g.DrawImage(sourceImage, targetRectangle, clip, GraphicsUnit.Pixel);
        }

        var bytes = ImageToByteArray(targetImage, ImageFormat.Jpeg, quality);

        targetImage.Dispose();

        return bytes;
    }
    /// <summary>
    /// Given a System.Drawing.Image it will return the corresponding byte array given 
    /// a format.
    /// </summary>
    /// <param name="imageIn"></param>
    /// <param name="format"></param>
    /// <returns></returns>
    public static byte[] ImageToByteArray(System.Drawing.Image imageIn, ImageFormat format, int quality)
    {
        //create an encoder parameter for the image quality
        EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
        //get the jpeg codec
        ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");

        //create a collection of all parameters that we will pass to the encoder
        EncoderParameters encoderParams = new EncoderParameters(1);
        //set the quality parameter for the codec
        encoderParams.Param[0] = qualityParam;
        //save the image using the codec and the parameters

        var ms = new MemoryStream();
        imageIn.Save(ms, jpegCodec, encoderParams);
        return ms.ToArray();
    }
    /// <summary>
    /// Converts a byte array to System.Drawing.Image.
    /// IMPORTANT: You must dispose of the returned image.
    /// </summary>
    /// <param name="byteArrayIn"></param>
    /// <returns>Returns an image of type System.Drawing.Image, you will have to take care of disposing it</returns>
    public static Image ByteArrayToImage(byte[] byteArrayIn)
    {
        var ms = new MemoryStream(byteArrayIn);
        var returnImage = Image.FromStream(ms);
        return returnImage;
    }

    /// <summary> 
    /// Returns the image codec with the given mime type 
    /// </summary> 
    public static ImageCodecInfo GetEncoderInfo(string mimeType)
    {
        //do a case insensitive search for the mime type
        string lookupKey = mimeType.ToLower();

        //the codec to return, default to null
        ImageCodecInfo foundCodec = null;

        //if we have the encoder, get it to return
        if (Encoders.ContainsKey(lookupKey))
        {
            //pull the codec from the lookup
            foundCodec = Encoders[lookupKey];
        }

        return foundCodec;
    } 
}

}

编辑:

上面上传的图片是实际图片的特写截图,我在这里添加另外几个实际源和输出图片的示例。

来源:

输出为 403x305(左边缘的关键线):

【问题讨论】:

  • 这通常是由 Graphics.Interpolation 属性设置产生的工件。更高质量的插值器通过查看多个像素来重新采样图像。边缘的问题是它用完了像素。它在图像的左侧更加明显,但在这种情况下您不会觉得它令人反感,因为它非常暗。它是 extra 发音的,因为您的源图像已经有这个工件。 InterpolationMode.NearestNeighbor 不会产生该伪影,但您可能不喜欢图像其余部分的结果。

标签: c# .net image-processing gdi


【解决方案1】:

问题已经从您的源图像开始。如果您在 Photoshop(或其他编辑器)中仔细查看它,您会注意到这些伪影在源图像中,但要暗得多。缩放图像时,源像素不仅被复制到它们的新位置,而且被插值。这意味着在某些情况下,颜色可能会发生很大变化。我稍微更改了颜色级别以使其清晰。

这是图像顶部的放大图:


现在的问题是,您显然希望最大限度地减少不良源图像改变颜色的影响,从而使压缩伪影更加突出。您应该尝试使用InterpolationMode 以及其他一些质量设置。

另一个想法是尝试使用 ImageMagick。 Here's a .NET wrapper 在它周围。

【讨论】:

  • 我认为源图像上的伪影是因为我确实上传了 Photoshop 页面的屏幕截图而不是实际图像,我猜截图工具添加了一些噪音,我正在上传另外几张图片,这次是原件。我来看看 InterpolationMode。
  • @GiuseppeRomagnuolo 这也可以解释。然而,正如 Hans Passant 所提到的,插值模式可能会出现问题并导致图像发生这种变化。
  • @GiuseppeRomagnuolo 使用您发布的新图像示例,很明显插值是问题所在。
  • 分析了各种插值的结果,尽管有些插值总是消除图像变得模糊的伪影。最后,我将 InterpolationMode 设置为 ImageResizer.Resize 方法的参数,对于使用它的应用程序,我为每个不同的作物选择了不同的插值,它是一个有限列表(少于十几个),所以很好。我会花更多时间看看 ImageMagick。
  • @GiuseppeRomagnuolo 这听起来像是一个聪明的解决方案。图像压缩、处理、调整大小等东西总是很棘手,因为源照片的格式和内容对最终结果影响很大。
猜你喜欢
  • 2018-07-17
  • 2011-09-28
  • 1970-01-01
  • 2018-12-31
  • 1970-01-01
  • 2019-02-27
  • 2020-12-18
  • 2021-05-18
  • 2018-11-11
相关资源
最近更新 更多