1

我的应用程序

我正在编写一个需要将 RGB 转换为灰度图像的应用程序。转换有效,但转换 3648 * 2736 像素的图像大约需要 7 秒。我知道 set 和 getpixel 需要一些时间。但我认为如果你使用 Lockbits,即使图像不小,也不应该花这么长时间。(如果有错请纠正我)。也许我只是在我的代码中犯了一个致命的错误。

编码

public static long ConvertToGrayScaleV2(Bitmap imageColor, bool useHDTVConversion)
        {
            Stopwatch stpw = new Stopwatch();
            stpw.Start();
            System.Drawing.Imaging.BitmapData imageColorData = imageColor.LockBits(new Rectangle(new Point(0, 0), imageColor.Size),
                System.Drawing.Imaging.ImageLockMode.ReadWrite, imageColor.PixelFormat);

            IntPtr PtrColor = imageColorData.Scan0;
            int strideColor = imageColorData.Stride;
            byte[] byteImageColor = new byte[Math.Abs(strideColor) * imageColor.Height];
            System.Runtime.InteropServices.Marshal.Copy(PtrColor, byteImageColor, 0, Math.Abs(strideColor) * imageColor.Height);

            int bytesPerPixel = getBytesPerPixel(imageColor);
            byte value;
            if (bytesPerPixel == -1)
                throw new Exception("Can't get bytes per pixel because it is not defined for this image format.");
            for (int x = 0, position; x < imageColor.Width * imageColor.Height; x++)
            {
                position = x * bytesPerPixel;
                if (useHDTVConversion)
                {
                    value = (byte)(byteImageColor[position] * 0.0722 + byteImageColor[position + 1] * 0.7152 + byteImageColor[position + 2] * 0.2126);                    
                }
                else
                {
                    value = (byte)(byteImageColor[position] * 0.114 + byteImageColor[position + 1] * 0.587 + byteImageColor[position + 2] * 0.299);
                }
                byteImageColor[position] = value;
                byteImageColor[position+1] = value;
                byteImageColor[position+2] = value;

            }

            System.Runtime.InteropServices.Marshal.Copy(byteImageColor, 0, PtrColor, Math.Abs(strideColor) * imageColor.Height);
            imageColor.UnlockBits(imageColorData);
            stpw.Stop();
            return stpw.ElapsedMilliseconds;
        }

        public static int getBytesPerPixel(Image img)
        {
            switch (img.PixelFormat)
            {
                case System.Drawing.Imaging.PixelFormat.Format16bppArgb1555:    return 2;
                case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:   return 2;
                case System.Drawing.Imaging.PixelFormat.Format16bppRgb555: return 2;
                case System.Drawing.Imaging.PixelFormat.Format16bppRgb565: return 2;
                case System.Drawing.Imaging.PixelFormat.Format1bppIndexed: return 1;
                case System.Drawing.Imaging.PixelFormat.Format24bppRgb: return 3;
                case System.Drawing.Imaging.PixelFormat.Format32bppArgb: return 4;
                case System.Drawing.Imaging.PixelFormat.Format32bppPArgb: return 4;
                case System.Drawing.Imaging.PixelFormat.Format32bppRgb: return 4;
                case System.Drawing.Imaging.PixelFormat.Format48bppRgb: return 6;
                case System.Drawing.Imaging.PixelFormat.Format4bppIndexed: return 1;
                case System.Drawing.Imaging.PixelFormat.Format64bppArgb: return 8;
                case System.Drawing.Imaging.PixelFormat.Format64bppPArgb: return 8;
                case System.Drawing.Imaging.PixelFormat.Format8bppIndexed: return 1;
                default: return -1;
            }

        }
4

4 回答 4

4

我知道这是旧的,但有一些可能有价值的观点:

imageColor.Width * imageColor.Height是一项昂贵的操作,您运行的次数比您需要的多近 1000 万次 (3648 * 2736)。

  • for循环正在重新计算每次迭代
  • 不仅如此,CLR 还必须每次都Width导航到 Bitmap 对象和Height属性1000 万次。这比您每次尝试在位图上运行时所需的操作多 3000 万次。

改变:

for (int x = 0, position; x < imageColor.Width * imageColor.Height; x++)
{
    ...
}

到:

var heightWidth = imageColor.Width * imageColor.Height;
for (int x = 0, position; x < heightWidth; x++)
{
    ...
}

如果您缓存三种不同操作(R、G、B,有 255 个可能值)的所有潜在结果,并使用查找新值而不是计算新值 1000 万次,您还将看到巨大的性能提升.

这是完整的、非常快速的代码(比 ColorMatrix 快得多)。请注意,我已将所有可能的预先计算的值移动到局部变量中,并且在循环中涉及的工作绝对最少。

                var lookupR = new byte[256];
                var lookupG = new byte[256];
                var lookupB = new byte[256];
                var rVal = hdtv ? 0.114 : 0.0722;
                var gVal = hdtv ? 0.587 : 0.7152;
                var bVal = hdtv ? 0.299 : 0.2126;
                for (var originalValue = 0; originalValue < 256; originalValue++)
                {
                    var r = (byte)(originalValue * rVal);
                    var g = (byte)(originalValue * gVal);
                    var b = (byte)(originalValue * bVal);
                    // Just in case...
                    if (r > 255) r = 255;
                    if (g > 255) g = 255;
                    if (b > 255) b = 255;
                    lookupR[originalValue] = r;
                    lookupG[originalValue] = g;
                    lookupB[originalValue] = b;
                }
                unsafe
                {
                    var pointer = (byte*)(void*)bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
                    var bytesPerPixel = getBytesPerPixel(bitmap);
                    var heightWidth = bitmap.Width * bitmap.Height;
                    for (var y = 0; y < heightWidth; ++y)
                    {
                        var value = (byte) (lookupR[pointer[0]] + lookupG[pointer[1]] + lookupB[pointer[2]]);
                        pointer[0] = value;
                        pointer[1] = value;
                        pointer[2] = value;
                        pointer += bytesPerPixel;
                    }
                    bitmap.UnlockBits();
                }
                break;
于 2014-04-20T02:49:47.703 回答
3

我遇到了一个类似的性能问题,我需要遍历一组位图数据。我发现在引用位图的宽度或高度属性或作为循环的边界时,性能会受到显着影响,就像您对 imagecolor.width 和 .height 所做的那样。通过简单地在循环外声明一个整数并提前缓存位图的高度和宽度,我将循环时间减半。

于 2012-10-27T04:19:05.403 回答
2

如果要转换为灰度,请尝试改用 ColorMatrix 转换。来自:https ://web.archive.org/web/20141230145627/http://bobpowell.net/grayscale.aspx

   Image img = Image.FromFile(dlg.FileName);
        Bitmap bm = new Bitmap(img.Width,img.Height);
        Graphics g = Graphics.FromImage(bm); 

  

        
        ColorMatrix cm = new ColorMatrix(new float[][]{   new float[]{0.5f,0.5f,0.5f,0,0},
                                  new float[]{0.5f,0.5f,0.5f,0,0},
                                  new float[]{0.5f,0.5f,0.5f,0,0},
                                  new float[]{0,0,0,1,0,0},
                                  new float[]{0,0,0,0,1,0},
                                  new float[]{0,0,0,0,0,1}}); 

        

        /* 

        //Gilles Khouzams colour corrected grayscale shear
        ColorMatrix cm = new ColorMatrix(new float[][]{   new float[]{0.3f,0.3f,0.3f,0,0},
                                  new float[]{0.59f,0.59f,0.59f,0,0},
                                  new float[]{0.11f,0.11f,0.11f,0,0},
                                  new float[]{0,0,0,1,0,0},
                                  new float[]{0,0,0,0,1,0},
                                  new float[]{0,0,0,0,0,1}}); 

        */ 


        ImageAttributes ia = new ImageAttributes();
        ia.SetColorMatrix(cm);
        g.DrawImage(img,new Rectangle(0,0,img.Width,img.Height),0,0,img.Width,img.Height,GraphicsUnit.Pixel,ia);
        g.Dispose();
于 2012-10-09T18:54:28.830 回答
0

我猜想编组和复制占用了大量时间。此链接描述了 3 种图像灰度化方法。

于 2012-10-09T21:06:55.720 回答