Python + OpenCV 实现红色印章清除:基于红色通道和 OTSU 阈值
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 修复或图像生成类方法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)