【问题标题】:Scanning images for finding rectangles扫描图像以查找矩形
【发布时间】:2017-10-15 16:09:36
【问题描述】:

我正在尝试扫描一个恒定大小的图像并在其中找到绘制的矩形。 矩形可以有任何大小,但只有红色。

不是问题开始的地方。

我将使用一个已经编写好的函数,稍后我将在我的代码逻辑中使用它作为伪代码调用。

Rectangle Locate(Rectangle scanArea); // 在给定的扫描区域中扫描一个矩形。 如果没有找到矩形,则返回 null。

我的逻辑是这样的:

使用Locate() 函数以完整图像大小作为参数查找第一个初始红色矩形。 现在,划分其余区域,并继续递归扫描。 该算法逻辑的要点是,您永远不会检查已检查的区域,并且您不必使用任何条件,因为 scanArea 参数始终是您之前没有扫描过的新区域(这要归功于分割技术)。 划分过程是这样进行的:当前找到的矩形的右侧区域,底部区域,左侧区域。

这是一个说明该过程的图像。 (白色虚线矩形和黄色箭头不是图像的一部分,我添加它们只是为了说明。) 如您所见,一旦找到一个红色矩形,我就会继续扫描它的右侧、底部和左侧。递归。

下面是该方法的代码:

List<Rectangle> difList=new List<Rectangle>();

private void LocateDifferences(Rectangle scanArea)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define right area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.

    difList.Add(rectFound);

    LocateDifferences(rightArea);
    LocateDifferences(bottomArea);
    LocateDifferences(leftArea);
}

到目前为止一切正常,它确实找到了每个红色矩形。 但有时,矩形保存为几个矩形。出于对我来说显而易见的原因: 重叠矩形大小写。

一个有问题的案例例如:

现在,在这种情况下,程序按计划找到了第一个红色区域,但是由于右侧区域仅从整个第二个区域的中间开始,因此它不会从第二个红色矩形的开头开始扫描!

以类似的方式,我可以划分区域,使底部区域从scanArea 的开头延伸到结尾,如下所示: 但是现在我们在扫描foundRect矩形左右重叠的矩形时会遇到问题,例如在这种情况下:

我只需要把每个矩形都放在一块。 我想获得与我的代码逻辑相结合的任何帮助或建议——因为它工作得很好,但我认为在递归方法中只需要一点点或两个额外的条件。我不知道该怎么做,如果能提供任何帮助,我将不胜感激。

如果有什么不清楚的地方,尽管说出来,我会尽我所能解释! 谢谢!

当然,这不是我面临的真正问题,它只是一个小演示,可以帮助我解决我正在处理的真正问题(这是一个实时互联网项目)。

【问题讨论】:

  • 基本(虽然可能不是最有效的)解决方案是找到分割的矩形,然后进行一些后期处理以将它们合并在一起。例如,对于每个矩形,查看是否有另一个矩形的右上角和右下角与另一个矩形的左上角和左下角对齐。将它们合并在一起。然后找到右下角和左下角与右上角和左上角匹配的位置并将它们合并在一起。将合并的矩形放回您的集合中。然后重复直到找不到这样的匹配。
  • @pinkfloydx33 做一个嵌套循环?因为我必须将每个区域与列表中的所有其他区域进行比较.. 你是这个意思吗?
  • 处理图像的副本并在找到后用背景颜色填充每个矩形,然后从第一次找到矩形的位置重新开始检查? (或者是检查图像的很大一部分 n+1 次以找到 n 个矩形太耗时?)
  • 这是一个糟糕的逻辑,你最好写一个完全不同的算法。解决有缺陷的逻辑从来都不是正确的做法。它会花费更多的时间来实现,并且比从一开始就正确的算法要慢。
  • @m69 当然!我刚刚写了一个小解释 Locate 函数是如何工作的:找到第一个(最左上角)红色像素,然后它继续向右扫描,然后当它没有找到更多红色像素时,它开始扫描高度,继续向下移动 - 轻松简单。由于那里使用了本机方法(例如memcmp()),它的工作速度非常快。扫描功能仅限于扫描区域参数。如果您有任何其他建议......或者如果您想查看完整功能,请随时询问!我只是不认为有瓶颈,所以我没有发布它

标签: c# algorithm image-processing computer-vision


【解决方案1】:

通过扫描图像一次就可以找到多个矩形的算法不必很复杂。与您现在所做的主要区别在于,当您找到矩形的顶角时,您不应该立即找到宽度和高度并存储矩形,而是将其暂时保存在未完成的矩形列表中,直到你遇到了它的底角。然后可以使用此列表有效地检查每个红色像素是新矩形的一部分,还是您已经找到的矩形。考虑这个例子:

我们开始从上到下和从左到右扫描。在第 1 行中,我们在位置 10 处找到了一个红色像素;我们继续扫描,直到找到下一个黑色像素(或到达行尾);现在我们可以将它存储在一个未完成的矩形列表中,如 {left,right,top}:

unfinished: {10,13,1}  

在扫描下一行时,我们会遍历未完成的矩形列表,因此我们知道什么时候需要一个矩形。当我们到达位置 10 时,我们按预期找到了一个红色像素,我们可以跳到位置 14 并遍历未完成的矩形。当我们到达位置 16 时,我们发现了一个意想不到的红色像素,并继续到位置 19 的第一个黑色像素,然后将第二个矩形添加到未完成列表中:

unfinished: {10,13,1},{16,18,2}  

扫描第 3 到 5 行后,我们得到了这个列表:

unfinished: {1,4,3},{6,7,3},{10,13,1},{16,18,2},{21,214}  

请注意,我们在迭代列表时插入新找到的矩形(使用例如链表),以便它们从左到右按顺序排列。这样,我们在扫描图像时一次只需要查看一个未完成的矩形。

在第 6 行,经过前两个未完成的矩形后,我们在位置 10 处发现了一个意想不到的黑色像素;我们现在可以从未完成列表中删除第三个矩形,并将其添加到完整矩形数组中,如 {left,right,top,bottom}:

unfinished: {1,4,3},{6,7,3},{16,18,2},{21,21,4}  
finished: {10,13,1,5}  

当我们到达第 9 行的末尾时,我们已经完成了第 5 行之后未完成的所有矩形,但我们在第 7 行发现了一个新的矩形:

unfinished: {12,16,7}  
finished: {10,13,1,5},{16,18,2,5},{1,4,3,6},{6,7,3,8},{21,21,4,8}  

如果我们继续到最后,结果是:

unfinished:  
finished: {10,13,1,5},{16,18,2,5},{1,4,3,6},{6,7,3,8},{21,21,4,8},  
          {12,16,7,10},{3,10,10,13},{13,17,13,14},{19,22,11,14}  

如果此时还有未完成的矩形,它们将与图像的底部边缘接壤,并且可以通过bottom=height-1来完成。

请注意,跳过未完成的矩形意味着您只需扫描黑色像素以及红色矩形的顶部和左侧边缘;在示例中,我们跳过了 384 个像素中的 78 个。

单击 [此处] 在 rextester.com 上查看一个简单的 C++ 版本(抱歉,我不会说 C#)。

(目前Rextester好像被黑了,所以我把链接去掉,把C++代码贴在这里。)

#include <vector>
#include <list>
#include <iostream>

struct rectangle {int left, right, top, bottom;};

std::vector<rectangle> locate(std::vector<std::vector<int>> &image) {
    std::vector<rectangle> finished;
    std::list<rectangle> unfinished;
    std::list<rectangle>::iterator current;
    int height = image.size(), width = image.front().size();
    bool new_found = false;                  // in C++17 use std::optional<rectangle>new_rect and check new_rect.has_value()

    for (int y = 0; y < height; ++y) {
        current = unfinished.begin();        // iterate over unfinished rectangles left-to-right
        for (int x = 0; x < width; ++x) {
            if (image[y][x] == 1) {          // red pixel (1 in mock-up data)
                if (current != unfinished.end() && x == current->left) {
                    x = current->right;      // skip through unfinished rectangle
                    ++current;
                }
                else if (!new_found) {       // top left corner of new rectangle found
                    new_found = true;
                    current = unfinished.insert(current, rectangle());
                    current->left = x;
                }
            } else {                         // black pixel (0 in mock-up data)
                if (new_found) {             // top right corner of new rectangle found
                    new_found = false;
                    current->right = x - 1; current->top = y;
                    ++current;
                }
                else if (current != unfinished.end() && x == current->left) {
                    current->bottom = y - 1; // bottom of unfinished rectangle found
                    finished.push_back(std::move(*current));
                    current = unfinished.erase(current);
                }
            }
        } // if there is still a new_rect at this point, it borders the right edge
    } // if there are unfinished rectangles at this point, they border the bottom edge
    return std::move(finished);
}

int main() {
    std::vector<std::vector<int>> image { // mock-up for image data
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0},
        {0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,1,0,0,0,0,0},
        {0,1,1,1,1,0,1,1,0,0,1,1,1,1,0,0,1,1,1,0,0,0,0,0},
        {0,1,1,1,1,0,1,1,0,0,1,1,1,1,0,0,1,1,1,0,0,1,0,0},
        {0,1,1,1,1,0,1,1,0,0,1,1,1,1,0,0,1,1,1,0,0,1,0,0},
        {0,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0},
        {0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,0},
        {0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,0,0,0,0},
        {0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0},
        {0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0},
        {0,0,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,0},
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
    };

    std::vector<rectangle> rectangles = locate(image);
    std::cout << "left,right,top,bottom:\n";
    for (rectangle r : rectangles) {
        std::cout << (int) r.left << "," << (int) r.right << "," << (int) r.top << "," << (int) r.bottom << "\n";
    }

    return 0;
}

如果你发现 C# 的链表实现不够快,你可以使用两个长度为 图像宽度 的数组,当你在第 y 行找到位置 x1 和 x2 之间的矩形顶部时,将不完整的矩形存储为 width[x1]=x2-x1 和 top[x1]=y,并在存储完整矩形时将它们重置为零。

此方法将找到小至 1 像素的矩形。如果有最小尺寸,您可以使用更大的步长扫描图像;最小尺寸为 10x10,您只需扫描大约 1% 的像素。

【讨论】:

  • 这是迄今为止最好的答案。您可能没有提及如果可以触摸矩形,您将如何行动,但支持这一点的变化很小 - 只有您无法跳过像素。我真的希望@Slashy 考虑这个解决方案并理解它有多好 - 它不仅仅是好,它是可以做到的绝对最好的。
  • @RomanCortes 我没有考虑触摸矩形,因为问题没有明确提及它们;如果它们的宽度或高度不同,则确实可以找到它们,但代价是无法跳过矩形。
  • 应该很容易修改:如果start和end不匹配,那么它是一个新的不同的矩形,前一个已经完成。然后在继续之前应该比较下一个间隔(假设一个 U 形,U 的底部关闭顶部的 2 个矩形)。当您有重叠的矩形时,有多种方法可以解释它们,因此我们可以使用对性能更好的一种
  • 通过一些小的改动,即使在 GPU 上,大部分行扫描也可以并行完成,最后一步完成行结果的合并。这是最快的解决方案,不会漏掉一个矩形。
  • 与坐标指定的“等向直线”完全相似,需要接触的数据更少。
【解决方案2】:

使用简单算法的最简单方法,例如:

function find(Image): Collection of Rects
   core_rect = FindRects(Image)
   split(core_rect) -> 4 rectangles (left-top, left-bottom, right-top, right-bottom)
   return Merge of (find(leftTop), find(leftBottom), ...)

function findAll(Image): Collection of Rects
   rects <- find(Image)
   sort rectangles by X, Y
   merge rectangles
   sort rectangles by Y, X
   merge rectangles
   return merged set

两个矩形的合并应该相当简单——它们应该有共享的边框。但是给定的方法仅在图像包含矩形且仅矩形的情况下才有效。如果几何图形比较复杂,最好使用逐行扫描算法进行区域检测和下一阶段的形状类型识别。

【讨论】:

  • 现在我们只讨论矩形 :) 我必须说不幸的是我并没有真正理解伪代码..什么是矩形集合?您可以扩展或编写更详细的代码吗?我只是无法理解它背后的概念:) @user486075
  • 你看到我的评论了吗?
【解决方案3】:

根据您的要求:

  • 保持Locate(Rectangle scanArea) 函数不变。
  • 使用递归算法扫描左/下/右(图)。

我将在递归函数中引入一个 Side 类型的额外参数。

internal enum Side : byte 
{
    Left,
    Bottom,
    Right
}

假设我们使用Bottom 作为“切割”方向,然后我们可以通过创建一个包装器来提高效率(重新组装切割的矩形),该包装器存储bottomAreas 中找到的矩形的额外信息。

internal class RectangleInfo
{
    public RectangleInfo(Rectangle rect, bool leftOverlap, bool rightOverlap)
    {
        Rectangle = rect;
        LeftOverlap = leftOverlap;
        RightOverlap = rightOverlap;
    }
    public Rectangle Rectangle { get; set; }
    public bool LeftOverlap { get; set; }
    public bool RightOverlap { get; set; }
}

为了更快地查找,您还可以将在 leftAreas 和 rightAreas 中找到的剪切矩形划分为单独的列表。这会将您的示例代码变成如下内容:

List<Rectangle> difList = new List<Rectangle>();

List<Rectangle> leftList = new List<Rectangle>();
List<RectangleInfo> bottomList = new List<RectangleInfo>();
List<Rectangle> rightList = new List<Rectangle>();

private void AccumulateDifferences(Rectangle scanArea, Side direction)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    switch (direction)
    {
        case Side.Left:
            if (foundRect.X + foundRect.Width == scanArea.X + scanArea.Width)
                leftList.Add(foundRect);
            else difList.Add(foundRect);
            break;

        case Side.Bottom:
            bottomList.Add(new RectangleInfo(foundRect, foundRect.X == scanArea.X, foundRect.X + foundRect.Width == scanArea.X + scanArea.Width));
            break;

        case Side.Right:
            if (foundRect.X == scanArea.X)
                rightList.Add(foundRect);
            else difList.Add(foundRect);
            break;
    }
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); //define right area.

    AccumulateDifferences(leftArea, Side.Left);
    AccumulateDifferences(bottomArea, Side.Bottom);
    AccumulateDifferences(rightArea, Side.Right);
}

private void ProcessDifferences()
{
    foreach (RectangleInfo rectInfo in bottomList)
    {
        if (rectInfo.LeftOverlap)
        {
            Rectangle leftPart =
                leftList.Find(r => r.X + r.Width == rectInfo.Rectangle.X
                                   && r.Y == rectInfo.Rectangle.Y
                                   && r.Height == rectInfo.Rectangle.Height
                             );
            if (leftPart != null)
            {
                rectInfo.Rectangle.X = leftPart.X;
                leftList.Remove(leftPart);
            }
        }

        if (rectInfo.RightOverlap)
        {
            Rectangle rightPart =
                rightList.Find(r => r.X == rectInfo.Rectangle.X + rectInfo.Rectangle.Width
                                    && r.Y == rectInfo.Rectangle.Y
                                    && r.Height == rectInfo.Rectangle.Height
                              );
            if (rightPart != null)
            {
                rectInfo.Rectangle.X += rightPart.Width;
                rightList.Remove(rightPart);
            }
        }

        difList.Add(rectInfo.Rectangle);
    }

    difList.AddRange(leftList);
    difList.AddRange(rightList);
}

private void LocateDifferences(Rectangle scanArea)
{
    AccumulateDifferences(scanArea, Side.Left);
    ProcessDifferences();

    leftList.Clear();
    bottomList.Clear();
    rightList.Clear();
}

寻找相邻的矩形

可能存在多个矩形,在rightList 中具有相同的X 值(或leftList 中的X + Width 值),因此我们需要在找到可能的匹配项时验证交集。

根据元素的数量,您还可以在leftListrightList 的情况下使用字典(以加快查找速度)。以最上面的交点为key,然后在合并前检查Heights。

【讨论】:

  • 感谢您的回答!真的很详细很清楚!我已经对其进行了几次测试,由于某种原因,它在这种情况下失败了,例如:oi66.tinypic.com/ml5ocg.jpg 当我说失败时,我的意思是它甚至没有找到正确准确的红色矩形。还有一点没看懂,函数开头为什么选择左边?有什么特别的原因吗?在此先感谢,伊塔马尔。 @Funk
  • @Slashy 我更新了我的帖子,修复了 switch 构造中的代码。让我知道事情的后续。我会坚持使用左(或右)来启动该功能。这样,您的第一个矩形将(很可能)立即添加到 difList
  • 非常感谢您的帮助..我刚刚又试了一次..根本没有成功..我得到了相同的结果:)
【解决方案4】:

按照您不更改 Locate() 函数而只是扩展您现有逻辑的标准,我们需要加入任何 rects 后扫描。试试这个:

首先稍微修改您的 LocateDifferences() 函数以跟踪可能需要连接的矩形。

private void LocateDifferences(Rectangle scanArea)
{
    Rectangle foundRect = Locate(scanArea);
    if (foundRect == null)
        return; // stop the recursion.

    Rectangle rightArea = new Rectangle(foundRect.X + foundRect.Width, foundRect.Y, (scanArea.X + scanArea.Width) - (foundRect.X + foundRect.Width), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); //define right area.
    Rectangle bottomArea = new Rectangle(foundRect.X, foundRect.Y + foundRect.Height, foundRect.Width, (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define bottom area.
    Rectangle leftArea = new Rectangle(scanArea.X, foundRect.Y, (foundRect.X - scanArea.X), (scanArea.Y + scanArea.Height) - (foundRect.Y + foundRect.Height)); // define left area.

    if (foundRect.X == scanArea.X || foundRect.Y == scanArea.Y || (foundRect.X + foundRect.Width == scanArea.X + scanArea.Width) || (foundRect.Y + foundRect.Height == scanArea.Y + scanArea.Height)) 
    {
        // edge may extend scanArea
        difList.Add(Tuple.Create(foundRect, false));
    } else {
        difList.Add(Tuple.Create(foundRect, true));
    }

    LocateDifferences(rightArea);
    LocateDifferences(bottomArea);
    LocateDifferences(leftArea);
}

我也添加了这两种方法供使用:

// JoinRects: will return a rectangle composed of r1 and r2.
private Rectangle JoinRects(Rectangle r1, Rectangle r2)
{
    return new Rectangle(Math.Min(r1.X, r2.X), 
                    Math.Min(r1.Y, r2.Y), 
                    Math.Max(r1.Y + r1.Width, r2.Y + r2.Width), 
                    Math.Max(r1.X + r1.Height, r2.X + r2.Height));
}

// ShouldJoinRects: determines if the rectangles are connected and the height or width matches.
private bool ShouldJoinRects(Rectangle r1, Rectangle r2)
{
    if ((r1.X + r1.Width + 1 == r2.X && r1.Y == r2.Y && r1.Height == r2.Height)
     || (r1.X - 1 == r2.x + r2.Width && r1.Y == r2.Y && r1.Height == r2.Height)
     || (r1.Y + r1.Height + 1 == r2.Y && r1.X == r2.X && r1.Width == r2.Width)
     || (r1.Y - 1 == r2.Y + r2.Height && r1.X == r2.X && r1.Width == r2.Width))
    {
        return true;
    } 
    else 
    {
        return false;
    }
}

最后是开始扫描的主要功能

List<Tuple<Rectangle, Bool>> difList = new List<Tuple<Rectangle, Bool>();

// HERE: fill our list by calling LocateDifferences
LocateDifferences();

var allGood = difList.Where(t => t.Item2 == true).ToList();
var checkThese = difList.Where(t => t.Item2 == false).ToArray();

for (int i = 0; i < checkThese.Length - 1; i++)
{
    // check that its not an empty Rectangle
    if (checkThese[i].IsEmpty == false) 
    {
        for (int j = i; j < checkThese.Length; j++)
        {
            // check that its not an empty Rectangle
            if (checkThese[j].IsEmpty == false) 
            {
                if (ShouldJoinRects(checkThese[i], checkThese[j])
                {
                    checkThese[i] = JoinRects(checkThese[i], checkThese[j]);
                    checkThese[j] = new Rectangle(0,0,0,0);
                    j = i // restart the inner loop in case we are dealing with a rect that crosses 3 scan areas
                }
            }
        }
        allGood.Add(checkThese[i]);
    }
}

//Now 'allGood' contains all the rects joined where needed.

【讨论】:

  • 根据您的解决方案,我必须在函数末尾使用嵌套循环来比较列表中的每个找到的矩形?我刚刚写了一个解释 Locate() 函数是如何工作的:它找到第一个(最左上角)红色像素,然后继续向右扫描,然后当它没有找到更多红色像素时,它开始扫描高度,继续向下移动 - 轻松简单。由于那里使用了本机方法(例如 memcmp()),它的工作速度非常快。扫描功能仅限于扫描区域参数。 @迈克尔哈德森
  • 每当它找到第一个矩形(在给定的扫描区域中)时,它就会停止,返回划分其余区域,然后再次执行递归调用。是否应该在这里进行改进? @迈克尔哈德森
  • @Slashy 有关嵌套循环的详细信息,请参阅我的更新答案,它可以很有效,因为它不会扫描任何不需要的矩形
【解决方案5】:

我会通过以下方式解决:

  1. 我将从第一个像素开始读取图像。
  2. 注册每个红色像素的位置(x,y)。 [将1(x,y)放入具有相同图像大小的结果矩阵中]
  3. cost 是O(nxm),其中n 是图像的行数,m 是图像的列数。
  4. 矩形是连接的1s 的集合,其中每个 x 的 sum(y) 相同。这是一项必要的检查,以确保仅在有斑点/圆圈(下图中的绿色部分)的情况下捕获矩形..等等。

下面是结果矩阵的照片:

【讨论】:

    【解决方案6】:

    没有必要重新发明轮子。这是一个连通分量标注问题。

    https://en.wikipedia.org/wiki/Connected-component_labeling

    有多种方法可以解决它。一种是通过对图像行进行游程编码并找到行与行的重叠。

    另一种方法是扫描图像并在每次遇到红色像素时执行填充填充(擦除整个矩形)。

    另一种方法是扫描图像并在遇到红色像素时执行轮廓跟踪(并标记每个轮廓像素,以便对 blob 进行更多处理)。

    所有这些方法都适用于任意形状,您可以将它们调整为矩形的特定形状。

    【讨论】:

      【解决方案7】:

      看看this post。在那里解决了类似的问题。我建议使用flood fill algorithm 来检测矩形。

      【讨论】:

        【解决方案8】:

        根据澄清的 cmets,您现有的方法是一个完美的起点,只是在我看来,它应该使用包含不应检查(完全或再次检查)哪些像素的辅助位图进行操作。

        假设大部分图像不是红色的:

        1. 将辅助位图清零
        2. 设置 x=y=0,扫描起始位置
        3. 从 x,y 扫描图像,按存储顺序进行以提高效率,定位图像上辅助位图为 0 的第一个红色像素
        4. 即矩形的角,使用现有方法查找其尺寸(开始水平和垂直扫描等)
        5. 记录矩形 (x,y,w,h)
        6. 在辅助位图中用 1-s 填充矩形 (x,y,w,h)
        7. x+=w+1,从 2 继续(意味着它将检查新的 x 位置是否仍在图像尺寸内,如有必要,从 0,y+1 开始尝试,并识别是否刚刚完成扫描)李>

        如果大部分图像被红色矩形覆盖,我会交换检查3(先检查辅助位图,然后查看像素是否为红色),并将填充步骤(6)用一个像素扩展到左、右、下方向(顶部的像素已经检查过)

        就我个人而言,我更相信按内存顺序读取相邻像素的缓存效率,而不是四处跳转(由于分区的想法),但仍然访问大多数像素加上必须加入可能大量的片段矩形最后。

        【讨论】:

          【解决方案9】:

          我很抱歉,但我没有阅读您的解决方案,因为我不确定您是否想要一个好的解决方案或使用该解决方案解决问题。

          使用现有构建块(如我不知道是否有 c# 端口的 OpenCV)的简单解决方案是:

          1. 取红色通道(因为你说你只想检测红色矩形)
          2. 查找轮廓
          3. 对于每个轮廓 3.1 取其边界框 3.2 将轮廓的总面积与边界框的总面积进行比较,检查轮廓是否为矩形。

          解决方案将根据您输入图像的种类而改变。 我希望我能帮助你。如果没有,请告诉我你想要什么样的帮助。

          【讨论】:

          • 首先,嗨,我也来自以色列(ken ken yesh harbe israelim besstackoverflow hh)。我想找到一个有效的和现有的解决方案的修复,只需对代码进行小幅调整。如果现在有什么不清楚的地方,请告诉我……我会尽力而为!
          • 我知道我在 facebook 上看到了你的消息。 ? 我再看一遍。但是我应该告诉你,这个解决方案真的没有效率。
          【解决方案10】:

          您在图像上按像素进行线扫描。

          如果上像素为黑色,左像素为黑色,但像素本身为红色 那么你有左上角(x1,y1)。 向右走直到再次变黑(即右上 y2+1) 转到底部找到 x2+1 的黑色,这样你就可以推导出右,底部 (x2,y2)

          将 x1,y1,x2,y2 存储在列表结构或类中 将刚刚找到的矩形涂成黑色,然后继续线扫描

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-12-22
            • 1970-01-01
            • 1970-01-01
            • 2014-11-07
            • 2014-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多