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庫應該可以解決.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM