0

我最近遇到了裁剪和调整图像大小的问题。我需要裁剪图像的“主要内容”,例如,如果我有一张与此类似的图像:(来源:msn.com替代文字

结果应该是带有 msn 内容的图像,没有白色边距(左和右)。

我在 X 轴上搜索第一个和最后一个颜色变化,在 Y 轴上搜索相同的东西。问题是逐行遍历图像需要一段时间。对于 2000x1600 像素的图像,返回 CropRect => x1,y1,x2,y2 数据最多需要 2 秒。

我尝试为每个坐标进行遍历并在找到的第一个值处停止,但它不适用于所有测试用例..有时返回的数据不是预期的数据,并且操作的持续时间相似..

知道如何减少遍历时间并发现围绕“主要内容”的矩形吗?

public static CropRect EdgeDetection(Bitmap Image, float Threshold)
        {
            CropRect cropRectangle = new CropRect();
            int lowestX = 0;
            int lowestY = 0;
            int largestX = 0;
            int largestY = 0;

            lowestX = Image.Width;
            lowestY = Image.Height;

            //find the lowest X bound;
            for (int y = 0; y < Image.Height - 1; ++y)
            {
                for (int x = 0; x < Image.Width - 1; ++x)
                {
                    Color currentColor = Image.GetPixel(x, y);
                    Color tempXcolor = Image.GetPixel(x + 1, y);
                    Color tempYColor = Image.GetPixel(x, y + 1);
                    if ((Math.Sqrt(((currentColor.R - tempXcolor.R) * (currentColor.R - tempXcolor.R)) +
                        ((currentColor.G - tempXcolor.G) * (currentColor.G - tempXcolor.G)) +
                        ((currentColor.B - tempXcolor.B) * (currentColor.B - tempXcolor.B))) > Threshold)) 
                    {
                        if (lowestX > x)
                            lowestX = x;

                        if (largestX < x)
                            largestX = x;
                    }

                    if ((Math.Sqrt(((currentColor.R - tempYColor.R) * (currentColor.R - tempYColor.R)) +
                        ((currentColor.G - tempYColor.G) * (currentColor.G - tempYColor.G)) +
                        ((currentColor.B - tempYColor.B) * (currentColor.B - tempYColor.B))) > Threshold))
                    {
                        if (lowestY > y)
                            lowestY = y;

                        if (largestY < y)
                            largestY = y;
                    }
                }                
            }

            if (lowestX < Image.Width / 4)
                cropRectangle.X = lowestX - 3 > 0 ? lowestX - 3 : 0;
            else
                cropRectangle.X = 0;

            if (lowestY < Image.Height / 4)
                cropRectangle.Y = lowestY - 3 > 0 ? lowestY - 3 : 0;
            else
                cropRectangle.Y = 0;

            cropRectangle.Width = largestX - lowestX + 8 > Image.Width ? Image.Width : largestX - lowestX + 8;
            cropRectangle.Height = largestY + 8 > Image.Height ? Image.Height - lowestY : largestY - lowestY + 8;
            return cropRectangle;
        }
    }
4

3 回答 3

3

一种可能的优化是使用Lockbits直接访问颜色值,而不是通过速度慢得多的 GetPixel。

LockBits 上的Bob Powell 页面是一个很好的参考。

另一方面,我的测试表明,如果您尝试编写与 GetPixel 等效的 GetPixelFast 并将其作为替代品放入,与 Lockbits 相关的开销会使该方法变慢。相反,您需要确保一次点击而不是多次点击完成所有像素访问。如果您不在每个像素上锁定/解锁,这应该非常适合您的代码。

这是一个例子

BitmapData bmd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);

byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);

//                           Blue                    Green                   Red 
Color c = Color.FromArgb(row[x * pixelSize + 2], row[x * pixelSize + 1], row[x * pixelSize]);

b.UnlockBits(bmd);

还有两点需要注意:

  1. 此代码不安全,因为它使用指针
  2. 这种方法取决于位图数据中的像素大小,因此您需要从 bitmap.PixelFormat 派生 pixelSize
于 2010-05-24T15:29:03.883 回答
2

GetPixel 可能是你的罪魁祸首(我建议运行一些分析测试来追踪它),但你可以像这样重构算法:

  1. 从左到右和从右到左扫描第一行(y = 0)并记录第一个和最后一个边缘位置。没有必要检查所有像素,因为您需要极端边缘。
  2. 扫描所有后续行,但现在我们只需要向外搜索(从中心到边缘),从我们最后一个已知的最小边缘开始。我们想要找到极值边界,所以我们只需要在可以找到新极值的区域中搜索。
  3. 对列重复前两个步骤,建立初始极值,然后使用这些极值迭代地限制搜索。

如果您的图像通常主要是内容,这应该会大大减少比较次数。最坏的情况是一个完全空白的图像,这可能比穷举搜索效率低。

在极端情况下,图像处理也可以受益于并行性(分割图像并在多核 CPU 上的多个线程中处理它),但这是相当多的额外工作,您仍然需要进行其他更简单的更改。线程开销往往会限制这种技术的适用性,如果您希望“实时”运行这个东西,并专门重复处理传入的数据(以弥补初始设置成本),这主要是有帮助的。

于 2010-05-24T16:17:51.880 回答
0

这不会使订单变得更好......但是如果您将阈值平方,则不需要进行平方根,这非常昂贵。

这应该会显着提高速度。

于 2010-05-24T15:28:33.683 回答