fitz insert_text插入OCR文本层偏移至PDF左下角字号偏小问题
故障根因
所有文本堆在左下角、字号偏小的核心原因是坐标系统一性失效,和扫描模式改成黑白高画质直接相关,具体触发逻辑:
- 扫描仪输出高DPI黑白扫描件时,会自动给PDF页面加上
/Rotate旋转标记(常见为90度/270度),调用get_pixmap渲染时PyMuPDF会自动应用旋转输出正向像素图,但insert_text接口使用的PDF原生页面坐标不会自动适配这个旋转值,从像素图上拿到的OCR坐标直接换算后,对应到原生坐标系就全部偏到页面左下角区域。 - 硬编码
CONVERT=3作为像素图到页面坐标的缩放系数,没有根据实际渲染的pixmap尺寸和页面真实尺寸动态计算缩放比。高DPI扫描件的默认渲染分辨率和普通扫描件不同,固定除以3的换算逻辑会同时导致坐标偏移、字号计算值偏小。 - 循环递增字号凑0.9倍文本宽度的逻辑鲁棒性极差,黑白扫描件下Tesseract返回的文本包围盒宽度本身比彩色扫描偏窄,叠加缩放系数偏差,最终算出来的字号会远小于正常值。
- 原代码后缀截取逻辑存在bug:
directory[:-3]会把.pdf后缀的首字符一起切掉,生成的输出文件名会出现异常。
修复方案
按以下逻辑修改代码即可覆盖黑白高DPI扫描的场景:
- 处理每页前先校正页面旋转,强制统一像素渲染坐标和文本插入坐标的基准
- 动态计算像素图到页面的实际缩放比,替换硬编码的
CONVERT=3换算逻辑 - 替换循环凑字号的低效逻辑,直接根据包围盒宽度反推准确字号
- 去掉
insert_text里多余的无效参数,用官方推荐的透明文本层渲染模式避免兼容性问题 - 修正输出文件名的后缀截取逻辑
修改后的核心代码段如下:
import fitz import cv2 import numpy as np from PIL import Image import pytesseract as pyt def ToOCR(directory): pdf = fitz.open(directory) for page in pdf: # 校正页面旋转,统一坐标系基准 page_rot = page.rotation if page_rot != 0: page.set_rotation(0) CONVERT = 3 pix = page.get_pixmap(matrix=fitz.Matrix(CONVERT, CONVERT), alpha=False) # 动态计算实际缩放比,不硬编码除以CONVERT scale_x = pix.width / page.rect.width scale_y = pix.height / page.rect.height img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) # 黑白扫描建议把高斯模糊核调整为5*5,降低噪点对OCR包围盒精度的影响 gauss = cv2.GaussianBlur(np.array(img), (5, 5), 0) data = pyt.image_to_data( gauss, output_type=pyt.Output.DICT, config='-c preserve_interword_spaces=1 --oem 1 --psm 1 -l spa', lang='spa' ) text_list = data['text'] left_list = data['left'] top_list = data['top'] w_list = data['width'] h_list = data['height'] for m in range(len(text_list)): text = text_list[m].strip() if len(text) == 0: continue box_w = w_list[m] if box_w <= 0: continue # 直接根据包围盒宽度反推字号,替换循环累加逻辑 unit_len = fitz.get_text_length(text, fontname="Times-Roman", fontsize=1) fz = (box_w / scale_x) * 0.98 / unit_len # 坐标换算用动态缩放比,文本基线对齐包围盒底部 insert_x = left_list[m] / scale_x insert_y = (top_list[m] + h_list[m]) / scale_y # render_mode=3为官方推荐的隐形文本层模式,无需额外设置透明度 page.insert_text( (insert_x, insert_y), text, fontname="Times-Roman", fontsize=fz, render_mode=3, overlay=True ) # 修正后缀截取逻辑,.pdf共4个字符 dest_dir = directory[:-4] pdf.save(f"{dest_dir}_ocr.pdf") pdf.close()
快速验证方法
如果需要定位具体触发的文件问题,可以在页面循环里加两行打印,对比正常文件和故障文件的输出值:
print(f"页面旋转值: {page.rotation}, 页面尺寸: {page.rect.width}x{page.rect.height}") print(f"渲染像素尺寸: {pix.width}x{pix.height}, 实际缩放比: {scale_x}x{scale_y}")
故障文件必然会出现旋转值非0、或者缩放比和3偏差较大的情况。
内容的提问来源于stack exchange,提问作者José Chamorro
相关产品推荐
相关产品推荐

