加速C#中的矩阵添加

Jea*_*rdi 11 c# image-processing

我想优化这段代码:

public void PopulatePixelValueMatrices(GenericImage image,int Width, int Height)
{            
        for (int x = 0; x < Width; x++)
        {
            for (int y = 0; y < Height; y++)
            {
                Byte  pixelValue = image.GetPixel(x, y).B;
                this.sumOfPixelValues[x, y] += pixelValue;
                this.sumOfPixelValuesSquared[x, y] += pixelValue * pixelValue;
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

这将用于图像处理,我们目前正在运行约200张图像.我们优化了GetPixel值以使用不安全的代码,我们没有使用image.Width或image.Height,因为这些属性增加了我们的运行时成本.

但是,我们仍然处于低速状态.问题是我们的图像是640x480,所以循环的中间被调用大约640x480x200倍.我想问一下是否有办法以某种方式加速它,或者让我相信它的速度足够快.也许一种方法是通过一些快速的Matrix Addition,或者Matrix Addition固有的n ^ 2操作无法加速它?

也许通过不安全的代码进行数组访问可以加快速度,但是我不知道如何去做,以及它是否值得花时间.可能不是.谢谢.

编辑:谢谢你的所有答案.

这是我们正在使用的GetPixel方法:

 public Color GetPixel(int x, int y)
    {
        int offsetFromOrigin = (y * this.stride) + (x * 3);
        unsafe
        {
            return Color.FromArgb(this.imagePtr[offsetFromOrigin + 2], this.imagePtr[offsetFromOrigin + 1], this.imagePtr[offsetFromOrigin]);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 18

尽管使用不安全的代码,很GetPixel可能是这里的瓶颈.您是否考虑过在一次调用中获取图像中所有像素的方法而不是每个像素一次?例如,Bitmap.LockBits可能是你的朋友......

在我的上网本中,一个非常简单的循环迭代640*480*200只需要大约100毫秒 - 所以如果你发现它一切都很慢,你应该再看一下循环内的位.

您可能需要考虑的另一个优化:避免使用多维数组.它们比单维阵列慢得多.

特别是,您可以拥有一个大小的一维数组,Width * Height并保留一个索引:

int index = 0;
for (int x = 0; x < Width; x++)
{
    for (int y = 0; y < Height; y++)
    {
        Byte pixelValue = image.GetPixel(x, y).B;
        this.sumOfPixelValues[index] += pixelValue;
        this.sumOfPixelValuesSquared[index] += pixelValue * pixelValue;
        index++;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用相同的简单测试工具,向2-D矩形阵列添加写入时,总循环时间超过200*640*480,最高可达850ms; 使用一维矩形阵列将其恢复到大约340ms - 所以它有点重要,目前你已经有两个每循环迭代.

  • 这不是从2-D到1-D的变化产生差异,而是你在阵列中移动的方式.您的1-D代码肯定会按顺序移动.您已经有效地将数据从行主要交错到列主要用于实现这一点(以前索引是y*Width + x,现在它是x*Height + y).改进引用的局部性是CPU缓存使用的真正胜利,但重新交错结果数组是错误的方法,你仍然严重访问行主要的源数据.交错源和目标相同并反转循环嵌套. (3认同)
  • @Paul:不安全的代码仍然执行数组边界检查. (2认同)
  • @Jon Skeet,Chloe:如果索引数组,则执行边界检查,而不是使用钉扎指针扫描它.但是,如果编写for循环以针对被索引的数组的Length成员进行测试,则边界检查也会在安全代码中进行优化. (2认同)

ani*_*arg 6

阅读这篇文章,其中还有一些代码,并提到了GetPixel的缓慢性.

链接文字

从文章中可以看出这是简单地反转位的代码.这也显示了LockBits的用法.

请务必注意,不安全的代码不允许您远程运行代码.

public static bool Invert(Bitmap b)
{

BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), 
                               ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 

int stride = bmData.Stride; 
System.IntPtr Scan0 = bmData.Scan0; 
unsafe 
{ 
    byte * p = (byte *)(void *)Scan0;
    int nOffset = stride - b.Width*3; 
    int nWidth = b.Width * 3;
    for(int y=0;y < b.Height;++y)
    {
        for(int x=0; x < nWidth; ++x )
        {
            p[0] = (byte)(255-p[0]);
            ++p;
        }
        p += nOffset;
    }
}

b.UnlockBits(bmData);

return true;
Run Code Online (Sandbox Code Playgroud)

}