C# LockBits性能问题(将int[,]转换为byte[])

3
Graphics g;
using (var bmp = new Bitmap(_frame, _height, PixelFormat.Format24bppRgb))
{
    var data = bmp.LockBits(new Rectangle(0, 0, _frame, _height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    var bmpWidth = data.Stride;
    var bytes = bmpWidth * _height;
    var rgb = new byte[bytes];
    var ptr = data.Scan0;
    Marshal.Copy(ptr, rgb, 0, bytes);

    for (var i = 0; i < _frame; i++)
    {
        var i3 = (i << 1) + i;
        for (var j = 0; j < _height; j++)
        {
            var ij = j * bmpWidth + i3;
            var val = (byte)(_values[i, j]);
            rgb[ij] = val;
            rgb[ij + 1] = val;
            rgb[ij + 2] = val;
        }
    }

    Marshal.Copy(rgb, 0, ptr, bytes);
    bmp.UnlockBits(data);

    g = _box.CreateGraphics();
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}
g.Dispose();

我使用这段代码将PictureBox中的RGB值数组(灰度)转换,但速度很慢。请指出我的错误。 目前,处理一个由441000个元素组成的数组需要35毫秒。 我需要在同样的时间内处理一个由400万个元素组成的数组。

大部分时间是花在转换上还是在代码的其他部分(分配、锁定、解锁)上? - user541686
大多数时间在两个嵌套循环中(约32毫秒)。也许这是由于将int转换为byte。但我不知道如何最优地解决这个问题。 - Kir
你是在调试器中进行这些计时吗?如果是的话,那么它们并不可靠。请在发布模式下运行计时,而不要附加调试器(使用Ctrl+F5运行)。 - Jim Mischel
运行一些性能测试会显示什么? - user166390
5个回答

5
你可以跳过第一个 Array.Copy,因为你无论如何都会覆盖数组中的所有数据。
这样可以减少大约25%的时间,但如果你想更快,你需要使用不安全代码块,以便使用指针。这样你就可以绕过访问数组时的范围检查,直接将数据写入图像数据中,而不是复制它。

3

我完全同意Guffa的回答。使用不安全代码块可以加快速度。 为了进一步提高性能,您可以使用.Net框架中的Parallel类并行执行for循环。对于大型位图,这将提高性能。 下面是一个小的代码示例:

using (Bitmap bmp = (Bitmap)Image.FromFile(@"mybitmap.bmp"))
{
  int width = bmp.Width;
  int height = bmp.Height;

  BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height),
    System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

  byte* s0 = (byte*)bd.Scan0.ToPointer();
  int stride = bd.Stride;

  Parallel.For(0, height, (y1) =>
  {
    int posY = y1*stride;
    byte* cpp = s0 + posY;

    for (int x = 0; x < width; x++)
    {              
      // Set your pixel values here.
      cpp[0] = 255;
      cpp[1] = 255;
      cpp[2] = 255;
      cpp += 3;
    }
  });

  bmp.UnlockBits(bd);
}

为了保持示例简单,我将像素值设置为常量。请注意,要编译上面的示例,您必须允许不安全的代码。
希望这可以帮助您。

1
除了Guffa的优秀建议外,我建议您对代码进行分析以查看它花费时间的地方。确保在计时时,您正在运行没有附加调试器的发布模式。
如果调用DrawImage占用大部分时间,我不会感到惊讶。您正在缩放图像,这可能非常昂贵。您绘制图像的框有多大?
最后,虽然这不会影响性能,但您应该更改代码如下:
using (Graphics g = _box.CreateGraphics())
{
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}

并且在你的示例中去掉第一行和最后一行。


1

尝试使用不安全的代码:

byte* rp0;
int* vp0;
fixed (byte* rp1 = rgb)
{
    rp0 = rp1;
    fixed (int* vp1 = _values)
    {
        vp0 = vp1;
        Parallel.For(0, _width, (i) =>
        {
            var val = (byte)vp0[i];
            rp0[i] = val;
            rp0[i + 1] = val;
            rp0[i + 2] = val;
        });
    }
}

对我来说运行非常快


0

我的理解是,在 .Net 中,多维(方)数组的速度相当慢。您可以尝试将 _values 数组更改为单维数组。

这里有一个参考链接,如果您搜索还会有更多: http://odetocode.com/articles/253.aspx

数组性能示例。

using System;
using System.Diagnostics;

class Program
{
static void Main(string[] args)
{
    int w = 1000;
    int h = 1000;

    int c = 1000;

    TestL(w, h);
    TestM(w, h);


    var swl = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestL(w, h);
    }
    swl.Stop();

    var swm = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestM(w, h);
    }
    swm.Stop();

    Console.WriteLine(swl.Elapsed);
    Console.WriteLine(swm.Elapsed);
    Console.ReadLine();
}


static void TestL(int w, int h)
{
    byte[] b = new byte[w * h];
    int q = 0;
    for (int x = 0; x < w; x++)
        for (int y = 0; y < h; y++)
            b[q++] = 1;
}

static void TestM(int w, int h)
{
    byte[,] b = new byte[w, h];

    for (int y = 0; y < h; y++)
        for (int x = 0; x < w; x++)
            b[y, x] = 1;
}
}

“pretty slow”只是指比正常的数组访问要慢。 - MarkPflug
1
这种理解是基于.NET 1.x的。在.NET 2.0及更高版本中,多维数组非常快。 - Jim Mischel
@Jim:如果是这样的话,请修改我刚刚添加的代码示例,并使时间大致相等。因为在我的机器上,单维数组更快,而且速度明显更快。只需将所有值设置为1即可。谢谢!我要指出的是,这仅适用于编译x86。在x64下,计时相等。 - MarkPflug
我承认错误。我误读了你的陈述,并考虑到多维数组[,]访问和锯齿数组[][]访问之间的区别。你是正确的,一维数组访问速度更快。 - Jim Mischel
更奇怪的是,在64位模式下,如果将数组设置为“静态”,则“TestM”方法比每次分配新数组的版本。当你这样做时,“TestL”方法大约快50%。确实很奇怪。 - Jim Mischel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接