9

我正在处理由摄像机拍摄的 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;
            }
        }
4

7 回答 7

5

Here are some more optimizations that may help:

  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;
            }
        }
    
于 2013-05-07T12:27:27.537 回答
4

Your code is converting from a row-major representation into a column-major representation. In the bitmap, pixel (x,y) is followed by (x+1,y) in memory; but in your GRAY array, pixel (x,y) is followed by (x,y+1).

This causes inefficient memory access when writing, as every write touches a different cache line; and you end up trashing the CPU cache if the image is big enough. This is especially bad if your image size is a power of two (see Why is transposing a matrix of 512x512 much slower than transposing a matrix of 513x513?).

Store your array in row-major order as well if possible to avoid the inefficient memory access (replace GRAY[x,y] with GRAY[y,x]).

If you really need it in column-major order, look at more cache-friendly algorithms for matrix transposition (e.g. A Cache Efficient Matrix Transpose Program?)

于 2013-05-07T13:19:52.947 回答
1

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

你是:

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

Here's an alternative transformation that uses only integer arithmetic, it's slightly different (due to rounding of the factors) but not anything you'd notice with the naked eye: (not tested)

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

The scale factors are approximately the old ones multiplied by 256, the shift in the end divides by 256.

于 2013-05-07T13:22:29.537 回答
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

        }
于 2013-05-07T11:33:51.200 回答
0

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

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

于 2013-05-08T06:26:52.110 回答
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 更改为其他类型则不起作用。

于 2013-05-07T12:51:29.507 回答