检查PDF中是不是有表格,如果存在表格需要做特殊处理

import cv2
import numpy as np
from pdf2image import convert_from_path
import os
import sys


def detect_table_in_image_pdf(pdf_path):
    """检测图像型PDF中的表格 - 修复版本"""

    # 检查文件是否存在
    if not os.path.exists(pdf_path):
        print(f"文件不存在: {pdf_path}")
        return []

    def visual_table_detection(img):
        """纯视觉检测表格"""
        try:
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

            # 二值化
            _, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)

            # 检测水平线
            horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1))
            horizontal_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel)

            # 检测垂直线
            vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 40))
            vertical_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, vertical_kernel)

            # 组合线条
            table_mask = cv2.addWeighted(horizontal_lines, 0.5, vertical_lines, 0.5, 0.0)

            # 查找表格区域
            contours, _ = cv2.findContours(table_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

            table_regions = []
            for contour in contours:
                area = cv2.contourArea(contour)
                if area > 5000:  # 足够大的区域
                    x, y, w, h = cv2.boundingRect(contour)
                    if w > 100 and h > 100:  # 合理的尺寸
                        table_regions.append((x, y, w, h))

            return len(table_regions) > 0, table_regions

        except Exception as e:
            print(f"视觉检测失败: {e}")
            return False, []

    def check_poppler():
        """检查poppler是否安装"""
        try:
            # 尝试转换第一页来测试
            convert_from_path(pdf_path, first_page=1, last_page=1, dpi=72)
            return True
        except Exception as e:
            print(f"Poppler检查失败: {e}")
            return False

    # 检查poppler安装
    if not check_poppler():
        print("错误: poppler未正确安装或不在PATH中")
        print("请运行以下命令安装:")
        print("macOS: brew install poppler")
        print("或者: conda install -c conda-forge poppler")
        return []

    # 转换PDF为图像
    try:
        print("正在转换PDF为图像...")
        pages = convert_from_path(pdf_path, dpi=200)
        print(f"成功转换 {len(pages)} 页")
    except Exception as e:
        print(f"PDF转图像失败: {e}")
        return []

    results = []

    for page_num, page in enumerate(pages):
        try:
            print(f"正在检测第 {page_num + 1} 页...")

            # 转换为OpenCV格式
            img = cv2.cvtColor(np.array(page), cv2.COLOR_RGB2BGR)

            # 视觉检测
            has_visual_table, regions = visual_table_detection(img)

            result = {
                'page': page_num + 1,
                'visual_detection': has_visual_table,
                'table_regions': len(regions) if has_visual_table else 0,
                'regions_coords': regions if has_visual_table else []
            }

            results.append(result)

            if has_visual_table:
                print(f"页面 {page_num + 1}: 检测到 {len(regions)} 个表格区域")
            else:
                print(f"页面 {page_num + 1}: 未检测到表格")

        except Exception as e:
            print(f"处理第 {page_num + 1} 页时出错: {e}")
            # 即使出错也要添加结果,保持列表完整
            results.append({
                'page': page_num + 1,
                'visual_detection': False,
                'table_regions': 0,
                'regions_coords': [],
                'error': str(e)
            })

    return results


def simple_table_detection(pdf_path):
    """简化版本的表格检测"""
    try:
        # 只转换第一页进行测试
        pages = convert_from_path(pdf_path, first_page=1, last_page=1, dpi=150)

        if not pages:
            return False

        # 转换为OpenCV格式
        img = cv2.cvtColor(np.array(pages[0]), cv2.COLOR_RGB2BGR)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        # 简单的线条检测
        edges = cv2.Canny(gray, 50, 150, apertureSize=3)

        # 检测水平线
        horizontal_lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100,
                                           minLineLength=100, maxLineGap=10)

        # 检测垂直线
        vertical_lines = cv2.HoughLinesP(edges, 1, np.pi / 2, threshold=100,
                                         minLineLength=100, maxLineGap=10)

        h_count = len(horizontal_lines) if horizontal_lines is not None else 0
        v_count = len(vertical_lines) if vertical_lines is not None else 0

        print(f"检测到水平线: {h_count}, 垂直线: {v_count}")

        # 如果同时有水平线和垂直线,可能存在表格
        return h_count > 5 and v_count > 3

    except Exception as e:
        print(f"简化检测失败: {e}")
        return False


# 主程序
if __name__ == "__main__":
    pdf_file = "wanzheng-15-17.pdf"  # 替换为你的PDF文件路径

    print("开始检测图像型PDF中的表格...")

    # 方法1: 完整检测
    try:
        results = detect_table_in_image_pdf(pdf_file)

        if results:  # 确保results不为空
            total_tables = sum(1 for r in results if r.get('visual_detection', False))
            print(f"\n=== 检测结果 ===")
            print(f"总共检测到 {total_tables} 页包含表格")

            for result in results:
                page_num = result['page']
                has_table = result.get('visual_detection', False)
                region_count = result.get('table_regions', 0)

                if has_table:
                    print(f"页面 {page_num}: ✅ 包含表格 ({region_count} 个区域)")
                else:
                    print(f"页面 {page_num}: ❌ 未发现表格")
        else:
            print("检测失败,尝试简化方法...")

            # 方法2: 简化检测
            has_table = simple_table_detection(pdf_file)
            if has_table:
                print("简化检测: ✅ 可能包含表格")
            else:
                print("简化检测: ❌ 未发现表格")

    except Exception as e:
        print(f"程序执行出错: {e}")
        import traceback

        traceback.print_exc()

下面把这段脚本按“做什么—怎么做—为什么这样做—可能踩坑与改进”的顺序解释清楚。你可以直接把它当成“图像型 PDF 表格探测”的入门范例来看。

这段代码总体在做什么
• 把 PDF 页转换为位图(用 pdf2image + Poppler)。
• 对每页位图做 纯视觉表格检测(OpenCV):
• 二值化 → 形态学提取 水平线 与 垂直线 → 合并 → 找外接轮廓区域,判定是否存在表格。
• 返回每一页是否检测到表格,以及表格的候选矩形区域坐标。
• 如果完整流程失败或无结果,退而求其次用一个“简化版”的方法:Canny 边缘 + Hough 直线统计。

逐段拆解

1)入口函数:detect_table_in_image_pdf(pdf_path)

职责:整本 PDF 分页检测,每页给出结果字典。

关键步骤:
1. 文件存在性检查

if not os.path.exists(pdf_path): …

2.	内嵌函数 visual_table_detection(img):纯视觉检测
•	转灰度:

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

•	全局阈值二值化(反白):

_, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV)

反白的目的是把“线条/文字”变成白色(255),背景变黑(0),利于后续形态学。

•	形态学“开运算”提线:

horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1))
horizontal_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel)

vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 40))
vertical_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, vertical_kernel)

(40,1)/(1,40) 让横/竖的长细结构被保留,杂点被去掉。

•	合并横竖线得到“表格掩膜”:

table_mask = cv2.addWeighted(horizontal_lines, 0.5, vertical_lines, 0.5, 0.0)

这一步在语义上等价于“横 OR 竖”,常见替代是 cv2.bitwise_or。

•	找外部轮廓并筛选:

contours, _ = cv2.findContours(table_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contour in contours:
area = cv2.contourArea(contour)
if area > 5000:
x, y, w, h = cv2.boundingRect(contour)
if w > 100 and h > 100:
table_regions.append((x, y, w, h))

通过面积与宽高阈值过滤掉噪声小块,保留“像表格”的大块网格结构。
返回 (has_table, table_regions)。

3.	内嵌函数 check_poppler():Poppler 可用性检查
•	试图仅转换第 1 页(72 DPI):

convert_from_path(pdf_path, first_page=1, last_page=1, dpi=72)

能成功则认为 Poppler 环境 OK,否则提示安装(mac brew install poppler 或 conda)。

4.	将 PDF 全部页转成图像

pages = convert_from_path(pdf_path, dpi=200)

•	200 DPI:质量和速度的折中。

5.	逐页检测
•	page(PIL.Image)→ np.array(page) → 转 BGR(给 OpenCV):

img = cv2.cvtColor(np.array(page), cv2.COLOR_RGB2BGR)

•	调用 visual_table_detection(img) 得到是否有表格与候选区域:

has_visual_table, regions = visual_table_detection(img)

•	组装结果字典追加到 results。出现异常也会给该页写入一个“失败结果”,保证输出页数与输入一致(便于上层流水线不出错)。

6.	返回每页结果的列表

2)退路函数:simple_table_detection(pdf_path)

职责:只看第一页,用更粗糙的方式判断“可能有表格”。

流程:
• 150 DPI 转第一页 → 灰度 → Canny 边缘。
• 两次 HoughLinesP:
• “水平线”:

horizontal_lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100,
minLineLength=100, maxLineGap=10)

•	“垂直线”(代码里把 theta 设置为 np.pi/2):

vertical_lines = cv2.HoughLinesP(edges, 1, np.pi/2, threshold=100,
minLineLength=100, maxLineGap=10)

•	分别统计数量,经验阈值 h_count > 5 and v_count > 3 则认为“可能有表格”。

注意:HoughLinesP 的第二个角度参数 不是“只检测某个方向的线”,它是角度分辨率(推荐都用 np.pi/180),真正区分水平/垂直应在拿到线段后按斜率/角度筛选。这个我在“改进建议”里给出修正写法。

3)main 主程序
• 指定 pdf_file → 先跑“完整检测”。
• 如果 results 为空(失败),再跑“简化检测”作为兜底。

这些参数为什么这样选
• DPI=200:表格线在 200 DPI 下宽度与长度都足够明显,计算量适中。
• 阈值二值化 + 反白:在扫描件里,线一般更深,反白后线为白色,形态学“开运算”更容易抓到线性结构。
• 形态学核大小 (40):经验值,适合 A4 在 150–300 DPI 的常见表格;核越长,越偏向“细长结构”,越能抑制噪点。
• 轮廓面积 5000 / 宽高>100:避免把页眉短线、页码边框等小块误识别为表格。

常见坑与如何改得更稳

1)合并横竖线后最好“再二值化”

当前是:

table_mask = cv2.addWeighted(horizontal_lines, 0.5, vertical_lines, 0.5, 0.0)

这会得到 灰阶 结果(0~255 的多个层级),findContours 通常能用,但更稳妥是 OR 并阈值一下:

table_mask = cv2.bitwise_or(horizontal_lines, vertical_lines)
_, table_mask = cv2.threshold(table_mask, 0, 255, cv2.THRESH_BINARY)

2)阈值选择:固定 128 容易翻车

光照、底色、扫描强度不同会导致失败。两种替代:

Otsu 自动阈值(全局)

_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

或自适应

binary = cv2.adaptiveThreshold(gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 31, 5)

3)纵横核大小自适应图像宽高

固定 40 在超高/超低 DPI 时不合适。可按宽高的比例设:

h_len = max(20, img.shape[1] // 50) # 宽度的 2%
v_len = max(20, img.shape[0] // 50) # 高度的 2%
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (h_len, 1))
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, v_len))

4)RETR_EXTERNAL 可能漏掉嵌套表/子表

如果你想保留所有层级,用:

contours, hierarchy = cv2.findContours(table_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

然后按层级/面积再筛。

5)简化版里的 HoughLinesP 用法不对

theta=np.pi/2 会把角分辨率设成 90°,几乎检测不到线。应该统一用 np.pi/180,再按角度分类:

lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100,
minLineLength=100, maxLineGap=10)
h_count = v_count = 0
if lines is not None:
for (x1,y1,x2,y2) in lines[:,0,:]:
ang = abs(np.degrees(np.arctan2(y2-y1, x2-x1)))
if ang < 10 or ang > 170: # 近水平
h_count += 1
elif 80 < ang < 100: # 近垂直
v_count += 1

6)Poppler 环境在 Windows 上的特殊性

Windows 不走 brew/conda,通常要:
• 安装 Poppler(e.g. 下载 poppler-xx\bin),
• 把 bin 加进 PATH,或在 convert_from_path(…, poppler_path=“C:\path\to\poppler\bin”) 显式指定。

7)速度与内存
• 大 PDF:可以先 缩放 到较低分辨率(比如 150 DPI)“探测”,检测到表格再对该页用 300 DPI 精抽。
• 多进程:分页独立,易于 multiprocessing 并行。

你可以立刻抄用的小修正片段

(A)更稳的视觉检测(替换 visual_table_detection 的中间几行)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

更鲁棒的阈值方法(二选一)

_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 31, 5)

自适应核

h_len = max(20, img.shape[1] // 50)
v_len = max(20, img.shape[0] // 50)
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (h_len, 1))
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, v_len))

horizontal_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
vertical_lines = cv2.morphologyEx(binary, cv2.MORPH_OPEN, vertical_kernel, iterations=1)

逻辑合并并二值化

table_mask = cv2.bitwise_or(horizontal_lines, vertical_lines)

可选:膨胀一点把网格连成块,便于框出整张表

table_mask = cv2.dilate(table_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)), iterations=1)

(B)修正简化版 Hough 方向统计

lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100,
minLineLength=100, maxLineGap=10)
h_count = v_count = 0
if lines is not None:
for x1, y1, x2, y2 in lines[:,0,:]:
ang = abs(np.degrees(np.arctan2(y2 - y1, x2 - x1)))
if ang < 10 or ang > 170:
h_count += 1
elif 80 < ang < 100:
v_count += 1

返回结果怎么用

detect_table_in_image_pdf 的单页结果形如:

{
‘page’: 3,
‘visual_detection’: True,
‘table_regions’: 2,
‘regions_coords’: [(x,y,w,h), (x,y,w,h)]
}

•	你可以把 regions_coords 用 cv2.rectangle 画出来做可视化,
•	或在这些区域内做更精细的 OCR/结构化(如表格结构还原)。

小结
• 这段代码采用“形态学提线 → 轮廓筛选”的经典套路,简单、可解释、易调参。
• 两个最关键的点:二值化策略 和 核大小/阈值的自适配。把这两个做好,鲁棒性会明显提升。
• “简化版”里的 Hough 使用要按角度再分类,不要把 theta 改成 np.pi/2。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MonkeyKing.sun

对你有帮助的话,可以打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值