比锁定位更快的图像处理

时间:2013-04-18 18:11:43

标签: c# image image-processing edge-detection lockbits

我一直在使用C#中的边缘检测程序,为了让它运行得更快,我最近使用了锁定位。但是,lockBits仍然没有我想要的那么快。虽然问题可能是我的一般算法,但我也想知道是否有比我可以用于图像处理的lockBits更好的东西。

如果问题是算法,这是一个基本的解释。浏览一系列颜色(使用锁定位表示像素),并为每种颜色检查该像素周围的八个像素的颜色。如果这些像素与当前像素不够匹配,则将当前像素视为边缘。

这是定义像素是否为边缘的基本代码。它采用了九种颜色的Color [],第一种是要检查的像素。

public Boolean isEdgeOptimized(Color[] colors)
{
    //colors[0] should be the checking pixel
    Boolean returnBool = true;
    float percentage = percentageInt; //the percentage used is set
    //equal to the global variable percentageInt

    if (isMatching(colors[0], colors[1], percentage) &&
            isMatching(colors[0], colors[2], percentage) &&
            isMatching(colors[0], colors[3], percentage) &&
            isMatching(colors[0], colors[4], percentage) &&
            isMatching(colors[0], colors[5], percentage) &&
            isMatching(colors[0], colors[6], percentage) &&
            isMatching(colors[0], colors[7], percentage) &&
            isMatching(colors[0], colors[8], percentage))
    {
        returnBool = false;
    }
    return returnBool;
}

此代码适用于每个像素,其颜色使用lockbits获取。

基本上,问题是,如何让我的程序运行得更快?这是我的算法,还是我可以使用比lockBits更快的东西?

顺便说一句,该项目位于gitHub上,here

4 个答案:

答案 0 :(得分:5)

而不是将每个图像复制到byte[],然后复制到Color[],为每个像素创建另一个临时Color[9],然后使用SetPixel设置颜色,使用/unsafe标记进行编译,将方法标记为不安全,将复制替换为byte[]Marshal.Copy为:

using (byte* bytePtr = ptr)
{
    //code goes here
}

确保使用设置正确的字节替换SetPixel调用。这不是LockBits的问题,你需要LockBits,问题在于你对与处理图像有关的其他一切都效率低下。

答案 1 :(得分:5)

您是否真的将浮点数作为isMatching的百分比传递?

我在GitHub上看了你的代码isMatching,好吧,yikes。你从Java移植了这个,对吧? C#使用bool而非Boolean虽然我不确定,但我不喜欢那些做了那么多拳击和拆箱的代码。此外,当您不需要时,您需要进行大量的浮点乘法和比较:

public static bool IsMatching(Color a, Color b, int percent)
{
    //this method is used to identify whether two pixels, 
    //of color a and b match, as in they can be considered
    //a solid color based on the acceptance value (percent)

    int thresh = (int)(percent * 255);

    return Math.Abs(a.R - b.R) < thresh &&
           Math.Abs(a.G - b.G) < thresh &&
           Math.Abs(a.B - b.B) < thresh;
}

这将减少您每个像素的工作量。我仍然不喜欢它,因为我试图避免在每像素循环中间进行方法调用,尤其是每像素8x循环。我将方法设为静态以减少未使用的实例。仅仅这些变化可能会使你的表现翻倍,因为我们只做了1次乘法,没有拳击,现在正在使用&amp;&amp; amp;固有的短路。减少工作。

如果我这样做,我更有可能做这样的事情:

// assert: bitmap.Height > 2 && bitmap.Width > 2
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                      ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

int scaledPercent = percent * 255;
unsafe {
    byte* prevLine = (byte*)data.Scan0;
    byte* currLine = prevLine + data.Stride;
    byte* nextLine = currLine + data.Stride;

    for (int y=1; y < bitmap.Height - 1; y++) {

       byte* pp = prevLine + 3;
       byte* cp = currLine + 3;
       byte* np = nextLine + 3;
       for (int x = 1; x < bitmap.Width - 1; x++) {
           if (IsEdgeOptimized(pp, cp, np, scaledPercent))
           {
               // do what you need to do
           }
           pp += 3; cp += 3; np += 3;
       }
       prevLine = currLine;
       currLine = nextLine;
       nextLine += data.Stride;
    }
}

private unsafe static bool IsEdgeOptimized(byte* pp, byte* cp, byte* np, int scaledPecent)
{
    return IsMatching(cp, pp - 3, scaledPercent) &&
           IsMatching(cp, pp, scaledPercent) &&
           IsMatching(cp, pp + 3, scaledPercent) &&
           IsMatching(cp, cp - 3, scaledPercent) &&
           IsMatching(cp, cp + 3, scaledPercent) &&
           IsMatching(cp, np - 3, scaledPercent) &&
           IsMatching(cp, np, scaledPercent) &&
           IsMatching(cp, np + 3, scaledPercent);
}

private unsafe static bool IsMatching(byte* p1, byte* p2, int thresh)
{
    return Math.Abs(p1++ - p2++) < thresh &&
           Math.Abs(p1++ - p2++) < thresh &&
           Math.Abs(p1 - p2) < thresh;
}

现在各种可怕的指针都会减少数组访问等等。如果所有这个指针都让你觉得不舒服,你可以为prevLine,currLine和nextLine分配字节数组,并为每一行做一个Marshal.Copy。

算法是这样的:从顶部和左边开始一个像素并迭代图像中除了外边缘之外的每个像素(没有边缘条件!耶!)。我一直指向每一行的开头,prevLine,currLine和nextLine。然后,当我开始x循环时,我组成pp,cp,np,它们是前一个像素,当前像素和下一个像素。目前的像素真的是我们关心的。 pp是它正上方的像素,np正好在它下面。我将这些传递给IsEdgeOptimized,它查看cp,为每个调用IsMatching。

现在这假设每像素24比特。如果您要查看每像素32位,那么所有那些神奇的3都需要4,但除此之外代码不会改变。您可以根据需要参数化每个像素的字节数,以便它可以处理。

仅供参考,像素中的通道通常为b,g,r,(a)。

颜色 存储为内存中的字节。您的实际位图,如果是24位图像,则存储为字节块。扫描线的宽度为data.Stride个字节,至少与行中像素数的3 *一样大(由于扫描线经常被填充,因此可能更大)。

当我在C#中声明类型byte *的变量时,我做了一些事情。首先,我说这个变量包含内存中一个字节位置的地址。其次,我说我要违反.NET中的所有安全措施,因为我现在可以在内存中读写任何字节,这可能很危险。

所以当我有类似的东西时:

Math.Abs(*p1++ - *p2++) < thresh

它说的是什么(这将很长):

  1. 取p1指向并保持的字节
  2. 将1添加到p1(这是++ - 它使指针指向下一个字节)
  3. 取p2指向并保持的字节
  4. 将1添加到p2
  5. 从步骤1减去步骤3
  6. 将其传递给Math.Abs
  7. 这背后的原因是,从历史上看,读取一个字节的内容并向前移动是一个非常常见的操作,并且很多CPU构建成一个指令的单个操作,这些指令流入一个周期左右。< / p>

    当我们输入IsMatching时,p1指向像素1,p2指向像素2,在内存中它们的布局如下:

    p1    : B
    p1 + 1: G
    p1 + 2: R
    
    p2    : B
    p2 + 1: G
    p2 + 2: R
    

    所以IsMatching只是通过记忆来实现绝对差异。

    您的后续问题告诉我,您并非真正理解指针。没关系 - 你可以学习它们。老实说,这些概念确实不是那么难,但是它们的问题在于,如果没有很多经验,你很可能会自己开枪,也许你应该考虑在你的代码上使用一个分析工具并冷却最坏的热点并称之为好。

    例如,您要注意我从第一行到倒数第二行,第一列到倒数第二列。这是为了避免必须处理&#34;我无法读取第0行&#34;以上的情况,这消除了一大类潜在的错误,这些错误可能涉及在合法的内存块之外读取,这可能是在许多运行时条件下都是良性的。

答案 2 :(得分:0)

如果要使用并行任务执行,可以在System.Threading.Tasks命名空间中使用Parallel类。以下链接有一些样本和解释。

答案 3 :(得分:0)

您可以将图像分割成10个位图并处理每个位图,然后最终将它们组合起来(只是一个想法)。

相关问题