Python + OpenCV 实现红色印章清除:基于红色通道和 OTSU 阈值

  • 在扫描件、表单、证书等图片中,红色印章经常会覆盖正文内容,影响后续文字识别或信息读取。这里介绍一种轻量级的印章清除方法:不依赖深度学习模型,只使用 OpenCV 对红色通道进行阈值处理,将红色印章尽量从图像中剔除。

这个方法的核心思路很简单:

  • 红色印章在 RGB/BGR 图像中通常会让红色通道明显偏高。
  • 先提取红色通道,再用 OTSU 自动阈值找出合适的二值化分界点。
  • 根据阈值动态调整过滤条件,把红色印章区域压到白底。
  • 最终输出一张黑白二值图,更适合作为文字识别或版面分析的前处理图像。

需要注意:这个方法不是图像修复算法,它的目标不是生成一张视觉上完全自然的“无章原图”,而是尽可能减少红章对黑色文字的干扰。

效果示例

下面是一个演示效果,左侧是带红色印章的图片,右侧是经过 clear_stamp 处理后的结果。
印章清除效果对比图

可以看到,红色印章基本被去掉,黑色文字和表格线仍然保留下来。但输出结果变成了黑白二值图,局部线条也可能出现断裂,这是阈值法的正常副作用。

完整代码

下面是一个可以单独保存为 clearstamp.py 的版本:

# coding: utf-8
import cv2
import numpy as np


def clear_stamp(target_path):
    """
    清除图片中的红色印章。

    Args:
        target_path: 图片路径,或者 OpenCV 读取后的 numpy.ndarray 图像。

    Returns:
        tuple:
            result_img: 清理后的图片;失败或无需清理时返回原输入。
            clean_type: "clean" 表示执行了清理,"no_clean" 表示未清理。
    """
    try:
        # 判断输入是文件路径还是 numpy 数组。
        if isinstance(target_path, str):
            target_img = cv2.imdecode(
                np.fromfile(target_path, dtype=np.uint8),
                cv2.IMREAD_COLOR,
            )
        elif isinstance(target_path, np.ndarray):
            target_img = target_path
        else:
            return target_path, "no_clean"

        if target_img is None:
            return target_path, "no_clean"

        # 获取原始图片尺寸。
        org_h, org_w, channel = target_img.shape

        # 放大图片,增强细节,降低小字和细线在阈值处理时丢失的概率。
        target_img = cv2.resize(target_img, (org_w * 2, org_h * 2))

        # OpenCV 默认通道顺序是 BGR。
        blue_c, green_c, red_c = cv2.split(target_img)

        # 如果三个通道完全一致,说明图片本身就是灰度或黑白图,没有红色印章可清理。
        if np.array_equal(blue_c, green_c) and np.array_equal(blue_c, red_c):
            return target_path, "no_clean"

        # 对红色通道使用 OTSU 自动阈值。
        thresh, _ = cv2.threshold(red_c, 0, 255, cv2.THRESH_OTSU)

        # 根据 OTSU 阈值动态调整过滤条件。
        if int(thresh) <= 170:
            filter_condition = 220
        elif 190 >= int(thresh) > 170:
            filter_condition = 210
        elif 200 >= int(thresh) > 190:
            filter_condition = 200
        elif 210 >= int(thresh) > 200:
            filter_condition = int(thresh * 1.0)
        else:
            filter_condition = int(thresh * 1.1)

        # 基于红色通道进行二值化。
        _, red_thresh = cv2.threshold(
            red_c,
            filter_condition,
            255,
            cv2.THRESH_BINARY,
        )

        # 将单通道二值图转换回三通道,方便后续保存或继续处理。
        result_img = np.expand_dims(red_thresh, axis=2)
        result_img = np.concatenate((result_img, result_img, result_img), axis=-1)

        # 缩放回原始尺寸。
        resized_img = cv2.resize(result_img, (org_w, org_h))

        return resized_img, "clean"

    except Exception as e:
        print(f"clear stamp failed: {e}")
        return target_path, "no_clean"


if __name__ == "__main__":
    input_path = "input.png"
    output_path = "output.png"

    result_img, clean_type = clear_stamp(input_path)
    print(f"clean_type: {clean_type}")

    if clean_type == "clean":
        cv2.imencode(".png", result_img)[1].tofile(output_path)

代码原理拆解

1. 同时支持图片路径和 numpy 数组

函数入口既可以传图片路径,也可以传 OpenCV 已经读取好的图片对象:

if isinstance(target_path, str):
    target_img = cv2.imdecode(
        np.fromfile(target_path, dtype=np.uint8),
        cv2.IMREAD_COLOR,
    )
elif isinstance(target_path, np.ndarray):
    target_img = target_path
else:
    return target_path, "no_clean"

这里没有使用 cv2.imread(),而是使用:

cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)

这种写法对包含中文路径的文件更友好。

2. 放大图片再处理

org_h, org_w, channel = target_img.shape
target_img = cv2.resize(target_img, (org_w * 2, org_h * 2))

放大图片的目的是保留更多细节。印章通常由很多细线构成,如果直接在低分辨率图片上做阈值处理,容易把文字、表格线和印章边缘混在一起。放大后再处理,可以让阈值分割更稳定一些。

处理完成后,代码会再把图片缩回原始尺寸:

resized_img = cv2.resize(result_img, (org_w, org_h))

3. 判断是否为黑白图

blue_c, green_c, red_c = cv2.split(target_img)

if np.array_equal(blue_c, green_c) and np.array_equal(blue_c, red_c):
    return target_path, "no_clean"

如果 B、G、R 三个通道完全一致,说明这张图本身就是灰度图或黑白图。既然没有颜色信息,就没有必要再做红章清除,直接返回 no_clean

4. 只处理红色通道

红色印章最明显的特征是红色通道数值高,因此代码只取红色通道:

blue_c, green_c, red_c = cv2.split(target_img)

后续所有阈值计算都基于 red_c

这种方式简单直接,速度也很快。但它也有一个缺点:如果图片里有红色文字、红色 logo 或红色批注,也可能被一起处理掉。

5. 使用 OTSU 自动阈值

thresh, _ = cv2.threshold(red_c, 0, 255, cv2.THRESH_OTSU)

OTSU 会根据图像灰度分布自动计算一个阈值。相比手写固定阈值,它对不同亮度、不同扫描质量的图片更有适应性。

不过 OTSU 得到的阈值并不一定可以直接使用,所以代码又做了一层动态修正。

6. 动态调整过滤阈值

if int(thresh) <= 170:
    filter_condition = 220
elif 190 >= int(thresh) > 170:
    filter_condition = 210
elif 200 >= int(thresh) > 190:
    filter_condition = 200
elif 210 >= int(thresh) > 200:
    filter_condition = int(thresh * 1.0)
else:
    filter_condition = int(thresh * 1.1)

这段逻辑的作用是根据 OTSU 阈值落在哪个区间,选择更适合的二值化阈值。

可以理解为:

  • 如果 OTSU 阈值偏低,说明图片整体较暗或红色干扰不突出,此时提高过滤阈值,避免误伤黑色文字。
  • 如果 OTSU 阈值处于中间区间,则使用较稳定的经验值。
  • 如果 OTSU 阈值已经很高,则在它的基础上略微放大。

这不是严格的数学公式,而是一组经验规则。实际使用时,可以根据自己的图片样本继续调整这些区间和阈值。

7. 二值化并还原为三通道

_, red_thresh = cv2.threshold(
    red_c,
    filter_condition,
    255,
    cv2.THRESH_BINARY,
)

这一步会把红色通道转换成黑白二值图。

然后将单通道结果复制成三通道:

result_img = np.expand_dims(red_thresh, axis=2)
result_img = np.concatenate((result_img, result_img, result_img), axis=-1)

这样输出图片仍然是常见的三通道格式,后续保存、显示或继续用 OpenCV 处理都更方便。

方法优点

  • 实现简单,只依赖 OpenCV 和 NumPy。
  • 速度快,适合做批量图片前处理。
  • 对红色印章覆盖黑色文字的场景比较直接有效。
  • 不需要训练模型,也不需要额外标注数据。
  • 代码容易调试,阈值策略可以根据实际图片微调。

局限性

  • 输出是黑白二值图,不保留原始彩色版式。
  • 如果红章颜色偏暗、偏棕、偏紫,清理效果可能下降。
  • 如果图片里有红色文字、红色 logo、红色标注,也可能被一起清除。
  • 如果印章压住了黑色文字,算法只能减少红色干扰,不能真正恢复被遮挡的笔画。
  • 对复杂背景、彩色底纹或低质量扫描件,可能产生噪声和断线。

适合的使用场景

这种方法适合用作前处理,例如:

  • 扫描件清理。
  • 表单图片预处理。
  • 证书、回执、审批单等带红章图片的二值化。
  • 文字识别前降低红章干扰。

如果目标是得到一张肉眼看起来非常自然的无章图片,更推荐使用 mask + 图像修复算法,例如 cv2.inpaint(),或者使用专门的图像修复模型。

可优化方向

如果要进一步提升效果,可以考虑下面几种优化:

  • 改用 HSV 色彩空间,通过 H 通道定位红色区域。
  • 增加红色像素占比判断,避免没有红章的彩色图也被二值化。
  • 对检测到的红色区域做形态学开闭运算,减少噪声。
  • 使用红色 mask 配合 cv2.inpaint(),尽量保留原始版式。
  • 针对不同扫描仪、不同印章颜色维护多套阈值策略。

总结

这个 clear_stamp 方法本质上是一个基于红色通道的阈值分割方案。它不复杂,但在“红色印章干扰黑色文字”的场景下很实用。

如果你的目标是后续文字识别或结构化信息读取,这种黑白二值化结果通常已经够用。如果你的目标是高质量图片修复,则需要在此基础上继续引入 mask 修复或图像生成类方法。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐