【问题标题】:Keep one line out of many that starts from one point从一个点开始的多条线中保留一条线
【发布时间】:2020-10-31 03:43:59
【问题描述】:

我正在使用 OpenCV 和 python 开发一个项目,但遇到了这个小问题。

我在列表中存储了许多行的端点坐标。有时会出现从一个点检测到多条线的情况。在这些线中,我想保留最短的线并消除所有其他线,因此我的图像将不包含绘制多条线的点。

存储最初检测到的所有线的信息(两个端点的坐标)的变量如下:

var = [[Line1_EndPoint1, Line1_EndPoint2],
       [Line2_EndPoint1, Line2_EndPoint2],
       [Line3_EndPoint1, Line3_EndPoint2],
       [Line4_EndPoint1, Line4_EndPoint2],
       [Line5_EndPoint1, Line5_EndPoint2]]

其中,LineX_EndPointY(行号“X”,该行的端点“Y”)的类型为 [x, y],其中 x 和 y 是图像中该点的坐标。

谁能建议我如何解决这个问题。

您可以修改行数据的存储方式。如果您修改,请说明您的数据结构以及它是如何创建的

此类数据示例:

[[[551, 752], [541, 730]], 
 [[548, 738], [723, 548]],
 [[285, 682], [226, 676]],
 [[416, 679], [345, 678]],
 [[345, 678], [388, 674]],
 [[249, 679], [226, 676]],
 [[270, 678], [388, 674]],
 [[472, 650], [751, 473]],
 [[751, 473], [716, 561]],
 [[731, 529], [751, 473]]]

Python 代码将是可观的。

【问题讨论】:

  • 我对这个问题感到困惑-您的标题表明您只想从任何点开始/发出一行,但问题的内容暗示了终点,而不是起点。你的意思是你应该只有一条长度最短的线,它有唯一的起点和终点,只是起点还是终点?
  • 行从哪里开始或从哪里结束都没有关系。问题是最终输出中的任何 2 行或更多行都不应该有任何公共端点。 “开始”和“结束”两个词只是用来命名端点。

标签: python numpy opencv image-processing cv2


【解决方案1】:

我认为基于 Pandas 编写解决方案更容易。 原因是:

  • 我可以使用列名(代码可读性更好),
  • Pandas API 更强大,虽然它的运行速度比“纯”Numpy 慢。

按以下步骤进行:

  1. var 转换为 DataFrame:

     lines = pd.DataFrame(var.reshape(10,4), columns=pd.MultiIndex.from_product(
         (['P1', 'P2'], ['x','y'])))
    

    lines的开头部分是:

         P1        P2     
          x    y    x    y
     0  551  752  541  730
     1  548  738  723  548
     2  285  682  226  676
     3  416  679  345  678
    
  2. 计算每行长度的平方:

     lines[('', 'lgth')] = (lines[('P1', 'x')] - lines[('P2', 'x')]) ** 2\
         + (lines[('P1', 'y')] - lines[('P2', 'y')]) ** 2
     lines.columns = lines.columns.droplevel()
    

    我故意“停”在正方形的长度,因为它是 足以比较长度(计算根不会改变 比较结果)。

    另请注意,列上的 MultiIndex 的第一级是必需的 只是为了更容易表达感兴趣的列。进一步他们将 不需要,所以我放弃了。

    这次我放了的全部内容:

          x    y    x    y    lgth
     0  551  752  541  730     584
     1  548  738  723  548   66725
     2  285  682  226  676    3517
     3  416  679  345  678    5042
     4  345  678  388  674    1865
     5  249  679  226  676     538
     6  270  678  388  674   13940
     7  472  650  751  473  109170
     8  751  473  716  561    8969
     9  731  529  751  473    3536
    
  3. 下一步是计算 points DataFrame,其中所有点(开始和 每行的末尾)在同一列中,以及(正方形)长度 对应行:

     points = pd.concat([lines.iloc[:,[0, 1, 4]],
         lines.iloc[:,[2, 3, 4]]], keys=['P1', 'P2'])\
         .sort_values(['x', 'y', 'lgth']).reset_index(level=1)
    

    现在我用 iloc 指定列(第一次作为起点 第二个用于结束点)。 为了更容易阅读这个 DataFrame,我传递了 keys,以包含“origin 指标”,然后我对行进行了排序。

    内容是:

         level_1    x    y    lgth
     P2        5  226  676     538
     P2        2  226  676    3517
     P1        5  249  679     538
     P1        6  270  678   13940
     P1        2  285  682    3517
     P1        4  345  678    1865
     P2        3  345  678    5042
     P2        4  388  674    1865
     P2        6  388  674   13940
     P1        3  416  679    5042
     P1        7  472  650  109170
     P2        0  541  730     584
     P1        1  548  738   66725
     P1        0  551  752     584
     P2        8  716  561    8969
     P2        1  723  548   66725
     P1        9  731  529    3536
     P2        9  751  473    3536
     P1        8  751  473    8969
     P2        7  751  473  109170
    

    注意例如该点 226, 676 出现两次。第一次发生 在第 5 行和第 2 行中的第二个(varlines 中的索引)。

  4. 要查找要删除的行的索引,请运行:

     toDrop = points[points.duplicated(subset=['x', 'y'])]\
         .level_1.reset_index(drop=True);
    

    为了更容易理解这段代码的工作原理,请逐步运行它并 检查每个步骤的结果。

    结果是:

     0    2
     1    3
     2    6
     3    8
     4    7
     Name: level_1, dtype: int64
    

    请注意,上面的左列只是索引(没关系)。 真正的信息在右列(值)。

  5. 要显示应该留下的行,运行:

     result = lines.drop(toDrop)
    

    得到:

          x    y    x    y   lgth
     0  551  752  541  730    584
     1  548  738  723  548  66725
     4  345  678  388  674   1865
     5  249  679  226  676    538
     9  731  529  751  473   3536
    

    以上结果不包含例如:

    • 2行,因为点226、676出现在5行,
    • 3行,因为点345、678出现在4行,

    只是这些行(23)已被删除,因为它们是 比第二条提到的两条线都长(参见前面的部分结果)。

也许这已经足够了,或者如果您需要从 var(原来的 Numpy 数组),并将结果保存在另一个 变量,运行:

var2 = np.delete(var, toDrop, axis=0)

【讨论】:

  • 谢谢你,很好的回答。对我来说,一口气理解有点棘手,因为我没有使用熊猫的经验。将尝试实现这一点,然后将问题标记为已回答。
【解决方案2】:

Numpy 解决方案

仅基于我的第一个答案即可获得相同的结果 在 Numpy 上。

首先定义两个函数:

  1. 计算直线长度的平方:

     def sqLgth(line):
         p1, p2 = line
         return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
    
  2. 将向量(1D 数组)转换为列数组(2D 数组 单列:

     def toColumn(tbl):
         return tbl.reshape(-1, 1)
    

以后都会用到。

然后进行如下操作:

  1. 获取行数:

     lineNo = var.shape[0]
    
  2. 生成线索引(points数组中lineInd列的内容 (稍后会创建)):

     id = np.repeat(np.arange(lineNo), 2)
    
  3. 生成“起源指标”(1 - 开始,2 - 结束),以简化分析 任何中间打印输出:

     origin = np.tile(np.array([1, 2]), lineNo)
    
  4. 计算线长(lgth列的内容):

     lgth = np.repeat([ sqLgth(line) for line in var ], 2)
    
  5. 用一些额外的数据创建一个点列表(连续 列包含 originlineIndxylgth):

     points = np.hstack([toColumn(origin), toColumn(id),
         var.reshape(-1, 2), toColumn(lgth)])
    
  6. 计算“标准数组”进行排序:

     r = np.core.records.fromarrays(points[:, 2:].transpose(),
         names='x, y, lgth')
    
  7. 排序(按xylgth):

     points = points[r.argsort()]
    
  8. 计算点的“逆唯一索引”:

     _, inv = np.unique(points[:,2:4], axis=0, return_inverse=True)
    
  9. inv 移动 1 个位置:

     rInv = np.roll(inv,1)
    

    将在下一步中使用,以获取上一个元素。

  10. 生成要删除的行索引列表:

    toDrop = points[[ i for i in range(2 * lineNo)
        if inv[i] == rInv[i] ], 1]
    

    行索引(在 points 数组中)是重复点(元素 在 inv 中等于前一个元素)。

    列索引 (1) - 指定 lineInd 列。

    整个结果 (toDrop) 是“拥有”行的索引列表 (包含重复点)。

  11. 生成结果:var 从在 上一步:

    var2 = np.delete(var, toDrop, axis=0)
    

要打印减少的行列表,您可以运行:

for line in var2:
    print(f'{line[0]}, {line[1]}')

结果是:

[551 752], [541 730]
[548 738], [723 548]
[345 678], [388 674]
[249 679], [226 676]
[731 529], [751 473]

要完全理解这段代码的工作原理:

  • 分别执行每个步骤,
  • 打印结果,
  • 将其与之前步骤的打印输出进行比较。

有时单独打印一些表达式也是有启发性的 (部分说明),例如var.reshape(-1, 2) - 转换你的 var(形状为 (10, 2, 2))成一个 2D 点数组(每一行是 一点)。

整个结果当然和我的第一个答案一样, 但是正如您所写,您在 Pandas 方面的经验很少,现在您可以 比较这两种方法并查看 Pandas 允许这样做的情况 更简单、更直观的东西。

很好的例子是例如按某些列排序或查找重复的行。 在 Pandas 中,这是一条指令的问题,有合适的 参数,而在 Numpy 中你必须使用更多指令 并知道如何做到这一点的各种细节和技巧。

【讨论】:

    猜你喜欢
    • 2019-01-23
    • 1970-01-01
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2016-06-07
    • 2023-03-05
    • 2023-03-21
    • 1970-01-01
    相关资源
    最近更新 更多