|
@@ -1,39 +1,182 @@
|
|
|
|
+import re
|
|
|
|
+
|
|
import cv2
|
|
import cv2
|
|
import numpy as np
|
|
import numpy as np
|
|
|
|
+from dataclasses import dataclass
|
|
|
|
+from enum import Enum
|
|
|
|
+from typing import Tuple, List
|
|
|
|
+
|
|
|
|
+import cv2
|
|
|
|
+import numpy as np
|
|
|
|
+from paddleocr import PaddleOCR
|
|
|
|
+
|
|
|
|
+from core.line_parser import LineParser
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 枚举
|
|
|
|
+class Direction(Enum):
|
|
|
|
+ TOP = 0
|
|
|
|
+ RIGHT = 1
|
|
|
|
+ BOTTOM = 2
|
|
|
|
+ LEFT = 3
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 父类
|
|
|
|
+class OcrAnchor(object):
|
|
|
|
+ # anchor的名字, 如身份证号、承办人等
|
|
|
|
+ def __init__(self, name: str, d: List[Direction]):
|
|
|
|
+ self.name = name
|
|
|
|
+ self.direction = d
|
|
|
|
+
|
|
|
|
+ # 定义枚举字典
|
|
|
|
+ def t_func(anchor, c, is_horizontal):
|
|
|
|
+ if is_horizontal:
|
|
|
|
+ return 0 if anchor[1] < c[1] else 2
|
|
|
|
+ else:
|
|
|
|
+ return 1 if anchor[0] > c[0] else 3
|
|
|
|
+
|
|
|
|
+ def l_func(anchor, c, is_horizontal):
|
|
|
|
+ if is_horizontal:
|
|
|
|
+ return 0 if anchor[0] < c[0] else 2
|
|
|
|
+ else:
|
|
|
|
+ return 1 if anchor[1] < c[1] else 3
|
|
|
|
+
|
|
|
|
+ def b_func(anchor, c, is_horizontal):
|
|
|
|
+ if is_horizontal:
|
|
|
|
+ return 0 if anchor[1] > c[1] else 2
|
|
|
|
+ else:
|
|
|
|
+ return 1 if anchor[0] < c[0] else 3
|
|
|
|
+
|
|
|
|
+ def r_func(anchor, c, is_horizontal):
|
|
|
|
+ if is_horizontal:
|
|
|
|
+ return 0 if anchor[0] > c[0] else 2
|
|
|
|
+ else:
|
|
|
|
+ return 1 if anchor[1] > c[1] else 3
|
|
|
|
+
|
|
|
|
+ self.direction_funcs = {
|
|
|
|
+ Direction.TOP: t_func,
|
|
|
|
+ Direction.LEFT: l_func,
|
|
|
|
+ Direction.BOTTOM: b_func,
|
|
|
|
+ Direction.RIGHT: r_func
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # pic中心点
|
|
|
|
+ def get_pic_center(self, res) -> Tuple[float, float]:
|
|
|
|
+ boxs = []
|
|
|
|
+ for row in res:
|
|
|
|
+ for r in row:
|
|
|
|
+ boxs.extend(r.box)
|
|
|
|
+ boxs = np.stack(boxs)
|
|
|
|
+ l, t = np.min(boxs, 0)
|
|
|
|
+ r, b = np.max(boxs, 0)
|
|
|
|
+ return (l + r) / 2, (t + b) / 2
|
|
|
|
+
|
|
|
|
+ # 是否有锚点
|
|
|
|
+ def is_anchor(self, txt, box):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ # 找锚点
|
|
|
|
+ def find_anchor(self, res):
|
|
|
|
+ for row in res:
|
|
|
|
+ for r in row:
|
|
|
|
+ if self.is_anchor(r.txt, r.box):
|
|
|
|
+ l, t = np.min(r.box, 0)
|
|
|
|
+ r, b = np.max(r.box, 0)
|
|
|
|
+ return True, (l + r) / 2, (t + b) / 2
|
|
|
|
+ # return True, r.center[0], r.center[1]
|
|
|
|
+ return False, 0., 0.
|
|
|
|
+
|
|
|
|
+ # get angle
|
|
|
|
+ def locate_anchor(self, res, is_horizontal):
|
|
|
|
+ found, a_cx, a_cy = self.find_anchor(res)
|
|
|
|
+ cx, cy = self.get_pic_center(res)
|
|
|
|
+
|
|
|
|
+ if found is False: raise Exception(f'识别不到anchor{self.name}')
|
|
|
|
+
|
|
|
|
+ pre = None
|
|
|
|
+ for d in self.direction:
|
|
|
|
+ angle_func = self.direction_funcs.get(d, None)
|
|
|
|
+ angle = angle_func((a_cx, a_cy), (cx, cy), is_horizontal)
|
|
|
|
+ if pre is None:
|
|
|
|
+ pre = angle
|
|
|
|
+ else:
|
|
|
|
+ if pre != angle:
|
|
|
|
+ raise Exception('angle is not compatible')
|
|
|
|
+ return pre
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 子类1 户口本首页1
|
|
|
|
+class FrontAnchor(OcrAnchor):
|
|
|
|
+ def __init__(self, name: str, d: List[Direction]):
|
|
|
|
+ super(FrontAnchor, self).__init__(name, d)
|
|
|
|
+
|
|
|
|
+ def is_anchor(self, txt, box):
|
|
|
|
+ txts = re.findall('承办人', txt)
|
|
|
|
+ if len(txts) > 0:
|
|
|
|
+ return True
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+ def locate_anchor(self, res, is_horizontal):
|
|
|
|
+ return super(FrontAnchor, self).locate_anchor(res, is_horizontal)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 子类2 常驻人口页0
|
|
|
|
+class PeopleAnchor(OcrAnchor):
|
|
|
|
+ def __init__(self, name: str, d: List[Direction]):
|
|
|
|
+ super(PeopleAnchor, self).__init__(name, d)
|
|
|
|
+
|
|
|
|
+ def is_anchor(self, txt, box):
|
|
|
|
+ txts = re.findall('常住', txt)
|
|
|
|
+ if len(txts) > 0:
|
|
|
|
+ return True
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+ def locate_anchor(self, res, is_horizontal):
|
|
|
|
+ return super(PeopleAnchor, self).locate_anchor(res, is_horizontal)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# 调用以上 🔧工具
|
|
|
|
+# <- ocr_生数据
|
|
|
|
+# == ocr_熟数据(行处理后)
|
|
|
|
+# -> 角度0/1/2/3
|
|
|
|
+def detect_angle(result, ocr_anchor: OcrAnchor):
|
|
|
|
+ lp = LineParser(result)
|
|
|
|
+ res = lp.parse()
|
|
|
|
+ print('------ angle ocr -------')
|
|
|
|
+ print(res)
|
|
|
|
+ print('------ angle ocr -------')
|
|
|
|
+ is_horizontal = lp.is_horizontal
|
|
|
|
+ return ocr_anchor.locate_anchor(res, is_horizontal)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+@dataclass
|
|
|
|
+class AngleDetector(object):
|
|
|
|
+ """
|
|
|
|
+ 角度检测器
|
|
|
|
+ """
|
|
|
|
+ ocr: PaddleOCR
|
|
|
|
+
|
|
|
|
+ # 角度检测器
|
|
|
|
+ # <- img(cv2格式) img_type
|
|
|
|
+ # == result <- img(cv2)
|
|
|
|
+ # -> angle result(ocr生)
|
|
|
|
+ def detect_angle(self, img, image_type):
|
|
|
|
+ image_type = int(image_type)
|
|
|
|
+ ocr_anchor = PeopleAnchor('常住', [Direction.TOP]) if image_type == 0 else FrontAnchor('承办人', [Direction.BOTTOM,
|
|
|
|
+ Direction.LEFT])
|
|
|
|
+
|
|
|
|
+ result = self.ocr.ocr(img, cls=True)
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ angle = detect_angle(result, ocr_anchor)
|
|
|
|
+ return angle, result
|
|
|
|
+
|
|
|
|
|
|
-def detect_angle(image):
|
|
|
|
- mask = np.zeros(image.shape, dtype=np.uint8)
|
|
|
|
- gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
|
|
|
- blur = cv2.GaussianBlur(gray, (3,3), 0)
|
|
|
|
- adaptive = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,15,4)
|
|
|
|
-
|
|
|
|
- cnts = cv2.findContours(adaptive, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
- cnts = cnts[0] if len(cnts) == 2 else cnts[1]
|
|
|
|
-
|
|
|
|
- for c in cnts:
|
|
|
|
- area = cv2.contourArea(c)
|
|
|
|
- if area < 45000 and area > 20:
|
|
|
|
- cv2.drawContours(mask, [c], -1, (255,255,255), -1)
|
|
|
|
-
|
|
|
|
- mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
|
|
|
|
- h, w = mask.shape
|
|
|
|
-
|
|
|
|
- # Horizontal
|
|
|
|
- if w > h:
|
|
|
|
- left = mask[0:h, 0:0+w//2]
|
|
|
|
- right = mask[0:h, w//2:]
|
|
|
|
- left_pixels = cv2.countNonZero(left)
|
|
|
|
- right_pixels = cv2.countNonZero(right)
|
|
|
|
- return 0 if left_pixels >= right_pixels else 180
|
|
|
|
- # Vertical
|
|
|
|
- else:
|
|
|
|
- top = mask[0:h//2, 0:w]
|
|
|
|
- bottom = mask[h//2:, 0:w]
|
|
|
|
- top_pixels = cv2.countNonZero(top)
|
|
|
|
- bottom_pixels = cv2.countNonZero(bottom)
|
|
|
|
- return 90 if bottom_pixels >= top_pixels else 270
|
|
|
|
-
|
|
|
|
-if __name__ == '__main__':
|
|
|
|
- image = cv2.imread('d40.jpg')
|
|
|
|
- angle = detect_angle(image)
|
|
|
|
- print(angle)
|
|
|
|
|
|
+ except Exception as e:
|
|
|
|
+ print(e)
|
|
|
|
+ # 如果第一次识别不到,旋转90度再识别
|
|
|
|
+ img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
|
|
|
|
+ result = self.ocr.ocr(img, cls=True)
|
|
|
|
+ angle = detect_angle(result, ocr_anchor)
|
|
|
|
+ # 旋转90度之后要重新计算角度
|
|
|
|
+ return (angle - 1 + 4) % 4, result
|