parser.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import re
  2. import string
  3. from dataclasses import dataclass
  4. from collections import defaultdict
  5. import numpy as np
  6. import cpca
  7. @dataclass
  8. class RecItem:
  9. text: str = ''
  10. confidence: float = 0.
  11. def to_dict(self):
  12. return {"text": self.text, "confidence": np.nan_to_num(self.confidence)}
  13. class Parser(object):
  14. def __init__(self, txts, confs):
  15. self.result = txts
  16. self.confs = confs
  17. assert len(self.result) == len(self.confs), 'result and confs do not match'
  18. self.res = defaultdict(RecItem)
  19. self.keys = ["name", "id", "ethnicity", "gender", "birthday",
  20. "address", "address_province", "address_city", "address_region", "address_detail", "expire_date"]
  21. for key in self.keys:
  22. self.res[key] = RecItem()
  23. def parse(self):
  24. return self.res
  25. @property
  26. def confidence(self):
  27. return 0.
  28. class FrontParser(Parser):
  29. """
  30. """
  31. def __init__(self, txts, confs):
  32. Parser.__init__(self, txts, confs)
  33. self.result = [
  34. i.replace(" ", "").translate(str.maketrans("", "", string.punctuation))
  35. for i in txts
  36. ]
  37. assert len(self.result) == len(self.confs), 'result and confs do not match'
  38. def birth(self):
  39. if len(self.res["id"].text) == 18:
  40. # 342423 2001 0 2 1 5 6552
  41. # 012345 6789 10 11 12 13 14
  42. str_num = self.res["id"].text
  43. date = list(str_num[6:10] + "年" + str_num[10:12] + "月" + str_num[12:14] + "日")
  44. if date[date.index("月") - 2] == "0":
  45. del date[date.index("月") - 2]
  46. if date[date.index("日") - 2] == "0":
  47. del date[date.index("日") - 2]
  48. self.res["birthday"].text = "".join(date)
  49. def card_no(self):
  50. """
  51. 身份证号码
  52. """
  53. for i in range(len(self.result)):
  54. txt = self.result[i]
  55. # 身份证号码
  56. if "X" in txt or "x" in txt:
  57. res = re.findall("\d*[X|x]", txt)
  58. else:
  59. res = re.findall("\d{16,18}", txt)
  60. if len(res) > 0:
  61. if len(res[0]) == 18:
  62. self.res["id"].text = res[0].replace("号码", "")
  63. self.res["id"].confidence = self.confs[i]
  64. self.res["gender"].text = "男" if int(res[0][16]) % 2 else "女"
  65. self.res["gender"].confidence = self.confs[i]
  66. break
  67. def full_name(self):
  68. """
  69. 身份证姓名
  70. """
  71. for i in range(len(self.result)):
  72. txt = self.result[i]
  73. length = len(txt)
  74. if "姓名" in txt:
  75. if len(txt) < 7:
  76. res = re.findall("姓名[\u4e00-\u9fa5]{1,4}", txt)
  77. # 三个字名字
  78. if len(res) > 0:
  79. self.res["name"].text = res[0].split("姓名")[-1]
  80. self.res["name"].confidence = self.confs[i]
  81. self.result[i] = "temp" # 避免身份证姓名对地址造成干扰
  82. break
  83. else:
  84. res = txt[2:]
  85. name_list = []
  86. point_unicode = ["\u2E31", "\u2218", "\u2219", "\u22C5", "\u25E6", "\u2981",
  87. "\u00B7", "\u0387", "\u05BC", "\u16EB", "\u2022", "\u2027",
  88. "\u2E30", "\uFF0E", "\u30FB", "\uFF65", "\u10101"]
  89. for n in range(len(point_unicode)):
  90. point = re.findall(point_unicode[n], res)
  91. if len(point) != 0:
  92. name_list = res.split(point[0])
  93. for m in range(len(name_list)):
  94. name_list[m] = name_list[m].replace(' ', '')
  95. res = name_list[0] + '\u00B7' + name_list[1]
  96. self.res["name"].text = res
  97. self.res["name"].confidence = self.confs[i]
  98. self.result[i] = "temp" # 避免身份证姓名对地址造成干扰
  99. def gender(self):
  100. """
  101. 性别女民族汉
  102. """
  103. if len(self.res["gender"].text) != 0: return
  104. for i in range(len(self.result)):
  105. txt = self.result[i]
  106. if "男" in txt:
  107. self.res["gender"] = RecItem("男", self.confs[i])
  108. break
  109. if "女" in txt:
  110. self.res["gender"] = RecItem("女", self.confs[i])
  111. break
  112. def national(self):
  113. # 性别女民族汉
  114. for i in range(len(self.result)):
  115. txt = self.result[i]
  116. res = re.findall(".*民族[\u4e00-\u9fa5]+", txt)
  117. if len(res) > 0:
  118. self.res["ethnicity"] = RecItem(res[0].split("族")[-1], self.confs[i])
  119. break
  120. def address(self):
  121. """
  122. 身份证地址
  123. """
  124. addString = []
  125. conf = []
  126. for i in range(len(self.result)):
  127. txt = self.result[i]
  128. txt = txt.replace("号码", "")
  129. if "公民" in txt:
  130. txt = "temp"
  131. # 身份证地址
  132. if (
  133. "住址" in txt
  134. or "址" in txt
  135. or "省" in txt
  136. or "市" in txt
  137. or "县" in txt
  138. or "街" in txt
  139. or "乡" in txt
  140. or "村" in txt
  141. or "镇" in txt
  142. or "区" in txt
  143. or "城" in txt
  144. or "组" in txt
  145. or "旗" in txt
  146. or "号" in txt
  147. ):
  148. # if "住址" in txt or "省" in txt or "址" in txt:
  149. if "住址" in txt or "省" in txt or "址" in txt or \
  150. ('市' in txt and len(addString) > 0 and '市' not in addString[0]):
  151. addString.insert(0, txt.split("址")[-1])
  152. else:
  153. addString.append(txt)
  154. conf.append(self.confs[i])
  155. self.result[i] = "temp"
  156. if len(addString) > 0:
  157. self.res["address"].text = "".join(addString)
  158. self.res["address"].confidence = np.mean(conf)
  159. # print(f'addr: {self.res["Address"]}')
  160. def split_addr(self):
  161. if self.res["address"].text:
  162. conf = self.res["address"].confidence
  163. df = cpca.transform([self.res["address"].text])
  164. # print(df)
  165. province = df.iloc[0, 0]
  166. city = df.iloc[0, 1]
  167. region = df.iloc[0, 2]
  168. detail = df.iloc[0, 3]
  169. print(f'pronvince: {province}, city: {city}, region: {region}, detail: {detail}')
  170. self.res["address_province"] = RecItem(province, conf)
  171. self.res["address_city"] = RecItem(city, conf)
  172. if detail and "旗" in detail:
  173. temp_region = []
  174. temp_region.insert(0, detail.split("旗")[0] + "旗")
  175. self.res["address_region"] = RecItem(temp_region[0], conf)
  176. self.res["address_detail"] = RecItem(detail.split("旗")[-1], conf)
  177. else:
  178. self.res["address_region"] = RecItem(region, conf)
  179. self.res["address_detail"] = RecItem(detail, conf)
  180. def expire_date(self):
  181. for txt, conf in zip(self.result, self.confs):
  182. txt = txt.replace('.', '')
  183. res = re.findall('\d{8}\-\d{8}', txt)
  184. if res:
  185. self.res["expire_date"] = RecItem(res[0], conf)
  186. break
  187. res = re.findall('\d{8}\-长期', txt)
  188. if res:
  189. self.res["expire_date"] = RecItem(res[0], conf)
  190. break
  191. def predict_name(self):
  192. """
  193. 如果PaddleOCR返回的不是姓名xx连着的,则需要去猜测这个姓名,此处需要改进
  194. """
  195. if len(self.res['name'].text) > 1: return
  196. for i in range(len(self.result)):
  197. txt = self.result[i]
  198. if 1 < len(txt) < 5:
  199. if (
  200. "性别" not in txt
  201. and "姓名" not in txt
  202. and "民族" not in txt
  203. and "住址" not in txt
  204. and "出生" not in txt
  205. and "号码" not in txt
  206. and "身份" not in txt
  207. ):
  208. result = re.findall("[\u4e00-\u9fa5]{2,4}", txt)
  209. if len(result) > 0:
  210. self.res["Name"] = RecItem(result[0], self.confs[i])
  211. break
  212. @property
  213. def confidence(self):
  214. return np.mean(self.confs)
  215. def parse(self):
  216. self.full_name()
  217. self.national()
  218. self.card_no()
  219. self.address()
  220. self.split_addr()
  221. self.birth()
  222. self.gender()
  223. self.expire_date()
  224. self.predict_name()
  225. if not self.res["id"].text:
  226. raise Exception("没有识别到身份证号")
  227. return {key: self.res[key].to_dict() for key in self.keys}
  228. class BackParser(Parser):
  229. def __init__(self, txts, confs):
  230. Parser.__init__(self, txts, confs)
  231. def expire_date(self):
  232. for txt, conf in zip(self.result, self.confs):
  233. txt = txt.replace('.', '')
  234. res = re.findall('\d{8}\-\d{8}', txt)
  235. if res:
  236. self.res["expire_date"] = RecItem(res[0], conf)
  237. break
  238. res = re.findall('\d{8}\-长期', txt)
  239. if res:
  240. self.res["expire_date"] = RecItem(res[0], conf)
  241. break
  242. @property
  243. def confidence(self):
  244. return np.mean(self.confs)
  245. def parse(self):
  246. self.expire_date()
  247. if not self.res["expire_date"].text:
  248. raise Exception("无法识别")
  249. return {key: self.res[key].to_dict() for key in self.keys}