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

如何使用OpenCV实现多列扫描文档的通用文本块分割以优化Tesseract OCR效果

如何使用OpenCV实现多列扫描文档的通用文本块分割以优化Tesseract OCR效果

我完全懂你这种批量处理大量多列扫描件的痛苦——试遍了Tesseract所有PSM和OEM参数,还是会把不同列的文本强行合并成一行,手动处理几万页根本不现实。你现有的OpenCV分割脚本靠边缘检测+膨胀找轮廓,对简单2列文档有效,但遇到3列、4列或者布局复杂的情况,很容易把整个页面当成一个大轮廓,问题出在这种方法太依赖边缘的连通性,而文档列之间的空白间隙有时候不够明显。

给你一套更通用的基于投影分析的分割思路,这是处理文档列分割的经典方法,能适配不同列数的扫描件:

核心思路:利用文本的水平/垂直投影定位列边界

文档的文本块在水平和垂直方向上有明显的像素密度差异——文本行的像素总和远高于空白行,列之间的空白列像素总和远低于文本列。我们可以利用这个特性来精准定位分割点。

步骤1:图像预处理(关键!)

先把图像处理成适合分析的状态,解决扫描件的明暗不均、噪点问题:

import cv2
import numpy as np

def preprocess_image(img):
    # 灰度化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 自适应二值化(应对明暗不均的扫描件)
    thresh = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2
    )
    # 去噪+轻微膨胀,让文本行更连贯
    kernel = np.ones((2,2), np.uint8)
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)
    return thresh

步骤2:水平投影定位文本区域

先找到页面上的有效文本区域,排除页眉、页脚或者大面积空白:

def get_text_region(thresh_img):
    # 计算水平投影:每一行的白色像素总和
    horizontal_proj = np.sum(thresh_img, axis=1)
    # 找到有文本的行的上下边界
    text_rows = np.where(horizontal_proj > 0)[0]
    if len(text_rows) == 0:
        return thresh_img, 0
    top = text_rows[0]
    bottom = text_rows[-1]
    # 裁剪出文本区域
    return thresh_img[top:bottom, :], top

步骤3:垂直投影找列分割点

在文本区域内,计算每一列的白色像素总和,找到列之间的空白间隙作为分割点:

def find_column_dividers(text_region, min_gap_width=10):
    # 计算垂直投影:每一列的白色像素总和
    vertical_proj = np.sum(text_region, axis=0)
    # 找到空白列(像素总和为0或接近0的列)
    gap_columns = np.where(vertical_proj < 50)[0]  # 阈值可根据图像调整
    # 把连续的空白列合并成间隙
    dividers = []
    current_gap_start = None
    for idx, val in enumerate(gap_columns):
        if idx == 0 or gap_columns[idx] != gap_columns[idx-1] + 1:
            current_gap_start = val
        # 判断间隙宽度是否达标
        if idx == len(gap_columns)-1 or gap_columns[idx+1] != gap_columns[idx] + 1:
            gap_width = gap_columns[idx] - current_gap_start + 1
            if gap_width >= min_gap_width:
                # 取间隙的中间位置作为分割点
                divider = (current_gap_start + gap_columns[idx]) // 2
                dividers.append(divider)
    # 加上左右边界
    dividers = [0] + dividers + [text_region.shape[1]]
    return dividers

步骤4:分割列并保存

根据找到的分割点,把原图切成单独的列:

def segment_columns(img, dividers, top_offset):
    segmented_columns = []
    img_height = img.shape[0]
    for i in range(len(dividers)-1):
        left = dividers[i]
        right = dividers[i+1]
        # 从原图裁剪对应列的区域(加上之前的top_offset)
        column_img = img[top_offset:min(top_offset+img_height, img.shape[0]), left:right]
        # 过滤掉过窄的无效列
        if column_img.shape[1] > 20:
            segmented_columns.append(column_img)
    return segmented_columns

完整的批量处理脚本

把这些函数整合起来,处理批量文件:

import os

def process_single_file(file_path, output_dir="segmented_columns"):
    os.makedirs(output_dir, exist_ok=True)
    img = cv2.imread(file_path)
    if img is None:
        print(f"无法读取文件:{file_path}")
        return
    
    # 预处理
    thresh = preprocess_image(img)
    # 获取文本区域
    text_region, top = get_text_region(thresh)
    # 找列分割点
    dividers = find_column_dividers(text_region)
    # 分割列
    columns = segment_columns(img, dividers, top)
    
    # 保存每一列
    base_name = os.path.splitext(os.path.basename(file_path))[0]
    for idx, col in enumerate(columns):
        save_path = os.path.join(output_dir, f"{base_name}_col_{idx+1}.png")
        cv2.imwrite(save_path, col)
    print(f"已完成{file_path}的分割,共生成{len(columns)}列")

# 批量处理命令行传入的文件
if __name__ == "__main__":
    import sys
    if len(sys.argv) < 2:
        print("请传入至少一个文件路径作为参数")
        sys.exit(1)
    for file_path in sys.argv[1:]:
        if os.path.exists(file_path):
            process_single_file(file_path)
        else:
            print(f"文件不存在:{file_path}")

额外优化建议

  • 处理倾斜文档:如果扫描件有倾斜,在预处理前先做纠偏,可以用OpenCV的霍夫变换检测倾斜角度,确保投影分析的准确性。
  • 调整阈值:不同扫描件的清晰度、字体大小不同,需要根据实际情况调整二值化的参数、垂直投影的空白阈值、最小间隙宽度等,可以先找几个典型样本调试。
  • 结合Tesseract的布局分析:分割后的单列图像可以使用Tesseract的PSM=6(假设一个统一的文本块),这样OCR准确率会更高。
  • 批量OCR:分割完成后,可以写个脚本批量处理所有列图像,用Tesseract或者OCRmyPDF生成可搜索的PDF,避免重复劳动。

你之前试过遍历所有PSM和OEM参数的方法,其实Tesseract的布局分析在面对复杂多列文档时确实容易失效,先通过OpenCV做物理分割,把多列拆成单列再OCR,是更可靠的路径,尤其适合几万页的批量任务。

备注:内容来源于stack exchange,提问作者drg22114

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 11:23:16