如何用pdfPlumber与OpenCV筛选指定区域文本及解决坐标不匹配问题
问题描述
我有一批会议论文集PDF文件,每个文件的结构如下:
大字号粗体标题 Author1 Author2 AuthorN 所属机构1 所属机构2 所属机构N Email1 Email2 Email3
我已经用pdfPlumber选取字号最大的字符作为标题,这个方法可行。现在想要提取作者-所属机构-邮箱信息,尝试用OpenCV识别文本块,再筛选每个OpenCV框内的字符,但无法成功,原因是OpenCV生成的框坐标x/y与pdfPlumber生成的x0/x1/top/bottom不匹配。以下是我的实现代码:
def getCharsInBox(box): left_point_x=np.min(box[:,0]) right_point_x=np.max(box[:,0]) top_point_y=np.min(box[:,1]) bottom_point_y=np.max(box[:,1]) return lambda x:((x.get("x0",0)>=left_point_x &\ x.get("x0",0)<=right_point_x &\ x.get("top",0)>=top_point_x &\ x.get("bottom",0)<=bottom_point_y)) for box in region: filtered=page.filter(getCharsInBox(box)) pdfAAE=filtered.extract_text()
解决建议
1. 先统一坐标系统
pdfPlumber用PDF原生**点(pt)作为单位,OpenCV处理的是图片像素(px)**坐标,两者缩放比例完全不同,必须先做转换:
- 用pdfPlumber导出页面为图片,记录pt到px的缩放比例:
# 导出页面为图片,dpi和OpenCV处理时保持一致 img = page.to_image(resolution=300) scale_x = img.width / page.width scale_y = img.height / page.height - 将OpenCV的像素框转换为pdfPlumber的点坐标:
def box_px_to_pt(box, scale_x, scale_y): left_x = np.min(box[:,0]) / scale_x right_x = np.max(box[:,0]) / scale_x top_y = np.min(box[:,1]) / scale_y bottom_y = np.max(box[:,1]) / scale_y return left_x, right_x, top_y, bottom_y
2. 修正筛选函数的逻辑错误
原代码误用了按位与&,Python逻辑判断要用and;同时优化筛选条件,判断字符区域与框是否重叠,而非仅匹配单个坐标:
def getCharsInBox(box_pt): left_x, right_x, top_y, bottom_y = box_pt def filter_func(char): char_x0 = char.get("x0", 0) char_x1 = char.get("x1", 0) char_top = char.get("top", 0) char_bottom = char.get("bottom", 0) # 判断字符区域和框是否存在重叠 overlap = not (char_x1 < left_x or char_x0 > right_x or char_bottom < top_y or char_top > bottom_y) return overlap return filter_func
3. 更高效的替代方案:纯用pdfPlumber提取
无需依赖OpenCV,直接利用pdfPlumber的文本分析能力:
- 提取所有带位置、字体信息的文本块,按行分组
- 根据字体特征、文本内容区分作者、机构、邮箱行(作者行常为粗体,邮箱行含@符号)
# 提取带字体、大小、位置的文本块 words = page.extract_words(extra_attrs=["fontname", "size"]) # 按行分组(用top值整数部分避免微小坐标误差) rows = {} for word in words: row_key = int(word["top"]) rows[row_key] = rows.get(row_key, []) + [word] # 按行从上到下排序 sorted_rows = sorted(rows.values(), key=lambda x: x[0]["top"]) # 筛选作者、机构、邮箱行 author_row = affiliation_row = email_row = None for row in sorted_rows: row_text = " ".join([w["text"] for w in row]) if "@" in row_text: email_row = row_text elif any("bold" in w["fontname"].lower() for w in row): author_row = row_text else: affiliation_row = row_text print("作者:", author_row) print("所属机构:", affiliation_row) print("邮箱:", email_row)
内容的提问来源于stack exchange,提问作者Valuex
相关产品推荐
相关产品推荐

