Python处理PDF-通过关键词定位-截取PDF中的图表

起因:

  因为个人原因, 这些天了解了一下Python处理PDF的方法.

  首先是PDF转txt, 这个方法比较多, 这里就不再赘述, 主要聊一下PDF中的图片获取.

  这里用我自己的例子, 不过具体情况还得具体分析.

 

工具:  pdfminer, pillow, fitz, re

思路:

  1.  使用pdfminer解析PDF, 通过当前页的LTpage对象, 获取关键词的position与当前LTpage的size.

  2.  使用fitz将当前页的PDF转换为PNG

  3.  使用pillow, 通过第一步得到的参数来从第二步得到的PNG中截取目标图表

关键词:  "[图表]*\s\d+[::]", "来源[::]"

代码:

  1 from pdfminer.pdfparser import PDFParser, PDFDocument
  2 from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
  3 from pdfminer.converter import PDFPageAggregator
  4 from pdfminer.layout import LAParams
  5 from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
  6 from PIL import Image
  7 import fitz
  8 import re
  9 import os
 10 
 11 
 12 class GetPic:
 13     def __init__(self, filename, password=''):
 14         """
 15         初始化
 16         :param filename: pdf路径
 17         :param password: 密码
 18         """
 19         with open(filename, 'rb') as file:
 20             # 创建文档分析器
 21             self.parser = PDFParser(file)
 22         # 创建文档
 23         self.doc = PDFDocument()
 24         # 连接文档与文档分析器
 25         self.parser.set_document(self.doc)
 26         self.doc.set_parser(self.parser)
 27         # 初始化, 提供初始密码, 若无则为空字符串
 28         self.doc.initialize(password)
 29         # 检测文档是否提供txt转换, 不提供就忽略, 抛出异常
 30         if not self.doc.is_extractable:
 31             raise PDFTextExtractionNotAllowed
 32         else:
 33             # 创建PDF资源管理器, 管理共享资源
 34             self.resource_manager = PDFResourceManager()
 35             # 创建一个PDF设备对象
 36             self.laparams = LAParams()
 37             self.device = PDFPageAggregator(self.resource_manager, laparams=self.laparams)
 38             # 创建一个PDF解释器对象
 39             self.interpreter = PDFPageInterpreter(self.resource_manager, self.device)
 40             # pdf的page对象列表
 41             self.doc_pdfs = list(self.doc.get_pages())
 42         #  打开PDF文件, 生成一个包含图片doc对象的可迭代对象
 43         self.doc_pics = fitz.open(filename)
 44 
 45     def to_pic(self, doc, zoom, pg, pic_path):
 46         """
 47         将单页pdf转换为pic
 48         :param doc: 图片的doc对象
 49         :param zoom: 图片缩放比例, type int, 数值越大分辨率越高
 50         :param pg: 对象在doc_pics中的索引
 51         :param pic_path: 图片保存路径
 52         :return: 图片的路径
 53         """
 54         rotate = int(0)
 55         trans = fitz.Matrix(zoom, zoom).preRotate(rotate)
 56         pm = doc.getPixmap(matrix=trans, alpha=False)
 57         path = os.path.join(pic_path, str(pg)) + '.png'
 58         pm.writePNG(path)
 59         return path
 60 
 61     def get_pic_loc(self, doc):
 62         """
 63         获取单页中图片的位置
 64         :param doc: pdf的doc对象
 65         :return: 返回一个list, 元素为图片名称和上下y坐标元组组成的tuple. 当前页的尺寸
 66         """
 67         self.interpreter.process_page(doc)
 68         layout = self.device.get_result()
 69         # pdf的尺寸, tuple, (width, height)
 70         canvas_size = layout.bbox
 71         # 图片名称坐标
 72         loc_top = []
 73         # 来源坐标
 74         loc_bottom = []
 75         # 图片名称与应截取的区域y1, y2坐标
 76         loc_named_pic = []
 77         # 遍历单页的所有LT对象
 78         for i in layout:
 79             if hasattr(i, 'get_text'):
 80                 text = i.get_text().strip()
 81                 # 匹配关键词
 82                 if re.search(r'图表*\s\d+[::]', text):
 83                     loc_top.append((i.bbox, text))
 84                 elif re.search(r'来源[::]', text):
 85                     loc_bottom.append((i.bbox, text))
 86         zip_loc = zip(loc_top, loc_bottom)
 87         for i in zip_loc:
 88             y1 = i[1][0][1]
 89             y2 = i[0][0][3]
 90             name = i[0][1]
 91             loc_named_pic.append((name, (y1, y2)))
 92         return loc_named_pic, canvas_size
 93 
 94     def get_crops(self, pic_path, canvas_size, position, cropped_pic_name, cropped_pic_path):
 95         """
 96         按给定位置截取图片
 97         :param pic_path: 被截取的图片的路径
 98         :param canvas_size: 图片为pdf时的尺寸, tuple, (0, 0, width, height)
 99         :param position: 要截取的位置, tuple, (y1, y2)
100         :param cropped_pic_name: 截取的图片名称
101         :param cropped_pic_path: 截取的图片保存路径
102         :return:
103         """
104         img = Image.open(pic_path)
105         # 当前图片的尺寸 tuple(width, height)
106         pic_size = img.size
107         # 截图的范围扩大值
108         size_increase = 10
109         x1 = 0
110         x2 = pic_size[0]
111         y1 = pic_size[1] * (1 - (position[1] + size_increase)/canvas_size[3])
112         y2 = pic_size[1] * (1 - (position[0] - size_increase)/canvas_size[3])
113         cropped_img = img.crop((x1, y1, x2, y2))
114         # 保存截图文件的路径
115         path = os.path.join(cropped_pic_path, cropped_pic_name) + '.png'
116         cropped_img.save(path)
117         print('成功截取图片:', cropped_pic_name)
118 
119     def main(self, pic_path, cropped_pic_path, pgn=None):
120         """
121         主函数
122         :param pic_path: 被截取的图片路径
123         :param cropped_pic_path: 图片的截图的保存路径
124         :param pgn: 指定获取截图的对象的索引
125         :return:
126         """
127         if pgn is not None:
128             # 获取当前页的doc
129             doc_pdf = self.doc_pdfs[pgn]
130             doc_pic = self.doc_pics[pgn]
131             # 将当前页转换为PNG, 返回值为图片路径
132             path = self.to_pic(doc_pic, 2, pgn, pic_path)
133             loc_name_pic, canvas_size = self.get_pic_loc(doc_pdf)
134             if loc_name_pic:
135                 for i in loc_name_pic:
136                     position = i[1]
137                     cropped_pic_name = re.sub('/', '_', i[0])
138                     self.get_crops(path, canvas_size, position, cropped_pic_name, cropped_pic_path)
139 
140 
141 if __name__ == '__main__':
142     pdf_path = '要处理的PDF的路径'
143     test = GetPic(pdf_path)
144     pic_path = 'PNG的保存路径'
145     cropped_pic_path = '截图的保存路径'
146     page_count = test.doc_pics.pageCount
147     for i in range(page_count):
148         test.main(pic_path, cropped_pic_path, pgn=i)

本例局限:

  1.  目标PDF需要可以用pdfminer里的LTPage对象解析出文字.

  2.  PDF中没有跳页的图表.

  3.  截图的时候只用了y轴截图, x轴上可能出现多个图表

局限解决方案:

  1.  目前没有去尝试, 或许PyPDF2可以试一试?

  2.  这里的函数都是处理单页的, 所有在处理连页图片时会出现问题, 不过解决方法也很简单. 就是将 loc_top、loc_bottom设置为全局变量并且加上页码的索引, 这样loc_top和loc_bottm中的元素就能够一一对应. 再加上一个判断, top的y轴坐标比bottom小的话, 就截取两张图片, top的y轴坐标至页尾和bottom的y轴坐标至页头. 有兴趣的可以自己尝试一下.

  3.  这个问题的话, 一是可以后期通过其它库再按照一定的方法截取一次; 二是可以在一次截取的时候加上x轴的左坐标来确定目标位置, 因为如果同一y轴范围内只有一个图表的话, x轴右坐标就无关紧要类, 如果同一y轴范围内有两个图标的话, 通过x轴左坐标也能化界, 如果有两个以上的图标时候就需要加上x轴的右坐标了.

 

结语: 

  这里只是提供了一种思路, 方法其实还是很不完善的, 很多小细节都没有去解决.

  还有一种思路是将PDF转换为PNG之后直接识别其中的关键词左边来获取截图, 这个的话大家也可以去了解一下, 用tesserocr库应该可以解决.

posted @ 2019-05-16 21:09  Sly_Yang  阅读(4486)  评论(2编辑  收藏  举报