46

当前情况:我正在尝试从图像中提取片段。多亏了 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()不幸的是提取的段不是重复的(即:它们相互重叠,但不严格相等,所以我可以' t 只是说“删除重复项”,而不是例如像素)

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

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


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

在此处输入图像描述

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


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

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

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

但两者都是最小生成树

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


回答 Tomalak 的评论:

在此处输入图像描述

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

这是一个包含太多细分的真实示例:

在此处输入图像描述

4

4 回答 4

16

使用 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] 

在此处输入图像描述

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

于 2011-06-27T15:33:29.707 回答
10

试试数学形态学。首先,您需要dilateclose您的图像来填补漏洞。

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

我得到了这张图片

在此处输入图像描述

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

在此处输入图像描述

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

另一个想法。

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

在此处输入图像描述

然后使用类似的东西cvAdaptiveThreshold

在此处输入图像描述

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

于 2011-06-20T13:46:42.097 回答
6

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

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 算子进行边缘检测,因此在您的情况下它应该更快。

于 2011-06-30T20:30:42.920 回答
0

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

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

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

于 2018-06-13T15:59:54.153 回答