2

我正在尝试创建一种方法,该方法将过滤掉给定灰度阈值以下的所有像素(例如,下面的都是黑色,上面的都是白色)。该方法有效,但没有我认为的那么快。

我决定使用该Parallel课程,但无论我设置什么,MaxDegreeOfParallelism我都没有获得任何速度优势。我也在位图上执行了一些其他操作,并且操作的总时间,不管是什么MaxDegreeOfParallelism,总是在 170 毫秒左右。调试时,执行此过滤本身所需的时间大约需要 160 毫秒,所以我认为会有明显的整体差异。

我使用的是 i7 处理器,4 个物理内核,8 个逻辑内核。

编码:

Color black = System.Drawing.Color.FromArgb(0, 0, 0);
Color white = System.Drawing.Color.FromArgb(255, 255, 255);

int lowerBound = (int)((float)lowerBoundPercent * 255.0 / 100.0);
int upperBound = (int)((float)upperBoundPercent * 255.0 / 100.0);

int[][] border = new int[8][];
for (int i=0;i<8;i++)
{
    border[i] = new int[] { i*height/8, (i+1)*height/8-1};
}

Parallel.For(0, 8, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
    {
        for (int k = 0; k < width; k++)
        {
            for (int j = border[i][0]; j <= border[i][1]; j++)
            {
                Color pixelColor;
                int grayscaleValue;
                pixelColor = color[k][j];
                grayscaleValue = (pixelColor.R + pixelColor.G + pixelColor.B) / 3;
                if (grayscaleValue >= lowerBound && grayscaleValue <= upperBound)
                    color[k][j] = white;
                else
                    color[k][j] = black;
            }
        }
    });

color[][]是一个锯齿状数组System.Drawing.Color

问题:这正常吗?如果没有,我该怎么做才能改变它?

编辑:

像素提取:

Color[][] color;
color = new Color[bitmap.Width][];
for (int i = 0; i < bitmap.Width; i++)
{
    color[i] = new Color[bitmap.Height];
    for (int j = 0; j < bitmap.Height; j++)
    {
        color[i][j] = bitmap.GetOriginalPixel(i, j);
    }
}

Bitmap 是我自己的类 Bitmap 的一个实例:

public class Bitmap
{
    System.Drawing.Bitmap processed;
    //...
    public Color GetOriginalPixel(int x, int y) { return processed.GetPixel(x, y); }
    //...
}
4

2 回答 2

3

要回答关于为什么您的并行方法没有更快的主要问题,Parralel.For仅从一个线程开始,然后添加更多线程,因为它检测到更多线程可能有利于加快工作速度,请注意并行选项是Max DegreeOfParallelism不仅仅是DegreeOfParallelism。很简单,没有足够的循环迭代来启动足够多的线程以使其有效,您需要减少每次迭代的工作量。

尝试通过循环宽度而不是 8 块高度来为并行操作提供更多工作。

Color black = System.Drawing.Color.FromArgb(0, 0, 0);
Color white = System.Drawing.Color.FromArgb(255, 255, 255);

int lowerBound = (int)((float)lowerBoundPercent * 255.0 / 100.0) * 3;
int upperBound = (int)((float)upperBoundPercent * 255.0 / 100.0) * 3;

Parallel.For(0, width, k =>
    {
        for (int j = 0; j < height; j++)
        {
                Color pixelColor;
                int grayscaleValue;
                pixelColor = color[k][j];
                grayscaleValue = (pixelColor.R + pixelColor.G + pixelColor.B);
                if (grayscaleValue >= lowerBound && grayscaleValue <= upperBound)
                    color[k][j] = white;
                else
                    color[k][j] = black;
        }
    });

我不会同时做宽度和高度,那么你可能会遇到相反的问题,即没有给每次迭代足够的工作去做。

我强烈推荐你去下载并阅读Patterns for Parallel Programming,当讨论你应该做多少工作时,它进入了这个确切的例子Parallel.For。查看从 C# 版本第 26 页底部开始的“非常小的循环体”和“太细粒度、太粗粒度”的反模式,以了解您遇到的确切问题。

此外,我会考虑使用 LockBits 来读取和读取像素数据,而不是像我们在评论中讨论的那样使用 GetPixel 和 SetPixel。

于 2013-10-05T21:06:22.177 回答
3

使用LockBits我设法将时间从每帧约 165 毫秒减少到约 55 毫秒。然后我继续做更多的研究,并结合LockBits了不安全上下文中的指针操作和 Parallel.For 循环。结果代码:

位图类:

public class Bitmap
{
    System.Drawing.Bitmap processed;
    public System.Drawing.Bitmap Processed { get { return processed; } set { processed = value; } }
    // ...
}    

方法:

int lowerBound = 3*(int)((float)lowerBoundPercent * 255.0 / 100.0);
int upperBound = 3*(int)((float)upperBoundPercent * 255.0 / 100.0);

System.Drawing.Bitmap bp = bitmap.Processed;

int width = bitmap.Width;
int height = bitmap.Height;

Rectangle rect = new Rectangle(0, 0, width, height);
System.Drawing.Imaging.BitmapData bpData = bp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bp.PixelFormat);

unsafe
{
    byte* s0 = (byte*)bpData.Scan0.ToPointer();
    int stride = bpData.Stride;

    Parallel.For(0, height, y1 =>
    {
        int posY = y1 * stride;
        byte* cpp = s0 + posY;

        for (int x =0; x<width; x++)
        {
            int total = cpp[0] + cpp[1] + cpp[2];
            if (total >= lowerBound && total <= upperBound)
            {
                cpp[0] = 255;
                cpp[1] = 255;
                cpp[2] = 255;
                cpp[3] = 255;
            }
            else
            {
                cpp[0] = 0;
                cpp[1] = 0;
                cpp[2] = 0;
                cpp[3] = 255;
            }

            cpp += 4;
        }
    });
}

bp.UnlockBits(bpData);

通过循环中的这种工作划分,Parallel.For代码在 1-5 毫秒内执行,这意味着大约加快了 70 倍!

我尝试让循环的块大 4 倍和 8 倍,时间范围仍然是 1-5 毫秒,所以我不会讨论这个。无论如何,循环足够快。

非常感谢您的回答,Scott,并感谢大家在评论中的投入。

于 2013-10-06T07:11:39.400 回答