1. 问题

把一本 100 页的报告转成 Markdown 时,页眉和页脚(公司 Logo、日期、页码、栏目名等)会在每一页重复。
如果不去掉它们,最后的文字会变成这样:

第 1 页:正文
页码:1 / 100
Logo

第 2 页:正文
页码:2 / 100
Logo

需要一种办法,让电脑自动找到“哪一条线以上是页眉”“哪一条线以下是页脚”,然后把它们剪掉。


2. 代码

_, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
kernel_width = max(5, w // 6)
detected_lines = cv2.morphologyEx(
    binary,
    cv2.MORPH_OPEN,
    cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_width, 1)),
    iterations=1,
)
  • _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
    1. gray 是灰度图;
    2. threshold 把每个像素分成“黑或白”;
    3. 200 是阈值(亮度高于 200 的变白,低于 200 的变黑);
    4. THRESH_BINARY_INV 表示“结果取反”,所以线条会变白、背景变黑;
    5. _ 表示我们不关心返回的第一个值,只要第二个 binary
  • kernel_width = max(5, w // 6)
    1. w 是页面宽度的像素数;
    2. w // 6 大约等于“把页面横向分成 6 份后的宽度”;
    3. max(5, ...) 确保尺子至少 5 像素,避免页面很窄时无从下手。
  • detected_lines = cv2.morphologyEx(
    1. cv2.morphologyEx 是 OpenCV 提供的“形态学运算”函数;
    2. 这里用它来执行“开运算”;
    3. 结果会存到 detected_lines
      • binary,:输入图像就是刚才得到的黑白图。
      • cv2.MORPH_OPEN,:指定操作模式为开运算(先腐蚀再膨胀)。
      • cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_width, 1)),
        • cv2.MORPH_RECT 表示矩形结构元素;
        • (kernel_width, 1) 表示“宽 kernel_width、高 1 像素”的横向尺子;
        • 这个尺子越长,对长线越友好,对短线越苛刻。
      • iterations=1,:只执行一次,保证既能过滤噪声又不会把主要线条削掉。
  • ):调用结束。此时 detected_lines 图像中就只剩下那些通过尺子考验的长横线。

3. 功能

想象把 PDF 页面打印出来,然后拿一把很长的尺子横着贴在纸上:

  1. 第一步:腐蚀(Erosion)——像用橡皮擦沿着尺子擦一遍
    • 所有比尺子短的线段都会被彻底擦掉;
    • 只有足够长、足够直的线还能撑住。
  2. 第二步:膨胀(Dilation)——再沿着尺子重描一次
    • 对刚才幸存下来的长线进行“加粗”,恢复它们原来的厚度;
    • 短线已经被擦没了,所以不会再回来。

为了更具体地理解,想象尺子在图上“逐像素滑动”:

  • 腐蚀阶段:尺子覆盖到某个位置时,只有在尺子下方的每一个像素点都为白色(表示线条)时,这个位置才会保留白色;只要尺子范围内出现一个黑点,就把整个位置判为黑色。也就是说,尺子要求“整段连续白像素”才能通过。
  • 膨胀阶段:在上一阶段保留下来的白色像素区域,尺子再次滑过时,只要尺子下方有一个像素是白色,就把整个尺子区域重新涂成白色,相当于把那条线补回原来的厚度。

这两步连在一起就是“开运算”:先用非常苛刻的条件确认“这里确实存在连续的白像素(长横线)”,再把它恢复成原来的粗细。
最终只剩下页眉、页脚那种“整块横向区域”,我们就能根据它们的高度去裁掉重复内容。

4. Debug

  1. 页眉/页脚本来就是纯文字,没有横线
    • 画面里压根没有“连续白像素”,尺子每次滑过都会遇到黑点 → 全判黑。
    • 解决:把 kernel_width 调短(例如从页宽 1/6 改成 1/10 或 1/12),让文字行也能被视作“长线”。
  2. 扫描模糊或纸张泛黄
    • 二值化时,灰度值落在阈值附近,导致大部分像素都被判成黑色。
    • 解决:把阈值从 200 调到 170~190,或使用自适应阈值,让亮度不足的线条仍能变成白色。
  3. 页面内容太复杂(表格/多栏)
    • 尺子滑过去时经常遇到内容断点,导致整个区域判黑。
    • 解决:先在样章上手动观察,记录能够稳定识别的 kernel_width 和阈值,再批量应用;必要时对页眉、正文、页脚分别设置不同的裁剪策略。

5. 小结

  • 开运算 = 找到长直线,借此推断页眉/页脚位置。
  • 目的 = 自动裁掉重复的表格化区域,只保留正文。
  • 记住:“尺子越长,过滤越严”,根据不同 PDF 自由调节即可。

把它想象成“先把 PDF 变黑白,再用大尺子扫一遍”:只要理解这个画面,就能明白开运算对清洗 PDF 有多重要。