起因:
因為個人原因, 這些天了解了一下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庫應該可以解決.