formula_segment_and_show.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # @Author : lightXu
  2. # @File : formula_segment_and_show.py
  3. # @Time : 2019/1/24 0024 下午 13:24
  4. import time
  5. import re
  6. import copy
  7. import math
  8. import cv2
  9. import numpy as np
  10. import xml.etree.cElementTree as ET
  11. from segment.formula import mathpix_ocr
  12. from segment.server import get_ocr_text_and_coordinate_formula
  13. from segment.image_operation import utils
  14. def get_coordinates(word_res, formula_words_list):
  15. res_list = []
  16. for formula_raw in formula_words_list:
  17. coordinates_start_index = formula_raw[1][0]
  18. coordinates_end_index = formula_raw[1][1] - 1
  19. coordinates_start = word_res['chars'][coordinates_start_index]['location']
  20. coordinates_end = word_res['chars'][coordinates_end_index]['location']
  21. coordinates = (coordinates_start['left'], # xmin
  22. min(coordinates_start['top'], coordinates_end['top']), # ymin
  23. coordinates_end['left'] + coordinates_end['width'], # xmax
  24. max(coordinates_start['top'] + coordinates_start['height'],
  25. coordinates_end['top'] + coordinates_end['height'])) # ymax
  26. tmp_dict = {'chars': formula_raw[0],
  27. 'raw_chars': formula_raw[0],
  28. 'coordinates': coordinates,
  29. 'middle': (coordinates[0] + int((coordinates[2] - coordinates[0]) // 2),
  30. coordinates[1] + int((coordinates[3] - coordinates[1]) // 2))}
  31. res_list.append(tmp_dict)
  32. return res_list
  33. def generate_char(words, index_pair, zh=True):
  34. if index_pair:
  35. # new_words = words.copy()
  36. length = index_pair[1] - index_pair[0]
  37. gen = ''
  38. if zh:
  39. for i in range(length):
  40. gen = '中' + gen
  41. else:
  42. for i in range(length):
  43. gen = 'F' + gen
  44. words = words.replace(words[index_pair[0]:index_pair[1]], gen)
  45. return words
  46. else:
  47. return words
  48. def segment(img, save_path, access_token):
  49. # raw_img = img.copy()
  50. # img = utils.preprocess(raw_img, None)
  51. word_result_list = get_ocr_text_and_coordinate_formula(img, access_token)
  52. formula_coordinates_dict_list = []
  53. zh_coordinates_dict_list = []
  54. zh_char_height = 20 # default
  55. zh_char_width = 15 # default
  56. zh_char_height_list = []
  57. zh_char_width_list = []
  58. exclude = r'{}|{}|{}|{}|{}|{}'.format(
  59. '[ABCD]\.', # A. B. C. D.
  60. '[((][))]', # ()
  61. '^[((]*[\d]+[))]', # (1)
  62. # '[((]*[a-zA-Z]{2,}[))]', # (km), (kg)
  63. '[①②③④⑤⑥⑦⑧⑨⑩]', # ①②③④⑤⑥⑦⑧⑨⑩
  64. '[\u4e00-\u9fa5][,;:。,;:.]', # 中.
  65. '[\u4e00-\u9fa5][\d]+[\u4e00-\u9fa5]') # 中123中
  66. for index, word_res in enumerate(word_result_list):
  67. words = word_res['words'].replace(' ', '').replace('兀', 'π') # 去除空格,baidu_api bug
  68. abcd_words_m = re.finditer(exclude, words)
  69. abcd_index_list = [(m.group(), m.span()) for m in abcd_words_m if m]
  70. words_tmp_zh = copy.copy(words)
  71. for ele in abcd_index_list:
  72. words_tmp_zh = generate_char(words_tmp_zh, ele[1], zh=True)
  73. formula_words_m = re.finditer(r'[^\u4e00-\u9fa5_"“”]+', words_tmp_zh)
  74. formula_index_list = [(m.group(), m.span()) for m in formula_words_m if m]
  75. formula_list = get_coordinates(word_res, formula_index_list)
  76. formula_coordinates_dict_list = formula_coordinates_dict_list + formula_list
  77. words_tmp_formula = copy.copy(words)
  78. for ele in abcd_index_list:
  79. words_tmp_formula = generate_char(words_tmp_formula, ele[1], zh=False)
  80. zh_words_m = re.finditer(r'[\u4e00-\u9fa5_"“”]+', words_tmp_formula)
  81. zh_index_list = [(m.group(), m.span()) for m in zh_words_m if m]
  82. zh_list = get_coordinates(word_res, zh_index_list + abcd_index_list)
  83. zh_coordinates_dict_list = zh_coordinates_dict_list + zh_list
  84. one_zh_char_m = re.match(r'[\u4e00-\u9fa5]+', words)
  85. if one_zh_char_m:
  86. index = one_zh_char_m.span()[0]
  87. zh_char_height_list.append(word_res['chars'][index]['location']['height'])
  88. zh_char_width_list.append(word_res['chars'][index]['location']['width'])
  89. if len(zh_char_width_list) > 0 and len(zh_char_height_list) > 0:
  90. zh_char_height = np.mean(zh_char_height_list)
  91. zh_char_width = np.mean(zh_char_width_list)
  92. formula_coordinates_list = [ele['coordinates'] for ele in formula_coordinates_dict_list]
  93. temp_img = img.copy()
  94. for ele in formula_coordinates_list:
  95. cv2.rectangle(temp_img, (int(ele[0]), int(ele[1])), (int(ele[2]), int(ele[3])), (0, 255, 0), 1)
  96. save_path0 = save_path.replace('.jpg', '_@_{:02d}.jpg'.format(1))
  97. utils.write_single_img(temp_img, save_path0)
  98. # 合并公式
  99. formula_combine_list = combine(img, save_path, formula_coordinates_list, zh_char_height, zh_char_width, 1) # 欧式距离
  100. formula_combine_dict_list = []
  101. for ele in formula_combine_list:
  102. middle = (ele[0] + int((ele[2] - ele[0]) // 2), ele[1] + int((ele[3] - ele[1]) // 2))
  103. ocr_region = utils.crop_region_direct(img, ele)
  104. y, x = ocr_region.shape[0], ocr_region.shape[1]
  105. if min(y, x) <= 50:
  106. ocr_region = utils.resize_by_percent(ocr_region, 1.50) # 放大若干倍
  107. # cv2.imshow('region', ocr_region)
  108. # if cv2.waitKey(0) == 27:
  109. # cv2.destroyAllWindows()
  110. try:
  111. mathpix_raw_chars, latex_confidence = mathpix_ocr.mathpix_api(ocr_region) # 识别公式
  112. render_mathpix_chars = '<img src="http://latex.codecogs.com/png.latex?{}" />'.format(mathpix_raw_chars)
  113. if latex_confidence < 0.2 or mathpix_raw_chars == '' or len(mathpix_raw_chars) == 1:
  114. for item in formula_coordinates_dict_list:
  115. if ele == item['coordinates']:
  116. mathpix_raw_chars = item['chars']
  117. render_mathpix_chars = '<img src="http://latex.codecogs.com/png.latex?{}" />' \
  118. .format(item['chars'])
  119. break
  120. except Exception:
  121. render_mathpix_chars = 'formula'
  122. mathpix_raw_chars = 'formula'
  123. for item in formula_coordinates_dict_list:
  124. if ele == item['coordinates']:
  125. mathpix_raw_chars = item['chars']
  126. render_mathpix_chars = '<img src="http://latex.codecogs.com/png.latex?{}" />' \
  127. .format(item['chars'])
  128. break
  129. print(render_mathpix_chars)
  130. tmp_dict = {'chars': render_mathpix_chars, 'middle': middle, 'coordinates': ele, 'raw_chars': mathpix_raw_chars}
  131. formula_combine_dict_list.append(tmp_dict)
  132. # res_dict = {'formula': formula_combine_list, 'zh_chars': zh_coordinates_dict_list}
  133. all_dict_list = zh_coordinates_dict_list + formula_combine_dict_list
  134. all_dict_list = sorted(all_dict_list, key=lambda k: k.get('middle')[1])
  135. # 相邻y做差
  136. former = np.array([ele['middle'][1] for ele in all_dict_list[:-1]])
  137. rear = np.array([ele['middle'][1] for ele in all_dict_list[1:]])
  138. dif = rear - former
  139. split_x_index = [index for index, ele in enumerate(dif) if ele >= zh_char_height] # y轴排序
  140. # 对整体图像大小进行resize
  141. scale = 1
  142. h, w = img.shape[0], img.shape[1]
  143. if w > 1000:
  144. scale = float(1000 / w)
  145. elif h < 100:
  146. scale = float(100 / h)
  147. img_resize = utils.resize_by_percent(img, scale)
  148. utils.write_single_img(img_resize, save_path)
  149. if not split_x_index:
  150. all_dict_list = sorted(all_dict_list, key=lambda k: k.get('middle')[0]) # x轴排序
  151. lines = [ele['chars'] for ele in all_dict_list]
  152. raw_lines = [ele['raw_chars'] for ele in all_dict_list]
  153. for ele in all_dict_list:
  154. bbox = [box * scale for box in ele['coordinates']]
  155. cv2.rectangle(img_resize, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), (0, 255, 0), 3)
  156. utils.write_single_img(img_resize, save_path)
  157. return lines, raw_lines, h
  158. else:
  159. res_list = []
  160. split_x_index = [ele + 1 for ele in split_x_index] # 索引值扩大
  161. split_x_index.insert(0, 0)
  162. split_x_index.insert(-1, len(all_dict_list))
  163. split_x_index = sorted(list(set(split_x_index)))
  164. for i, split in enumerate(split_x_index[1:]):
  165. one_line = all_dict_list[split_x_index[i]:split_x_index[i + 1]]
  166. one_line = sorted(one_line, key=lambda k: k.get('middle')[0]) # x轴排序
  167. res_list.append(one_line)
  168. lines = []
  169. raw_lines = []
  170. for ele in res_list:
  171. line_chars = ''
  172. raw_lines_chars = ''
  173. for ele1 in ele:
  174. chars = ele1['chars']
  175. raw_chars = ele1['raw_chars']
  176. line_chars = line_chars + chars
  177. raw_lines_chars = raw_lines_chars + raw_chars
  178. bbox = [box * scale for box in ele1['coordinates']]
  179. cv2.rectangle(img_resize, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), (0, 255, 0), 1)
  180. lines.append(line_chars + '\n')
  181. raw_lines.append(raw_lines_chars + '\n')
  182. utils.write_single_img(img_resize, save_path)
  183. # print(lines)
  184. return lines, raw_lines, h
  185. def combine(img, save_path, formula_coordinates_list, zh_char_height, zh_char_width, draw_index):
  186. img_draw = img.copy()
  187. formula_coordinates_list = sorted(formula_coordinates_list, key=lambda k: k[0])
  188. formula_coordinates_list = sorted(formula_coordinates_list, key=lambda k: k[1]) # 先x轴,再y轴排序
  189. recursion_flag = False
  190. del_list = []
  191. temp_list = formula_coordinates_list.copy()
  192. for i, outer in enumerate(temp_list): # xmin, ymin, xmax, ymax
  193. for j, inner in enumerate(temp_list): # xmin, ymin, xmax, ymax
  194. if not i == j:
  195. min_distance, flag = get_min_distance(outer, inner)
  196. combine_coordinate = ()
  197. if flag == 'i':
  198. recursion_flag = True
  199. combine_coordinate = (min(outer[0], inner[0]), min(outer[1], inner[1]),
  200. max(outer[2], inner[2]), max(outer[3], inner[3]))
  201. elif flag == 'h' and min_distance <= 1:
  202. recursion_flag = True
  203. combine_coordinate = (min(outer[0], inner[0]), min(outer[1], inner[1]),
  204. max(outer[2], inner[2]), max(outer[3], inner[3]))
  205. elif flag == 'w' and min_distance <= zh_char_width*2//3:
  206. recursion_flag = True
  207. combine_coordinate = (min(outer[0], inner[0]), min(outer[1], inner[1]),
  208. max(outer[2], inner[2]), max(outer[3], inner[3]))
  209. elif flag == 'c' and min_distance <= 1:
  210. recursion_flag = True
  211. combine_coordinate = (min(outer[0], inner[0]), min(outer[1], inner[1]),
  212. max(outer[2], inner[2]), max(outer[3], inner[3]))
  213. if combine_coordinate:
  214. if not combine_coordinate == outer and not combine_coordinate == inner: # 避免全包围的情况
  215. del_list.append(outer)
  216. del_list.append(inner)
  217. if combine_coordinate == outer:
  218. del_list.append(inner)
  219. if combine_coordinate == inner:
  220. del_list.append(outer)
  221. formula_coordinates_list.append(combine_coordinate)
  222. res = list(set(formula_coordinates_list) - set(del_list))
  223. if recursion_flag:
  224. draw_index = draw_index + 1
  225. for ele in res:
  226. cv2.rectangle(img_draw, (int(ele[0]), int(ele[1])), (int(ele[2]), int(ele[3])), (0, 255, 0), 1)
  227. save_path_temp = save_path.replace('.jpg', '_@_{:02d}.jpg'.format(draw_index))
  228. utils.write_single_img(img_draw, save_path_temp)
  229. return combine(img, save_path, res, zh_char_height, zh_char_width, draw_index)
  230. else:
  231. for ele in res:
  232. cv2.rectangle(img_draw, (int(ele[0]), int(ele[1])), (int(ele[2]), int(ele[3])), (0, 255, 0), 1)
  233. save_path_temp = save_path.replace('.jpg', '_@_final.jpg')
  234. utils.write_single_img(img_draw, save_path_temp)
  235. return res
  236. def get_min_distance_square(coordinate1, coordinate2): # 顶点间欧式距离最小值的平方和
  237. all_points1 = [(x, y) for x in [coordinate1[0], coordinate1[2]] for y in [coordinate1[1], coordinate1[3]]]
  238. all_points2 = [(x, y) for x in [coordinate2[0], coordinate2[2]] for y in [coordinate2[1], coordinate2[3]]]
  239. distance_list = []
  240. for index1, point1 in enumerate(all_points1):
  241. for index2, point2 in enumerate(all_points2):
  242. distance = (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2
  243. distance_list.append(distance)
  244. min_distance = min(distance_list)
  245. return min_distance
  246. def get_min_distance(coordinate1, coordinate2): # 欧式距离最小值
  247. def dist(point1, point2):
  248. distance = (point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2
  249. return math.sqrt(distance)
  250. (x1, y1, x1b, y1b) = coordinate1
  251. (x2, y2, x2b, y2b) = coordinate2
  252. left = x2b < x1 # 2在1的坐标左边
  253. right = x1b < x2 # 2在1的坐标右边
  254. bottom = y2b < y1 # 2在1的坐标下边
  255. top = y1b < y2 # 2在1的坐标上边
  256. if top and left:
  257. return dist((x1, y1b), (x2b, y2)), 'c'
  258. elif left and bottom:
  259. return dist((x1, y1), (x2b, y2b)), 'c'
  260. elif bottom and right:
  261. return dist((x1b, y1), (x2, y2b)), 'c'
  262. elif right and top:
  263. return dist((x1b, y1b), (x2, y2)), 'c'
  264. elif left:
  265. return x1 - x2b, 'w'
  266. elif right:
  267. return x2 - x1b, 'w'
  268. elif bottom:
  269. return y1 - y2b, 'h'
  270. elif top:
  271. return y2 - y1b, 'h'
  272. else: # rectangles intersect
  273. return 0, 'i'