从一点开始的许多行中保留一条线
Keep one line out of many that starts from one point
我正在使用 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 代码将是可观的。
我决定写一个基于Pandas的解决方案更容易。
原因是:
- 我可以使用列名(代码可读性更好),
- Pandas API 更强大,尽管它比“纯”Numpy.
进行如下操作:
将var转换为DataFrame:
lines = pd.DataFrame(var.reshape(10,4), columns=pd.MultiIndex.from_product(
(['P1', 'P2'], ['x','y'])))
行的开头部分是:
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
计算每条线的长度的平方:
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
下一步是计算点 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 行中的第二行(var 和 行中的索引).
要查找要删除的行的索引,运行:
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
请注意,上面的左列只是索引(无关紧要)。
真实信息在右栏(值)中。
要显示应该保留的行,运行:
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,
只有这些行(2 和 3)已被删除,因为它们是
比第二条提到的两行都长(见前面的部分结果)。
也许这就足够了,或者如果您需要从
var(原来的Numpy数组),并将结果保存在另一个
变量,运行:
var2 = np.delete(var, toDrop, axis=0)
一个Numpy解决方案
与我第一个答案相同的结果仅基于
在 Numpy.
上
首先定义2个函数:
计算直线长度的平方:
def sqLgth(line):
p1, p2 = line
return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
将向量(1D数组)转换为列数组(2D数组
单列:
def toColumn(tbl):
return tbl.reshape(-1, 1)
后面都会用到。
然后进行如下操作:
获取行数:
lineNo = var.shape[0]
生成线索引(lineInd列在points数组中的内容
(稍后创建)):
id = np.repeat(np.arange(lineNo), 2)
生成“起点指标”(1 - 开始,2 - 结束),以方便分析
任何中间打印输出:
origin = np.tile(np.array([1, 2]), lineNo)
计算线长(lgth列的内容点):
lgth = np.repeat([ sqLgth(line) for line in var ], 2)
创建一个包含一些附加数据的点列表(连续
列包含 origin、lineInd、x、y 和lgth):
points = np.hstack([toColumn(origin), toColumn(id),
var.reshape(-1, 2), toColumn(lgth)])
计算要排序的“标准数组”:
r = np.core.records.fromarrays(points[:, 2:].transpose(),
names='x, y, lgth')
排序点(按x、y和lgth):
points = points[r.argsort()]
计算“反向唯一索引”到点:
_, inv = np.unique(points[:,2:4], axis=0, return_inverse=True)
将 inv 移动 1 个位置:
rInv = np.roll(inv,1)
将在下一步中使用,获取上一个元素。
生成要删除的线索引列表:
toDrop = points[[ i for i in range(2 * lineNo)
if inv[i] == rInv[i] ], 1]
行索引(在点数组中)是重复点(元素
in inv 等于前一个元素)。
列索引 (1) - 指定 lineInd 列。
整个结果 (toDrop) 是“拥有”行的索引列表
(包含重复点)。
生成结果: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 中你必须使用更多指令
并知道如何做同样的事情的各种细节和技巧。
我正在使用 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 代码将是可观的。
我决定写一个基于Pandas的解决方案更容易。 原因是:
- 我可以使用列名(代码可读性更好),
- Pandas API 更强大,尽管它比“纯”Numpy.
进行如下操作:
将var转换为DataFrame:
lines = pd.DataFrame(var.reshape(10,4), columns=pd.MultiIndex.from_product( (['P1', 'P2'], ['x','y'])))
行的开头部分是:
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
计算每条线的长度的平方:
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
下一步是计算点 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 行中的第二行(var 和 行中的索引).
要查找要删除的行的索引,运行:
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
请注意,上面的左列只是索引(无关紧要)。 真实信息在右栏(值)中。
要显示应该保留的行,运行:
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,
只有这些行(2 和 3)已被删除,因为它们是 比第二条提到的两行都长(见前面的部分结果)。
也许这就足够了,或者如果您需要从 var(原来的Numpy数组),并将结果保存在另一个 变量,运行:
var2 = np.delete(var, toDrop, axis=0)
一个Numpy解决方案
与我第一个答案相同的结果仅基于 在 Numpy.
上首先定义2个函数:
计算直线长度的平方:
def sqLgth(line): p1, p2 = line return (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
将向量(1D数组)转换为列数组(2D数组 单列:
def toColumn(tbl): return tbl.reshape(-1, 1)
后面都会用到。
然后进行如下操作:
获取行数:
lineNo = var.shape[0]
生成线索引(lineInd列在points数组中的内容 (稍后创建)):
id = np.repeat(np.arange(lineNo), 2)
生成“起点指标”(1 - 开始,2 - 结束),以方便分析 任何中间打印输出:
origin = np.tile(np.array([1, 2]), lineNo)
计算线长(lgth列的内容点):
lgth = np.repeat([ sqLgth(line) for line in var ], 2)
创建一个包含一些附加数据的点列表(连续 列包含 origin、lineInd、x、y 和lgth):
points = np.hstack([toColumn(origin), toColumn(id), var.reshape(-1, 2), toColumn(lgth)])
计算要排序的“标准数组”:
r = np.core.records.fromarrays(points[:, 2:].transpose(), names='x, y, lgth')
排序点(按x、y和lgth):
points = points[r.argsort()]
计算“反向唯一索引”到点:
_, inv = np.unique(points[:,2:4], axis=0, return_inverse=True)
将 inv 移动 1 个位置:
rInv = np.roll(inv,1)
将在下一步中使用,获取上一个元素。
生成要删除的线索引列表:
toDrop = points[[ i for i in range(2 * lineNo) if inv[i] == rInv[i] ], 1]
行索引(在点数组中)是重复点(元素 in inv 等于前一个元素)。
列索引 (1) - 指定 lineInd 列。
整个结果 (toDrop) 是“拥有”行的索引列表 (包含重复点)。
生成结果: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 中你必须使用更多指令 并知道如何做同样的事情的各种细节和技巧。