utils.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Created on Tue Jun 21 10:53:51 2022
  5. utils
  6. @author: fangjiasheng
  7. """
  8. import platform
  9. import cv2
  10. import numpy as np
  11. from skimage import measure
  12. from scipy.spatial import distance as dist
  13. from PIL import Image
  14. from scipy.ndimage import filters, interpolation
  15. from numpy import amin, amax
  16. def get_platform():
  17. _sys = platform.system()
  18. return _sys
  19. def nms_box(boxes, scores, score_threshold=0.5, nms_threshold=0.3):
  20. # nms box
  21. boxes = np.array(boxes)
  22. scores = np.array(scores)
  23. ind = scores > score_threshold
  24. boxes = boxes[ind]
  25. scores = scores[ind]
  26. def box_to_center(box):
  27. xmin, ymin, xmax, ymax = [round(float(x), 4) for x in box]
  28. w = xmax - xmin
  29. h = ymax - ymin
  30. return [round(xmin, 4), round(ymin, 4), round(w, 4), round(h, 4)]
  31. newBoxes = [box_to_center(box) for box in boxes]
  32. newscores = [round(float(x), 6) for x in scores]
  33. index = cv2.dnn.NMSBoxes(newBoxes, newscores, score_threshold=score_threshold, nms_threshold=nms_threshold)
  34. if len(index) > 0:
  35. index = index.reshape((-1,))
  36. return boxes[index], scores[index]
  37. else:
  38. return np.array([]), np.array([])
  39. def resize_im(im, scale, max_scale=None):
  40. f = float(scale) / min(im.shape[0], im.shape[1])
  41. if max_scale is not None and f * max(im.shape[0], im.shape[1]) > max_scale:
  42. f = float(max_scale) / max(im.shape[0], im.shape[1])
  43. return cv2.resize(im, (0, 0), fx=f, fy=f)
  44. def estimate_skew_angle(raw, angleRange=[-15, 15]):
  45. """
  46. 估计图像文字偏转角度,
  47. angleRange:角度估计区间
  48. """
  49. raw = resize_im(raw, scale=600, max_scale=900)
  50. image = raw - amin(raw)
  51. image = image / amax(image)
  52. m = interpolation.zoom(image, 0.5)
  53. m = filters.percentile_filter(m, 80, size=(20, 2))
  54. m = filters.percentile_filter(m, 80, size=(2, 20))
  55. m = interpolation.zoom(m, 1.0 / 0.5)
  56. # w,h = image.shape[1],image.shape[0]
  57. w, h = min(image.shape[1], m.shape[1]), min(image.shape[0], m.shape[0])
  58. flat = np.clip(image[:h, :w] - m[:h, :w] + 1, 0, 1)
  59. d0, d1 = flat.shape
  60. o0, o1 = int(0.1 * d0), int(0.1 * d1)
  61. flat = amax(flat) - flat
  62. flat -= amin(flat)
  63. est = flat[o0:d0 - o0, o1:d1 - o1]
  64. angles = range(angleRange[0], angleRange[1])
  65. estimates = []
  66. for a in angles:
  67. roest = interpolation.rotate(est, a, order=0, mode='constant')
  68. v = np.mean(roest, axis=1)
  69. v = np.var(v)
  70. estimates.append((v, a))
  71. _, a = max(estimates)
  72. return a
  73. def eval_angle(img, angleRange=[-5, 5]):
  74. """
  75. 估计图片文字的偏移角度
  76. """
  77. im = Image.fromarray(img)
  78. degree = estimate_skew_angle(np.array(im.convert('L')), angleRange=angleRange)
  79. im = im.rotate(degree, center=(im.size[0] / 2, im.size[1] / 2), expand=1, fillcolor=(255, 255, 255))
  80. img = np.array(im)
  81. return img, degree
  82. def letterbox_image(image, size, fillValue=[128, 128, 128]):
  83. """
  84. resize image with unchanged aspect ratio using padding
  85. """
  86. image_h, image_w = image.shape[:2]
  87. w, h = size
  88. new_w = int(image_w * min(w * 1.0 / image_w, h * 1.0 / image_h))
  89. new_h = int(image_h * min(w * 1.0 / image_w, h * 1.0 / image_h))
  90. resized_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
  91. # cv2.imwrite('tmp/test.png', resized_image[...,::-1])
  92. if fillValue is None:
  93. # cv2.split 通道拆分
  94. fillValue = [int(x.mean()) for x in cv2.split(np.array(image))]
  95. boxed_image = np.zeros((size[1], size[0], 3), dtype=np.uint8)
  96. boxed_image[:] = fillValue
  97. boxed_image[:new_h, :new_w, :] = resized_image
  98. return boxed_image, new_w / image_w, new_h / image_h
  99. def get_table_line(binimg, axis=0, lineW=10):
  100. # 获取表格线
  101. # axis=0 竖线
  102. # axis=1 横线
  103. labels = measure.label(binimg > 0, connectivity=2) # 8连通区域标记
  104. regions = measure.regionprops(labels)
  105. if axis == 1:
  106. # 如果是横线,判断线的bbox的右下角点的x-左上角点的x,判断横线长度
  107. lineboxes = [minAreaRect(line.coords) for line in regions if line.bbox[2] - line.bbox[0] > lineW]
  108. else:
  109. # 如果是竖线,判断线的bbox的右下角点的y-左上角点的y,判断竖线长度
  110. lineboxes = [minAreaRect(line.coords) for line in regions if line.bbox[3] - line.bbox[1] > lineW]
  111. return lineboxes
  112. def sqrt(p1, p2):
  113. return np.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
  114. def adjust_lines(RowsLines, ColsLines, alph=50):
  115. # 调整line
  116. nrow = len(RowsLines)
  117. ncol = len(ColsLines)
  118. newRowsLines = []
  119. newColsLines = []
  120. for i in range(nrow):
  121. x1, y1, x2, y2 = RowsLines[i]
  122. cx1, cy1 = (x1 + x2) / 2, (y1 + y2) / 2
  123. for j in range(nrow):
  124. if i != j:
  125. x3, y3, x4, y4 = RowsLines[j]
  126. cx2, cy2 = (x3 + x4) / 2, (y3 + y4) / 2
  127. if (x3 < cx1 < x4 or y3 < cy1 < y4) or (x1 < cx2 < x2 or y1 < cy2 < y2):
  128. continue
  129. else:
  130. r = sqrt((x1, y1), (x3, y3))
  131. if r < alph:
  132. newRowsLines.append([x1, y1, x3, y3])
  133. r = sqrt((x1, y1), (x4, y4))
  134. if r < alph:
  135. newRowsLines.append([x1, y1, x4, y4])
  136. r = sqrt((x2, y2), (x3, y3))
  137. if r < alph:
  138. newRowsLines.append([x2, y2, x3, y3])
  139. r = sqrt((x2, y2), (x4, y4))
  140. if r < alph:
  141. newRowsLines.append([x2, y2, x4, y4])
  142. for i in range(ncol):
  143. x1, y1, x2, y2 = ColsLines[i]
  144. cx1, cy1 = (x1 + x2) / 2, (y1 + y2) / 2
  145. for j in range(ncol):
  146. if i != j:
  147. x3, y3, x4, y4 = ColsLines[j]
  148. cx2, cy2 = (x3 + x4) / 2, (y3 + y4) / 2
  149. if (x3 < cx1 < x4 or y3 < cy1 < y4) or (x1 < cx2 < x2 or y1 < cy2 < y2):
  150. continue
  151. else:
  152. r = sqrt((x1, y1), (x3, y3))
  153. if r < alph:
  154. newColsLines.append([x1, y1, x3, y3])
  155. r = sqrt((x1, y1), (x4, y4))
  156. if r < alph:
  157. newColsLines.append([x1, y1, x4, y4])
  158. r = sqrt((x2, y2), (x3, y3))
  159. if r < alph:
  160. newColsLines.append([x2, y2, x3, y3])
  161. r = sqrt((x2, y2), (x4, y4))
  162. if r < alph:
  163. newColsLines.append([x2, y2, x4, y4])
  164. return newRowsLines, newColsLines
  165. def minAreaRect(coords):
  166. """
  167. 多边形外接矩形
  168. """
  169. rect = cv2.minAreaRect(coords[:, ::-1])
  170. # print("minAreaRect rect", rect)
  171. box = cv2.boxPoints(rect)
  172. # print("minAreaRect box", box)
  173. box = box.reshape((8,)).tolist()
  174. # print("minAreaRect box2", box)
  175. box = image_location_sort_box(box)
  176. x1, y1, x2, y2, x3, y3, x4, y4 = box
  177. degree, w, h, cx, cy = solve(box)
  178. if w < h:
  179. xmin = (x1 + x2) / 2
  180. xmax = (x3 + x4) / 2
  181. ymin = (y1 + y2) / 2
  182. ymax = (y3 + y4) / 2
  183. else:
  184. xmin = (x1 + x4) / 2
  185. xmax = (x2 + x3) / 2
  186. ymin = (y1 + y4) / 2
  187. ymax = (y2 + y3) / 2
  188. # degree,w,h,cx,cy = solve(box)
  189. # x1,y1,x2,y2,x3,y3,x4,y4 = box
  190. # return {'degree':degree,'w':w,'h':h,'cx':cx,'cy':cy}
  191. return [xmin, ymin, xmax, ymax]
  192. def fit_line(p1, p2):
  193. """A = Y2 - Y1
  194. B = X1 - X2
  195. C = X2*Y1 - X1*Y2
  196. AX+BY+C=0
  197. 直线一般方程
  198. """
  199. x1, y1 = p1
  200. x2, y2 = p2
  201. A = y2 - y1
  202. B = x1 - x2
  203. C = x2 * y1 - x1 * y2
  204. return A, B, C
  205. def point_line_cor(p, A, B, C):
  206. # 判断点与之间的位置关系
  207. # 一般式直线方程(Ax+By+c)=0
  208. x, y = p
  209. r = A * x + B * y + C
  210. return r
  211. def line_to_line(points1, points2, alpha=10):
  212. """
  213. 线段之间的距离
  214. """
  215. x1, y1, x2, y2 = points1
  216. ox1, oy1, ox2, oy2 = points2
  217. A1, B1, C1 = fit_line((x1, y1), (x2, y2))
  218. A2, B2, C2 = fit_line((ox1, oy1), (ox2, oy2))
  219. flag1 = point_line_cor([x1, y1], A2, B2, C2)
  220. flag2 = point_line_cor([x2, y2], A2, B2, C2)
  221. if (flag1 > 0 and flag2 > 0) or (flag1 < 0 and flag2 < 0):
  222. x = (B1 * C2 - B2 * C1) / (A1 * B2 - A2 * B1)
  223. y = (A2 * C1 - A1 * C2) / (A1 * B2 - A2 * B1)
  224. p = (x, y)
  225. r0 = sqrt(p, (x1, y1))
  226. r1 = sqrt(p, (x2, y2))
  227. if min(r0, r1) < alpha:
  228. if r0 < r1:
  229. points1 = [p[0], p[1], x2, y2]
  230. else:
  231. points1 = [x1, y1, p[0], p[1]]
  232. return points1
  233. def _order_points(pts):
  234. # 根据x坐标对点进行排序
  235. x_sorted = pts[np.argsort(pts[:, 0]), :]
  236. left_most = x_sorted[:2, :]
  237. right_most = x_sorted[2:, :]
  238. left_most = left_most[np.argsort(left_most[:, 1]), :]
  239. (tl, bl) = left_most
  240. distance = dist.cdist(tl[np.newaxis], right_most, "euclidean")[0]
  241. (br, tr) = right_most[np.argsort(distance)[::-1], :]
  242. return np.array([tl, tr, br, bl], dtype="float32")
  243. def image_location_sort_box(box):
  244. x1, y1, x2, y2, x3, y3, x4, y4 = box[:8]
  245. pts = (x1, y1), (x2, y2), (x3, y3), (x4, y4)
  246. pts = np.array(pts, dtype="float32")
  247. (x1, y1), (x2, y2), (x3, y3), (x4, y4) = _order_points(pts)
  248. return [x1, y1, x2, y2, x3, y3, x4, y4]
  249. def solve(box):
  250. """
  251. 绕 cx,cy点 w,h 旋转 angle 的坐标
  252. x = cx-w/2
  253. y = cy-h/2
  254. x1-cx = -w/2*cos(angle) +h/2*sin(angle)
  255. y1 -cy= -w/2*sin(angle) -h/2*cos(angle)
  256. h(x1-cx) = -wh/2*cos(angle) +hh/2*sin(angle)
  257. w(y1 -cy)= -ww/2*sin(angle) -hw/2*cos(angle)
  258. (hh+ww)/2sin(angle) = h(x1-cx)-w(y1 -cy)
  259. """
  260. x1, y1, x2, y2, x3, y3, x4, y4 = box[:8]
  261. cx = (x1 + x3 + x2 + x4) / 4.0
  262. cy = (y1 + y3 + y4 + y2) / 4.0
  263. w = (np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + np.sqrt((x3 - x4) ** 2 + (y3 - y4) ** 2)) / 2
  264. h = (np.sqrt((x2 - x3) ** 2 + (y2 - y3) ** 2) + np.sqrt((x1 - x4) ** 2 + (y1 - y4) ** 2)) / 2
  265. # x = cx-w/2
  266. # y = cy-h/2
  267. sinA = (h * (x1 - cx) - w * (y1 - cy)) * 1.0 / (h * h + w * w) * 2
  268. angle = np.arcsin(sinA)
  269. return angle, w, h, cx, cy
  270. def xy_rotate_box(cx, cy, w, h, angle=0, degree=None, **args):
  271. """
  272. 绕 cx,cy点 w,h 旋转 angle 的坐标
  273. x_new = (x-cx)*cos(angle) - (y-cy)*sin(angle)+cx
  274. y_new = (x-cx)*sin(angle) + (y-cy)*sin(angle)+cy
  275. """
  276. if degree is not None:
  277. angle = degree
  278. cx = float(cx)
  279. cy = float(cy)
  280. w = float(w)
  281. h = float(h)
  282. angle = float(angle)
  283. x1, y1 = rotate(cx - w / 2, cy - h / 2, angle, cx, cy)
  284. x2, y2 = rotate(cx + w / 2, cy - h / 2, angle, cx, cy)
  285. x3, y3 = rotate(cx + w / 2, cy + h / 2, angle, cx, cy)
  286. x4, y4 = rotate(cx - w / 2, cy + h / 2, angle, cx, cy)
  287. return x1, y1, x2, y2, x3, y3, x4, y4
  288. from numpy import cos, sin
  289. def rotate(x, y, angle, cx, cy):
  290. angle = angle # *pi/180
  291. x_new = (x - cx) * cos(angle) - (y - cy) * sin(angle) + cx
  292. y_new = (x - cx) * sin(angle) + (y - cy) * cos(angle) + cy
  293. return x_new, y_new
  294. def minAreaRectbox(regions, flag=True, W=0, H=0, filtersmall=False, adjustBox=False):
  295. """
  296. 多边形外接矩形
  297. """
  298. boxes = []
  299. for region in regions:
  300. rect = cv2.minAreaRect(region.coords[:, ::-1])
  301. box = cv2.boxPoints(rect)
  302. box = box.reshape((8,)).tolist()
  303. box = image_location_sort_box(box)
  304. x1, y1, x2, y2, x3, y3, x4, y4 = box
  305. angle, w, h, cx, cy = solve(box)
  306. if adjustBox:
  307. x1, y1, x2, y2, x3, y3, x4, y4 = xy_rotate_box(cx, cy, w + 5, h + 5, angle=0, degree=None)
  308. if w > 32 and h > 32 and flag:
  309. if abs(angle / np.pi * 180) < 20:
  310. if filtersmall and w < 10 or h < 10:
  311. continue
  312. boxes.append([x1, y1, x2, y2, x3, y3, x4, y4])
  313. else:
  314. if w * h < 0.5 * W * H:
  315. if filtersmall and w < 8 or h < 8:
  316. continue
  317. boxes.append([x1, y1, x2, y2, x3, y3, x4, y4])
  318. return boxes
  319. def rectangle(img, boxes):
  320. tmp = np.copy(img)
  321. for box in boxes:
  322. xmin, ymin, xmax, ymax = box[:4]
  323. cv2.rectangle(tmp, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (0, 0, 0), 1, lineType=cv2.LINE_AA)
  324. return Image.fromarray(tmp)
  325. def draw_lines(im, bboxes, color=(0, 0, 0), lineW=3):
  326. """
  327. boxes: bounding boxes
  328. """
  329. tmp = np.copy(im)
  330. c = color
  331. h, w = im.shape[:2]
  332. for box in bboxes:
  333. x1, y1, x2, y2 = box[:4]
  334. cv2.line(tmp, (int(x1), int(y1)), (int(x2), int(y2)), c, lineW, lineType=cv2.LINE_AA)
  335. return tmp
  336. def draw_boxes(im, bboxes, color=(0, 0, 255)):
  337. """
  338. boxes: bounding boxes
  339. """
  340. tmp = np.copy(im)
  341. c = color
  342. h, w, _ = im.shape
  343. for box in bboxes:
  344. if type(box) is dict:
  345. x1, y1, x2, y2, x3, y3, x4, y4 = xy_rotate_box(**box)
  346. else:
  347. x1, y1, x2, y2, x3, y3, x4, y4 = box[:8]
  348. cv2.line(tmp, (int(x1), int(y1)), (int(x2), int(y2)), c, 1, lineType=cv2.LINE_AA)
  349. cv2.line(tmp, (int(x2), int(y2)), (int(x3), int(y3)), c, 1, lineType=cv2.LINE_AA)
  350. cv2.line(tmp, (int(x3), int(y3)), (int(x4), int(y4)), c, 1, lineType=cv2.LINE_AA)
  351. cv2.line(tmp, (int(x4), int(y4)), (int(x1), int(y1)), c, 1, lineType=cv2.LINE_AA)
  352. return tmp