










档案在线借阅的普及带来一个安全难题:用户有权限看,但可以截图/拍照外泄。审批流程管得住"谁能看",管不住"看的人把内容带出去"。

水印是防外泄的核心手段,分两大类:
两者不是替代关系,而是组合使用。这篇讲清楚各自的技术方案、适用场景和坑。
from PIL import Image, ImageDraw, ImageFont
def add_visible_watermark(img, text, pos="bottom_right",
font_size=24, opacity=0.6):
"""叠加半透明文字水印(PNG 上做 alpha 叠加)"""
img = img.convert("RGBA")
overlay = Image.new("RGBA", img.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(overlay)
font = ImageFont.truetype("simhei.ttf", font_size)
# 计算位置
if pos == "bottom_right":
x = img.width - len(text) * font_size - 40
y = img.height - font_size - 40
draw.text((x, y), text, fill=(255, 0, 0, int(255 * opacity)), font=font)
return Image.alpha_composite(img, overlay)
盲水印把信息(借阅人 ID、时间戳)编码后嵌入图像的频域/空间域,人眼看不出,但通过提取算法能还原。经典做法:
# DCT 盲水印(示意:嵌入 -> 提取)
import numpy as np
from scipy.fftpack import dct, idct
def embed_dct_watermark(img_block, bit):
"""把一个 bit 嵌入 8x8 块的 DCT 中频系数"""
dct_block = dct(dct(img_block, axis=0, norm='ortho'), axis=1, norm='ortho')
mid = 4 # 中频系数位置(低频被压缩保留,高频易丢)
# 量化嵌入:把中频系数调到奇偶对应 bit
q = 8 # 量化步长(越大越鲁棒,但越明显)
dct_block[mid, mid] = (abs(dct_block[mid, mid]) // q * q + q // 2) \
* (1 if bit else -1)
return idct(idct(dct_block, axis=0, norm='ortho'), axis=1, norm='ortho')
(示意代码,生产实现要处理分块、误差补偿、抗攻击,发布前建议替换为实际方案或标注示意。)
| 方案 | 隐蔽性(视觉) | 抗 JPEG 压缩 | 抗截图裁剪 | 提取成功率 | 实现成本 |
|---|---|---|---|---|---|
| 可见水印(四角+对角) | 差(明显) | 不影响 | 中(裁掉可能丢) | 取证靠肉眼 | 低 |
| LSB 盲水印 | 好 | 差(压缩即毁) | 差 | 低 | 低 |
| DCT 频域盲水印 | 好 | 中(低强度压缩存活) | 中 | 中 | 中 |
| 深度学习水印 | 好 | 好 | 好 | 高 | 高 |
(数据为经验性评估,发布前建议补充你们自己的实测数据。)
借阅在线预览 → 叠加"可见水印"(用户可感知,威慑)
+ 嵌入"盲水印"(用户无感知,取证)
→ 双保险
坑 1:水印盖住了关键内容。 可见水印压住了档号、落款等关键区域,用户投诉"看不清"。解法:水印位置避开顶部/底部关键区(档号、标题、日期带),或做成"斜向铺满"(每条水印半透明、间距大,不遮挡特定位置)。
坑 2:盲水印嵌完文件变大 + 花屏。 嵌入强度过大,图像出现可见噪点(花屏),且压缩后体积变大。解法:嵌入强度自适应(内容复杂区域强度小),嵌入后人工目检。
坑 3:截图软件直接绕过在线预览。 有用户用浏览器开发者工具直接拉原图接口,绕过水印层。教训:原图接口必须鉴权 + 只能走带水印的出图通道,不允许直出原图。
坑 4:提取失败追责扯皮。 水印提取失败(截图修得太狠),无法证明泄露者。解法:可见水印 + 盲水印双轨,可见水印已留证据;盲水印提取失败也有"该图经过二次处理"的结论——都能支撑处置。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。