【问题标题】:LockBits appears to be too slow for my needs - alternatives?LockBits 似乎对我的需求来说太慢了 - 替代方案?
【发布时间】:2013-05-01 07:34:43
【问题描述】:

我正在处理由摄像机拍摄的 10 兆像素图像。

目的是在矩阵(二维数组)中注册每个像素的灰度值。

我第一次使用 GetPixel,但花了 25 秒才完成。现在我使用 Lockbits,但它需要 10 秒,如果我不将结果保存在文本文件中,则需要 3 秒。

我的导师说他们不需要注册结果,但 3 秒还是太慢了。那么我在我的程序中做错了什么,或者我的应用程序有什么比 Lockbits 更快的东西吗?

这是我的代码:

public void ExtractMatrix()
{
    Bitmap bmpPicture = new Bitmap(nameNumber + ".bmp");

    int[,] GRAY = new int[3840, 2748]; //Matrix with "grayscales" in INTeger values

    unsafe
    {
        //create an empty bitmap the same size as original
        Bitmap bmp = new Bitmap(bmpPicture.Width, bmpPicture.Height);

        //lock the original bitmap in memory
        BitmapData originalData = bmpPicture.LockBits(
           new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
           ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

        //lock the new bitmap in memory
        BitmapData newData = bmp.LockBits(
           new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
           ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

        //set the number of bytes per pixel
        // here is set to 3 because I use an Image with 24bpp
        int pixelSize = 3;

        for (int y = 0; y < bmpPicture.Height; y++)
        {
            //get the data from the original image
            byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);

            //get the data from the new image
            byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);

            for (int x = 0; x < bmpPicture.Width; x++)
            {
                //create the grayscale version
                byte grayScale =
                   (byte)((oRow[x * pixelSize] * .114) + //B
                   (oRow[x * pixelSize + 1] * .587) +  //G
                   (oRow[x * pixelSize + 2] * .299)); //R

                //set the new image's pixel to the grayscale version
                //   nRow[x * pixelSize] = grayScale; //B
                //   nRow[x * pixelSize + 1] = grayScale; //G
                //   nRow[x * pixelSize + 2] = grayScale; //R

                GRAY[x, y] = (int)grayScale;
            }
        }

【问题讨论】:

  • 您可以通过使用 TPL 使 for 循环并行运行来加快速度。
  • 锁定图片时指定的像素格式和图片原生格式一样吗?
  • 找出慢的部分。您正在进行 1000 万次迭代。如果在内部循环中可以优化某些东西,您可以获得很大的性能提升。
  • 展开内部循环。
  • 如果您想要更快,请使用单精度(又名float)而不是double。只需将f 添加到这些浮点常量即可。

标签: c# lockbits


【解决方案1】:

以下是一些可能有所帮助的优化:

  1. 使用锯齿状数组 ([][]);在 .NET 中,accessing them is faster than multidimensional;

  2. 将在循环内使用的缓存属性。虽然this answer 声明 JIT 会对其进行优化,但我们不知道内部发生了什么;

  3. Multiplication is (generally) slower than addition;

  4. 正如其他人所说,floatdoublewhich applies to older processors 快​​(大约 10 年以上)。这里唯一的好处是您将它们用作常量,因此消耗的内存更少(尤其是因为迭代次数很多);

    Bitmap bmpPicture = new Bitmap(nameNumber + ".bmp");
    
    // jagged instead of multidimensional 
    int[][] GRAY = new int[3840][]; //Matrix with "grayscales" in INTeger values
    for (int i = 0, icnt = GRAY.Length; i < icnt; i++)
        GRAY[i] = new int[2748];
    
    unsafe
    {
        //create an empty bitmap the same size as original
        Bitmap bmp = new Bitmap(bmpPicture.Width, bmpPicture.Height);
    
        //lock the original bitmap in memory
        BitmapData originalData = bmpPicture.LockBits(
           new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
           ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    
        //lock the new bitmap in memory
        BitmapData newData = bmp.LockBits(
           new Rectangle(0, 0, bmpPicture.Width, bmpPicture.Height),
           ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    
        //set the number of bytes per pixel
        // here is set to 3 because I use an Image with 24bpp
        const int pixelSize = 3; // const because it doesn't change
        // store Scan0 value for reuse...we don't know if BitmapData caches it internally, or recalculated it every time, or whatnot
        int originalScan0 = originalData.Scan0;
        int newScan0 = newData.Scan0;
        // incrementing variables
        int originalStride = originalData.Stride;
        int newStride = newData.Stride;
        // store certain properties, because accessing a variable is normally faster than a property (and we don't really know if the property recalculated anything internally)
        int bmpwidth = bmpPicture.Width;
        int bmpheight = bmpPicture.Height;
    
        for (int y = 0; y < bmpheight; y++)
        {
            //get the data from the original image
            byte* oRow = (byte*)originalScan0 + originalStride++; // by doing Variable++, you're saying "give me the value, then increment one" (Tip: DON'T add parenthesis around it!)
    
            //get the data from the new image
            byte* nRow = (byte*)newScan0 + newStride++;
    
            int pixelPosition = 0;
            for (int x = 0; x < bmpwidth; x++)
            {
                //create the grayscale version
                byte grayScale =
                   (byte)((oRow[pixelPosition] * .114f) + //B
                   (oRow[pixelPosition + 1] * .587f) +  //G
                   (oRow[pixelPosition + 2] * .299f)); //R
    
                //set the new image's pixel to the grayscale version
                //   nRow[pixelPosition] = grayScale; //B
                //   nRow[pixelPosition + 1] = grayScale; //G
                //   nRow[pixelPosition + 2] = grayScale; //R
    
                GRAY[x][y] = (int)grayScale;
    
                pixelPosition += pixelSize;
            }
        }
    

【讨论】:

  • 很好的建议,但我认为您错过了代码的主要问题:它(意外地)转置位图,如果天真地完成,这是一个非常不友好的缓存操作。
  • @Daniel 是的,我注意到了这一点,但决定只专注于使用现有代码进行优化。不过,好点。 =)
【解决方案2】:

您的代码正在从以行为主的表示形式转换为以列为主的表示形式。 在位图中,像素 (x,y) 后面是内存中的 (x+1,y);但在您的GRAY 数组中,像素 (x,y) 后跟 (x,y+1)。

这会导致写入时内存访问效率低下,因为每次写入都会触及不同的缓存行;如果图像足够大,您最终会破坏 CPU 缓存。如果您的图像大小是 2 的幂(参见 Why is transposing a matrix of 512x512 much slower than transposing a matrix of 513x513?),这尤其糟糕。

尽可能以行优先顺序存储您的数组以避免低效的内存访问(将GRAY[x,y] 替换为GRAY[y,x])。

如果您确实需要按列优先顺序,请查看更多对缓存友好的矩阵转置算法(例如A Cache Efficient Matrix Transpose Program?

【讨论】:

  • 我不明白你为什么说灰色寄存器 (x,y) 然后 (x, y+1)。第一个循环 y=0, x=0, 然后 y=0 和 x=1 等等...
  • @EloMonval:我说的是元素存储在内存中的顺序。您的循环访问数组的顺序与存储在内存中的顺序不同,这会由于缓存使用效率低下而导致显着减慢。
【解决方案3】:

您的代码可能不是最佳的,但快速浏览似乎表明即使此版本也应该在几分之一秒内运行。这表明还有一些其他问题:

你是:

  • 在发布模式下编译?调试模式会关闭各种优化
  • 在附加调试器的情况下运行?如果您使用 F5 从 Visual Studio 运行,则(使用默认的 C# 快捷键)将附加调试器。这会显着降低您的程序速度,尤其是在您启用了任何断点或智能跟踪的情况下。
  • 在某些受限设备上运行?听起来您是在 PC 上运行,但如果不是,则可能与特定设备的限制有关。
  • I/O 受限?尽管您谈论的是摄像机,但您的代码表明您正在处理文件系统。任何文件系统交互都可能成为瓶颈,尤其是当网络磁盘、病毒扫描程序、物理盘片和碎片开始发挥作用时。 10 mp 图像为 30MB(如果未压缩的 RGB 没有 alpha 通道),根据文件系统的详细信息,读取/写入可能很容易需要 3 秒。

【讨论】:

    【解决方案4】:

    我不知道为什么内部 for 循环的第二部分被注释掉了,但是如果你不需要它,你就是在做一些不必要的转换。删除它可能会提高您的性能。

    另外,正如leppie 所建议的,您可以使用单精度浮点数:

            for (int x = 0; x < bmpPicture.Width; x++)
            {
                //create the grayscale version
               GRAY[x, y] =
                   (int)((oRow[x * pixelSize] * .114f) + //B
                   (oRow[x * pixelSize + 1] * .587f) +  //G
                   (oRow[x * pixelSize + 2] * .299f)); //R
    
            }
    

    【讨论】:

    • 所以你是说投射到int 会比投射到byte 更快?
    • @leppie,不,我是说转换为 int 可能比转换为字节并 then 转换为 int 更快。
    • 第二部分设置为 cmets 因为我不需要它。我有时只是使用它来获得视觉结果,以确保我在做什么。
    【解决方案5】:

    您可以尝试避免乘法和增量设置一个具有 x * pixelSize 起始值的指针并将您的代码更改为:

    for (int x = 0; x < bmpPicture.Width; x++)
                {    
                   int *p = x * pixelSize;
    
                    GRAY[x, y]=
                       (int)((oRow[*p] * .114) + //B
                       (oRow[*p++] * .587) +  //G
                       (oRow[*p++] * .299)); //R
                 }
    

    这将加快您的代码速度,但我不确定它是否会明显更快。

    注意:这只会在遍历值类型数组时加速代码,并且如果 oRow 更改为其他类型时将不起作用。

    【讨论】:

      【解决方案6】:

      这是一个仅使用整数算术的替代转换,它略有不同(由于因子的四舍五入),但肉眼看不到任何东西:(未经测试)

      byte grayScale = (byte)((
            (oRow[pixelPosition] * 29) +
            (oRow[pixelPosition + 1] * 151) +
            (oRow[pixelPosition + 2] * 105)) >> 8);
      

      比例因子大约是旧的乘以 256,最后的偏移除以 256。

      【讨论】:

        【解决方案7】:

        巨大的优化将通过使用 1D array 而不是 2D array 来实现。

        所有其他都不会给你一个高加速...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-11-26
          • 2012-06-19
          • 2015-01-07
          • 2011-01-03
          • 2020-08-08
          • 1970-01-01
          • 2013-07-30
          相关资源
          最近更新 更多