【问题标题】:Bitmap Bounding Rectangle Algorithm C++位图边界矩形算法 C++
【发布时间】:2012-06-17 20:18:33
【问题描述】:

我目前将位图像素数据存储在一个字符数组中。我想知道根据图像的边界框裁剪图像的最有效算法是什么。

我在下面提供了一个相对准确的示例来说明我想要实现的目标。基于基本“像素颜色”。

Bounding Box Example

【问题讨论】:

  • 你能说说数组的内容吗?如何获取像素所属位置的索引,即 pixel[i][j]
  • 我已经将像素数据逐个像素地读出到一个指向字符指针的多维指针中。

标签: c++ algorithm colors bitmap pixel


【解决方案1】:

蛮力很好,但你可以更好地使用加速StretchBlt 来计算水平和垂直投影。

获取位图,将其绘制到一个 1 像素高的全宽矩形上。

获取位图,将其绘制到一个 1 像素宽、全高的矩形上。

这两者都必须处理整个图像,但将使用高度并行的 GPU 加速渲染来完成。

根据这些计算界限。

好的,如果整列的平均值恰好是背景颜色,则结果可能会出错。

【讨论】:

  • 有趣,我从来没有想过(我对图像处理的了解已经足够危险了,仅此而已)。谢谢+1
【解决方案2】:

好吧,对于具有已知输入格式的如此简单的东西(即图像中只有一个与背景之间具有高对比度的圆圈),您可以很容易地暴力破解它。只需遍历图像数据并寻找这些对比度差异。保存最顶部、最左侧、最右侧和最底部的位置,您就完成了。

如果图像不是这样简单的格式,那么您将需要更高级的东西,例如blob detection algorithm

编辑:仅供参考,前段时间我写了这个蛮力算法来做一些非常相似的事情。它远非完美且没有高度优化,尽管它很简单并且方法应该清晰。我将在这里发布整个内容(不要太苛刻地评价我;我是几年前在自学 C# 时写的)。该算法使用强度阈值来查找圆(与对比度相反,我的输入非常明确)。

/// <summary>
/// Locates the spot of light on the image and returns an AnalysisResults object.
/// </summary>        
unsafe private Rectangle AnalyzeImage( )
{
    // function assumes 24bpp RGB format
    Bitmap image = m_originalImage;
    if ( image.PixelFormat != PixelFormat.Format24bppRgb )
    {
        throw new ArgumentException( "Image format must be 24bpp RGB" );
    }

    // using the GDI+ GetPixel method is too slow for a 
    // 1280x1024 image, so get directly at the image buffer instead.                    
    GraphicsUnit unit = GraphicsUnit.Pixel;
    imageRect = Rectangle.Truncate( image.GetBounds( ref unit ) );                

    BitmapData data = image.LockBits( imageRect, ImageLockMode.ReadWrite, image.PixelFormat );

    int intensityThreshold = IntensityThreshold;
    // initialize 'top' to -1 so that we can check if it has been set before setting it.
    // once a valid value for 'top' is found we don't need to set it again.
    int top = -1;

    // search for the left point starting at a high value so that we can simply
    // pull it towards the left as we find pixels inside of the spot.
    int left = imageRect.Right;
    int bottom = 0;
    int right = 0;

    // locate the circle in the image by finding pixels with average
    // intesity values above the threshold and then performing
    // some edge checks to set the top, left, right, and bottom values.
    int height = imageRect.Height + imageRect.Y;
    int width = imageRect.Width + imageRect.X;
    byte* pSrc = ( byte* ) data.Scan0;

    int rowOffset = 1;            
    for ( int y = imageRect.Y ; y < height ; ++y, ++rowOffset )
    {
        for ( int x = imageRect.X ; x < width ; ++x )
        {
            // windows stores images in memory in reverse byte order ( BGR )
            byte b = *pSrc++;
            byte g = *pSrc++;
            byte r = *pSrc++;                    

            // get the average intensity and see if it is above the threshold
            int intensity = GetIntensity( r, g, b );
            if ( intensity > intensityThreshold )
            {
                if ( !StrayPixel( pSrc, data, intensityThreshold ) )
                {
                    // found a point in the circle
                    if ( top == -1 ) top = y;
                    if ( x < left ) left = x;
                    if ( y > bottom ) bottom = y;
                    if ( x > right ) right = x;
                }
            }                    
        }

        // next row
        pSrc = ( ( byte* ) data.Scan0 ) + ( rowOffset * data.Stride );
    }

    image.UnlockBits( data );            

    // bounding rectangle of our spot
    return Rectangle.FromLTRB( left, top, right, bottom );
}                   

/// <summary>
/// Returns true if the pixel at (x,y) is surrounded in four 
/// directions by pixels that are below the specified intesity threshold.
/// This method only checks the first pixel above, below, left, and right
/// of the location currently pointed to by 'pSrc'.
/// </summary>        
private unsafe bool StrayPixel( byte* pSrc, BitmapData data, int intensityThreshold )
{
    // this method uses raw pointers instead of GetPixel because
    // the original image is locked and this is the only way to get at the data.

    // if we have a pixel with a relatively high saturation 
    // value we can safely assume that it is a camera artifact.
    if ( Color.FromArgb( pSrc[ 2 ], pSrc[ 1 ], *pSrc ).GetSaturation( ) > MAX_PIXEL_SAT )
    {
        return true;
    }

    byte* pAbove = pSrc - data.Stride;
    int above = GetIntensity( pAbove[ 2 ], pAbove[ 1 ], *pAbove );

    byte* pRight = pSrc + 3;
    int right = GetIntensity( pRight[ 2 ], pRight[ 1 ], *pRight );

    byte* pBelow = pSrc + data.Stride;
    int below = GetIntensity( pBelow[ 2 ], pBelow[ 1 ], *pBelow );

    byte* pLeft = pSrc - 3;
    int left = GetIntensity( pLeft[ 2 ], pLeft[ 1 ], *pLeft );

    // if all of the surrounding pixels are below the threshold we have found a stray
    return above < intensityThreshold &&
           right < intensityThreshold &&
           below < intensityThreshold &&
           left  < intensityThreshold;
}

/// <summary>
/// Returns the average of ( r, g, b )
/// </summary>  
private int GetIntensity( byte r, byte g, byte b )
{
    return GetIntensity( Color.FromArgb( r, g, b ) );
}

/// <summary>
/// Returns the average of ( c.r, c.g, c.b )
/// </summary>
private int GetIntensity( Color c )
{
    return ( c.R + c.G + c.B ) / 3;
}

【讨论】:

  • 我目前正在按照描述进行操作,但是我在两个单独的嵌套 for 循环中进行操作。一个用于宽度,另一个用于高度。有没有一种方法可以在一个嵌套循环中保存所述位置?我想尽可能优化算法。
  • 只使用在循环外声明的四个变量。
  • @Aequitas:另外,从您的描述来看,您似乎正在逐列循环图像,即for(w = 0; w &lt; width; ++w) { for(y = 0; y &lt; height; ++y) {} },这意味着您可能会在每次读取时刷新缓存。您应该逐行循环,以便每次连续读取都在查看下一个连续的内存块。您的最外层循环应该是高度索引,最内层是宽度索引。
  • 我正在循环播放 Ed,但感谢您的提示。我唯一剩下的问题是最好测试对比度差异吗?目前我正在保存我的左上角像素。我不相信这是最可靠的方法。如果在边界内的某处添加了像素怎么办?
  • 不,总是比较相邻像素来判断对比度。不过,这又取决于您的要求,即您需要处理什么样的图像?从您的示例中,您可以简单地检查像素是否不是蓝色的。这是您需要考虑的唯一情况吗?如果您的输入未知,那么您需要更高级的算法。
【解决方案3】:

晚上好游戏!谢谢! 快速编写了一些 C# 代码来执行此蛮力:

public class Dimensions
{
    public int left = 0;
    public int right = 0;
    public int top = 0;
    public int bottom = 0;
}

public class ImageDetection
{
    private const int xSize = 2000;
    private const int ySize = 2000;

    private const int xMin = 1800;
    private const int yMin = 1800;
    private const int defaultPixelColor = 0;


    public void GetArray(out char[,] arr)
    {
        arr = new char[xSize, ySize];
        Random rand = new Random();

        for (int i=0; i<xSize; ++i)
        {
            for (int j=0; j<ySize; ++j)
            {
                var d = rand.NextDouble();

                if (d < 0.5)
                {
                    arr[i, j] = Convert.ToChar(defaultPixelColor);
                }
                else
                {
                    int theInt = Convert.ToInt32(255 * d);
                    arr[i, j] = Convert.ToChar(theInt);
                }
            }
        }

        // cut top
        for (int k = 0; k < (xSize - xMin); k++)
        {
            for (int l = 0; l < ySize; l++)
            {
                arr[k, l] = Convert.ToChar(defaultPixelColor);
            }
        }

        // cut bottom
        for (int k = xMin; k < xSize; k++)
        {
            for (int l = 0; l < ySize; l++)
            {
                arr[k, l] = Convert.ToChar(defaultPixelColor);
            }
        }

        // cut left
        for (int k = 0; k < xSize; k++)
        {
            for (int l = 0; l < (ySize - xMin); l++)
            {
                arr[k, l] = Convert.ToChar(defaultPixelColor);
            }
        }

        // cut right 
        for (int k = 0; k < xSize; k++)
        {
            for (int l = xMin; l < ySize; l++)
            {
                arr[k, l] = Convert.ToChar(defaultPixelColor);
            }
        }

    }

    public void WriteArr(ref char[,] arr)
    {
        char[] line = new char[xSize];

        // all lines
        for (int i=0; i<ySize; ++i)
        {
            // build one line
            for (int j = 0; j < xSize; ++j)
            {
                char curChar = arr[i, j];

                if (curChar == '\0')
                {
                    line[j] = '.';
                }
                else
                {
                    line[j] = curChar;
                }
            }

            string s = new string(line);
            s += "\r\n";
            //FileIO.WriteFileText
            System.IO.File.AppendAllText("Matrix.txt", s);
         }
    }

    public void DetectSize(ref char[,] arr, out Dimensions dim)
    {
        dim = new Dimensions();

        dim.left = xSize;
        dim.top = ySize;
        dim.right = 0;
        dim.bottom = 0;

        for (int i = 0; i < xSize; ++i)
        {
            for (int j = 0; j < ySize; ++j)
            {
                if (!arr[i, j].Equals(Convert.ToChar(defaultPixelColor)))
                {
                    if (i < dim.left)
                    {
                        dim.left = i;
                    }

                    if (j < dim.top)
                    {
                        dim.top = j;
                    }

                    if (i > dim.right)
                    {
                        dim.right = i;
                    }

                    if (j > dim.bottom)
                    {
                        dim.bottom = j;
                    }
                }
            }
        }
    }
}

【讨论】:

  • 您最好交换循环,因为您当前正在按列循环,即在内存中跳跃而不是顺序访问元素并有效地使用缓存。此外,锯齿状数组比多维数组更快,当然性能在图像处理算法中也很重要。
  • 除了使用四个单独的定向嵌套 for 循环之外,肯定还有更有效的方法吗?这几乎就是我之前完成蛮力技术的方式(也是首先发表这篇文章的原因)。目前我正在使用两个带有额外变量的循环来从高度/宽度向后检查到 0。
  • 感谢您的帮助 Ed,这就是我现在尝试的方法。我正在使用一个带有两个额外变量的嵌套 for 循环来跟踪反向迭代,如果这有意义的话!
  • @Aequitas:我发布了一个示例,它可能会有所帮助(虽然它没有将 1:1 映射到您的问题,但很接近)。
  • 用秒表(高分辨率计时器)测量...如果循环与我发布的一样,我的平均时间约为 0.07 秒。如果交换它的平均时间约为 0.1 秒。慢了 50%……我的代码肯定有问题! (我只是按原样交换了两个循环头)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-08
  • 2014-08-13
  • 2014-12-20
  • 1970-01-01
  • 2014-09-22
  • 2020-07-11
相关资源
最近更新 更多