【问题标题】:Extracting segments from a list of 8-connected pixels从 8 个连接的像素列表中提取片段
【发布时间】:2011-09-18 13:56:19
【问题描述】:

当前情况:我正在尝试从图像中提取片段。多亏了 openCV 的 findContours() 方法,我现在有了每个轮廓的 8 个连接点的列表。但是,这些列表不能直接使用,因为它们包含很多重复项。

问题给定一个包含重复的 8 个连接点的列表,从中提取线段。

可能的解决方案:

  • 一开始我使用openCV的approxPolyDP()方法。然而,结果很糟糕......这是缩放的轮廓:

这是approxPolyDP()的结果:(9段!有些重叠)

但我想要的更像是:

这很糟糕,因为approxPolyDP() 可以将“看起来像几个段”的东西转换为“几个段”。但是,我所拥有的是一个点列表,这些点往往会对其自身进行多次迭代。

例如,如果我的观点是:

0 1 2 3 4 5 6 7 8 
  9   

然后,点列表将是0 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 9... 如果点数变大(>100),那么很遗憾,approxPolyDP() 提取的段不是重复的(即:它们相互重叠,但是不是严格相等的,所以我不能只说“删除重复项”,而不是像素)

  • 也许,我有一个解决方案,但它很长(虽然很有趣)。首先,对于所有 8 连通列表,我创建一个稀疏矩阵(为了提高效率),如果像素属于列表,则将矩阵值设置为 1。然后,我创建了一个 graph,其中节点对应于像素,边缘位于相邻像素之间。这也意味着我在像素之间添加所有缺失的边缘(复杂性很小,可能是因为稀疏矩阵)。然后我删除所有可能的“正方形”(4 个相邻节点),这是可能的,因为我已经在处理非常细的轮廓。然后我可以启动一个最小生成树算法。最后,我可以用 openCV 的 approxPolyDP() 近似树的每个分支

总结一下:我有一个乏味的方法,我还没有实现它,因为它似乎很容易出错。但是,我问你们,Stack Overflow 的人:还有其他现有的方法,可能有很好的实现吗?


编辑:澄清一下,一旦我有一棵树,我可以提取“分支”(分支从链接到 3 个或更多其他节点的叶子或节点开始)然后,openCV 的approxPolyDP() 中的算法是Ramer–Douglas–Peucker algorithm,这是它所做的维基百科图片:

有了这张图,很容易理解为什么当点可能相互重复时它会失败


另一个编辑:在我的方法中,有一些有趣的地方需要注意。当您考虑位于网格中的点(如像素)时,一般来说,最小生成树算法没有用处,因为有许多可能的最小树

X-X-X-X
|
X-X-X-X

有着根本的不同
X-X-X-X
| | | |
X X X X

但两者都是最小生成树

但是,在我的例子中,我的节点很少形成集群,因为它们应该是轮廓,并且已经有一个细化算法预先在 findContours() 中运行。


对 Tomalak 评论的回答:

如果 DP 算法返回 4 段(从点 2 到中心的段有两次)我会很高兴!当然,通过良好的参数,我可以达到“偶然”拥有相同片段的状态,并且可以删除重复片段。但是,很明显,该算法不是为它设计的。

这是一个包含太多片段的真实示例:

【问题讨论】:

  • are unfortunately not duplicates (i.e : they overlap each other, but are not stricly equal, so I can't just say "remove duplicates", as opposed to pixels for example)我不明白这个。为什么不呢?
  • 我的回答是用另一张精美的画图编辑我的帖子 =)
  • 我正在查看您为响应 Tomalak 添加的图片,并且我有一个疑问:DP 算法的结果实际上是一个有效的解决方案,如果您接受观察点可以为远非“真实”!段作为图像的中心点。您对算法必须遵守的点线距离是否有特定阈值?
  • 不,我没有特定的阈值,但问题是我正在处理具有数千个轮廓的数千个图像。如果我为阈值设置一个特定的值,那么对于大多数轮廓,我将有一个可怕的重叠段混乱。我了解 DP 算法返回的是“有效”。但只要看看我帖子第一张照片中的蓝色部分。您可以看到有很多段,其中只需要两个或三个。由于列表的 8 连接性质,它们重叠。

标签: c++ image-processing graph opencv boost-graph


【解决方案1】:

试试数学形态学。首先你需要dilateclose 你的图片来填补漏洞。

cvDilate(pimg, pimg, NULL, 3);
cvErode(pimg, pimg, NULL);

我得到了这张图片

下一步应该是应用thinning 算法。不幸的是,它没有在 OpenCV 中实现(MATLAB 有 bwmorphthin 参数)。例如,我使用 MATLAB 将图像改进为这个:

然而OpenCV 具有实现细化所需的所有基本形态学操作(cvMorphologyExcvCreateStructuringElementEx 等)。

另一个想法。

他们说距离变换在此类任务中似乎非常有用。可能是吧。 考虑cvDistTransform 函数。它会创建这样的图像:

然后使用类似cvAdaptiveThreshold:

那是骷髅。我想你可以遍历所有连接的白色像素,找到曲线并过滤掉小片段。

【讨论】:

  • 感谢您的意见!这是有趣的东西,但我担心存在误解:图像是我所拥有的图像的放大版本。我已经在我的帖子中清除了这一点。我想要的是在输入中拍摄你的第二张照片,并输出 3 段。
【解决方案2】:

使用 Mathematica 8,我从图像中的白色像素列表创建了一个形态图。它在您的第一张图片上运行良好:

创建形态图:

graph = MorphologicalGraph[binaryimage];

然后你可以查询你感兴趣的图表属性。

这给出了图中顶点的名称:

vertex = VertexList[graph]

边的列表:

EdgeList[graph]

这给出了顶点的位置:

pos = PropertyValue[{graph, #}, VertexCoordinates] & /@ vertex

这是第一张图片的结果:

In[21]:= vertex = VertexList[graph]

Out[21]= {1, 3, 2, 4, 5, 6, 7, 9, 8, 10}

In[22]:= EdgeList[graph]

Out[22]= {1 \[UndirectedEdge] 3, 2 \[UndirectedEdge] 4,  3 \[UndirectedEdge] 4, 
          3 \[UndirectedEdge] 5, 4 \[UndirectedEdge] 6,  6 \[UndirectedEdge] 7, 
          6 \[UndirectedEdge] 9, 8 \[UndirectedEdge] 9,  9 \[UndirectedEdge] 10}

In[26]:= pos = PropertyValue[{graph, #}, VertexCoordinates] & /@ vertex

Out[26]= {{54.5, 191.5}, {98.5, 149.5},  {42.5, 185.5}, 
          {91.5, 138.5}, {132.5, 119.5}, {157.5, 72.5},
          {168.5, 65.5}, {125.5, 52.5},  {114.5, 53.5}, 
          {120.5, 29.5}}

鉴于文档 http://reference.wolfram.com/mathematica/ref/MorphologicalGraph.html,命令 MorphologicalGraph 首先通过形态细化计算骨架:

skeleton = Thinning[binaryimage, Method -> "Morphological"]

然后检测到顶点;它们是分支点和终点:

verteximage = ImageAdd[
                  MorphologicalTransform[skeleton, "SkeletonEndPoints"],   
                  MorphologicalTransform[skeleton, "SkeletonBranchPoints"]]

然后在分析它们的连通性后将顶点链接起来。

例如,可以先破坏顶点周围的结构,然后寻找连接的组件,从而显示图形的边缘:

comp = MorphologicalComponents[
           ImageSubtract[
               skeleton, 
               Dilation[vertices, CrossMatrix[1]]]];
Colorize[comp] 

魔鬼在细节中,但如果您想开发自己的实现,这听起来像是一个可靠的起点。

【讨论】:

  • @karlphillip:谢谢,还不知道
  • +1 但是由于 OP 没有要求 Mathematica 中的解决方案,您应该解释MorphologicalGraph[] 背后的算法
  • 相应编辑,感谢您并允许图片进入。
  • 哦,非常感谢!好吧,我不使用 Mathematica,也无法合并它。我会尝试查看MorphologicalGraph[],但如果您知道它是如何工作的,我将永远感激不尽 =)。而且,如果没有人想出惊人的东西,赏金就是你的了!
  • @Fezvez 感谢您的鼓励。我不知道 MorphologicalGraph 是如何工作的,但我建议更进一步。该算法无论如何都需要一组形态学工具,正如 Andrey 所说,这些工具在 OpenCV 中可能不可用。如果您不能使用 Mathematica,您可能需要寻找另一个可以提供形态特征的库。
【解决方案3】:

我之前已经实现了一个类似的算法,并且我以一种增量最小二乘的方式实现了它。它工作得相当好。伪代码有点像:

L = empty set of line segments
for each white pixel p
  line = new line containing only p
  C = empty set of points
  P = set of all neighboring pixels of p
  while P is not empty
    n = first point in P
    add n to C
    remove n from P
    line' = line with n added to it
    perform a least squares fit of line'
    if MSE(line) < max_mse and d(line, n) < max_distance
      line = line'
      add all neighbors of n that are not in C to P
  if size(line) > min_num_points
    add line to L

其中 MSE(line) 是直线的均方误差(直线中所有点到最佳拟合直线的平方距离之和),d(line,n) 是从点 n 到点的距离线。 max_distance 的好值似乎是一个像素左右,而 max_mse 似乎要小得多,并且取决于图像中线段的平均大小。对我来说,0.1 或 0.2 像素适用于相当大的图像。

我一直在用 Canny 运算符预处理过的实际图像上使用它,所以我得到的唯一结果就是这样。这是上述算法在图像上的结果:

也可以使算法更快。我拥有的 C++ 实现(由我的工作强制执行的封闭源代码,对不起,否则我会把它给你)在大约 20 毫秒内处理了上面的图像。这包括应用 Canny 算子进行边缘检测,因此在您的情况下它应该更快。

【讨论】:

    【解决方案4】:

    您可以首先使用 openCV 提供的HoughLinesP 从轮廓图像中提取直线:

    HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0)  
    

    如果你选择threshold = 1minLineLenghtsmall,你甚至可以获取所有的单个元素。不过要小心,因为如果您有很多边缘像素,它会产生很多结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-15
      • 2010-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多