pyxllib 0.0.43__py3-none-any.whl → 0.3.197__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 (186) hide show
  1. pyxllib/__init__.py +9 -2
  2. pyxllib/algo/__init__.py +8 -0
  3. pyxllib/algo/disjoint.py +54 -0
  4. pyxllib/algo/geo.py +541 -0
  5. pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
  6. pyxllib/algo/matcher.py +389 -0
  7. pyxllib/algo/newbie.py +166 -0
  8. pyxllib/algo/pupil.py +629 -0
  9. pyxllib/algo/shapelylib.py +67 -0
  10. pyxllib/algo/specialist.py +241 -0
  11. pyxllib/algo/stat.py +494 -0
  12. pyxllib/algo/treelib.py +149 -0
  13. pyxllib/algo/unitlib.py +66 -0
  14. pyxllib/autogui/__init__.py +5 -0
  15. pyxllib/autogui/activewin.py +246 -0
  16. pyxllib/autogui/all.py +9 -0
  17. pyxllib/autogui/autogui.py +852 -0
  18. pyxllib/autogui/uiautolib.py +362 -0
  19. pyxllib/autogui/virtualkey.py +102 -0
  20. pyxllib/autogui/wechat.py +827 -0
  21. pyxllib/autogui/wechat_msg.py +421 -0
  22. pyxllib/autogui/wxautolib.py +84 -0
  23. pyxllib/cv/__init__.py +1 -11
  24. pyxllib/cv/expert.py +267 -0
  25. pyxllib/cv/{imlib.py → imfile.py} +18 -83
  26. pyxllib/cv/imhash.py +39 -0
  27. pyxllib/cv/pupil.py +9 -0
  28. pyxllib/cv/rgbfmt.py +1525 -0
  29. pyxllib/cv/slidercaptcha.py +137 -0
  30. pyxllib/cv/trackbartools.py +163 -49
  31. pyxllib/cv/xlcvlib.py +1040 -0
  32. pyxllib/cv/xlpillib.py +423 -0
  33. pyxllib/data/__init__.py +0 -0
  34. pyxllib/data/echarts.py +240 -0
  35. pyxllib/data/jsonlib.py +89 -0
  36. pyxllib/{util/oss2_.py → data/oss.py} +11 -9
  37. pyxllib/data/pglib.py +1127 -0
  38. pyxllib/data/sqlite.py +568 -0
  39. pyxllib/{util → data}/sqllib.py +13 -31
  40. pyxllib/ext/JLineViewer.py +505 -0
  41. pyxllib/ext/__init__.py +6 -0
  42. pyxllib/{util → ext}/demolib.py +119 -35
  43. pyxllib/ext/drissionlib.py +277 -0
  44. pyxllib/ext/kq5034lib.py +12 -0
  45. pyxllib/{util/main.py → ext/old.py} +122 -284
  46. pyxllib/ext/qt.py +449 -0
  47. pyxllib/ext/robustprocfile.py +497 -0
  48. pyxllib/ext/seleniumlib.py +76 -0
  49. pyxllib/{util/tklib.py → ext/tk.py} +10 -11
  50. pyxllib/ext/unixlib.py +827 -0
  51. pyxllib/ext/utools.py +351 -0
  52. pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
  53. pyxllib/ext/win32lib.py +40 -0
  54. pyxllib/ext/wjxlib.py +88 -0
  55. pyxllib/ext/wpsapi.py +124 -0
  56. pyxllib/ext/xlwork.py +9 -0
  57. pyxllib/ext/yuquelib.py +1105 -0
  58. pyxllib/file/__init__.py +17 -0
  59. pyxllib/file/docxlib.py +761 -0
  60. pyxllib/{util → file}/gitlib.py +40 -27
  61. pyxllib/file/libreoffice.py +165 -0
  62. pyxllib/file/movielib.py +148 -0
  63. pyxllib/file/newbie.py +10 -0
  64. pyxllib/file/onenotelib.py +1469 -0
  65. pyxllib/file/packlib/__init__.py +330 -0
  66. pyxllib/{util → file/packlib}/zipfile.py +598 -195
  67. pyxllib/file/pdflib.py +426 -0
  68. pyxllib/file/pupil.py +185 -0
  69. pyxllib/file/specialist/__init__.py +685 -0
  70. pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
  71. pyxllib/file/specialist/download.py +193 -0
  72. pyxllib/file/specialist/filelib.py +2829 -0
  73. pyxllib/file/xlsxlib.py +3131 -0
  74. pyxllib/file/xlsyncfile.py +341 -0
  75. pyxllib/prog/__init__.py +5 -0
  76. pyxllib/prog/cachetools.py +64 -0
  77. pyxllib/prog/deprecatedlib.py +233 -0
  78. pyxllib/prog/filelock.py +42 -0
  79. pyxllib/prog/ipyexec.py +253 -0
  80. pyxllib/prog/multiprogs.py +940 -0
  81. pyxllib/prog/newbie.py +451 -0
  82. pyxllib/prog/pupil.py +1197 -0
  83. pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
  84. pyxllib/prog/specialist/__init__.py +391 -0
  85. pyxllib/prog/specialist/bc.py +203 -0
  86. pyxllib/prog/specialist/browser.py +497 -0
  87. pyxllib/prog/specialist/common.py +347 -0
  88. pyxllib/prog/specialist/datetime.py +199 -0
  89. pyxllib/prog/specialist/tictoc.py +240 -0
  90. pyxllib/prog/specialist/xllog.py +180 -0
  91. pyxllib/prog/xlosenv.py +108 -0
  92. pyxllib/stdlib/__init__.py +17 -0
  93. pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
  94. pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
  95. pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
  96. pyxllib/text/__init__.py +8 -0
  97. pyxllib/text/ahocorasick.py +39 -0
  98. pyxllib/text/airscript.js +744 -0
  99. pyxllib/text/charclasslib.py +121 -0
  100. pyxllib/text/jiebalib.py +267 -0
  101. pyxllib/text/jinjalib.py +32 -0
  102. pyxllib/text/jsa_ai_prompt.md +271 -0
  103. pyxllib/text/jscode.py +922 -0
  104. pyxllib/text/latex/__init__.py +158 -0
  105. pyxllib/text/levenshtein.py +303 -0
  106. pyxllib/text/nestenv.py +1215 -0
  107. pyxllib/text/newbie.py +300 -0
  108. pyxllib/text/pupil/__init__.py +8 -0
  109. pyxllib/text/pupil/common.py +1121 -0
  110. pyxllib/text/pupil/xlalign.py +326 -0
  111. pyxllib/text/pycode.py +47 -0
  112. pyxllib/text/specialist/__init__.py +8 -0
  113. pyxllib/text/specialist/common.py +112 -0
  114. pyxllib/text/specialist/ptag.py +186 -0
  115. pyxllib/text/spellchecker.py +172 -0
  116. pyxllib/text/templates/echart_base.html +11 -0
  117. pyxllib/text/templates/highlight_code.html +17 -0
  118. pyxllib/text/templates/latex_editor.html +103 -0
  119. pyxllib/text/vbacode.py +17 -0
  120. pyxllib/text/xmllib.py +747 -0
  121. pyxllib/xl.py +39 -0
  122. pyxllib/xlcv.py +17 -0
  123. pyxllib-0.3.197.dist-info/METADATA +48 -0
  124. pyxllib-0.3.197.dist-info/RECORD +126 -0
  125. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
  126. pyxllib/basic/_1_strlib.py +0 -945
  127. pyxllib/basic/_2_timelib.py +0 -488
  128. pyxllib/basic/_3_pathlib.py +0 -916
  129. pyxllib/basic/_4_loglib.py +0 -419
  130. pyxllib/basic/__init__.py +0 -54
  131. pyxllib/basic/arrow_.py +0 -250
  132. pyxllib/basic/chardet_.py +0 -66
  133. pyxllib/basic/dirlib.py +0 -529
  134. pyxllib/basic/dprint.py +0 -202
  135. pyxllib/basic/extension.py +0 -12
  136. pyxllib/basic/judge.py +0 -31
  137. pyxllib/basic/log.py +0 -204
  138. pyxllib/basic/pathlib_.py +0 -705
  139. pyxllib/basic/pytictoc.py +0 -102
  140. pyxllib/basic/qiniu_.py +0 -61
  141. pyxllib/basic/strlib.py +0 -761
  142. pyxllib/basic/timer.py +0 -132
  143. pyxllib/cv/cv.py +0 -834
  144. pyxllib/cv/cvlib/_1_geo.py +0 -543
  145. pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
  146. pyxllib/cv/cvlib/_2_imgproc.py +0 -594
  147. pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
  148. pyxllib/cv/cvlib/_4_cvimg.py +0 -211
  149. pyxllib/cv/cvlib/__init__.py +0 -10
  150. pyxllib/cv/debugtools.py +0 -82
  151. pyxllib/cv/fitz_.py +0 -300
  152. pyxllib/cv/installer.py +0 -42
  153. pyxllib/debug/_0_installer.py +0 -38
  154. pyxllib/debug/_1_typelib.py +0 -277
  155. pyxllib/debug/_2_chrome.py +0 -198
  156. pyxllib/debug/_3_showdir.py +0 -161
  157. pyxllib/debug/_4_bcompare.py +0 -140
  158. pyxllib/debug/__init__.py +0 -49
  159. pyxllib/debug/bcompare.py +0 -132
  160. pyxllib/debug/chrome.py +0 -198
  161. pyxllib/debug/installer.py +0 -38
  162. pyxllib/debug/showdir.py +0 -158
  163. pyxllib/debug/typelib.py +0 -278
  164. pyxllib/image/__init__.py +0 -12
  165. pyxllib/torch/__init__.py +0 -20
  166. pyxllib/torch/modellib.py +0 -37
  167. pyxllib/torch/trainlib.py +0 -344
  168. pyxllib/util/__init__.py +0 -20
  169. pyxllib/util/aip_.py +0 -141
  170. pyxllib/util/casiadb.py +0 -59
  171. pyxllib/util/excellib.py +0 -495
  172. pyxllib/util/filelib.py +0 -612
  173. pyxllib/util/jsondata.py +0 -27
  174. pyxllib/util/jsondata2.py +0 -92
  175. pyxllib/util/labelmelib.py +0 -139
  176. pyxllib/util/onepy/__init__.py +0 -29
  177. pyxllib/util/onepy/onepy.py +0 -574
  178. pyxllib/util/onepy/onmanager.py +0 -170
  179. pyxllib/util/pyautogui_.py +0 -219
  180. pyxllib/util/textlib.py +0 -1305
  181. pyxllib/util/unorder.py +0 -22
  182. pyxllib/util/xmllib.py +0 -639
  183. pyxllib-0.0.43.dist-info/METADATA +0 -39
  184. pyxllib-0.0.43.dist-info/RECORD +0 -80
  185. pyxllib-0.0.43.dist-info/top_level.txt +0 -1
  186. {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
pyxllib/cv/cv.py DELETED
@@ -1,834 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # @Author : 陈坤泽
4
- # @Email : 877362867@qq.com
5
- # @Data : 2020/08/13 14:53
6
-
7
-
8
- """
9
- 以后这里cv功能多了,可以再拆子文件夹
10
- """
11
-
12
- import copy
13
- import re
14
-
15
- import numpy as np
16
- import cv2
17
- import PIL.Image
18
- from shapely.geometry import Polygon
19
-
20
- from pyxllib.basic import Path
21
- from pyxllib.debug import dprint, showdir
22
-
23
- ____ensure_array_type = """
24
- 数组方面的类型转换,相关类型有
25
-
26
- list (tuple) 1d, 2d
27
- np.ndarray 1d, 2d
28
- PIL.Image.Image
29
- Polygon
30
-
31
- 这里封装的目的,是尽量减少不必要的数据重复拷贝,需要做些底层的特定判断优化
32
- """
33
-
34
-
35
- def np_array(x, dtype=None, shape=None):
36
- """确保数据是np.ndarray结构,如果不是则做一个转换
37
-
38
- 如果x已经是np.ndarray,尽量减小数据的拷贝,提高效率
39
-
40
- :param dtype: 还可以顺便指定数据类型,可以修改值的存储类型
41
-
42
- TODO 增加一些字符串初始化方法,例如类似matlab这样的 [1 2 3 4]。虽然从性能角度不推荐,但是工程化应该提供尽可能完善全面的功能。
43
- """
44
- if isinstance(x, np.ndarray):
45
- if x.dtype == np.dtype(dtype):
46
- y = x
47
- else:
48
- y = np.array(x, dtype=dtype)
49
- elif isinstance(x, PIL.Image.Image):
50
- # PIL RGB图像数据转 np的 BGR数据
51
- y = cv2.cvtColor(np.array(x), cv2.COLOR_RGB2BGR)
52
- if dtype: y = np.array(x, dtype=dtype)
53
- elif isinstance(x, Polygon):
54
- y = np.array(x.exterior.coords, dtype=dtype)
55
- else:
56
- y = np.array(x, dtype=dtype)
57
-
58
- if shape:
59
- y = y.reshape(shape)
60
-
61
- return y
62
-
63
-
64
- def to_list(x, dtype=None, shape=None):
65
- """
66
- :param x:
67
- :param shape: 输入格式如:-1, (-1, ), (-1, 2)
68
- :return: list、tuple嵌套结构
69
- """
70
- if isinstance(x, (list, tuple)):
71
- # 1 尽量不要用这两个参数,否则一定会使用到np矩阵作为中转
72
- if dtype or shape:
73
- y = np_array(x, dtype, shape)
74
- else:
75
- y = x
76
- else:
77
- y = np_array(x, dtype, shape).tolist()
78
- return y
79
-
80
-
81
- def pil_image(x):
82
- if isinstance(x, PIL.Image.Image):
83
- y = x
84
- else:
85
- y = np_array(x)
86
- y = PIL.Image.fromarray(cv2.cvtColor(y, cv2.COLOR_BGR2RGB)) if y.size else None
87
- return y
88
-
89
-
90
- def shapely_polygon(x):
91
- """ 转成shapely的Polygon对象
92
-
93
- :param x: 支持多种格式,详见代码
94
- :return: Polygon
95
-
96
- >>> print(shapely_polygon([[0, 0], [10, 20]])) # list
97
- POLYGON ((0 0, 10 0, 10 20, 0 20, 0 0))
98
- >>> print(shapely_polygon({'shape_type': 'polygon', 'points': [[0, 0], [10, 0], [10, 20], [0, 20]]})) # labelme shape
99
- POLYGON ((0 0, 10 0, 10 20, 0 20, 0 0))
100
- >>> print(shapely_polygon('107,247,2358,209,2358,297,107,335')) # 字符串格式
101
- POLYGON ((107 247, 2358 209, 2358 297, 107 335, 107 247))
102
- >>> print(shapely_polygon('107 247.5, 2358 209.2, 2358 297, 107.5 335')) # 字符串格式
103
- POLYGON ((107 247.5, 2358 209.2, 2358 297, 107.5 335, 107 247.5))
104
- """
105
- from shapely.geometry import Polygon
106
-
107
- if isinstance(x, Polygon):
108
- return x
109
- elif isinstance(x, dict) and 'points' in x:
110
- if x['shape_type'] in ('rectangle', 'polygon'):
111
- # 目前这种情况一般是输入了labelme的shape格式
112
- return shapely_polygon(x['points'])
113
- else:
114
- raise ValueError('无法转成多边形的类型')
115
- elif isinstance(x, str):
116
- coords = re.findall(r'[\d\.]+', x)
117
- return shapely_polygon(coords)
118
- else:
119
- x = np_array(x, shape=(-1, 2))
120
- if x.shape[0] == 2:
121
- x = rect2polygon(x)
122
- x = np.array(x)
123
- if x.shape[0] >= 3:
124
- return Polygon(x)
125
- else:
126
- raise ValueError
127
-
128
-
129
- def ensure_array_type(src_data, target_type):
130
- """ 参考 target 的数据结构,重设src的结构
131
-
132
- 目前支持的数据结构
133
- PIL.Image.Image,涉及到图像格式转换的,为了与opencv兼容,一律以BGR为准,除了Image自身默认用RGB
134
- np.ndarray
135
- list
136
- shapely polygon
137
-
138
- :param src_data: 原数据
139
- :param target_type: 正常是指定一个type类型
140
- :return: 重置结构后的src数据
141
-
142
- >>> ensure_array_type([1, 2, 3, 4], type([[1, 1], [2, 2]]))
143
- [1, 2, 3, 4]
144
- >>> ensure_array_type([1, 2, 3, 4], type(np.array([[1, 1], [2, 2]])))
145
- array([1, 2, 3, 4])
146
- >>> ensure_array_type([1, 2, 3, 4], np.ndarray)
147
- array([1, 2, 3, 4])
148
- >>> ensure_array_type(np.array([[1, 2], [3, 4]]), type([10, 20, 30, 40]))
149
- [[1, 2], [3, 4]]
150
- >>> ensure_array_type(np.array([]), type([10, 20, 30, 40]))
151
- []
152
-
153
- 其他测试:
154
- # np矩阵转PIL图像
155
- img = cv2.imread(r'textline.jpg')
156
- dst = reset_arr_struct(img, PIL.Image.Image)
157
- print(type(dst)) # <class 'PIL.Image.Image'>
158
-
159
- # PIL图像转np矩阵
160
- img = Image.open('textline.jpg')
161
- dst = reset_arr_struct(img, np.ndarray)
162
- print(type(dst)) # <class 'numpy.ndarray'>
163
- """
164
- # 根据不同的目标数据类型,进行格式转换
165
- if target_type == np.ndarray:
166
- return np_array(src_data)
167
- elif target_type in (list, tuple):
168
- return np_array(src_data).tolist()
169
- elif target_type == PIL.Image.Image:
170
- return pil_image(src_data)
171
- elif target_type == Polygon:
172
- return shapely_polygon(src_data)
173
- else:
174
- raise TypeError(f'未知目标类型 {target_type}')
175
-
176
-
177
- ____base = """
178
-
179
- """
180
-
181
-
182
- def get_ndim(coords):
183
- # 注意 np.array(coords[:1]),只需要取第一个元素就可以判断出ndim
184
- coords = coords if isinstance(coords, np.ndarray) else np.array(coords[:1])
185
- return coords.ndim
186
-
187
-
188
- def coords1d(coords, dtype=None):
189
- """ 转成一维点数据
190
-
191
- [(x1, y1), (x2, y2), ...] --> [x1, y1, x2, y2, ...]
192
- 会尽量遵循原始的array、list等结构返回
193
-
194
- >>> coords1d([(1, 2), (3, 4)])
195
- [1, 2, 3, 4]
196
- >>> coords1d(np.array([[1, 2], [3, 4]]))
197
- array([1, 2, 3, 4])
198
- >>> coords1d([1, 2, 3, 4])
199
- [1, 2, 3, 4]
200
-
201
- >>> coords1d([[1.5, 2], [3.5, 4]])
202
- [1.5, 2.0, 3.5, 4.0]
203
- >>> coords1d([1, 2, [3, 4], [5, 6, 7]]) # 这种情况,[3,4]、[5,6,7]都是一个整体
204
- [1, 2, [3, 4], [5, 6, 7]]
205
- >>> coords1d([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
206
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
207
- """
208
-
209
- if isinstance(coords, (list, tuple)):
210
- return np_array(coords, dtype=dtype).reshape(-1).tolist()
211
- elif isinstance(coords, np.ndarray):
212
- return np_array(coords, dtype=dtype).reshape(-1)
213
- else:
214
- raise TypeError(f'未知类型 {coords}')
215
-
216
-
217
- def coords2d(coords, m=2, dtype=None):
218
- """ 一维的点数据转成二维点数据
219
-
220
- :param m: 转成行列结构后,每列元素数,默认2个
221
-
222
- [x1, y1, x2, y2, ...] --> [(x1, y1), (x2, y2), ...]
223
- 会尽量遵循原始的array、list等结构返回
224
-
225
- >>> coords2d([1, 2, 3, 4])
226
- [[1, 2], [3, 4]]
227
- >>> coords2d(np.array([1, 2, 3, 4]))
228
- array([[1, 2],
229
- [3, 4]])
230
- >>> coords2d([[1, 2], [3, 4]])
231
- [[1, 2], [3, 4]]
232
-
233
- >>> coords2d([1.5, 2, 3.5, 4])
234
- [[1.5, 2.0], [3.5, 4.0]]
235
- >>> coords2d([1.5, 2, 3.5, 4], dtype=int) # 数据类型转换
236
- [[1, 2], [3, 4]]
237
- """
238
- if isinstance(coords, (list, tuple)):
239
- return np_array(coords, dtype=dtype).reshape((-1, m)).tolist()
240
- elif isinstance(coords, np.ndarray):
241
- return np_array(coords, dtype=dtype).reshape((-1, m))
242
- else:
243
- raise TypeError(f'未知类型 {coords}')
244
-
245
-
246
- def rect_bounds1d(coords, dtype=int):
247
- """ 多边形的最大外接矩形
248
- :param coords: 任意多边形的一维值[x1, y1, x2, y2, ...],或者二维结构[(x1, y1), (x2, y2), ...]
249
- :param dtype: 默认存储的数值类型
250
- :return: rect的两个点坐标,同时也是 [left, top, right, bottom]
251
- """
252
- pts = coords2d(coords)
253
- if len(pts) > 2:
254
- p = Polygon(pts).bounds
255
- else:
256
- pts = coords1d(pts)
257
- p = [min(pts[::2]), min(pts[1::2]), max(pts[::2]), max(pts[1::2])]
258
- return [dtype(v) for v in p]
259
-
260
-
261
- def rect_bounds(coords, dtype=int):
262
- """ 多边形的最大外接矩形
263
- :param coords: 任意多边形的一维值[x1, y1, x2, y2, ...],或者二维结构[(x1, y1), (x2, y2), ...]
264
- :param dtype: 默认存储的数值类型
265
- :return: rect的两个点坐标
266
- """
267
- x1, y1, x2, y2 = rect_bounds1d(coords, dtype=dtype)
268
- return [[x1, y1], [x2, y2]]
269
-
270
-
271
- def rect2polygon(src):
272
- """ 矩形转成四边形结构来表达存储
273
-
274
- >>> rect2polygon([[0, 0], [10, 20]])
275
- array([[ 0, 0],
276
- [10, 0],
277
- [10, 20],
278
- [ 0, 20]])
279
- >>> rect2polygon(np.array([0, 0, 10, 20]))
280
- array([[ 0, 0],
281
- [10, 0],
282
- [10, 20],
283
- [ 0, 20]])
284
- """
285
- x1, y1, x2, y2 = np_array(src).reshape(-1)
286
- dst = np.array([[x1, y1], [x2, y1], [x2, y2], [x1, y2]])
287
- return dst
288
-
289
-
290
- def resort_quad_points(src_pts):
291
- """ 重置四边形点集顺序,确保以左上角为起点,顺时针罗列点集
292
-
293
- 算法:先确保pt1、pt2在上面,然后再确保pt1在pt2左边
294
-
295
- >>> pts = [[100, 50], [200, 0], [100, 0], [0, 50]]
296
- >>> resort_quad_points(pts)
297
- [[100, 0], [200, 0], [100, 50], [0, 50]]
298
- >>> pts # 原来的点不会被修改
299
- [[100, 50], [200, 0], [100, 0], [0, 50]]
300
- """
301
- # numpy的交换会有问题!必须要转为list结构
302
- src_type = type(src_pts)
303
- pts = to_list(src_pts)
304
- if src_type == list:
305
- # list的时候比较特别,要拷贝、不能引用数据
306
- pts = copy.copy(pts)
307
- if pts[0][1] > pts[2][1]:
308
- pts[0], pts[2] = pts[2], pts[0]
309
- if pts[1][1] > pts[3][1]:
310
- pts[1], pts[3] = pts[3], pts[1]
311
- if pts[0][0] > pts[1][0]:
312
- pts[0], pts[1] = pts[1], pts[0]
313
- pts[2], pts[3] = pts[3], pts[2]
314
- return ensure_array_type(pts, src_type)
315
-
316
-
317
- def xywh2ltrb(p):
318
- return [p[0], p[1], p[0] + p[2], p[1] + p[3]]
319
-
320
-
321
- ____warp_perspective = """
322
- 仿射、透视变换相关功能
323
-
324
- https://www.yuque.com/xlpr/pyxllib/warpperspective
325
- """
326
-
327
-
328
- def get_warp_mat(src, dst):
329
- """
330
- :param src: 原点集,支持多种格式输入
331
- :param dst: 变换后的点集
332
- :return: np.ndarray,3*3的变换矩阵
333
- """
334
-
335
- def cvt_data(pts):
336
- # opencv的透视变换,输入的点集有类型限制,必须使用float32
337
- return np.array(pts, dtype='float32').reshape((-1, 2))
338
-
339
- src, dst = cvt_data(src), cvt_data(dst)
340
- n = src.shape[0]
341
- if n == 3:
342
- # 只有3个点,则使用仿射变换
343
- warp_mat = cv2.getAffineTransform(src, dst)
344
- warp_mat = np.concatenate([warp_mat, [[0, 0, 1]]], axis=0)
345
- elif n == 4:
346
- # 有4个点,则使用透视变换
347
- warp_mat = cv2.getPerspectiveTransform(src, dst)
348
- else:
349
- raise ValueError('点集数量过多')
350
- return warp_mat
351
-
352
-
353
- def warp_points(pts, warp_mat, reserve_struct=True):
354
- """ 透视等点集坐标转换
355
-
356
- :param pts: 支持list、tuple、np.ndarray等结构,支持1d、2d的维度
357
- 其实这个坐标变换就是一个简单的矩阵乘法,只是pts的数据结构往往比较特殊,
358
- 并不是一个n*3的矩阵结构,所以需要进行一些简单的格式转换
359
- 例如 [x1, y1, x2, y2, x3, y3] --> [[x1, x2, x3], [y1, y2, y3], [1, 1, 1]]
360
- :param warp_mat: 变换矩阵,一般是个3*3的矩阵,但是只输入2*3的矩阵也行,因为第3行并用不到(点集只要取前两个维度X'Y'的结果值)
361
- TODO 不过这里我有个点也没想明白,如果不用第3行,本质上不是又变回仿射变换了,如何达到透视变换效果?第三维的深度信息能完全舍弃?
362
- :param reserve_struct: 是否保留原来pts的结构返回,默认True
363
- 关掉该功能可以提高性能,此时返回结果统一为 n*2 的np矩阵
364
- :return: 会遵循原始的 pts 数据类型、维度结构返回
365
-
366
- >>> warp_mat = [[0, 1, 0], [1, 0, 0], [0, 0, 1]] # 对换x、y
367
- >>> warp_points([[1, 2], [11, 22]], warp_mat) # 处理两个点
368
- array([[ 2, 1],
369
- [22, 11]])
370
- >>> warp_points([[1, 2], [11, 22]], [[0, 1, 0], [1, 0, 0]]) # 输入2*3的变换矩阵也可以
371
- array([[ 2, 1],
372
- [22, 11]])
373
- >>> warp_points([1, 2, 11, 22], warp_mat) # 也可以用一维的结构来输入点集
374
- array([[ 2, 1],
375
- [22, 11]])
376
- >>> warp_points([1, 2, 11, 22, 111, 222], warp_mat) # 点的数量任意,返回的结构同输入的结构形式
377
- array([[ 2, 1],
378
- [ 22, 11],
379
- [222, 111]])
380
- >>> warp_points(np.array([1, 2, 11, 22, 111, 222]), warp_mat) # 也可以用np.ndarray等结构
381
- array([[ 2, 1],
382
- [ 22, 11],
383
- [222, 111]])
384
- >>> warp_points([1, 2, 11, 22], warp_mat, reserve_struct=False) # 也可以用一维的结构来输入点集
385
- array([[ 2, 1],
386
- [22, 11]])
387
- """
388
- pts1 = np_array(pts).reshape(-1, 2).T
389
- pts1 = np.concatenate([pts1, [[1] * pts1.shape[1]]], axis=0)
390
- pts2 = np.dot(warp_mat[:2], pts1)
391
- pts2 = pts2.T
392
- return pts2
393
-
394
-
395
- def warp_image(img, warp_mat, dsize=None, *, view_rate=False, max_zoom=1, reserve_struct=True):
396
- """ 对图像进行透视变换
397
-
398
- :param img: np.ndarray的图像数据
399
- TODO 支持PIL.Image格式?
400
- :param warp_mat: 变换矩阵
401
- :param dsize: 目标图片尺寸
402
- 没有任何输入时,同原图
403
- 如果有指定,则会决定最终的图片大小
404
- 如果使用了view_rate、max_zoom,会改变变换矩阵所展示的内容
405
- :param view_rate: 视野比例,默认不开启,当输入非0正数时,几个数值功能效果如下
406
- 1,关注原图四个角点位置在变换后的位置,确保新的4个点依然在目标图中
407
- 为了达到该效果,会增加【平移】变换,以及自动控制dsize
408
- 2,将原图依中心面积放到至2倍,记录新的4个角点变换后的位置,确保变换后的4个点依然在目标图中
409
- 0.5,同理,只是只关注原图局部的一半位置
410
- :param max_zoom: 默认1倍,当设置时(只在开启view_rate时有用),会增加【缩小】变换,限制view_rate扩展的上限
411
- :param reserve_struct: 是否保留原来img的数据类型返回,默认True
412
- 关掉该功能可以提高性能,此时返回结果统一为 np 矩阵
413
- :return: 见 reserve_struct
414
- """
415
- from math import sqrt
416
-
417
- # 0 参数整理
418
- img0 = img
419
- if not isinstance(img, np.ndarray):
420
- img = np_array(img)
421
-
422
- # 1 得到3*3的变换矩阵
423
- warp_mat = np_array(warp_mat)
424
- if warp_mat.shape[0] == 2:
425
- warp_mat = np.concatenate([warp_mat, [[0, 0, 1]]], axis=0)
426
-
427
- # 2 view_rate,视野比例改变导致的变换矩阵规则变化
428
- if view_rate:
429
- # 2.1 视野变化后的四个角点
430
- h, w = img.shape[:2]
431
- y, x = h / 2, w / 2 # 图片中心点坐标
432
- h1, w1 = view_rate * h / 2, view_rate * w / 2
433
- l, t, r, b = [-w1 + x, -h1 + y, w1 + x, h1 + y]
434
- pts1 = np.array([[l, t], [r, t], [r, b], [l, b]])
435
- # 2.2 变换后角点位置产生的外接矩形
436
- left, top, right, bottom = rect_bounds1d(warp_points(pts1, warp_mat))
437
- # 2.3 增加平移变换确保左上角在原点
438
- warp_mat = np.dot([[1, 0, -left], [0, 1, -top], [0, 0, 1]], warp_mat)
439
- # 2.4 控制面积变化率
440
- h2, w2 = (bottom - top, right - left)
441
- if max_zoom:
442
- rate = w2 * h2 / w / h # 目标面积比原面积
443
- if rate > max_zoom:
444
- r = 1 / sqrt(rate / max_zoom)
445
- warp_mat = np.dot([[r, 0, 0], [0, r, 0], [0, 0, 1]], warp_mat)
446
- h2, w2 = round(h2 * r), round(w2 * r)
447
- if not dsize:
448
- dsize = (w2, h2)
449
-
450
- # 3 标准操作,不做额外处理,按照原图默认的图片尺寸展示
451
- if dsize is None:
452
- dsize = (img.shape[1], img.shape[0])
453
- dst = cv2.warpPerspective(img, warp_mat, dsize)
454
-
455
- # 4 返回值
456
- return dst
457
-
458
-
459
- ____get_sub_image = """
460
- """
461
-
462
-
463
- def quad_warp_wh(pts, method='average'):
464
- """ 四边形转为矩形的宽、高
465
-
466
- :param pts: 四个点坐标
467
- TODO 暂时认为pts是按点集顺时针顺序输入的
468
- TODO 暂时认为pts[0]就是第一个坐标点
469
- :param method:
470
- 记四条边分别为w1, h1, w2, h2
471
- average: 平均宽、高
472
- max: 最大宽、高
473
- min: 最小宽、高
474
- :return: (w, h) 变换后的矩形宽、高
475
- """
476
- # 1 计算四边长
477
- from math import hypot
478
- pts = coords2d(pts)
479
- lens = [0] * 4
480
- for i in range(4):
481
- pt1, pt2 = pts[i], pts[(i + 1) % 4]
482
- lens[i] = hypot(pt1[0] - pt2[0], pt1[1] - pt2[1])
483
-
484
- # 2 目标宽、高
485
- if method == 'average':
486
- w, h = (lens[0] + lens[2]) / 2, (lens[1] + lens[3]) / 2
487
- elif method == 'max':
488
- w, h = max(lens[0], lens[2]), max(lens[1], lens[3])
489
- elif method == 'min':
490
- w, h = min(lens[0], lens[2]), min(lens[1], lens[3])
491
- else:
492
- raise ValueError(f'不支持的方法 {method}')
493
- # 这个主要是用于图像变换的,而图像一般像素坐标要用整数,所以就取整运算了
494
- return round(w), round(h)
495
-
496
-
497
- def warp_quad_pts(pts, method='average'):
498
- """ 将不规则四边形转为矩形
499
- :param pts: 不规则四边形的四个点坐标
500
- :param method: 计算矩形宽、高的算法
501
- :return: 规则矩形的四个点坐标
502
-
503
- >>> warp_quad_pts([[89, 424], [931, 424], [399, 290], [621, 290]])
504
- array([[ 0, 0],
505
- [532, 0],
506
- [532, 549],
507
- [ 0, 549]])
508
- """
509
- w, h = quad_warp_wh(pts, method)
510
- return rect2polygon([0, 0, w, h])
511
-
512
-
513
- def get_sub_image(src_image, pts, warp_quad=False):
514
- """ 从src_image取一个子图
515
-
516
- :param src_image: 原图
517
- 可以是图片路径、np.ndarray、PIL.Image对象
518
- TODO 目前先只支持np.ndarray格式
519
- :param pts: 子图位置信息
520
- 只有两个点,认为是矩形的两个对角点
521
- 只有四个点,认为是任意四边形
522
- 同理,其他点数量,默认为
523
- :param warp_quad: 变形的四边形
524
- 默认是截图pts的外接四边形区域,使用该参数
525
- 且当pts为四个点时,是否强行扭转为矩形
526
- 一般写 'average',也可以写'max'、'min',详见 quad_warp_wh()
527
- :return: 子图
528
- 文件、np.ndarray --> np.ndarray
529
- PIL.Image --> PIL.Image
530
- """
531
- src_img = ensure_array_type(src_image, np.ndarray)
532
- pts = coords2d(pts)
533
- if not warp_quad or len(pts) != 4:
534
- x1, y1, x2, y2 = rect_bounds1d(pts)
535
- dst = src_img[y1:y2, x1:x2] # 这里越界不会报错,只是越界的那个维度shape为0
536
- else:
537
- w, h = quad_warp_wh(pts, method=warp_quad)
538
- warp_mat = get_warp_mat(pts, rect2polygon([0, 0, w, h]))
539
- dst = warp_image(src_img, warp_mat, (w, h))
540
- return dst
541
-
542
-
543
- ____opencv = """
544
- 对opencv相关功能的一些优化、 封装
545
- """
546
-
547
-
548
- def imread_v1(path, flags=1):
549
- """ opencv 源生的 imread不支持中文路径,所以要用PIL先读取,然后再转np.ndarray
550
-
551
- :param flags:
552
- 0,转成 GRAY
553
- 1,转成 BGR
554
-
555
- TODO 不知道新版的imread会不会出什么问题~~
556
- 所以这个版本的代码暂时先留着
557
- """
558
- src = PIL.Image.open(str(path))
559
- src = np.array(src) # 如果原图是灰度图,获得的可能是单通道的结果
560
-
561
- if flags == 0:
562
- if src.ndim == 3:
563
- src = cv2.cvtColor(src, cv2.COLOR_RGB2GRAY)
564
- elif flags == 1:
565
- if src.ndim == 3:
566
- src = cv2.cvtColor(src, cv2.COLOR_RGB2BGR)
567
- else:
568
- src = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
569
- else:
570
- raise ValueError(flags)
571
- return src
572
-
573
-
574
- def imread(path, flags=1):
575
- """ https://www.yuque.com/xlpr/pyxllib/imread
576
-
577
- cv2.imread的flags默认参数相当于是1
578
-
579
- """
580
- return cv2.imdecode(np.fromfile(str(path), dtype=np.uint8), flags)
581
-
582
-
583
- def imwrite(path, img, if_exists='replace'):
584
- """
585
- TODO 200922周二16:21,如何更好与Path融合?能直接Path('a.jpg').write(img)?
586
- """
587
- if not isinstance(path, Path):
588
- path = Path(path)
589
- data = cv2.imencode(ext=path.suffix, img=img)[1]
590
- return path.write(data.tobytes(), if_exists=if_exists)
591
-
592
-
593
- def imshow(mat, winname=None, flags=0):
594
- """ 展示窗口
595
-
596
- :param mat:
597
- :param winname: 未输入时,则按test1、test2依次生成窗口
598
- :param flags:
599
- cv2.WINDOW_NORMAL,0,输入2等偶数值好像也等价于输入0
600
- cv2.WINDOW_AUTOSIZE,1,输入3等奇数值好像等价于1
601
- cv2.WINDOW_OPENGL,4096
602
- :return:
603
- """
604
- if winname is None:
605
- imshow.num = getattr(imshow, 'num', 0) + 1
606
- winname = f'test{imshow.num}'
607
- cv2.namedWindow(winname, flags)
608
- cv2.imshow(winname, mat)
609
-
610
-
611
- class CvPlot:
612
- @classmethod
613
- def get_plot_color(cls, src):
614
- """ 获得比较适合的作画颜色
615
-
616
- TODO 可以根据背景色智能推导画线用的颜色,目前是固定红色
617
- """
618
- if src.ndim == 3:
619
- return 0, 0, 255
620
- elif src.ndim == 2:
621
- return 255 # 灰度图,默认先填白色
622
-
623
- @classmethod
624
- def get_plot_args(cls, src, color=None):
625
- # 1 作图颜色
626
- if not color:
627
- color = cls.get_plot_color(src)
628
-
629
- # 2 画布
630
- if len(color) >= 3 and src.ndim < 2:
631
- dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
632
- else:
633
- dst = np.array(src)
634
-
635
- return dst, color
636
-
637
- @classmethod
638
- def lines(cls, src, lines, color=None, thickness=1, line_type=cv2.LINE_AA, shift=None):
639
- """ 在src图像上画系列线段
640
- """
641
- # 1 判断 lines 参数内容
642
- lines = np_array(lines).reshape(-1, 4)
643
- if not lines.size:
644
- return src
645
-
646
- # 2 参数
647
- dst, color = cls.get_plot_args(src, color)
648
-
649
- # 3 画线
650
- if lines.any():
651
- for line in lines:
652
- x1, y1, x2, y2 = line
653
- cv2.line(dst, (x1, y1), (x2, y2), color, thickness, line_type, shift)
654
- return dst
655
-
656
- @classmethod
657
- def circles(cls, src, circles, color=None, thickness=1, center=False):
658
- """ 在图片上画圆形
659
-
660
- :param src: 要作画的图
661
- :param circles: 要画的圆形参数 (x, y, 半径 r)
662
- :param color: 画笔颜色
663
- :param center: 是否画出圆心
664
- """
665
- # 1 圆 参数
666
- circles = np_array(circles, dtype=int).reshape(-1, 3)
667
- if not circles.size:
668
- return src
669
-
670
- # 2 参数
671
- dst, color = cls.get_plot_args(src, color)
672
-
673
- # 3 作画
674
- for x in circles:
675
- cv2.circle(dst, (x[0], x[1]), x[2], color, thickness)
676
- if center:
677
- cv2.circle(dst, (x[0], x[1]), 2, color, thickness)
678
-
679
- return dst
680
-
681
-
682
- class TrackbarTool:
683
- """ 滑动条控件组
684
- """
685
-
686
- def __init__(self, winname, img, flags=0):
687
- if not isinstance(img, np.ndarray):
688
- img = imread(str(img))
689
- cv2.namedWindow(winname, flags)
690
- cv2.imshow(winname, img)
691
- self.winname = winname
692
- self.img = img
693
- self.trackbar_names = {}
694
-
695
- def imshow(self, img=None):
696
- """ 刷新显示的图片 """
697
- if img is None:
698
- img = self.img
699
- cv2.imshow(self.winname, img)
700
-
701
- def default_run(self, x):
702
- """ 默认执行器,这个在类继承后,基本都是要自定义成自己的功能的
703
-
704
- TODO 从1滑到20,会运行20次,可以研究一个机制,来只运行一次
705
- """
706
- kwargs = {}
707
- for k in self.trackbar_names.keys():
708
- kwargs[k] = self[k]
709
- print(kwargs)
710
-
711
- def create_trackbar(self, trackbar_name, count, value=0, on_change=None):
712
- """ 创建一个滑动条
713
-
714
- :param trackbar_name: 滑动条名称
715
- :param count: 上限值
716
- :param on_change: 回调函数
717
- :param value: 初始值
718
- :return:
719
- """
720
- if on_change is None:
721
- on_change = self.default_run
722
- cv2.createTrackbar(trackbar_name, self.winname, value, count, on_change)
723
-
724
- def __getitem__(self, item):
725
- """ 可以通过 Trackbars 来获取滑动条当前值
726
-
727
- :param item: 滑动条名称
728
- :return: 当前取值
729
- """
730
- return cv2.getTrackbarPos(item, self.winname)
731
-
732
-
733
- def get_background_color(src_img, edge_size=5, binary_img=None):
734
- """ 智能判断图片背景色
735
-
736
- 对全图二值化后,考虑最外一层宽度未edge_size的环中,0、1分布最多的作为背景色
737
- 然后取全部背景色的平均值返回
738
-
739
- :param src_img: 支持黑白图、彩图
740
- :param edge_size: 边缘宽度,宽度越高一般越准确,但也越耗性能
741
- :param binary_img: 运算中需要用二值图,如果外部已经计算了,可以直接传入进来,避免重复运算
742
- :return: color
743
-
744
- TODO 可以写个获得前景色,道理类似,只是最后再图片中心去取平均值
745
- """
746
- from itertools import chain
747
-
748
- # 1 获得二值图,区分前背景
749
- if binary_img is None:
750
- gray_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2GRAY) if src_img.ndim == 3 else src_img
751
- _, binary_img = cv2.threshold(gray_img, np.mean(gray_img), 255, cv2.THRESH_BINARY)
752
-
753
- # 2 分别存储点集
754
- n, m = src_img.shape[:2]
755
- colors0, colors1 = [], []
756
- for i in range(n):
757
- if i < edge_size or i >= n - edge_size:
758
- js = range(m)
759
- else:
760
- js = chain(range(edge_size), range(m - edge_size, m))
761
- for j in js:
762
- if binary_img[i, j]:
763
- colors1.append(src_img[i, j])
764
- else:
765
- colors0.append(src_img[i, j])
766
-
767
- # 3 计算平均像素
768
- # 以数量多的作为背景像素
769
- colors = colors0 if len(colors0) > len(colors1) else colors1
770
- return np.mean(np.array(colors), axis=0, dtype='int').tolist()
771
-
772
-
773
- ____polygon = """
774
- """
775
-
776
-
777
- def intersection_over_union(pts1, pts2):
778
- """ 两个多边形的交并比 Intersection Over Union
779
- :param pts1: 可以转成polygon的数据类型
780
- :param pts2:可以转成polygon的数据类型
781
- :return: 交并比
782
-
783
- >>> intersection_over_union([[0, 0], [10, 10]], [[5, 5], [15, 15]])
784
- 0.14285714285714285
785
-
786
- TODO 其实,如果有大量的双循环两两对比,每次判断shapely类型是比较浪费性能的
787
- 此时可以考虑直接在业务层用三行代码计算,不必调用该函数
788
- 同时,我也要注意一些密集型的底层计算函数,应该尽量避免这种工程性类型判断的泛用操作,会影响性能
789
- """
790
- polygon1, polygon2 = shapely_polygon(pts1), shapely_polygon(pts2)
791
- inter_area = polygon1.intersection(polygon2).area
792
- union_area = polygon1.area + polygon2.area - inter_area
793
- return inter_area / union_area
794
-
795
-
796
- def non_maximun_suppression():
797
- raise NotImplementedError
798
-
799
-
800
- ____other = """
801
- """
802
-
803
-
804
- def divide_quadrangle(coords, r1=0.5, r2=None):
805
- """ 切分一个四边形为两个四边形
806
- :param coords: 1*8的坐标,或者4*2的坐标
807
- :param r1: 第一个切分比例,0.5相当于中点(即第一个四边形右边位置)
808
- :param r2: 第二个切分比例,即第二个四边形左边位置
809
- :return: 返回切割后所有的四边形
810
-
811
- 一般用在改标注结果中,把一个框拆成两个框
812
- TODO 把接口改成切分一个四边形为任意多个四边形?即把r1、r2等整合为一个list参数输入
813
- """
814
-
815
- # 1 计算分割点工具
816
- def segment_point(pt1, pt2, rate=0.5):
817
- """ 两点间的分割点
818
- :param rate: 默认0.5是二分点,rate为0时即pt1,rate为1时为pt2,取值可以小于0、大于-1
819
- :return:
820
- """
821
- x1, y1 = pt1
822
- x2, y2 = pt2
823
- x, y = x1 + rate * (x2 - x1), y1 + rate * (y2 - y1)
824
- return int(x), int(y)
825
-
826
- # 2 优化参数值
827
- coords = coords2d(coords)
828
- if not r2: r2 = 1 - r1
829
-
830
- # 3 计算切分后的四边形坐标
831
- pt1, pt2, pt3, pt4 = coords
832
- pt5, pt6 = segment_point(pt1, pt2, r1), segment_point(pt4, pt3, r1)
833
- pt7, pt8 = segment_point(pt1, pt2, r2), segment_point(pt4, pt3, r2)
834
- return [pt1, pt5, pt6, pt4], [pt7, pt2, pt3, pt8]