pyxllib 0.3.197__py3-none-any.whl → 0.3.200__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. pyxllib/__init__.py +21 -21
  2. pyxllib/algo/__init__.py +8 -8
  3. pyxllib/algo/disjoint.py +54 -54
  4. pyxllib/algo/geo.py +541 -541
  5. pyxllib/algo/intervals.py +964 -964
  6. pyxllib/algo/matcher.py +389 -389
  7. pyxllib/algo/newbie.py +166 -166
  8. pyxllib/algo/pupil.py +629 -629
  9. pyxllib/algo/shapelylib.py +67 -67
  10. pyxllib/algo/specialist.py +241 -241
  11. pyxllib/algo/stat.py +494 -494
  12. pyxllib/algo/treelib.py +149 -149
  13. pyxllib/algo/unitlib.py +66 -66
  14. pyxllib/autogui/__init__.py +5 -5
  15. pyxllib/autogui/activewin.py +246 -246
  16. pyxllib/autogui/all.py +9 -9
  17. pyxllib/autogui/autogui.py +852 -852
  18. pyxllib/autogui/uiautolib.py +362 -362
  19. pyxllib/autogui/virtualkey.py +102 -102
  20. pyxllib/autogui/wechat.py +827 -827
  21. pyxllib/autogui/wechat_msg.py +421 -421
  22. pyxllib/autogui/wxautolib.py +84 -84
  23. pyxllib/cv/__init__.py +5 -5
  24. pyxllib/cv/expert.py +267 -267
  25. pyxllib/cv/imfile.py +159 -159
  26. pyxllib/cv/imhash.py +39 -39
  27. pyxllib/cv/pupil.py +9 -9
  28. pyxllib/cv/rgbfmt.py +1525 -1525
  29. pyxllib/cv/slidercaptcha.py +137 -137
  30. pyxllib/cv/trackbartools.py +251 -251
  31. pyxllib/cv/xlcvlib.py +1040 -1040
  32. pyxllib/cv/xlpillib.py +423 -423
  33. pyxllib/data/echarts.py +240 -240
  34. pyxllib/data/jsonlib.py +89 -89
  35. pyxllib/data/oss.py +72 -72
  36. pyxllib/data/pglib.py +1127 -1127
  37. pyxllib/data/sqlite.py +568 -568
  38. pyxllib/data/sqllib.py +297 -297
  39. pyxllib/ext/JLineViewer.py +505 -505
  40. pyxllib/ext/__init__.py +6 -6
  41. pyxllib/ext/demolib.py +246 -246
  42. pyxllib/ext/drissionlib.py +277 -277
  43. pyxllib/ext/kq5034lib.py +12 -12
  44. pyxllib/ext/old.py +663 -663
  45. pyxllib/ext/qt.py +449 -449
  46. pyxllib/ext/robustprocfile.py +497 -497
  47. pyxllib/ext/seleniumlib.py +76 -76
  48. pyxllib/ext/tk.py +173 -173
  49. pyxllib/ext/unixlib.py +827 -827
  50. pyxllib/ext/utools.py +351 -351
  51. pyxllib/ext/webhook.py +124 -119
  52. pyxllib/ext/win32lib.py +40 -40
  53. pyxllib/ext/wjxlib.py +88 -88
  54. pyxllib/ext/wpsapi.py +124 -124
  55. pyxllib/ext/xlwork.py +9 -9
  56. pyxllib/ext/yuquelib.py +1105 -1105
  57. pyxllib/file/__init__.py +17 -17
  58. pyxllib/file/docxlib.py +761 -761
  59. pyxllib/file/gitlib.py +309 -309
  60. pyxllib/file/libreoffice.py +165 -165
  61. pyxllib/file/movielib.py +148 -148
  62. pyxllib/file/newbie.py +10 -10
  63. pyxllib/file/onenotelib.py +1469 -1469
  64. pyxllib/file/packlib/__init__.py +330 -330
  65. pyxllib/file/packlib/zipfile.py +2441 -2441
  66. pyxllib/file/pdflib.py +426 -426
  67. pyxllib/file/pupil.py +185 -185
  68. pyxllib/file/specialist/__init__.py +685 -685
  69. pyxllib/file/specialist/dirlib.py +799 -799
  70. pyxllib/file/specialist/download.py +193 -193
  71. pyxllib/file/specialist/filelib.py +2829 -2829
  72. pyxllib/file/xlsxlib.py +3131 -3131
  73. pyxllib/file/xlsyncfile.py +341 -341
  74. pyxllib/prog/__init__.py +5 -5
  75. pyxllib/prog/cachetools.py +64 -64
  76. pyxllib/prog/deprecatedlib.py +233 -233
  77. pyxllib/prog/filelock.py +42 -42
  78. pyxllib/prog/ipyexec.py +253 -253
  79. pyxllib/prog/multiprogs.py +940 -940
  80. pyxllib/prog/newbie.py +451 -451
  81. pyxllib/prog/pupil.py +1197 -1197
  82. pyxllib/prog/sitepackages.py +33 -33
  83. pyxllib/prog/specialist/__init__.py +391 -391
  84. pyxllib/prog/specialist/bc.py +203 -203
  85. pyxllib/prog/specialist/browser.py +497 -497
  86. pyxllib/prog/specialist/common.py +347 -347
  87. pyxllib/prog/specialist/datetime.py +198 -198
  88. pyxllib/prog/specialist/tictoc.py +240 -240
  89. pyxllib/prog/specialist/xllog.py +180 -180
  90. pyxllib/prog/xlosenv.py +108 -108
  91. pyxllib/stdlib/__init__.py +17 -17
  92. pyxllib/stdlib/tablepyxl/__init__.py +10 -10
  93. pyxllib/stdlib/tablepyxl/style.py +303 -303
  94. pyxllib/stdlib/tablepyxl/tablepyxl.py +130 -130
  95. pyxllib/text/__init__.py +8 -8
  96. pyxllib/text/ahocorasick.py +39 -39
  97. pyxllib/text/airscript.js +744 -744
  98. pyxllib/text/charclasslib.py +121 -121
  99. pyxllib/text/jiebalib.py +267 -267
  100. pyxllib/text/jinjalib.py +32 -32
  101. pyxllib/text/jsa_ai_prompt.md +271 -271
  102. pyxllib/text/jscode.py +922 -922
  103. pyxllib/text/latex/__init__.py +158 -158
  104. pyxllib/text/levenshtein.py +303 -303
  105. pyxllib/text/nestenv.py +1215 -1215
  106. pyxllib/text/newbie.py +300 -300
  107. pyxllib/text/pupil/__init__.py +8 -8
  108. pyxllib/text/pupil/common.py +1121 -1121
  109. pyxllib/text/pupil/xlalign.py +326 -326
  110. pyxllib/text/pycode.py +47 -47
  111. pyxllib/text/specialist/__init__.py +8 -8
  112. pyxllib/text/specialist/common.py +112 -112
  113. pyxllib/text/specialist/ptag.py +186 -186
  114. pyxllib/text/spellchecker.py +172 -172
  115. pyxllib/text/templates/echart_base.html +10 -10
  116. pyxllib/text/templates/highlight_code.html +16 -16
  117. pyxllib/text/templates/latex_editor.html +102 -102
  118. pyxllib/text/vbacode.py +17 -17
  119. pyxllib/text/xmllib.py +747 -747
  120. pyxllib/xl.py +42 -39
  121. pyxllib/xlcv.py +17 -17
  122. {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/METADATA +1 -1
  123. pyxllib-0.3.200.dist-info/RECORD +126 -0
  124. {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/licenses/LICENSE +190 -190
  125. pyxllib-0.3.197.dist-info/RECORD +0 -126
  126. {pyxllib-0.3.197.dist-info → pyxllib-0.3.200.dist-info}/WHEEL +0 -0
pyxllib/algo/geo.py CHANGED
@@ -1,541 +1,541 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # @Author : 陈坤泽
4
- # @Email : 877362867@qq.com
5
- # @Date : 2020/11/15 10:16
6
-
7
- """ 几何、数学运算
8
-
9
- specialist级别
10
- """
11
-
12
- from pyxllib.prog.pupil import check_install_package
13
-
14
- check_install_package('cv2', 'opencv-python')
15
-
16
- import copy
17
-
18
- import numpy as np
19
- import cv2
20
-
21
- from pyxllib.algo.intervals import Intervals
22
-
23
- ____base = """
24
-
25
- """
26
-
27
-
28
- def xywh2ltrb(p):
29
- return [p[0], p[1], p[0] + p[2], p[1] + p[3]]
30
-
31
-
32
- def ltrb2xywh(p):
33
- return [p[0], p[1], p[2] - p[0], p[3] - p[1]]
34
-
35
-
36
- def ltrb2polygon(p):
37
- """ ltrb坐标转多边形
38
-
39
- :param list|tuple p: [left, top, right, bottom]
40
- :rtype: list
41
-
42
- >>> ltrb2polygon([100, 50, 200, 150])
43
- [[100, 50], [200, 50], [200, 150], [100, 150]]
44
- """
45
- return [p[:2], [p[2], p[1]], p[2:], [p[0], p[3]]]
46
-
47
-
48
- def rect2polygon(src_pts):
49
- """ 矩形对角线两个点,转成四边形四个点的模式来表达
50
- (输入左上、右下两个顶点坐标)
51
-
52
- :param list|np.ndarray src_pts: size 2*2
53
- :rtype: list
54
-
55
- >>> rect2polygon([[0, 0], [10, 20]])
56
- [[0, 0], [10, 0], [10, 20], [0, 20]]
57
- >>> rect2polygon(np.array([[0, 0], [10, 20]]))
58
- [[0, 0], [10, 0], [10, 20], [0, 20]]
59
- >>> rect2polygon([[10, 0], [0, 20]])
60
- [[0, 0], [10, 0], [10, 20], [0, 20]]
61
- """
62
- [[x1, y1], [x2, y2]] = src_pts
63
- dst_pts = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]]
64
- dst_pts = resort_quad_points(dst_pts)
65
- return dst_pts
66
-
67
-
68
- def reshape_coords(coords, m, dtype=None):
69
- """ 重置坐标点的维度
70
-
71
- :param list coords: 这个函数主要还是封装了对list情况的处理
72
- 其实np.ndarray结构也行,但这种情况直接用np接口操作就行,不需要引用该函数
73
- :rtype: list
74
-
75
- # 转成 n*1 的矩阵
76
-
77
- >>> reshape_coords([(1, 2), (3, 4)], 1)
78
- [1, 2, 3, 4]
79
- >>> reshape_coords(np.array([[1, 2], [3, 4]]), 1)
80
- [1, 2, 3, 4]
81
- >>> reshape_coords([1, 2, 3, 4], 1)
82
- [1, 2, 3, 4]
83
-
84
- >>> reshape_coords([[1.5, 2], [3.5, 4]], 1)
85
- [1.5, 2.0, 3.5, 4.0]
86
-
87
- # 这种情况,[3,4]、[5,6,7]都是一个整体
88
- # VisibleDeprecationWarning
89
- >>> reshape_coords([1, 2, [3, 4], [5, 6, 7]], 1)
90
- [1, 2, [3, 4], [5, 6, 7]]
91
-
92
- >>> reshape_coords([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]], 1)
93
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
94
-
95
- # 变成 n*2 的矩阵
96
-
97
- >>> reshape_coords([1, 2, 3, 4], 2)
98
- [[1, 2], [3, 4]]
99
- >>> reshape_coords(np.array([1, 2, 3, 4]), 2)
100
- [[1, 2], [3, 4]]
101
- >>> reshape_coords([[1, 2], [3, 4]], 2)
102
- [[1, 2], [3, 4]]
103
- >>> reshape_coords([1.5, 2, 3.5, 4], 2)
104
- [[1.5, 2.0], [3.5, 4.0]]
105
- >>> reshape_coords([1.5, 2, 3.5, 4], 2, dtype=int) # 数据类型转换
106
- [[1, 2], [3, 4]]
107
- """
108
- if m == 1:
109
- return np.array(coords, dtype=dtype).reshape(-1).tolist()
110
- else:
111
- return np.array(coords, dtype=dtype).reshape((-1, m)).tolist()
112
-
113
-
114
- def rect_bounds(coords):
115
- """ 多边形的最大外接矩形
116
-
117
- :param coords: 支持list、np等类型,支持1d、2d两种维度表达方式
118
- :return: rect的两个点坐标,同时也是 [left, top, right, bottom]
119
- """
120
- pts = np.array(coords).reshape(-1).tolist() # tolist不能删,不然int类型就变了。比如int64不能json.dump
121
- p = [min(pts[::2]), min(pts[1::2]), max(pts[::2]), max(pts[1::2])]
122
- return [v for v in p]
123
-
124
-
125
- def resort_quad_points(src_pts):
126
- """ 重置四边形点集顺序,确保以左上角为起点,顺时针罗列点集
127
-
128
- 算法:先确保pt1、pt2在上面,然后再确保pt1在pt2左边
129
-
130
- :param list|tuple|np.ndarray src_pts: 点集
131
- :rtype: list|np.ndarray
132
-
133
- >>> pts = [[100, 50], [200, 0], [100, 0], [0, 50]]
134
- >>> resort_quad_points(pts)
135
- [[100, 0], [200, 0], [100, 50], [0, 50]]
136
- >>> pts # 原来的点不会被修改
137
- [[100, 50], [200, 0], [100, 0], [0, 50]]
138
-
139
- >>> pts = np.array([[100, 50], [200, 0], [100, 0], [0, 50]])
140
- >>> resort_quad_points(pts)
141
- array([[100, 0],
142
- [200, 0],
143
- [100, 0],
144
- [ 0, 50]])
145
- >>> pts # 原来的点不会被修改
146
- array([[100, 50],
147
- [200, 0],
148
- [100, 0],
149
- [ 0, 50]])
150
- """
151
- pts = copy.copy(src_pts)
152
- if isinstance(pts, np.ndarray):
153
- pts = pts.tolist()
154
- if pts[0][1] > pts[2][1]:
155
- pts[0], pts[2] = pts[2], pts[0]
156
- if pts[1][1] > pts[3][1]:
157
- pts[1], pts[3] = pts[3], pts[1]
158
- if pts[0][0] > pts[1][0]:
159
- pts[0], pts[1] = pts[1], pts[0]
160
- pts[2], pts[3] = pts[3], pts[2]
161
- return pts
162
-
163
-
164
- def ltrb_border(ltrb, border, size=None):
165
- """ 给原来的ltrb定位扩展border像素
166
-
167
- Args:
168
- ltrb:
169
- border: 可以一个数字,表示统一添加的像素值
170
- 也可以四个数字,表示每个维度分别加的像素值
171
- size:
172
- 原图的 (width, height),防止越界
173
- 可以不填,默认不考虑越界问题
174
- Returns: 新的ltrb坐标
175
- """
176
- if isinstance(border, int):
177
- border = [border] * 4
178
-
179
- l = max(0, ltrb[0] - border[0])
180
- t = max(0, ltrb[1] - border[1])
181
- r = min(size[0], ltrb[2] + border[2])
182
- b = min(size[1], ltrb[3] + border[3])
183
-
184
- return [l, t, r, b]
185
-
186
-
187
- ____warp_perspective = """
188
- 仿射、透视变换相关功能
189
-
190
- https://www.yuque.com/xlpr/pyxllib/warpperspective
191
- """
192
-
193
-
194
- def warp_points(pts, warp_mat):
195
- """ 透视等点集坐标转换
196
-
197
- :param list|tuple|np.ndarray pts: 支持1d、2d的维度
198
- 其实这个坐标变换就是一个简单的矩阵乘法,只是pts的数据结构往往比较特殊,
199
- 并不是一个n*3的矩阵结构,所以需要进行一些简单的格式转换
200
- 例如 [x1, y1, x2, y2, x3, y3] --> [[x1, x2, x3], [y1, y2, y3], [1, 1, 1]]
201
- :param list|tuple|np.ndarray warp_mat: 变换矩阵,一般是个3*3的矩阵,但是只输入2*3的矩阵也行,因为第3行并用不到(点集只要取前两个维度X'Y'的结果值)
202
- TODO 不过这里我有个点也没想明白,如果不用第3行,本质上不是又变回仿射变换了,如何达到透视变换效果?第三维的深度信息能完全舍弃?
203
- :rtype: np.ndarray
204
-
205
- >>> warp_mat = [[0, 1, 0], [1, 0, 0], [0, 0, 1]] # 对换x、y
206
- >>> warp_points([[1, 2], [11, 22]], warp_mat) # 处理两个点
207
- array([[ 2, 1],
208
- [22, 11]])
209
- >>> warp_points([[1, 2], [11, 22]], [[0, 1, 0], [1, 0, 0]]) # 输入2*3的变换矩阵也可以
210
- array([[ 2, 1],
211
- [22, 11]])
212
- >>> warp_points([1, 2, 11, 22], warp_mat) # 也可以用一维的结构来输入点集
213
- array([[ 2, 1],
214
- [22, 11]])
215
- >>> warp_points([1, 2, 11, 22, 111, 222], warp_mat) # 点的数量任意,返回的结构同输入的结构形式
216
- array([[ 2, 1],
217
- [ 22, 11],
218
- [222, 111]])
219
- >>> warp_points(np.array([1, 2, 11, 22, 111, 222]), warp_mat) # 也可以用np.ndarray等结构
220
- array([[ 2, 1],
221
- [ 22, 11],
222
- [222, 111]])
223
- >>> warp_points([1, 2, 11, 22], warp_mat) # 也可以用一维的结构来输入点集
224
- array([[ 2, 1],
225
- [22, 11]])
226
- """
227
- pts1 = np.array(pts).reshape(-1, 2).T
228
- pts1 = np.concatenate([pts1, [[1] * pts1.shape[1]]], axis=0)
229
- pts2 = np.dot(warp_mat[:2], pts1)
230
- pts2 = pts2.T
231
- return pts2
232
-
233
-
234
- def get_warp_mat(src, dst):
235
- """ 从前后点集计算仿射变换矩阵
236
-
237
- :param src: 原点集,支持多种格式输入
238
- :param dst: 变换后的点集
239
- :return np.ndarray: 3*3的变换矩阵
240
- """
241
-
242
- def cvt_data(pts):
243
- # opencv的透视变换,输入的点集有类型限制,必须使用float32
244
- return np.array(pts, dtype='float32').reshape((-1, 2))
245
-
246
- src, dst = cvt_data(src), cvt_data(dst)
247
- n = src.shape[0]
248
- if n == 3:
249
- # 只有3个点,则使用仿射变换
250
- warp_mat = cv2.getAffineTransform(src, dst)
251
- warp_mat = np.concatenate([warp_mat, [[0, 0, 1]]], axis=0)
252
- elif n == 4:
253
- # 有4个点,则使用透视变换
254
- warp_mat = cv2.getPerspectiveTransform(src, dst)
255
- else:
256
- raise ValueError('点集数量过多')
257
- return warp_mat
258
-
259
-
260
- def quad_warp_wh(pts, method='average'):
261
- """ 四边形转为矩形的宽、高
262
-
263
- :param pts: 四个点坐标
264
- TODO 暂时认为pts是按点集顺时针顺序输入的
265
- TODO 暂时认为pts[0]就是第一个坐标点
266
- :param method:
267
- 记四条边分别为w1, h1, w2, h2
268
- average: 平均宽、高
269
- max: 最大宽、高
270
- min: 最小宽、高
271
- :return: (w, h) 变换后的矩形宽、高
272
- """
273
- # 1 计算四边长
274
- from math import hypot
275
- # pts = ReshapeCoords.list_2d(pts)
276
- lens = [0] * 4
277
- for i in range(4):
278
- pt1, pt2 = pts[i], pts[(i + 1) % 4]
279
- lens[i] = hypot(pt1[0] - pt2[0], pt1[1] - pt2[1])
280
-
281
- # 2 目标宽、高
282
- if method is True:
283
- method = 'average'
284
- if method == 'average':
285
- w, h = (lens[0] + lens[2]) / 2, (lens[1] + lens[3]) / 2
286
- elif method == 'max':
287
- w, h = max(lens[0], lens[2]), max(lens[1], lens[3])
288
- elif method == 'min':
289
- w, h = min(lens[0], lens[2]), min(lens[1], lens[3])
290
- else:
291
- raise ValueError(f'不支持的方法 {method}')
292
- # 这个主要是用于图像变换的,而图像一般像素坐标要用整数,所以就取整运算了
293
- return round(w), round(h)
294
-
295
-
296
- def warp_quad_pts(pts, method='average'):
297
- """ 将不规则四边形转为矩形
298
-
299
- :param pts: 不规则四边形的四个点坐标
300
- :param method: 计算矩形宽、高的算法
301
- :return: 返回时,仍然用四个点的坐标表达,规则矩形的四个点坐标
302
-
303
- >>> warp_quad_pts([[89, 424], [931, 424], [399, 290], [621, 290]])
304
- [[0, 0], [532, 0], [532, 549], [0, 549]]
305
- """
306
- w, h = quad_warp_wh(pts, method)
307
- return rect2polygon([[0, 0], [w, h]])
308
-
309
-
310
- ____polygon = """
311
- """
312
-
313
-
314
- class ComputeIou:
315
- """ 两个多边形的交并比 Intersection Over Union """
316
-
317
- @classmethod
318
- def ltrb(cls, pts1, pts2):
319
- """ https://gist.github.com/meyerjo/dd3533edc97c81258898f60d8978eddc
320
- """
321
- # determine the (x, y)-coordinates of the intersection rectangle
322
- x_a = max(pts1[0], pts2[0])
323
- y_a = max(pts1[1], pts2[1])
324
- x_b = min(pts1[2], pts2[2])
325
- y_b = min(pts1[3], pts2[3])
326
-
327
- # compute the area of intersection rectangle
328
- inter_area = abs(max((x_b - x_a, 0)) * max((y_b - y_a), 0))
329
- if inter_area == 0:
330
- return 0
331
- # compute the area of both the prediction and ground-truth
332
- # rectangles
333
- box_a_area = abs((pts1[2] - pts1[0]) * (pts1[3] - pts1[1]))
334
- box_b_area = abs((pts2[2] - pts2[0]) * (pts2[3] - pts2[1]))
335
-
336
- # compute the intersection over union by taking the intersection
337
- # area and dividing it by the sum of prediction + ground-truth
338
- # areas - the interesection area
339
- iou = inter_area / float(box_a_area + box_b_area - inter_area)
340
-
341
- # return the intersection over union value
342
- return iou
343
-
344
- @classmethod
345
- def polygon(cls, pts1, pts2):
346
- inter_area = pts1.intersection(pts2).area
347
- if inter_area:
348
- union_area = pts1.area + pts2.area - inter_area
349
- return (inter_area / union_area) if union_area else 0
350
- else:
351
- return 0
352
-
353
- @classmethod
354
- def polygon2(cls, pts1, pts2):
355
- """ 会强制转为polygon对象再处理
356
-
357
- >>> ComputeIou.polygon2([[0, 0], [10, 10]], [[5, 5], [15, 15]])
358
- 0.14285714285714285
359
- """
360
- from pyxllib.algo.shapelylib import ShapelyPolygon
361
- polygon1, polygon2 = ShapelyPolygon.gen(pts1), ShapelyPolygon.gen(pts2)
362
- return cls.polygon(polygon1, polygon2)
363
-
364
- @classmethod
365
- def nms_basic(cls, boxes, func, iou=0.5, *, key=None, index=False):
366
- """ 假设boxes已经按权重从大到小排过序
367
-
368
- :param boxes: 支持输入一组box列表 [box1, box2, box3, ...]
369
- :param key: 将框映射为可计算对象
370
- :param index: 返回不是原始框,而是对应的下标 [i1, i2, i3, ...]
371
- """
372
- # 1 映射到items来操作
373
- if callable(key):
374
- items = list(enumerate([key(b) for b in boxes]))
375
- else:
376
- items = list(enumerate(boxes))
377
-
378
- # 2 正常nms功能
379
- idxs = []
380
- while items:
381
- # 1 加入权值大的框
382
- i, b = items[0]
383
- idxs.append(i)
384
- # 2 抑制其他框
385
- left_items = []
386
- for j in range(1, len(items)):
387
- if func(b, items[j][1]) < iou:
388
- left_items.append(items[j])
389
- items = left_items
390
-
391
- # 3 返回值
392
- if index:
393
- return idxs
394
- else:
395
- return [boxes[i] for i in idxs]
396
-
397
- @classmethod
398
- def nms_ltrb(cls, boxes, iou=0.5, *, key=None, index=False):
399
- return cls.nms_basic(boxes, cls.ltrb, iou, key=key, index=index)
400
-
401
- @classmethod
402
- def nms_xywh(cls, boxes, iou=0.5, *, key=None, index=False):
403
- if callable(key):
404
- func = lambda x: xywh2ltrb(key(x))
405
- else:
406
- func = xywh2ltrb
407
- return cls.nms_ltrb(boxes, iou, key=func, index=index)
408
-
409
- @classmethod
410
- def nms_polygon(cls, boxes, iou=0.5, *, key=None, index=False):
411
- # ShapelyPolygon.gen
412
- return cls.nms_basic(boxes, cls.polygon, iou, key=key, index=index)
413
-
414
-
415
- ____other = """
416
- """
417
-
418
-
419
- def divide_quadrangle(coords, r1=0.5, r2=None):
420
- """ 切分一个四边形为两个四边形
421
-
422
- :param coords: 4*2的坐标
423
- :param r1: 第一个切分比例,0.5相当于中点(即第一个四边形右边位置)
424
- :param r2: 第二个切分比例,即第二个四边形左边位置
425
- :return: 返回切割后所有的四边形
426
-
427
- 一般用在改标注结果中,把一个框拆成两个框
428
- TODO 把接口改成切分一个四边形为任意多个四边形?即把r1、r2等整合为一个list参数输入
429
- """
430
-
431
- # 1 计算分割点工具
432
- def segment_point(pt1, pt2, rate=0.5):
433
- """ 两点间的分割点
434
- :param rate: 默认0.5是二分点,rate为0时即pt1,rate为1时为pt2,取值可以小于0、大于-1
435
- :return:
436
- """
437
- x1, y1 = pt1
438
- x2, y2 = pt2
439
- x, y = x1 + rate * (x2 - x1), y1 + rate * (y2 - y1)
440
- return int(x), int(y)
441
-
442
- # 2 优化参数值
443
- # coords = ReshapeCoords.list_2d(coords)
444
- if not r2: r2 = 1 - r1
445
-
446
- # 3 计算切分后的四边形坐标
447
- pt1, pt2, pt3, pt4 = coords
448
- pt5, pt6 = segment_point(pt1, pt2, r1), segment_point(pt4, pt3, r1)
449
- pt7, pt8 = segment_point(pt1, pt2, r2), segment_point(pt4, pt3, r2)
450
- return [pt1, pt5, pt6, pt4], [pt7, pt2, pt3, pt8]
451
-
452
-
453
- def split_vector_interval(vec, maxsplit=None, minwidth=3):
454
- """
455
- :param vec: 一个一维向量,需要对这个向量进行切割
456
- 需要前置工作先处理好数值
457
- 使得背景在非正数,背景概率越大,负值绝对值越大
458
- 前景在正值,前景概率越大,数值越大
459
- 要得到能量最大(数值最大、前景内容)的几个区域
460
- 但是因为有噪声的原因,该算法要有一定的抗干扰能力
461
-
462
- 一般情况下
463
- 用 0 代表背景
464
- 用 <1 的正值表示这一列黑点所占比例(np.mean)
465
- 用 np.sum 传入整数暂时也行,但考虑以后功能扩展性,用比例会更好
466
- 传入负数,表示特殊背景,该背景可以抵消掉的minwidth宽度数
467
- :param maxsplit: 最大切分数量,即最多得到几个子区间
468
- 没设置的时候,会对所有满足条件的情况进行切割
469
- :param minwidth: 每个切分位置最小具有的宽度
470
- :return: [(l, r), (l, r), ...] 每一段文本的左右区间
471
- """
472
- # 1 裁剪左边、右边
473
- n_vec = len(vec)
474
- left, right = 0, n_vec
475
- while left < right and vec[left] <= 0:
476
- left += 1
477
- while right > left and vec[right - 1] <= 0:
478
- right -= 1
479
- # 左右空白至少也要达到minwidth才去除
480
- # if left < minwidth: left = 0
481
- # if n_vec - right + 1 < minwidth: right = n_vec
482
-
483
- vec = vec[left:right]
484
- width = len(vec)
485
- if width == 0:
486
- return [] # 没有内容,返回空list
487
-
488
- # 2 找切分位置
489
- # 统计每一段连续的背景长度,并且对其数值求和,作为这段是背景的置信度
490
- bg_probs, bg_start, cnt = [], 0, 0
491
-
492
- def update_fg():
493
- """ 遇到前景内容,或者循环结束,更新一下 """
494
- nonlocal cnt
495
- prob = vec[bg_start:bg_start + cnt].sum()
496
- # print(cnt, prob)
497
- if cnt >= (minwidth + prob): # 负值可以减小minwidth限定
498
- itv = [bg_start, bg_start + cnt]
499
- bg_probs.append([itv, prob])
500
- cnt = 0
501
-
502
- for i in range(width):
503
- if vec[i] <= 0:
504
- if not cnt:
505
- bg_start = i
506
- cnt += 1
507
- else:
508
- update_fg()
509
- else:
510
- update_fg()
511
-
512
- # 3 取置信度最大的几个分割点
513
- if maxsplit:
514
- bg_probs = sorted(bg_probs, key=lambda x: x[1])[:(maxsplit - 1)]
515
- bg_probs = sorted(bg_probs, key=lambda x: x[0]) # 从左到右排序
516
-
517
- # 4 返回文本区间(反向计算)
518
- res = []
519
- intervals = Intervals([itv for itv, prob in bg_probs]).invert(width) + left
520
- # print(intervals)
521
- for interval in intervals:
522
- res.append([interval.start(), interval.end()])
523
- return res
524
-
525
-
526
- def bound_scale(bound, scale):
527
- """ 一个矩形,以中心为原点,缩放面积为原来scale的新矩形
528
-
529
- :param bound: [x1, y1, x2, y2]
530
- :param scale: 比例,例如0.5,就是缩放一半
531
- """
532
- x1, y1, x2, y2 = bound
533
- x0 = (x2 + x1) / 2
534
- y0 = (y2 + y1) / 2
535
- r = 1 - scale ** 0.5
536
-
537
- x1 += r * abs(x0 - x1)
538
- y1 += r * abs(y0 - y1)
539
- x2 -= r * abs(x0 - x2)
540
- y2 -= r * abs(y0 - y2)
541
- return x1, y1, x2, y2
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # @Author : 陈坤泽
4
+ # @Email : 877362867@qq.com
5
+ # @Date : 2020/11/15 10:16
6
+
7
+ """ 几何、数学运算
8
+
9
+ specialist级别
10
+ """
11
+
12
+ from pyxllib.prog.pupil import check_install_package
13
+
14
+ check_install_package('cv2', 'opencv-python')
15
+
16
+ import copy
17
+
18
+ import numpy as np
19
+ import cv2
20
+
21
+ from pyxllib.algo.intervals import Intervals
22
+
23
+ ____base = """
24
+
25
+ """
26
+
27
+
28
+ def xywh2ltrb(p):
29
+ return [p[0], p[1], p[0] + p[2], p[1] + p[3]]
30
+
31
+
32
+ def ltrb2xywh(p):
33
+ return [p[0], p[1], p[2] - p[0], p[3] - p[1]]
34
+
35
+
36
+ def ltrb2polygon(p):
37
+ """ ltrb坐标转多边形
38
+
39
+ :param list|tuple p: [left, top, right, bottom]
40
+ :rtype: list
41
+
42
+ >>> ltrb2polygon([100, 50, 200, 150])
43
+ [[100, 50], [200, 50], [200, 150], [100, 150]]
44
+ """
45
+ return [p[:2], [p[2], p[1]], p[2:], [p[0], p[3]]]
46
+
47
+
48
+ def rect2polygon(src_pts):
49
+ """ 矩形对角线两个点,转成四边形四个点的模式来表达
50
+ (输入左上、右下两个顶点坐标)
51
+
52
+ :param list|np.ndarray src_pts: size 2*2
53
+ :rtype: list
54
+
55
+ >>> rect2polygon([[0, 0], [10, 20]])
56
+ [[0, 0], [10, 0], [10, 20], [0, 20]]
57
+ >>> rect2polygon(np.array([[0, 0], [10, 20]]))
58
+ [[0, 0], [10, 0], [10, 20], [0, 20]]
59
+ >>> rect2polygon([[10, 0], [0, 20]])
60
+ [[0, 0], [10, 0], [10, 20], [0, 20]]
61
+ """
62
+ [[x1, y1], [x2, y2]] = src_pts
63
+ dst_pts = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]]
64
+ dst_pts = resort_quad_points(dst_pts)
65
+ return dst_pts
66
+
67
+
68
+ def reshape_coords(coords, m, dtype=None):
69
+ """ 重置坐标点的维度
70
+
71
+ :param list coords: 这个函数主要还是封装了对list情况的处理
72
+ 其实np.ndarray结构也行,但这种情况直接用np接口操作就行,不需要引用该函数
73
+ :rtype: list
74
+
75
+ # 转成 n*1 的矩阵
76
+
77
+ >>> reshape_coords([(1, 2), (3, 4)], 1)
78
+ [1, 2, 3, 4]
79
+ >>> reshape_coords(np.array([[1, 2], [3, 4]]), 1)
80
+ [1, 2, 3, 4]
81
+ >>> reshape_coords([1, 2, 3, 4], 1)
82
+ [1, 2, 3, 4]
83
+
84
+ >>> reshape_coords([[1.5, 2], [3.5, 4]], 1)
85
+ [1.5, 2.0, 3.5, 4.0]
86
+
87
+ # 这种情况,[3,4]、[5,6,7]都是一个整体
88
+ # VisibleDeprecationWarning
89
+ >>> reshape_coords([1, 2, [3, 4], [5, 6, 7]], 1)
90
+ [1, 2, [3, 4], [5, 6, 7]]
91
+
92
+ >>> reshape_coords([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]], 1)
93
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
94
+
95
+ # 变成 n*2 的矩阵
96
+
97
+ >>> reshape_coords([1, 2, 3, 4], 2)
98
+ [[1, 2], [3, 4]]
99
+ >>> reshape_coords(np.array([1, 2, 3, 4]), 2)
100
+ [[1, 2], [3, 4]]
101
+ >>> reshape_coords([[1, 2], [3, 4]], 2)
102
+ [[1, 2], [3, 4]]
103
+ >>> reshape_coords([1.5, 2, 3.5, 4], 2)
104
+ [[1.5, 2.0], [3.5, 4.0]]
105
+ >>> reshape_coords([1.5, 2, 3.5, 4], 2, dtype=int) # 数据类型转换
106
+ [[1, 2], [3, 4]]
107
+ """
108
+ if m == 1:
109
+ return np.array(coords, dtype=dtype).reshape(-1).tolist()
110
+ else:
111
+ return np.array(coords, dtype=dtype).reshape((-1, m)).tolist()
112
+
113
+
114
+ def rect_bounds(coords):
115
+ """ 多边形的最大外接矩形
116
+
117
+ :param coords: 支持list、np等类型,支持1d、2d两种维度表达方式
118
+ :return: rect的两个点坐标,同时也是 [left, top, right, bottom]
119
+ """
120
+ pts = np.array(coords).reshape(-1).tolist() # tolist不能删,不然int类型就变了。比如int64不能json.dump
121
+ p = [min(pts[::2]), min(pts[1::2]), max(pts[::2]), max(pts[1::2])]
122
+ return [v for v in p]
123
+
124
+
125
+ def resort_quad_points(src_pts):
126
+ """ 重置四边形点集顺序,确保以左上角为起点,顺时针罗列点集
127
+
128
+ 算法:先确保pt1、pt2在上面,然后再确保pt1在pt2左边
129
+
130
+ :param list|tuple|np.ndarray src_pts: 点集
131
+ :rtype: list|np.ndarray
132
+
133
+ >>> pts = [[100, 50], [200, 0], [100, 0], [0, 50]]
134
+ >>> resort_quad_points(pts)
135
+ [[100, 0], [200, 0], [100, 50], [0, 50]]
136
+ >>> pts # 原来的点不会被修改
137
+ [[100, 50], [200, 0], [100, 0], [0, 50]]
138
+
139
+ >>> pts = np.array([[100, 50], [200, 0], [100, 0], [0, 50]])
140
+ >>> resort_quad_points(pts)
141
+ array([[100, 0],
142
+ [200, 0],
143
+ [100, 0],
144
+ [ 0, 50]])
145
+ >>> pts # 原来的点不会被修改
146
+ array([[100, 50],
147
+ [200, 0],
148
+ [100, 0],
149
+ [ 0, 50]])
150
+ """
151
+ pts = copy.copy(src_pts)
152
+ if isinstance(pts, np.ndarray):
153
+ pts = pts.tolist()
154
+ if pts[0][1] > pts[2][1]:
155
+ pts[0], pts[2] = pts[2], pts[0]
156
+ if pts[1][1] > pts[3][1]:
157
+ pts[1], pts[3] = pts[3], pts[1]
158
+ if pts[0][0] > pts[1][0]:
159
+ pts[0], pts[1] = pts[1], pts[0]
160
+ pts[2], pts[3] = pts[3], pts[2]
161
+ return pts
162
+
163
+
164
+ def ltrb_border(ltrb, border, size=None):
165
+ """ 给原来的ltrb定位扩展border像素
166
+
167
+ Args:
168
+ ltrb:
169
+ border: 可以一个数字,表示统一添加的像素值
170
+ 也可以四个数字,表示每个维度分别加的像素值
171
+ size:
172
+ 原图的 (width, height),防止越界
173
+ 可以不填,默认不考虑越界问题
174
+ Returns: 新的ltrb坐标
175
+ """
176
+ if isinstance(border, int):
177
+ border = [border] * 4
178
+
179
+ l = max(0, ltrb[0] - border[0])
180
+ t = max(0, ltrb[1] - border[1])
181
+ r = min(size[0], ltrb[2] + border[2])
182
+ b = min(size[1], ltrb[3] + border[3])
183
+
184
+ return [l, t, r, b]
185
+
186
+
187
+ ____warp_perspective = """
188
+ 仿射、透视变换相关功能
189
+
190
+ https://www.yuque.com/xlpr/pyxllib/warpperspective
191
+ """
192
+
193
+
194
+ def warp_points(pts, warp_mat):
195
+ """ 透视等点集坐标转换
196
+
197
+ :param list|tuple|np.ndarray pts: 支持1d、2d的维度
198
+ 其实这个坐标变换就是一个简单的矩阵乘法,只是pts的数据结构往往比较特殊,
199
+ 并不是一个n*3的矩阵结构,所以需要进行一些简单的格式转换
200
+ 例如 [x1, y1, x2, y2, x3, y3] --> [[x1, x2, x3], [y1, y2, y3], [1, 1, 1]]
201
+ :param list|tuple|np.ndarray warp_mat: 变换矩阵,一般是个3*3的矩阵,但是只输入2*3的矩阵也行,因为第3行并用不到(点集只要取前两个维度X'Y'的结果值)
202
+ TODO 不过这里我有个点也没想明白,如果不用第3行,本质上不是又变回仿射变换了,如何达到透视变换效果?第三维的深度信息能完全舍弃?
203
+ :rtype: np.ndarray
204
+
205
+ >>> warp_mat = [[0, 1, 0], [1, 0, 0], [0, 0, 1]] # 对换x、y
206
+ >>> warp_points([[1, 2], [11, 22]], warp_mat) # 处理两个点
207
+ array([[ 2, 1],
208
+ [22, 11]])
209
+ >>> warp_points([[1, 2], [11, 22]], [[0, 1, 0], [1, 0, 0]]) # 输入2*3的变换矩阵也可以
210
+ array([[ 2, 1],
211
+ [22, 11]])
212
+ >>> warp_points([1, 2, 11, 22], warp_mat) # 也可以用一维的结构来输入点集
213
+ array([[ 2, 1],
214
+ [22, 11]])
215
+ >>> warp_points([1, 2, 11, 22, 111, 222], warp_mat) # 点的数量任意,返回的结构同输入的结构形式
216
+ array([[ 2, 1],
217
+ [ 22, 11],
218
+ [222, 111]])
219
+ >>> warp_points(np.array([1, 2, 11, 22, 111, 222]), warp_mat) # 也可以用np.ndarray等结构
220
+ array([[ 2, 1],
221
+ [ 22, 11],
222
+ [222, 111]])
223
+ >>> warp_points([1, 2, 11, 22], warp_mat) # 也可以用一维的结构来输入点集
224
+ array([[ 2, 1],
225
+ [22, 11]])
226
+ """
227
+ pts1 = np.array(pts).reshape(-1, 2).T
228
+ pts1 = np.concatenate([pts1, [[1] * pts1.shape[1]]], axis=0)
229
+ pts2 = np.dot(warp_mat[:2], pts1)
230
+ pts2 = pts2.T
231
+ return pts2
232
+
233
+
234
+ def get_warp_mat(src, dst):
235
+ """ 从前后点集计算仿射变换矩阵
236
+
237
+ :param src: 原点集,支持多种格式输入
238
+ :param dst: 变换后的点集
239
+ :return np.ndarray: 3*3的变换矩阵
240
+ """
241
+
242
+ def cvt_data(pts):
243
+ # opencv的透视变换,输入的点集有类型限制,必须使用float32
244
+ return np.array(pts, dtype='float32').reshape((-1, 2))
245
+
246
+ src, dst = cvt_data(src), cvt_data(dst)
247
+ n = src.shape[0]
248
+ if n == 3:
249
+ # 只有3个点,则使用仿射变换
250
+ warp_mat = cv2.getAffineTransform(src, dst)
251
+ warp_mat = np.concatenate([warp_mat, [[0, 0, 1]]], axis=0)
252
+ elif n == 4:
253
+ # 有4个点,则使用透视变换
254
+ warp_mat = cv2.getPerspectiveTransform(src, dst)
255
+ else:
256
+ raise ValueError('点集数量过多')
257
+ return warp_mat
258
+
259
+
260
+ def quad_warp_wh(pts, method='average'):
261
+ """ 四边形转为矩形的宽、高
262
+
263
+ :param pts: 四个点坐标
264
+ TODO 暂时认为pts是按点集顺时针顺序输入的
265
+ TODO 暂时认为pts[0]就是第一个坐标点
266
+ :param method:
267
+ 记四条边分别为w1, h1, w2, h2
268
+ average: 平均宽、高
269
+ max: 最大宽、高
270
+ min: 最小宽、高
271
+ :return: (w, h) 变换后的矩形宽、高
272
+ """
273
+ # 1 计算四边长
274
+ from math import hypot
275
+ # pts = ReshapeCoords.list_2d(pts)
276
+ lens = [0] * 4
277
+ for i in range(4):
278
+ pt1, pt2 = pts[i], pts[(i + 1) % 4]
279
+ lens[i] = hypot(pt1[0] - pt2[0], pt1[1] - pt2[1])
280
+
281
+ # 2 目标宽、高
282
+ if method is True:
283
+ method = 'average'
284
+ if method == 'average':
285
+ w, h = (lens[0] + lens[2]) / 2, (lens[1] + lens[3]) / 2
286
+ elif method == 'max':
287
+ w, h = max(lens[0], lens[2]), max(lens[1], lens[3])
288
+ elif method == 'min':
289
+ w, h = min(lens[0], lens[2]), min(lens[1], lens[3])
290
+ else:
291
+ raise ValueError(f'不支持的方法 {method}')
292
+ # 这个主要是用于图像变换的,而图像一般像素坐标要用整数,所以就取整运算了
293
+ return round(w), round(h)
294
+
295
+
296
+ def warp_quad_pts(pts, method='average'):
297
+ """ 将不规则四边形转为矩形
298
+
299
+ :param pts: 不规则四边形的四个点坐标
300
+ :param method: 计算矩形宽、高的算法
301
+ :return: 返回时,仍然用四个点的坐标表达,规则矩形的四个点坐标
302
+
303
+ >>> warp_quad_pts([[89, 424], [931, 424], [399, 290], [621, 290]])
304
+ [[0, 0], [532, 0], [532, 549], [0, 549]]
305
+ """
306
+ w, h = quad_warp_wh(pts, method)
307
+ return rect2polygon([[0, 0], [w, h]])
308
+
309
+
310
+ ____polygon = """
311
+ """
312
+
313
+
314
+ class ComputeIou:
315
+ """ 两个多边形的交并比 Intersection Over Union """
316
+
317
+ @classmethod
318
+ def ltrb(cls, pts1, pts2):
319
+ """ https://gist.github.com/meyerjo/dd3533edc97c81258898f60d8978eddc
320
+ """
321
+ # determine the (x, y)-coordinates of the intersection rectangle
322
+ x_a = max(pts1[0], pts2[0])
323
+ y_a = max(pts1[1], pts2[1])
324
+ x_b = min(pts1[2], pts2[2])
325
+ y_b = min(pts1[3], pts2[3])
326
+
327
+ # compute the area of intersection rectangle
328
+ inter_area = abs(max((x_b - x_a, 0)) * max((y_b - y_a), 0))
329
+ if inter_area == 0:
330
+ return 0
331
+ # compute the area of both the prediction and ground-truth
332
+ # rectangles
333
+ box_a_area = abs((pts1[2] - pts1[0]) * (pts1[3] - pts1[1]))
334
+ box_b_area = abs((pts2[2] - pts2[0]) * (pts2[3] - pts2[1]))
335
+
336
+ # compute the intersection over union by taking the intersection
337
+ # area and dividing it by the sum of prediction + ground-truth
338
+ # areas - the interesection area
339
+ iou = inter_area / float(box_a_area + box_b_area - inter_area)
340
+
341
+ # return the intersection over union value
342
+ return iou
343
+
344
+ @classmethod
345
+ def polygon(cls, pts1, pts2):
346
+ inter_area = pts1.intersection(pts2).area
347
+ if inter_area:
348
+ union_area = pts1.area + pts2.area - inter_area
349
+ return (inter_area / union_area) if union_area else 0
350
+ else:
351
+ return 0
352
+
353
+ @classmethod
354
+ def polygon2(cls, pts1, pts2):
355
+ """ 会强制转为polygon对象再处理
356
+
357
+ >>> ComputeIou.polygon2([[0, 0], [10, 10]], [[5, 5], [15, 15]])
358
+ 0.14285714285714285
359
+ """
360
+ from pyxllib.algo.shapelylib import ShapelyPolygon
361
+ polygon1, polygon2 = ShapelyPolygon.gen(pts1), ShapelyPolygon.gen(pts2)
362
+ return cls.polygon(polygon1, polygon2)
363
+
364
+ @classmethod
365
+ def nms_basic(cls, boxes, func, iou=0.5, *, key=None, index=False):
366
+ """ 假设boxes已经按权重从大到小排过序
367
+
368
+ :param boxes: 支持输入一组box列表 [box1, box2, box3, ...]
369
+ :param key: 将框映射为可计算对象
370
+ :param index: 返回不是原始框,而是对应的下标 [i1, i2, i3, ...]
371
+ """
372
+ # 1 映射到items来操作
373
+ if callable(key):
374
+ items = list(enumerate([key(b) for b in boxes]))
375
+ else:
376
+ items = list(enumerate(boxes))
377
+
378
+ # 2 正常nms功能
379
+ idxs = []
380
+ while items:
381
+ # 1 加入权值大的框
382
+ i, b = items[0]
383
+ idxs.append(i)
384
+ # 2 抑制其他框
385
+ left_items = []
386
+ for j in range(1, len(items)):
387
+ if func(b, items[j][1]) < iou:
388
+ left_items.append(items[j])
389
+ items = left_items
390
+
391
+ # 3 返回值
392
+ if index:
393
+ return idxs
394
+ else:
395
+ return [boxes[i] for i in idxs]
396
+
397
+ @classmethod
398
+ def nms_ltrb(cls, boxes, iou=0.5, *, key=None, index=False):
399
+ return cls.nms_basic(boxes, cls.ltrb, iou, key=key, index=index)
400
+
401
+ @classmethod
402
+ def nms_xywh(cls, boxes, iou=0.5, *, key=None, index=False):
403
+ if callable(key):
404
+ func = lambda x: xywh2ltrb(key(x))
405
+ else:
406
+ func = xywh2ltrb
407
+ return cls.nms_ltrb(boxes, iou, key=func, index=index)
408
+
409
+ @classmethod
410
+ def nms_polygon(cls, boxes, iou=0.5, *, key=None, index=False):
411
+ # ShapelyPolygon.gen
412
+ return cls.nms_basic(boxes, cls.polygon, iou, key=key, index=index)
413
+
414
+
415
+ ____other = """
416
+ """
417
+
418
+
419
+ def divide_quadrangle(coords, r1=0.5, r2=None):
420
+ """ 切分一个四边形为两个四边形
421
+
422
+ :param coords: 4*2的坐标
423
+ :param r1: 第一个切分比例,0.5相当于中点(即第一个四边形右边位置)
424
+ :param r2: 第二个切分比例,即第二个四边形左边位置
425
+ :return: 返回切割后所有的四边形
426
+
427
+ 一般用在改标注结果中,把一个框拆成两个框
428
+ TODO 把接口改成切分一个四边形为任意多个四边形?即把r1、r2等整合为一个list参数输入
429
+ """
430
+
431
+ # 1 计算分割点工具
432
+ def segment_point(pt1, pt2, rate=0.5):
433
+ """ 两点间的分割点
434
+ :param rate: 默认0.5是二分点,rate为0时即pt1,rate为1时为pt2,取值可以小于0、大于-1
435
+ :return:
436
+ """
437
+ x1, y1 = pt1
438
+ x2, y2 = pt2
439
+ x, y = x1 + rate * (x2 - x1), y1 + rate * (y2 - y1)
440
+ return int(x), int(y)
441
+
442
+ # 2 优化参数值
443
+ # coords = ReshapeCoords.list_2d(coords)
444
+ if not r2: r2 = 1 - r1
445
+
446
+ # 3 计算切分后的四边形坐标
447
+ pt1, pt2, pt3, pt4 = coords
448
+ pt5, pt6 = segment_point(pt1, pt2, r1), segment_point(pt4, pt3, r1)
449
+ pt7, pt8 = segment_point(pt1, pt2, r2), segment_point(pt4, pt3, r2)
450
+ return [pt1, pt5, pt6, pt4], [pt7, pt2, pt3, pt8]
451
+
452
+
453
+ def split_vector_interval(vec, maxsplit=None, minwidth=3):
454
+ """
455
+ :param vec: 一个一维向量,需要对这个向量进行切割
456
+ 需要前置工作先处理好数值
457
+ 使得背景在非正数,背景概率越大,负值绝对值越大
458
+ 前景在正值,前景概率越大,数值越大
459
+ 要得到能量最大(数值最大、前景内容)的几个区域
460
+ 但是因为有噪声的原因,该算法要有一定的抗干扰能力
461
+
462
+ 一般情况下
463
+ 用 0 代表背景
464
+ 用 <1 的正值表示这一列黑点所占比例(np.mean)
465
+ 用 np.sum 传入整数暂时也行,但考虑以后功能扩展性,用比例会更好
466
+ 传入负数,表示特殊背景,该背景可以抵消掉的minwidth宽度数
467
+ :param maxsplit: 最大切分数量,即最多得到几个子区间
468
+ 没设置的时候,会对所有满足条件的情况进行切割
469
+ :param minwidth: 每个切分位置最小具有的宽度
470
+ :return: [(l, r), (l, r), ...] 每一段文本的左右区间
471
+ """
472
+ # 1 裁剪左边、右边
473
+ n_vec = len(vec)
474
+ left, right = 0, n_vec
475
+ while left < right and vec[left] <= 0:
476
+ left += 1
477
+ while right > left and vec[right - 1] <= 0:
478
+ right -= 1
479
+ # 左右空白至少也要达到minwidth才去除
480
+ # if left < minwidth: left = 0
481
+ # if n_vec - right + 1 < minwidth: right = n_vec
482
+
483
+ vec = vec[left:right]
484
+ width = len(vec)
485
+ if width == 0:
486
+ return [] # 没有内容,返回空list
487
+
488
+ # 2 找切分位置
489
+ # 统计每一段连续的背景长度,并且对其数值求和,作为这段是背景的置信度
490
+ bg_probs, bg_start, cnt = [], 0, 0
491
+
492
+ def update_fg():
493
+ """ 遇到前景内容,或者循环结束,更新一下 """
494
+ nonlocal cnt
495
+ prob = vec[bg_start:bg_start + cnt].sum()
496
+ # print(cnt, prob)
497
+ if cnt >= (minwidth + prob): # 负值可以减小minwidth限定
498
+ itv = [bg_start, bg_start + cnt]
499
+ bg_probs.append([itv, prob])
500
+ cnt = 0
501
+
502
+ for i in range(width):
503
+ if vec[i] <= 0:
504
+ if not cnt:
505
+ bg_start = i
506
+ cnt += 1
507
+ else:
508
+ update_fg()
509
+ else:
510
+ update_fg()
511
+
512
+ # 3 取置信度最大的几个分割点
513
+ if maxsplit:
514
+ bg_probs = sorted(bg_probs, key=lambda x: x[1])[:(maxsplit - 1)]
515
+ bg_probs = sorted(bg_probs, key=lambda x: x[0]) # 从左到右排序
516
+
517
+ # 4 返回文本区间(反向计算)
518
+ res = []
519
+ intervals = Intervals([itv for itv, prob in bg_probs]).invert(width) + left
520
+ # print(intervals)
521
+ for interval in intervals:
522
+ res.append([interval.start(), interval.end()])
523
+ return res
524
+
525
+
526
+ def bound_scale(bound, scale):
527
+ """ 一个矩形,以中心为原点,缩放面积为原来scale的新矩形
528
+
529
+ :param bound: [x1, y1, x2, y2]
530
+ :param scale: 比例,例如0.5,就是缩放一半
531
+ """
532
+ x1, y1, x2, y2 = bound
533
+ x0 = (x2 + x1) / 2
534
+ y0 = (y2 + y1) / 2
535
+ r = 1 - scale ** 0.5
536
+
537
+ x1 += r * abs(x0 - x1)
538
+ y1 += r * abs(y0 - y1)
539
+ x2 -= r * abs(x0 - x2)
540
+ y2 -= r * abs(y0 - y2)
541
+ return x1, y1, x2, y2