• 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中截取目标图表

    关键词:  "[图表]*sd+[::]", "来源[::]"

    代码:

      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'图表*sd+[::]', 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库应该可以解决.

  • 相关阅读:
    多线程(5)async&await
    多线程(4)Task
    多线程(3)ThreadPool
    多线程(2)Thread
    多线程(1)认识多线程
    泛型
    反射(4)反射性能问题:直接调用vs反射调用
    反射(3)反射应用:一个插件项目
    反射(2)使用反射
    反射(1)认识反射
  • 原文地址:https://www.cnblogs.com/yangshaolun/p/10878033.html
Copyright © 2020-2023  润新知