fix_image.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import re
  2. from magic_pdf.libs.boxbase import _is_in_or_part_overlap, _is_part_overlap, find_bottom_nearest_text_bbox, find_left_nearest_text_bbox, find_right_nearest_text_bbox, find_top_nearest_text_bbox
  3. from magic_pdf.libs.textbase import get_text_block_base_info
  4. def fix_image_vertical(image_bboxes:list, text_blocks:list):
  5. """
  6. 修正图片的位置
  7. 如果图片与文字block发生一定重叠(也就是图片切到了一部分文字),那么减少图片边缘,让文字和图片不再重叠。
  8. 只对垂直方向进行。
  9. """
  10. for image_bbox in image_bboxes:
  11. for text_block in text_blocks:
  12. text_bbox = text_block["bbox"]
  13. if _is_part_overlap(text_bbox, image_bbox) and any([text_bbox[0]>=image_bbox[0] and text_bbox[2]<=image_bbox[2], text_bbox[0]<=image_bbox[0] and text_bbox[2]>=image_bbox[2]]):
  14. if text_bbox[1] < image_bbox[1]:#在图片上方
  15. image_bbox[1] = text_bbox[3]+1
  16. elif text_bbox[3]>image_bbox[3]:#在图片下方
  17. image_bbox[3] = text_bbox[1]-1
  18. return image_bboxes
  19. def __merge_if_common_edge(bbox1, bbox2):
  20. x_min_1, y_min_1, x_max_1, y_max_1 = bbox1
  21. x_min_2, y_min_2, x_max_2, y_max_2 = bbox2
  22. # 检查是否有公共的水平边
  23. if y_min_1 == y_min_2 or y_max_1 == y_max_2:
  24. # 确保一个框的x范围在另一个框的x范围内
  25. if max(x_min_1, x_min_2) <= min(x_max_1, x_max_2):
  26. return [min(x_min_1, x_min_2), min(y_min_1, y_min_2), max(x_max_1, x_max_2), max(y_max_1, y_max_2)]
  27. # 检查是否有公共的垂直边
  28. if x_min_1 == x_min_2 or x_max_1 == x_max_2:
  29. # 确保一个框的y范围在另一个框的y范围内
  30. if max(y_min_1, y_min_2) <= min(y_max_1, y_max_2):
  31. return [min(x_min_1, x_min_2), min(y_min_1, y_min_2), max(x_max_1, x_max_2), max(y_max_1, y_max_2)]
  32. # 如果没有公共边
  33. return None
  34. def fix_seperated_image(image_bboxes:list):
  35. """
  36. 如果2个图片有一个边重叠,那么合并2个图片
  37. """
  38. new_images = []
  39. droped_img_idx = []
  40. for i in range(0, len(image_bboxes)):
  41. for j in range(i+1, len(image_bboxes)):
  42. new_img = __merge_if_common_edge(image_bboxes[i], image_bboxes[j])
  43. if new_img is not None:
  44. new_images.append(new_img)
  45. droped_img_idx.append(i)
  46. droped_img_idx.append(j)
  47. break
  48. for i in range(0, len(image_bboxes)):
  49. if i not in droped_img_idx:
  50. new_images.append(image_bboxes[i])
  51. return new_images
  52. def __check_img_title_pattern(text):
  53. """
  54. 检查文本段是否是表格的标题
  55. """
  56. patterns = [r"^(fig|figure).*", r"^(scheme).*"]
  57. text = text.strip()
  58. for pattern in patterns:
  59. match = re.match(pattern, text, re.IGNORECASE)
  60. if match:
  61. return True
  62. return False
  63. def __get_fig_caption_text(text_block):
  64. txt = " ".join(span['text'] for line in text_block['lines'] for span in line['spans'])
  65. line_cnt = len(text_block['lines'])
  66. txt = txt.replace("Ž . ", '')
  67. return txt, line_cnt
  68. def __find_and_extend_bottom_caption(text_block, pymu_blocks, image_box):
  69. """
  70. 继续向下方寻找和图片caption字号,字体,颜色一样的文字框,合并入caption。
  71. text_block是已经找到的图片catpion(这个caption可能不全,多行被划分到多个pymu block里了)
  72. """
  73. combined_image_caption_text_block = list(text_block.copy()['bbox'])
  74. base_font_color, base_font_size, base_font_type = get_text_block_base_info(text_block)
  75. while True:
  76. tb_add = find_bottom_nearest_text_bbox(pymu_blocks, combined_image_caption_text_block)
  77. if not tb_add:
  78. break
  79. tb_font_color, tb_font_size, tb_font_type = get_text_block_base_info(tb_add)
  80. if tb_font_color==base_font_color and tb_font_size==base_font_size and tb_font_type==base_font_type:
  81. combined_image_caption_text_block[0] = min(combined_image_caption_text_block[0], tb_add['bbox'][0])
  82. combined_image_caption_text_block[2] = max(combined_image_caption_text_block[2], tb_add['bbox'][2])
  83. combined_image_caption_text_block[3] = tb_add['bbox'][3]
  84. else:
  85. break
  86. image_box[0] = min(image_box[0], combined_image_caption_text_block[0])
  87. image_box[1] = min(image_box[1], combined_image_caption_text_block[1])
  88. image_box[2] = max(image_box[2], combined_image_caption_text_block[2])
  89. image_box[3] = max(image_box[3], combined_image_caption_text_block[3])
  90. text_block['_image_caption'] = True
  91. def include_img_title(pymu_blocks, image_bboxes: list):
  92. """
  93. 向上方和下方寻找符合图片title的文本block,合并到图片里
  94. 如果图片上下都有fig的情况怎么办?寻找标题距离最近的那个。
  95. ---
  96. 增加对左侧和右侧图片标题的寻找
  97. """
  98. for tb in image_bboxes:
  99. # 优先找下方的
  100. max_find_cnt = 3 # 向上,向下最多找3个就停止
  101. temp_box = tb.copy()
  102. while max_find_cnt>0:
  103. text_block_btn = find_bottom_nearest_text_bbox(pymu_blocks, temp_box)
  104. if text_block_btn:
  105. txt, line_cnt = __get_fig_caption_text(text_block_btn)
  106. if len(txt.strip())>0:
  107. if not __check_img_title_pattern(txt) and max_find_cnt>0 and line_cnt<3: # 设置line_cnt<=2目的是为了跳过子标题,或者有时候图片下方文字没有被图片识别模型放入图片里
  108. max_find_cnt = max_find_cnt - 1
  109. temp_box[3] = text_block_btn['bbox'][3]
  110. continue
  111. else:
  112. break
  113. else:
  114. temp_box[3] = text_block_btn['bbox'][3] # 宽度不变,扩大
  115. max_find_cnt = max_find_cnt - 1
  116. else:
  117. break
  118. max_find_cnt = 3 # 向上,向下最多找3个就停止
  119. temp_box = tb.copy()
  120. while max_find_cnt>0:
  121. text_block_top = find_top_nearest_text_bbox(pymu_blocks, temp_box)
  122. if text_block_top:
  123. txt, line_cnt = __get_fig_caption_text(text_block_top)
  124. if len(txt.strip())>0:
  125. if not __check_img_title_pattern(txt) and max_find_cnt>0 and line_cnt <3:
  126. max_find_cnt = max_find_cnt - 1
  127. temp_box[1] = text_block_top['bbox'][1]
  128. continue
  129. else:
  130. break
  131. else:
  132. b = text_block_top['bbox']
  133. temp_box[1] = b[1] # 宽度不变,扩大
  134. max_find_cnt = max_find_cnt - 1
  135. else:
  136. break
  137. if text_block_btn and text_block_top and text_block_btn.get("_image_caption", False) is False and text_block_top.get("_image_caption", False) is False :
  138. btn_text, _ = __get_fig_caption_text(text_block_btn)
  139. top_text, _ = __get_fig_caption_text(text_block_top)
  140. if __check_img_title_pattern(btn_text) and __check_img_title_pattern(top_text):
  141. # 取距离图片最近的
  142. btn_text_distance = text_block_btn['bbox'][1] - tb[3]
  143. top_text_distance = tb[1] - text_block_top['bbox'][3]
  144. if btn_text_distance<top_text_distance: # caption在下方
  145. __find_and_extend_bottom_caption(text_block_btn, pymu_blocks, tb)
  146. else:
  147. text_block = text_block_top
  148. tb[0] = min(tb[0], text_block['bbox'][0])
  149. tb[1] = min(tb[1], text_block['bbox'][1])
  150. tb[2] = max(tb[2], text_block['bbox'][2])
  151. tb[3] = max(tb[3], text_block['bbox'][3])
  152. text_block_btn['_image_caption'] = True
  153. continue
  154. text_block = text_block_btn # find_bottom_nearest_text_bbox(pymu_blocks, tb)
  155. if text_block and text_block.get("_image_caption", False) is False:
  156. first_text_line, _ = __get_fig_caption_text(text_block)
  157. if __check_img_title_pattern(first_text_line):
  158. # 发现特征之后,继续向相同方向寻找(想同颜色,想同大小,想同字体)的textblock
  159. __find_and_extend_bottom_caption(text_block, pymu_blocks, tb)
  160. continue
  161. text_block = text_block_top # find_top_nearest_text_bbox(pymu_blocks, tb)
  162. if text_block and text_block.get("_image_caption", False) is False:
  163. first_text_line, _ = __get_fig_caption_text(text_block)
  164. if __check_img_title_pattern(first_text_line):
  165. tb[0] = min(tb[0], text_block['bbox'][0])
  166. tb[1] = min(tb[1], text_block['bbox'][1])
  167. tb[2] = max(tb[2], text_block['bbox'][2])
  168. tb[3] = max(tb[3], text_block['bbox'][3])
  169. text_block['_image_caption'] = True
  170. continue
  171. """向左、向右寻找,暂时只寻找一次"""
  172. left_text_block = find_left_nearest_text_bbox(pymu_blocks, tb)
  173. if left_text_block and left_text_block.get("_image_caption", False) is False:
  174. first_text_line, _ = __get_fig_caption_text(left_text_block)
  175. if __check_img_title_pattern(first_text_line):
  176. tb[0] = min(tb[0], left_text_block['bbox'][0])
  177. tb[1] = min(tb[1], left_text_block['bbox'][1])
  178. tb[2] = max(tb[2], left_text_block['bbox'][2])
  179. tb[3] = max(tb[3], left_text_block['bbox'][3])
  180. left_text_block['_image_caption'] = True
  181. continue
  182. right_text_block = find_right_nearest_text_bbox(pymu_blocks, tb)
  183. if right_text_block and right_text_block.get("_image_caption", False) is False:
  184. first_text_line, _ = __get_fig_caption_text(right_text_block)
  185. if __check_img_title_pattern(first_text_line):
  186. tb[0] = min(tb[0], right_text_block['bbox'][0])
  187. tb[1] = min(tb[1], right_text_block['bbox'][1])
  188. tb[2] = max(tb[2], right_text_block['bbox'][2])
  189. tb[3] = max(tb[3], right_text_block['bbox'][3])
  190. right_text_block['_image_caption'] = True
  191. continue
  192. return image_bboxes
  193. def combine_images(image_bboxes:list):
  194. """
  195. 合并图片,如果图片有重叠,那么合并
  196. """
  197. new_images = []
  198. droped_img_idx = []
  199. for i in range(0, len(image_bboxes)):
  200. for j in range(i+1, len(image_bboxes)):
  201. if j not in droped_img_idx and _is_in_or_part_overlap(image_bboxes[i], image_bboxes[j]):
  202. # 合并
  203. image_bboxes[i][0], image_bboxes[i][1],image_bboxes[i][2],image_bboxes[i][3] = min(image_bboxes[i][0], image_bboxes[j][0]), min(image_bboxes[i][1], image_bboxes[j][1]), max(image_bboxes[i][2], image_bboxes[j][2]), max(image_bboxes[i][3], image_bboxes[j][3])
  204. droped_img_idx.append(j)
  205. for i in range(0, len(image_bboxes)):
  206. if i not in droped_img_idx:
  207. new_images.append(image_bboxes[i])
  208. return new_images