LockBits似乎对我的需求来说太慢了 - 有替代方案吗?

11

我正在处理由视频摄像机拍摄的1000万像素图像。

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

我最初使用了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;
            }
        }

4
你可以尝试使用TPL让for循环并行运行来提高速度。 - Bob Vale
当锁定图像时指定的像素格式是否与图像本机格式相同? - CodesInChaos
1
找出哪一部分代码较慢。你正在进行1000万次迭代。如果在内部循环中有可以优化的内容,你可以获得极大的性能提升。 - CodeCaster
1
展开内部循环。 - leppie
2
如果您想要更快的速度,使用单精度(又名“float”)而不是“double”。只需将“f”添加到这些浮点常量中即可。 - leppie
显示剩余4条评论
7个回答

6
以下是一些可能有所帮助的优化措施:
  1. Use jagged arrays ([][]); in .NET, accessing them is faster than multidimensional;

  2. Cache properties that will be used inside of a loop. Though this answer states that JIT will optimize it, we don't know what's happening internally;

  3. Multiplication is (generally) slower than addition;

  4. As others have stated, float is faster than double, which applies to older processors (~10+ years). The only upside here is that you're using them as constants, and thus consume less memory (especially because of the many iterations);

    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;
            }
        }
    

3
建议不错,但我认为你忽略了代码的主要问题:它(无意中)转置了位图,如果以天真的方式执行,这是一个非常不友好的缓存操作。 - Daniel
@Daniel 是的,我注意到了,但决定只专注于使用现有代码进行优化。不过你说得很对。=) - Jesse

4
您的代码正在将行主序表示转换为列主序表示。
在位图中,像素(x,y)在内存中后跟(x + 1,y); 但在您的GRAY数组中,像素(x,y)后跟(x,y + 1)。
这会导致写入时内存访问效率低下,因为每次写入都会触及不同的缓存行;如果图像足够大,您最终会破坏CPU缓存。如果您的图像大小是2的幂,则情况尤其严重(请参见为什么转置512x512矩阵比转置513x513矩阵要慢得多?)。
如果可能,请将数组按行主序存储以避免低效的内存访问(用GRAY[y,x]替换GRAY[x,y])。
如果您确实需要按列主序排序,请查看更多缓存友好的矩阵转置算法(例如高效缓存的矩阵转置程序?

我不明白为什么你说灰色寄存器是(x,y),然后是(x,y+1)。第一个循环中y=0,x=0,然后y=0,x=1等等... - Elo Monval
@EloMonval:我在谈论元素在内存中存储的顺序。你的循环以与内存中存储的顺序不同的顺序访问数组,这会导致由于缓存使用效率低下而出现显着的减速。 - Daniel

1
您的代码可能不是最优的,但快速浏览似乎显示即使这个版本也应该在几分之一秒内运行。这表明存在其他问题:
您是否:
在“发布”模式下编译?调试模式会关闭各种优化。
带有调试器附加运行?如果您使用F5从Visual Studio运行,则(使用默认的C#键快捷方式)调试器将被附加。这可能会显着减慢您的程序,特别是如果您启用了任何断点或Intellitrace。
在某些受限设备上运行?听起来您正在运行PC,但如果不是,则特定于设备的限制可能会相关。
I/O 受限?虽然您谈论了视频摄像头,但您的代码表明您正在处理文件系统。任何文件系统交互都可能成为瓶颈,特别是一旦涉及到网络磁盘、病毒扫描程序、物理盘片和碎片整理等细节时。一个10mp的图像是30MB(如果没有alpha通道的未压缩RGB),读/写它可能需要3秒,具体取决于文件系统的详细信息。

0

我不确定为什么内部 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
@leppie,不,我的意思是将其转换为int可能比将其转换为byte然后再转换为int更快。 - Rik
第二部分被设置为注释,因为我不需要它。有时候我只是用它来获得视觉结果以确保我正在做的事情。 - Elo Monval

0

您可以尝试避免使用乘法和增量设置,而是使用指针来设置 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 更改为其他类型,则无法使用。


0

这里有一种使用整数算术的替代转换方法,它略有不同(由于因子的四舍五入),但肉眼不会注意到任何区别:(未经测试)

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

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


0
使用一维数组而不是二维数组将实现巨大的优化。
其他方法都无法给你带来高速增长...

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