C# - 查找图像的边界(不是大小)

时间:2010-08-11 17:13:03

标签: c# recursion bitmap stack-overflow image-manipulation

我正在开发一个应用程序来均匀地分割图像网格并使图像居中(基于它们的相似性)。到目前为止,我可以设法修复一个小尺寸的图像网格,但每当我尝试更大的“精灵”大小(例如100x100)时,我就会出现Stack Overflow错误。

是的我正在使用递归,但每当检查一个像素时,我设置一个布尔值来停用它,将它复制到一个列表并继续检查其他(在所有方向),直到列表填满一个来自网格的图像。我不确定这是否是最好的方式,因为对于每个调用,我调用相同的方法7次(假设有7个相邻的像素尚未检查)...直到没有像素可以检查,并且我可以转到网格中的下一个图像。

我尝试跟踪错误开始发生的位置,检查了大约1600像素并将它们添加到List中。 MyPixel是一个包含4个变量的类:x(int),y(int),color(Color)和checked(bool)

    public void processSprite(int i, int j)
    {
        //OOO
        //OXO
        //OOO
        pixeltemp.Add(new MyPixel(imap.pixels[i, j].x, imap.pixels[i, j].y, imap.pixels[i, j].color));
        imap.pixels[i, j].read = true;
        //OOO
        //OOX
        //OOO
        try
        {
            if (!imap.pixels[i + 1, j].read)
            {
                if (imap.pixels[i + 1, j].color.A == 0) //Found a Border
                {
                    imap.pixels[i + 1, j].read = true;
                }
                else
                {
                    processSprite(i + 1, j);
                }
            }
        }
        //... (code goes on)
    }
  • pixeltemp是保存图像(List<MyPixel>)
  • 的临时像素列表
  • imap包含整个图片(List<MyPixel>)

我想这不是内存问题,因为我的应用只占用了大约16mb。

我的问题是,如果不是无限递归,为什么我会出现“堆栈溢出”错误?有更简单的方法吗?我认为我的代码看起来很难看,我只是不知道如何让它变得更好。

提前致谢!

1 个答案:

答案 0 :(得分:1)

堆栈溢出不是由无限递归引起的,而是由进程可以处理的更多递归(或者更确切地说,调用堆栈)引起的。在您的情况下,每次递归调用processSprite将对processSprite进行相同数量的递归调用。因此,在没有找到边界的1600像素的最坏情况下,您的调用树将如下所示:

  processSprite(0, j)
    processSprite(1, j)
      processSprite(2, j)
        ...
          processSprite(1599, j) <-- That's 1600 call frames,
                                     enough for an overflow.

您需要将算法重新组织为执行深度优先搜索的线性循环,如果您想要找到最接近起点的像素,则可能采用螺旋模式。我确信已经有其他已经开发出的其他spiffy算法来解决这个问题。

修改

我想我现在更清楚你要解决的问题了。听起来你的图像可能包含多个由0-alpha像素包围的图像切片,并且您希望找到每个切片的边界矩形。这看起来像一个有趣的问题需要解决,所以我实现了它:

IEnumerable<Rectangle> FindImageTiles(Bitmap compositeImage)
{
    var result = new List<Rectangle>();

    // Scan for a non-empty region that hasn't already been "captured"
    for (var x = 0; x < compositeImage.Width; x++)
    {
        for (var y = 0; y < compositeImage.Height; y++)
        {
            // Only process the pixel if we don't have a rectangle that
            // already contains this and if it's not empty
            if (!result.Any(r => r.Contains(x, y)) 
                && compositeImage.GetPixel(x, y).A != 0)
            {
                // Now that we've found a point, create a rectangle
                // surrounding that point, then expand outward until
                // we have a bounding rectangle that doesn't intersect
                // with the tile
                var rect = new Rectangle(x - 1, y - 1, 2, 2);
                bool foundBounds = false;
                while (!foundBounds)
                {
                    var xRange = Enumerable.Range(rect.Left, rect.Right)
                        .Where(px => px >= 0 && px < compositeImage.Width);
                    var yRange = Enumerable.Range(rect.Top, rect.Bottom)
                        .Where(py => py >= 0 && py < compositeImage.Height);

                    // Adjust the top
                    if (rect.Top >= 0 
                        && xRange
                            .Select(bx => compositeImage.GetPixel(bx, rect.Top))
                            .Any(p => p.A != 0))
                    {
                        rect.Y--;
                        rect.Height++;
                    }
                    else if (rect.Bottom < compositeImage.Height
                        && xRange
                            .Select(bx => compositeImage.GetPixel(bx, rect.Bottom))
                            .Any(p => p.A != 0))
                    {
                        rect.Height++;
                    }
                    else if (rect.Left >= 0
                        && yRange
                            .Select(by => compositeImage.GetPixel(rect.Left, by))
                            .Any(p => p.A != 0))
                    {
                        rect.X--;
                        rect.Width++;
                    }
                    else if (rect.Right < compositeImage.Width
                        && yRange
                            .Select(by => compositeImage.GetPixel(rect.Right, by))
                            .Any(p => p.A != 0))
                    {
                        rect.Width++;
                    }
                    else
                    {
                        foundBounds = true;
                    }
                }
                result.Add(rect);
            }
        }
    }

    return result;
}