比锁定位更快的图像处理

vko*_*ves 4 c# image image-processing lockbits edge-detection

我一直在研究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;
}
Run Code Online (Sandbox Code Playgroud)

此代码适用于每个像素,其颜色使用锁定位获取.

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

顺便说,该项目是在GitHub上,在这里

pli*_*nth 6

你真的以一个百分比的形式传递浮点数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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

// 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;
}
Run Code Online (Sandbox Code Playgroud)

现在,各种可怕的指针变形以减少阵列访问等等.如果所有这个指针都让你觉得不舒服,你可以为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*一样大(它可能更大,因为扫描线经常被填充).

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

所以当我有类似的东西:

Math.Abs(*p1++ - *p2++) < thresh
Run Code Online (Sandbox Code Playgroud)

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

  1. 取p1指向的字节并保持住它
  2. 将1添加到p1(这是++ - 它使指针指向下一个字节)
  3. 取p2指向的字节并保持住它
  4. 将1添加到p2
  5. 从步骤1中减去步骤3
  6. 传递给Math.Abs.

这背后的原因是,从历史上看,读取字节的内容并向前移动是一种非常常见的操作,并且许多CPU构建成一对指令的单个操作,这些指令流入一个周期左右.

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

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

p2    : B
p2 + 1: G
p2 + 2: R
Run Code Online (Sandbox Code Playgroud)

因此IsMatching,在踩过内存时,绝对差异就是这样.

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

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


Rob*_*ani 5

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

using (byte* bytePtr = ptr)
{
    //code goes here
}
Run Code Online (Sandbox Code Playgroud)

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