util.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import cv2
  2. import numpy as np
  3. from PIL import Image
  4. from io import BytesIO
  5. from typing import Union
  6. from flask import jsonify
  7. from random import randint
  8. from hmOCR import HuiMvOCR, Args
  9. from time import localtime, strftime
  10. __all__ = [
  11. "Response", "rand_str", "current_time", "get_ext_name", "is_image_ext",
  12. "str_include", "read_img", "compress_img", "resize_img", "preprocess_img",
  13. "rot_img_2", "rot_img_4", "save_img", "Engine"
  14. ]
  15. __StrBase = "qwertyuioplkjhgfdsazxcvbnm1234567890ZXCVBNMLKJHGFDSAQWERTYUIOP"
  16. __StrBaseLen = len(__StrBase) - 1
  17. __MaxImageSize = 3500 * 2000
  18. __BorderWidth = 5
  19. __AreaSizeLimit = 5
  20. __AcceptExtNames = ["jpg", "jpeg", "bmp", "png", "rgb", "tif", "tiff", "gif"]
  21. Engine = HuiMvOCR(Args())
  22. def __isWhiteBack(img: "np.ndarray") -> "bool":
  23. row = np.concatenate([img[:__BorderWidth], img[-__BorderWidth:]], axis=0).reshape((__BorderWidth * 2, -1))
  24. col = np.concatenate([img[:, :__BorderWidth], img[:, -__BorderWidth:]], axis=1).reshape((__BorderWidth * 2, -1))
  25. full = np.concatenate([row, col], axis=1)
  26. return np.average(full) > 127
  27. def __distance(pa: "list", pb: "list") -> "float":
  28. return np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
  29. def __rect_area(points: "np.ndarray") -> "float":
  30. return __distance(points[0], points[1]) * __distance(points[0], points[3])
  31. def __trans_img(img: "np.ndarray", points: "np.ndarray") -> "np.ndarray":
  32. tl, bl, br, tr = points
  33. width = int(max(__distance(tl, tr), __distance(bl, br)))
  34. height = int(max(__distance(tl, bl), __distance(tr, br)))
  35. rect = np.array([tl, tr, bl, br], dtype="float32")
  36. dest = np.array(
  37. [[0, 0], [width - 1, 0],
  38. [0, height - 1], [width - 1, height - 1]],
  39. dtype="float32"
  40. )
  41. matrix = cv2.getPerspectiveTransform(rect, dest) # noqa
  42. return cv2.warpPerspective(img, matrix, (width, height)) # noqa
  43. def Response(message: "str" = None, data=None):
  44. if message is None:
  45. return jsonify(success=True, message="操作成功", data=data)
  46. return jsonify(success=False, message=message, data=data)
  47. def rand_str(size: "int" = 8) -> "str":
  48. return "".join([__StrBase[randint(0, __StrBaseLen)] for _ in range(size)])
  49. def current_time(fmt: "str" = "%Y-%m-%d_%H-%M-%S") -> "str":
  50. return strftime(fmt, localtime())
  51. def get_ext_name(name: "str") -> "str":
  52. return name.split(".")[-1].lower()
  53. def is_image_ext(ext: "str") -> bool:
  54. return ext in __AcceptExtNames
  55. def str_include(str_long: "str", str_short: "str") -> "bool":
  56. for it in str_short:
  57. if it not in str_long:
  58. return False
  59. return True
  60. def read_img(content: "str") -> "np.ndarray":
  61. return cv2.imdecode(np.frombuffer(content, np.uint8), 1) # noqa
  62. def resize_img(img: "np.ndarray", height: "int" = None, width: "int" = None) -> "np.ndarray":
  63. if height is None and width is None:
  64. return img
  65. h, w = img.shape[:2]
  66. if height is not None:
  67. dim = int(w * height / h), height
  68. else:
  69. dim = width, int(h * width / w)
  70. return cv2.resize(img, dim, interpolation=cv2.INTER_AREA) # noqa
  71. def compress_img(img: "np.ndarray", tar_size: "int" = __MaxImageSize) -> "np.ndarray":
  72. cur_size = img.shape[0] * img.shape[1]
  73. if cur_size <= tar_size:
  74. return img
  75. image = Image.fromarray(img)
  76. output = BytesIO()
  77. image.save(output, format="JPEG", quality=int(tar_size / cur_size * 100))
  78. output.seek(0)
  79. compressed_image = Image.open(output)
  80. return np.array(compressed_image)
  81. def preprocess_img(img: "np.ndarray") -> "np.ndarray":
  82. img = compress_img(img, 1000 * 600)
  83. # RGB -> gray
  84. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # noqa
  85. # 二值化
  86. thresh_type = cv2.THRESH_BINARY # noqa
  87. if __isWhiteBack(gray):
  88. thresh_type = cv2.THRESH_BINARY_INV # noqa
  89. _, thresh = cv2.threshold(gray, 158, 255, thresh_type) # noqa
  90. # 边缘检测
  91. contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # noqa
  92. if not contours:
  93. return img
  94. max_contour = max(contours, key=cv2.contourArea) # noqa 找到最大的轮廓
  95. # 图像校正
  96. approx = cv2.approxPolyDP(max_contour, 0.02 * cv2.arcLength(max_contour, True), True) # noqa
  97. area = img.shape[0] * img.shape[1]
  98. if len(approx) == 4: # 较为规则,可以校正
  99. points = approx.reshape(4, 2)
  100. if __rect_area(points) * __AreaSizeLimit < area:
  101. return img
  102. return __trans_img(img, points)
  103. else: # 不太规则,尝试裁剪
  104. rect = cv2.minAreaRect(max_contour) # noqa 计算最小外接矩形
  105. box = np.intp(cv2.boxPoints(rect)) # noqa 获取矩形的四个角点
  106. if __rect_area(box) * __AreaSizeLimit < area:
  107. return img
  108. return img[min(box[:, 1]):max(box[:, 1]), min(box[:, 0]):max(box[:, 0])]
  109. def rot_img_2(img: "np.ndarray") -> "list[np.ndarray]":
  110. if img.shape[0] > img.shape[1]: # 0: height, 1: width
  111. return [np.rot90(img), np.rot90(img, 3)]
  112. return [img, np.rot90(img, 2)]
  113. def rot_img_4(img: "np.ndarray") -> "list[np.ndarray]":
  114. return [img, np.rot90(img), np.rot90(img, 2), np.rot90(img, 3)]
  115. def save_img(filename: "str", content: "Union[bytes, np.ndarray]"):
  116. if isinstance(content, np.ndarray):
  117. return cv2.imwrite(filename, content) # noqa
  118. with open(filename, "wb") as fp:
  119. fp.write(content)
  120. fp.close()