快速将图像转换为内存中的8位调色板格式

时间:2019-04-08 23:57:31

标签: c# image-processing .net-core

我正在编写对快速图像处理有严格要求的应用程序。将会有大量的图像。我需要将它们保存为可快速处理的格式。因此,我决定保存具有8位色深的彩色贴图(使用调色板),然后将其保存为字节数组。因此,我需要一种非常快速且在内存中转换格式的方法。另外,它必须尽可能地跨平台(我在Windows上编写代码并将部署到AWS)。

我尝试了ImageSharp.NET Core System.Drawing.Common软件包。在使用ImageSharp的情况下,我得到的结果几乎可以接受:调整大小的图像的质量是完美的,但是效果确实很慢...对于4096x4096 24-bit full palette image,我得到了12秒。生成的调色板中有256种不同的颜色,这对我来说很完美。 对于System.Drawing.Common库,我的转换速度要快得多:

  • tiff格式为900毫秒
  • 2500毫秒(gif格式)。

但是,结果调色板中不同颜色的数量:

  • 用于tiff:224(对于我的解决方案而言不太好)
  • 对于gif:252(好) 另外,以tiff格式生成的图像质量非常较差,因此可能无法使用(也许我错了)。

System.Drawing.Common实现:

var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8L);

ImageCodecInfo bmpEncoder = ImageCodecInfo.GetImageEncoders()
    .FirstOrDefault(x => x.FormatID == ImageFormat.Gif.Guid);

Stopwatch sw = Stopwatch.StartNew();

var allColors = new Bitmap(@"16777216colors.png");

Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 462

using (MemoryStream ms = new MemoryStream())
{
    allColors.Save(ms, bmpEncoder, parameters);

    Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 2104

    ms.Position = 0;
    Bitmap bitmap = new Bitmap(ms);

    Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 2383

    // set to determine palette size
    HashSet<int> set = new HashSet<int>();

    // Temporary solution. It is needed to replace GetPixel to faster analog
    for (int i = 0; i < bitmap.Width; i++)
    {
        for (int j = 0; j < bitmap.Height; j++)
        {
            var val = bitmap.GetPixel(i, j).ToArgb();

            set.Add(val);
        }
    }

    Console.WriteLine("Palette size: " + set.Count); // 252
    Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 26345
}

/*
    Output:

    Loaded: 462
    Saved: 2104
    Loaded: 2383
    Palette size: 252
    All done: 26345
*/

ImageSharp实现:

PngEncoder encoder = new PngEncoder()
{
    ColorType = PngColorType.Palette,
    BitDepth = PngBitDepth.Bit8,
    CompressionLevel = 1
};

Image<Rgba32> img;

Stopwatch sw = Stopwatch.StartNew();

using (var file = File.OpenRead(@"16777216colors.png"))
{
    img = Image.Load(file);
}

Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 1302

using (MemoryStream ms = new MemoryStream())
{
    img.Save(ms, encoder);
    Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 12608

    ms.Position = 0;
    img = Image.Load(ms);
    Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 12813
}


// set to determine palette size
HashSet<uint> set = new HashSet<uint>();

for (int i = 0; i < img.Width; i++)
{
    for (int j = 0; j < img.Height; j++)
    {
        var val = img[i, j].PackedValue;

        set.Add(val);
    }
}

Console.WriteLine("Palette size: " + set.Count); // 256
Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 15807

/*
    Output:

    Loaded: 1302
    Saved: 12608
    Loaded: 12813
    Palette size: 256
    All done: 15807
*/

因此,最好的方法似乎是使用System.Drawing.Common实现,但是可能会有与像素读取慢有关的开销。 因此,问题

  • 我期望ImageSharp具有高性能,所以我想我在做 出了点问题...知道如何提高性能会很酷!如果有人知道我做错了,请告诉我。
  • 是否确保调色板颜色是 任何图片都一样,因此,我将能够缓存这256种颜色之间的任何颜色差异?
  • 有人知道以其他方式解决我的问题的最佳方法吗?我很高兴看到建议。该解决方案必须跨平台并且尽可能快,直到字节数组数据表示为止

1 个答案:

答案 0 :(得分:0)

尝试使用Accord。很难说是否很快。您需要在您的场景中尝试一下。

您要有8bpp图像吗?所以你想成为GreyScale吗?然后,从using Accord.Imaging.Filters;使用此功能。

private Bitmap Create8bppGreyscaleImage(Bitmap bitmap) 
                                  => Grayscale.CommonAlgorithms.BT709.Apply(bitmap);

This jpg图像上的转换采用500ms。产生的图像如下所示:enter image description here

如您所见,Grayscale.CommonAlghoritms.BT709Bitmap作为参数。如果要字节数组,只需使用Marshall.Copy

BitmapData bitmapData = this.Bitmap.LockBits(
            new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
            ImageLockMode.ReadWrite,
            this.Bitmap.PixelFormat);

        int pixels = bitmapData.Stride * this.Bitmap.Height;
        byte[] bytes = new byte[pixels];

        Marshal.Copy(bitmapData.Scan0, bytes, 0, pixels);

        this.Bitmap.UnlockBits(bitmapData);
        this.Bytes = bytes;

通过此方法将图像加载为8bpp字节数组(上图)为18ms

它将字节数组保存到内存中。然后,您可以将位解锁到位图并将其保存为例如.png

BitmapData bitmapData = this.Bitmap.LockBits(
            new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
            ImageLockMode.ReadWrite,
            this.Bitmap.PixelFormat);

        int stride = bitmapData.Stride * this.Height;

        Marshal.Copy(bytes, 0, bitmapData.Scan0, stride);
        this.Bitmap.UnlockBits(bitmapData);
        this.Bitmap.Save("test.png");

如果我的回答对您有帮助,请告诉我。 .NET Core中没有开箱即用的引用System.Drawing.dll,您需要自己添加