这是一个Python脚本,用于自动或半自动去除图片水印,支持白色、颜色、Top-hat等多种检测模式。
```python
-*- coding: utf-8 -*-
"""
watermark_remover.py ------ 自动 / 半自动去除图片水印
依赖: pip install opencv-python numpy
用法示例:
1) 白色水印,限定在右下角区域自动检测
python watermark_remover.py -i in.jpg -o out.jpg --mode white --thresh 200 \
--roi 0.55 0.75 1.0 1.0
2) 用自己画好的 mask(白色=水印区域)
python watermark_remover.py -i in.jpg -o out.jpg --mask mask.png
3) 彩色水印,指定 HSV 范围
python watermark_remover.py -i in.jpg -o out.jpg --mode color \
--hsv 0 0 200 180 40 255
4) 浅色细线半透明水印
python watermark_remover.py -i in.jpg -o out.jpg --mode tophat --thresh 25
"""
import argparse
import cv2
import numpy as np
---------------------------------------------------------------------------
1. 生成 mask 的几种方式
---------------------------------------------------------------------------
def parse_roi(img, roi):
"""roi = x1, y1, x2, y2(相对坐标 0~1) -> 像素坐标 (x1, y1, x2, y2)"""
h, w = img.shape:2
x1, y1, x2, y2 = roi
return int(x1 * w), int(y1 * h), int(x2 * w), int(y2 * h)
def limit_to_roi(mask, roi_px):
"""把 mask 限制在 ROI 之内,避免误伤画面其他区域"""
if roi_px is None:
return mask
x1, y1, x2, y2 = roi_px
out = np.zeros_like(mask)
outy1:y2, x1:x2 = masky1:y2, x1:x2
return out
def mask_by_white(img, thresh=200, roi_px=None):
"""亮度过阈值 ------ 适合白色 / 半透明白色水印"""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, m = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY)
return limit_to_roi(m, roi_px)
def mask_by_color(img, lower, upper, roi_px=None):
"""HSV 颜色范围 ------ 适合固定颜色的水印"""
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
m = cv2.inRange(hsv, np.array(lower, np.uint8), np.array(upper, np.uint8))
return limit_to_roi(m, roi_px)
def mask_by_tophat(img, ksize=15, thresh=25, roi_px=None):
"""
形态学 Top-hat:提取"比周围更亮的细小结构",
对半透明的浅色线条 / 文字水印比较有效。
"""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ksize, ksize))
top = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, k)
_, m = cv2.threshold(top, thresh, 255, cv2.THRESH_BINARY)
return limit_to_roi(m, roi_px)
def filter_components(mask, min_area=6, max_area_ratio=0.30):
"""按连通域面积过滤:去掉噪点,以及大到不可能是水印的亮块"""
num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, 8)
h, w = mask.shape
out = np.zeros_like(mask)
for i in range(1, num):
area = statsi, cv2.CC_STAT_AREA
if area < min_area or area > max_area_ratio * h * w:
continue
outlabels == i = 255
return out
---------------------------------------------------------------------------
2. 核心:用 mask 修复图像
---------------------------------------------------------------------------
def remove_watermark(img, mask,
radius=3, method="telea",
dilate=3, use_cc_filter=False):
"""
img : BGR 原图
mask : 单通道,非 0 = 需要修复的区域
radius : inpaint 邻域半径,水印越粗越大
method : "telea"(快) 或 "ns"(平滑)
dilate : 对 mask 膨胀的核大小,保证把水印边缘也覆盖到
"""
if mask.ndim == 3:
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)
if use_cc_filter:
mask = filter_components(mask)
if dilate and dilate > 0:
k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (dilate, dilate))
mask = cv2.dilate(mask, k, iterations=1)
flag = cv2.INPAINT_TELEA if method.lower() == "telea" else cv2.INPAINT_NS
return cv2.inpaint(img, mask, radius, flag)
---------------------------------------------------------------------------
3. 命令行入口
---------------------------------------------------------------------------
def main():
ap = argparse.ArgumentParser(description="自动 / 半自动去除图片水印")
ap.add_argument("-i", "--input", required=True, help="输入图片")
ap.add_argument("-o", "--output", default="output.jpg", help="输出图片")
ap.add_argument("--mask", help="已有 mask 图(白色=水印)")
ap.add_argument("--mode", choices="white", "color", "tophat", "none",
default="white", help="自动检测方式")
ap.add_argument("--roi", nargs=4, type=float,
metavar=("X1", "Y1", "X2", "Y2"),
help="水印所在区域的相对坐标 0~1,强烈建议指定")
ap.add_argument("--thresh", type=int, default=200,
help="white / tophat 模式的阈值")
ap.add_argument("--hsv", nargs=6, type=int,
metavar=("H1", "S1", "V1", "H2", "S2", "V2"),
help="color 模式的 HSV 上下界")
ap.add_argument("--ksize", type=int, default=15, help="tophat 结构元大小")
ap.add_argument("--radius", type=int, default=3, help="inpaint 半径")
ap.add_argument("--method", choices="telea", "ns", default="telea")
ap.add_argument("--dilate", type=int, default=3, help="mask 膨胀核大小")
ap.add_argument("--filter-cc", action="store_true", help="按连通域面积过滤 mask")
ap.add_argument("--show-mask", action="store_true", help="输出 mask 便于调参")
args = ap.parse_args()
img = cv2.imread(args.input, cv2.IMREAD_COLOR)
if img is None:
raise SystemExit(f"错误 无法读取图片: {args.input}")
roi_px = parse_roi(img, args.roi) if args.roi else None
---- 生成 mask ----
if args.mask:
mask = cv2.imread(args.mask, cv2.IMREAD_GRAYSCALE)
if mask is None:
raise SystemExit(f"错误 无法读取 mask: {args.mask}")
if mask.shape:2 != img.shape:2:
mask = cv2.resize(mask, (img.shape1, img.shape0),
interpolation=cv2.INTER_NEAREST)
mask = limit_to_roi(mask, roi_px) if roi_px else mask
elif args.mode == "white":
mask = mask_by_white(img, args.thresh, roi_px)
elif args.mode == "color":
if not args.hsv:
raise SystemExit("错误 color 模式需要 --hsv 参数")
mask = mask_by_color(img, args.hsv:3, args.hsv3:, roi_px)
elif args.mode == "tophat":
mask = mask_by_tophat(img, args.ksize, args.thresh, roi_px)
else:
mask = np.zeros(img.shape:2, np.uint8)
if args.show_mask:
cv2.imwrite("mask_debug.png", mask)
print("提示 mask 已保存为 mask_debug.png")
---- 修复 ----
result = remove_watermark(img, mask,
radius=args.radius,
method=args.method,
dilate=args.dilate,
use_cc_filter=args.filter_cc)
cv2.imwrite(args.output, result)
print(f"完成 已保存到 {args.output}")
if name == "main":
main()
```
水印去除的几种检测模式与修复流程
脚本把去水印拆成两步:先定位水印区域,再让算法把这块区域"补"回来。
mask 生成策略
· white 模式按亮度阈值抓取高亮区域,适合白色或半透明白色水印。
· color 模式通过 HSV 颜色范围匹配固定颜色的水印。
· tophat 模式利用形态学顶帽运算,提取比周围更亮的细线或半透明文字。
· 也可以用 --mask 直接传入自己画好的掩膜图。
区域限制与过滤
· --roi 参数把检测范围限定在水印所在区域,避免误伤画面其他部分。
· --filter-cc 会按连通域面积过滤噪点,同时排除面积过大的亮块。
图像修复
· 核心用 OpenCV 的 cv2.inpaint 做修复,支持 telea 和 ns 两种算法。
· --dilate 会先膨胀 mask,确保水印边缘也被覆盖,避免残留。
· --radius 控制修复邻域大小,水印越粗就调得越大。
优化建议: 如果水印位置固定,建议先用 --roi 框定大致范围;白色水印可尝试调整 --thresh,半透明细线水印可微调 --ksize 和 --thresh,并加 --filter-cc 减少噪点误判。
此文章仅供参考借鉴。