
blind_watermark 完整指南基于 DWT-DCT-SVD 的图片盲水印嵌入、盲提取与抗攻击实战【免费下载链接】blind_watermarkBlindInvisible Watermark 图片盲水印提取水印无须原图项目地址: https://gitcode.com/GitHub_Trending/bl/blind_watermarkblind_watermark 是一个基于频域DWT-DCT-SVD的数字盲水印库把文字、图片或二进制数据嵌入自然图像且提取水印时无需原图这就是盲的含义。本文以仓库中文文档 README_cn.md 为主体完整覆盖安装、命令行与 Python 嵌入/提取的三类水印模式、八种攻击下的鲁棒性表现并结合 bwm_core.py 等源码深入讲解水印编解码的底层原理、密钥机制与容量计算读完即可在项目中落地一套完整的图片水印方案。安装与环境安装方式通过 PyPI 安装发布版本pip install blind-watermark或者克隆源码安装最新开发版本git clone gitgithub.com:guofei9987/blind_watermark.git cd blind_watermark pip install .运行环境与依赖事实均可在仓库中核对当前仓库版本为0.4.4见 version.pysetup.py声明python_requires3.5MIT 协议跨 linux / windows / macos运行依赖numpy、opencv-python、PyWavelets三个库见 setup.py 与 requirements.txt首次创建WaterMark对象时会打印一条版本提示提醒编码与解码端版本必须一致提示仅显示一次可用blind_watermark.bw_notes.close()关闭实现见 version.py。安装后除了 Python API还会注册一个命令行入口blind_watermark它由setup.py中entry_points指向blind_watermark.cli_tools:main见 setup.py。命令行中使用文档给出的两条核心命令在仓库根目录执行# 嵌入水印 blind_watermark --embed --pwd 1234 examples/pic/ori_img.jpeg watermark text examples/output/embedded.png # 提取水印 blind_watermark --extract --pwd 1234 --wm_shape 111 examples/output/embedded.png参数说明与实现对照源码见 cli_tools.py| 参数 | 说明 | | -- | -- | |--embed| 进入嵌入模式位置参数依次为原图路径、水印文本、输出图片路径 | |--extract| 进入提取模式位置参数为待提取的水印图路径 | |-p/--pwd| 密码整数。注意 CLI 里它只作为password_img传入WaterMark见 cli_tools.py水印侧密码使用默认值1嵌入与提取两端必须保持一致 | |--wm_shape| 水印形状/长度提取时必填。文档示例111与嵌入文本watermark text14 个字符 × UTF-8 编码位数对应即嵌入时终端打印的Put down watermark size数值 |嵌入时命令行会打印输出文件名和水印比特长度提取时按modestr还原出字符串并打印。命令行实现固定走字符串模式因此 CLI 场景只支持文本水印图片、二进制水印请使用 Python API。Python 中使用嵌入文本水印文档给出的核心等式原图 水印 打上水印的图。guofei9987 开源万岁完整可运行的嵌入 提取示例对应 examples/example_str.pyfrom blind_watermark import WaterMark # 嵌入 bwm1 WaterMark(password_img1, password_wm1) bwm1.read_img(pic/ori_img.jpg) # 读入原图 wm guofei9987 开源万岁 bwm1.read_wm(wm, modestr) # 水印是字符串 bwm1.embed(output/embedded.png) # 输出打水印的图 len_wm len(bwm1.wm_bit) # 记录水印比特长度提取时要用到 print(Put down the length of wm_bit {len_wm}.format(len_wmlen_wm)) # 提取无须原图 bwm1 WaterMark(password_img1, password_wm1) wm_extract bwm1.extract(output/embedded.png, wm_shapelen_wm, modestr) print(wm_extract) # 输出: guofei9987 开源万岁关键点WaterMark(password_img, password_wm)两个密钥password_img决定水印在图像分块中的位置与系数乱序password_wm决定水印比特本身的打乱顺序详见下文原理一节read_wm的mode支持str/img/bit三种字符串模式会把 UTF-8 字节转成比特数组extract的wm_shape是必填参数源码中有assert wm_shape is not None见 blind_watermark.py对字符串/二进制水印来说它等于len(wm_bit)对图片水印则是二维形状(h, w)。字符串在源码中的编码/解码逻辑read_wm先把 UTF-8 文本转成十六进制再转成比特串blind_watermark.py提取时按 0.5 阈值判位、再还原成字节与 UTF-8 文本见 blind_watermark.py。原理深入DWT-DCT-SVD 编码流水线这一节从源码层面解释为什么提取不需要原图以及水印为何抗攻击核心文件是 bwm_core.py。前处理YUV、补边与 4×4 分块WaterMarkCore.read_img_arr对原图做如下准备bwm_core.py若图片带透明通道RGBA 且 Alpha 有小于 255 的像素先剥离 Alpha 单独保存最后再贴回BGR 转 YUV并对奇数尺寸补白边使高、宽都变为偶数copyMakeBorder对 Y、U、V三个通道分别做 Haar 小波二维分解dwt2(..., haar)得到低频子带ca用np.lib.stride_tricks.as_strided把ca切成交错的4×4 块四维数组每个块将承载 1 bit 信息。嵌入每块 DCT → SVD → 改奇异值每个块的处理函数block_add_wm_slowbwm_core.py流程为DCT → 块内系数按密钥乱序 → SVD → 改写 s0/s1 → 逆 SVD → 恢复系数顺序 → 逆 DCT其中改写奇异值的公式是水印嵌入的核心s[0] (s[0] // d1 1/4 1/2 * wm_1) * d1 # 若 d2 有效s[1] 同理wm_1是当前块要埋的比特0 或 1默认d136, d220。源码注释明确说明d1/d2 越大鲁棒性越强但输出图片失真越大bwm_core.py。把奇异值量化到以d1/2为间隔的格点上使得即使图像遭受旋转、裁剪、噪声等扰动s[0] % d1与d1/2的大小关系大概率保持不变——这是频域盲水印抗攻击的数学基础。密钥机制两把密码各管什么password_wmread_wm中用np.random.RandomState(password_wm).shuffle(wm_bit)打乱水印比特顺序blind_watermark.py提取完成后由extract_decrypt用同一随机种子做逆置换还原blind_watermark.py。这层打乱让水印比特在空间上分散局部篡改难以成片破坏。password_img通过random_strategy1生成每个块内 16 个 DCT 系数的乱序排列并决定块遍历顺序见 bwm_core.py。乱序后再做 SVD使嵌入对系数位置更鲁棒。提取循环嵌入 三通道平均 KMeans 判决提取端无须原图走extract → extract_raw → extract_avg对被打水印的图重复同样的前处理YUV→Haar→4×4 分块对每个块做DCT → 乱序 → SVD用(s[0] % d1 d1/2)判出 1 bit若d2有效则同时判s[1]并按 3:1 加权bwm_core.py由于水印比特是循环写入block_num个块的wm_bit[i % wm_size]且 3 个通道独立写入extract_avg对同一比特在所有重复位置、所有通道上的判决结果取平均bwm_core.py把个别块被攻击破坏的影响摊平str/bit模式下再经过one_dim_kmeans对判决向量做一维 KMeans 二分类自动确定 0/1 分界阈值bwm_core.pyimg模式则跳过 KMeans直接输出 0~1 实数矩阵供用户自行卡阈值。容量上限init_block_index在嵌入前会断言水印长度小于可容纳的块数否则抛出最多可嵌入 X kb 信息……溢出的IndexErrorbwm_core.py。块数由ca原图 1/2 分辨率按 4×4 取整决定块数 (H/2/4) × (W/2/4)即一张 1108×738 的原图约可容纳约 441×284 ≈ 12.5 万 bit。从源码结构看4×4 分块尺寸block_shape目前固定为[4, 4]bwm_core.py。各种攻击后的效果文档用字符串水印guofei9987 开源万岁验证了 8 种攻击全部能完整提取。下表完整继承文档的测试结果攻击后图片可用仓库内文件核对| 攻击方式 | 攻击后的图片 | 提取的水印 | | -- | -- | -- | | 旋转攻击45度 | 攻击后图片 | guofei9987 开源万岁 | | 随机截图 | 攻击后图片 | guofei9987 开源万岁 | | 多遮挡 | 攻击后图片 | guofei9987 开源万岁 | | 纵向裁剪 | 攻击后图片 | guofei9987 开源万岁 | | 横向裁剪 | 攻击后图片 | guofei9987 开源万岁 | | 缩放攻击 | 攻击后图片 | guofei9987 开源万岁 | | 椒盐攻击 | 攻击后图片 | guofei9987 开源万岁 | | 亮度攻击 | 攻击后图片 | guofei9987 开源万岁 |这些攻击不是凭空实现的仓库自带一套攻击与恢复工具完整演示在 examples/example_str.py每个攻击场景后都带assert wm wm_extract断言验证| 工具 | 所在文件 | 说明 | | -- | -- | -- | |rot_att(angle)| att.py | 绕中心旋转恢复时对攻击图再反向旋转一次 | |resize_att(out_shape)| att.py | 缩放恢复时缩回原图尺寸注意out_shape是宽×高需与shape[:2]的行列顺序颠倒 | |bright_att(ratio)| att.py | 亮度整体乘ratio示例中 0.9 调暗 10%再以 1.1 近似还原 | |shelter_att(ratio, n)| att.py | 随机n个、各占ratio比例的白色遮挡块 | |salt_pepper_att(ratio)| att.py | 以概率ratio置白像素示例用 0.05 | |cut_att3(loc, scale)| att.py | 裁剪 可选缩放模拟截图 |对于裁剪/截图攻击由于图像几何位置被破坏直接提取前需要先找回来、补回去recover.py提供两步estimate_crop_parameters以水印图为模板、在攻击图上做cv2.matchTemplate归一化相关系数配合尺度暴力搜索默认scale(0.5, 2)、search_num200的两轮局部搜索推断出攻击图在原图中的位置与缩放比见 recover.pyrecover_crop把攻击图按估计的位置贴回全尺寸画布其余区域补零见 recover.py。example_str.py中演示了知道攻击参数与不知道攻击参数自动估计两条恢复路径例如未知参数场景examples/example_str.py# 截屏攻击2 剪切攻击 缩放攻击 不知道攻击参数需 estimate_crop_parameters 推测 att.cut_att3(input_filenameoutput/embedded.png, output_file_nameoutput/截屏攻击2.png, loc(x1, y1, x2, y2), scalescale) # 推测攻击参数位置 缩放比 (x1, y1, x2, y2), image_o_shape, score, scale_infer estimate_crop_parameters( original_fileoutput/embedded.png, template_fileoutput/截屏攻击2.png, scale(0.5, 2), search_num200) # 还原为原尺寸后再提取 recover_crop(template_fileoutput/截屏攻击2.png, output_file_nameoutput/截屏攻击2_还原.png, loc(x1, y1, x2, y2), image_o_shapeimage_o_shape) wm_extract bwm1.extract(output/截屏攻击2_还原.png, wm_shapelen_wm, modestr)从源码结构看att.py中旧的cut_att/cut_att_height/cut_att_width/anti_cut_att等函数已标注为待弃用warnings.warn(will be deprecated...)新代码应统一使用cut_att3与recover_crop。嵌入图片水印图片水印是read_wm的默认modeimg按灰度读入 128的像素取 1得到一维比特数组blind_watermark.py。仓库自带的示例水印为 examples/pic/watermark.png完整演示见 examples/example_img.py。嵌入from blind_watermark import WaterMark bwm1 WaterMark(password_wm1, password_img1) # 读入原图 bwm1.read_img(pic/ori_img.jpg) # 读入水印图片 bwm1.read_wm(pic/watermark.png) # 嵌入 bwm1.embed(output/embedded.png)提取注意wm_shape必须与水印图的比特总数一致即水印图的宽 × 高bwm1 WaterMark(password_wm1, password_img1) # notice that wm_shape is necessary bwm1.extract(filenameoutput/embedded.png, wm_shape(128, 128), out_wm_nameoutput/extracted.png)文档验证了图片水印在旋转 45 度、随机截图、多遮挡三类攻击后仍可还原其中多遮挡攻击后提取出的水印图如下对应 docs/多遮挡攻击_提取水印.png注意提取端用wm_shape(128, 128)时要求水印图本身是 128×128wm_size np.array(wm_shape).prod()见 blind_watermark.py实际使用时请以所用水印图的真实尺寸为准。img模式不走 KMeans 判决返回 0~1 实数矩阵并按 255 缩放写出灰度图便于人工检查。嵌入二进制数据modebit允许嵌入任意比特数组。文档的 demo 是嵌入长度 6 的数据[True, False, True, True, True, False]完整可运行版本在 examples/example_bit.pyfrom blind_watermark import WaterMark # 嵌入除了嵌入图片也可以嵌入比特类数据 bwm1 WaterMark(password_img1, password_wm1) bwm1.read_img(pic/ori_img.jpeg) bwm1.read_wm([True, False, True, True, True, False], modebit) bwm1.embed(output/embedded.png) # 提取wm_shape 是 extract 的必填参数 bwm1 WaterMark(password_img1, password_wm1) wm_extract bwm1.extract(output/embedded.png, wm_shape6, modebit) print(wm_extract)两点说明文档原文把wm_shape写在了构造器里且使用了read_ori_img对照源码构造器签名为WaterMark(password_wm, password_img, block_shape, mode, processes)blind_watermark.pywm_shape是extract()的参数、读图方法名为read_img上例已按源码修正为可运行形式文档称解出的水印是 0~1 之间的实数方便用户自行卡阈值如果水印信息量远小于图片可容纳量偏差极小。从源码结构看bit模式实际经过one_dim_kmeans后返回 0/1 判定结果如需连续实数置信度可改用img模式的输出方式。example_bit.py同样验证了该模式在截屏、椒盐、旋转、遮挡、缩放攻击下的可恢复性。并行计算文档声明的 APIWaterMark(..., processesNone)processes整数指定线程数默认为None表示使用全部线程。源码层面pool.py由AutoPool承担任务分发mode参数支持common默认串行map、multithreadingmultiprocessing.dummy线程池、multiprocessing进程池以及vectorization/cached两种占位模式在 Windows 上选择multiprocessing会自动降级为多线程并给出警告非 Windows 平台则把fork设为默认启动方式。所有分块级加/取水印操作都通过self.pool.map分发bwm_core.py因此并行粒度是单个 4×4 块。演示见 examples/example_str_multi.py。示例代码与资产清单| 文件 | 内容 | | -- | -- | | examples/example_str.py | 字符串水印全流程 全部 8 类攻击/恢复演示带断言 | | examples/example_img.py | 图片水印嵌入/提取 | | examples/example_bit.py | 二进制水印嵌入/提取 多种攻击 | | examples/example_str_multi.py | 多进程/多线程并行演示 | | examples/example_no_writing.py | 不解码、仅提取场景 | | examples/example_demo.ipynb | Notebook 演示 | | examples/pic/ori_img.jpeg、examples/pic/watermark.png | 示例原图与示例水印图 |各脚本内用os.chdir(os.path.dirname(__file__))切换到 examples 目录后运行即可输出统一落在examples/output/。使用限制与注意事项wm_shape必须记录提取不依赖原图但必须知道水印比特总数str/bit 为一维长度img 为二维形状嵌入时务必打印/保存len(bwm.wm_bit)容量上限水印比特数不能超过分块总数超出会抛IndexError密钥与版本一致编码端与解码端的password_img、password_wm及库版本必须一致首次运行的版本提示也强调这一点d1/d2 的权衡调大可增强鲁棒性但增大图像失真默认 36/20 是源码给出的折中值裁剪/截图类攻击建议先用estimate_crop_parametersrecover_crop还原再提取旋转、缩放、亮度攻击则按示例先做逆操作。相关项目文档末尾提及同作者的扩展方向text_blind_watermark文本盲水印把信息隐秘地打入文本与HideInfo藏物于图、藏物于音、藏图于文可作为图片盲水印之外的补充方案参考。小结blind_watermark 的完整链路是read_imgYUV Haar 小波 4×4 分块→read_wmstr/img/bit 转比特并用password_wm打乱→embed每块 DCT→SVD→奇异值量化循环多通道嵌入→extract逆流程逐块判决 → 循环位置与三通道平均 → KMeans 阈值/直接输出。命令行适合文本水印的简单场景Python API 覆盖三种水印模式与并行计算attrecover工具链则提供了从攻击模拟到几何参数自动估计的完整验证手段配合 examples/ 下的可运行示例可以直接作为项目中文档、版权保护与溯源水印的工程起点。【免费下载链接】blind_watermarkBlindInvisible Watermark 图片盲水印提取水印无须原图项目地址: https://gitcode.com/GitHub_Trending/bl/blind_watermark创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考