You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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扫描的场景:

  1. 处理每页前先校正页面旋转,强制统一像素渲染坐标和文本插入坐标的基准
  2. 动态计算像素图到页面的实际缩放比,替换硬编码的CONVERT=3换算逻辑
  3. 替换循环凑字号的低效逻辑,直接根据包围盒宽度反推准确字号
  4. 去掉insert_text里多余的无效参数,用官方推荐的透明文本层渲染模式避免兼容性问题
  5. 修正输出文件名的后缀截取逻辑

修改后的核心代码段如下:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:00:57