
这次来看一个基于 MATLAB 的数字水印提取算法项目编号 453。它的核心思路是在传统 DWT-SVD 水印方案中引入 DCT形成 DWT-DCT-SVD 三域级联结构。简单来说嵌入时先对载体图像做离散小波变换取低频子带做离散余弦变换再对 DCT 系数矩阵做奇异值分解把水印的奇异值加权叠加到载体奇异值上提取时用同样的正向变换流程做差值运算还原水印奇异值最终重构水印图像。这个项目适合正在做课程设计、毕业设计或者刚开始接触图像水印算法的人。相比于单纯 DWT 或 DCT 水印DWT-DCT-SVD 组合的优势体现在两点DWT 提供多分辨率分解适合把水印藏进低频区域DCT 进一步做频域能量集中让嵌入对常见图像处理攻击更稳定而 SVD 的奇异值本身具有非常好的稳定性微小的扰动不会让提取结果严重失真。三项级联之后不可见性和鲁棒性会更均衡。本文会给出完整的 MATLAB 实现包括水印嵌入脚本、水印提取脚本和鲁棒性批量测试脚本并说明 PSNR、NC 两个指标怎么算、怎么判断效果。还会把环境依赖、常见报错、调参建议一并整理出来。你可以直接照着跑通一个“嵌入 - 攻击 - 提取 - 评估”的完整闭环。1. 核心能力速览能力项说明项目类型MATLAB 图像水印算法实现核心技术DWT离散小波变换 DCT离散余弦变换 SVD奇异值分解主要功能灰度和二值水印的嵌入、提取、抗攻击鲁棒性评估运行环境MATLAB建议使用 R2018a 以上版本依赖工具箱Wavelet Toolbox小波变换、Image Processing Toolbox图像读写与 PSNR 计算输入素材载体图像灰度或彩色、水印图像灰度或二值 Logo输出结果含水印图像、提取水印图像、密钥文件、PSNR/NC 指标提取类型非盲提取需要嵌入阶段保存的密钥矩阵批量支持通过脚本循环即可批量处理多张图像接口扩展可封装为.m函数供其他脚本调用也可借助 MATLAB Web App Server 或 Production Server 发布这个项目不是一个开箱即用的 GUI 工具而是一套清晰可复用的算法框架。核心代码量不大重点是理解三个变换如何级联以及提取时的密钥依赖关系。2. 适用场景与使用边界2.1 适合什么场景DWT-DCT-SVD 水印算法最典型的应用是图像版权标识和内容溯源。你可以把一段标识信息转成水印图像嵌入到待保护的图片中发生纠纷时通过提取算法证明图片的来源关系。这类需求常见于课程设计、毕业论文演示以及数字资产管理的前期方案验证。这个项目非常适合下面几类人正在写图像水印相关课设、毕设的学生需要一套可运行的 MATLAB 代码。刚开始接触 DWT、DCT、SVD 三个变换关系想通过代码理解原理的研究者。想对比不同嵌入强度、小波基函数对水印效果影响的技术人员。2.2 不适合什么场景如果你的目标是“对任意图片直接提取水印、不需要任何额外信息”这套非盲提取方案并不合适因为提取时依赖嵌入阶段保存的密钥矩阵。如果追求盲提取需要换半盲或盲水印方案复杂度会高很多。如果你的图片是彩色高清大图且要求实时处理纯 MATLAB 脚本的性能可能达不到要求建议先做小尺寸验证再决定是否改用 C 或 GPU 加速。2.3 合规与授权边界水印技术的核心是标识内容归属而不是隐藏或盗用内容。嵌入水印的载体图像必须是本人创作、已获得授权或处于测试范围内的素材。使用他人照片、商用图片或受版权保护的 Logo 做实验必须事先确认授权。对视频、人脸照片、声像作品做水印处理时还要注意肖像权和隐私保护。任何绕过版权保护、窃取标识信息的做法都不在本项目使用范围之内。3. DWT-DCT-SVD 算法原理拆解要跑通代码先得理解三个变换在算法里各自干了什么。不要把 DWT、DCT、SVD 当成三个黑盒它们的组合逻辑并不复杂。3.1 DWT多分辨率分解离散小波变换把图像分解成一个低频近似子带和三个高频细节子带分别记为LL、LH、HL、HH。LL是原图的低频近似包含图像大部分能量视觉上相当于一个缩小后的原图三个高频子带包含边缘、纹理等细节信息。水印嵌入通常选LL子带因为人眼对低频区域的细微变化不敏感低频子带经历噪声、压缩、缩放等攻击后也更稳定。MATLAB 中使用dwt2做一级分解重构时使用idwt2。3.2 DCT频域能量集中离散余弦变换能把图像从空间域转到频率域并让能量集中到低频系数上。JPEG 压缩本身就是基于 DCT 设计的所以对LL子带再做一次 DCT会让后续嵌入的区域更贴近压缩算法的保留部分从而增强抗 JPEG 压缩的能力。MATLAB 中使用dct2做二维 DCT用idct2做逆变换。经过 DCT 后矩阵左上角的低频系数集中了大部分能量这也为 SVD 分解提供了更稳定的输入矩阵。3.3 SVD奇异值的强稳定性奇异值分解把任意实矩阵A分解为U * S * V其中S是对角矩阵对角线上的值就是奇异值。SVD 在图像水印中有两个明显优点奇异值对图像微小扰动不敏感。加入小幅度水印或受到轻度噪声干扰时奇异值变化很小。奇异值反映矩阵的固有信息抗几何攻击的能力比直接在像素域嵌入更强。因此水印嵌入通常是对载体矩阵做 SVD 后把水印的奇异值或水印本身叠加到载体奇异值上。3.4 三域组合的嵌入与提取流程整个算法的正向流程可以写成对载体图像做一级 DWT取LL子带。对LL子带做 DCT得到LL_dct。对LL_dct做 SVD得到U * S * V。对水印图像做 SVD得到Uw * Sw * Vw。用嵌入强度alpha将水印奇异值叠加到载体奇异值上S_new S alpha * Sw。用U * S_new * V重构 DCT 系数再idct2回到空间域最后idwt2重构出含水印图像。提取时对含水印图像重复步骤 1 到 3得到含水印图像的奇异值矩阵S_w然后计算Sw_extracted (S_w - S) / alpha最后用保存的Uw、Vw重构水印W Uw * Sw_extracted * Vw这里的S、Uw、Vw、alpha就是提取水印必须的密钥。这也是非盲水印的典型特征。4. 环境准备与前置条件4.1 MATLAB 版本与工具箱检查代码用到了dwt2、idwt2、dct2、idct2、svd、psnr、corr2、imresize等函数分别来自 Wavelet Toolbox 和 Image Processing Toolbox。建议使用 R2018a 及以上版本低版本也能运行但个别函数可能存在差异。在 MATLAB 命令行输入以下命令可以确认当前环境下相关工具箱是否可用% 查看已安装的工具箱 ver % 确认关键函数是否存在 which dwt2 which dct2 which psnr如果提示找不到函数默认是没有安装对应工具箱或 MATLAB 版本过低。4.2 工作目录与素材准备建议把所有脚本和测试素材放在同一个目录下方便路径管理。目录结构可以参考下面这种方式DWT-DCT-SVD/ ├── watermark_embed.m ├── watermark_extract.m ├── test_robustness.m ├── lena.png ├── watermark.png └── results/其中lena.png是载体图像watermark.png是水印图。results目录放含水印图像、密钥文件和提取结果。如果不想使用 Lena 图片可以换成任意自拍的灰度或彩色测试图注意代码会自动转灰度。4.3 水印图像建议水印图像不一定是二值图但二值 Logo 的效果最好判断。如果使用复杂灰度图做水印提取结果会受alpha取值影响容易出现灰度偏移。建议先用黑白文字或简单图形 Logo 测试跑通之后再换更复杂的水印。5. MATLAB 水印嵌入完整实现5.1 嵌入脚本将下面的代码保存为watermark_embed.m放在当前工作目录% watermark_embed.m % 基于 DWT-DCT-SVD 的数字水印嵌入 clear; clc; close all; % ---------- 参数设置 ---------- alpha 0.05; % 嵌入强度越大鲁棒性越强但不可见性下降 wname haar; % 小波基函数可选 haar / db1 / db2 / sym4 % ---------- 读取载体图像 ---------- coverImg imread(lena.png); if size(coverImg, 3) 3 coverImg rgb2gray(coverImg); end coverImg double(coverImg); % ---------- 读取水印图像 ---------- watermark imread(watermark.png); if size(watermark, 3) 3 watermark rgb2gray(watermark); end wm_size size(watermark); % 保存原始水印尺寸 watermark double(watermark); % ---------- 一级小波分解 ---------- [LL, LH, HL, HH] dwt2(coverImg, wname); % 将水印缩放到 LL 子带尺寸 watermark imresize(watermark, [size(LL, 1), size(LL, 2)]); % ---------- DCT 变换 ---------- LL_dct dct2(LL); % ---------- SVD 分解 ---------- [U, S, V] svd(LL_dct); [Uw, Sw, Vw] svd(watermark); % ---------- 水印嵌入 ---------- S_new S alpha * Sw; % ---------- 重构图像 ---------- LL_dct_new U * S_new * V; LL_new idct2(LL_dct_new); watermarkedImg idwt2(LL_new, LH, HL, HH, wname); watermarkedImg uint8(watermarkedImg); % ---------- 保存结果 ---------- imwrite(watermarkedImg, watermarked_lena.png); save(watermark_key.mat, U, V, Uw, Vw, S, alpha, wname, wm_size); % ---------- 输出指标 ---------- psnr_val psnr(watermarkedImg, uint8(coverImg)); fprintf(水印嵌入完成PSNR %.4f dB\n, psnr_val); % ---------- 显示结果 ---------- figure; subplot(1, 2, 1); imshow(uint8(coverImg)); title(原始载体图像); subplot(1, 2, 2); imshow(watermarkedImg); title(含水印图像);5.2 关键参数说明参数作用建议alpha嵌入强度控制水印叠加幅度从 0.01 到 0.1 之间调试越小越隐蔽越大越鲁棒wname小波基函数haar计算最快db2、sym4鲁棒性更好但耗时增加wm_size原始水印尺寸提取阶段需要恢复到原尺寸保存脚本运行结束后会在当前目录生成两个文件watermarked_lena.png嵌入水印后的图像。watermark_key.mat后续提取水印所需的密钥文件。6. MATLAB 水印提取算法实现6.1 提取脚本将下面的代码保存为watermark_extract.m% watermark_extract.m % 基于 DWT-DCT-SVD 的数字水印提取 clear; clc; close all; % ---------- 加载密钥 ---------- load(watermark_key.mat, U, V, Uw, Vw, S, alpha, wname, wm_size); % ---------- 读取含水印图像 ---------- watermarkedImg imread(watermarked_lena.png); if size(watermarkedImg, 3) 3 watermarkedImg rgb2gray(watermarkedImg); end watermarkedImg double(watermarkedImg); % ---------- 与嵌入相同的正向变换 ---------- [LL_w, ~, ~, ~] dwt2(watermarkedImg, wname); LL_w_dct dct2(LL_w); [~, S_w, ~] svd(LL_w_dct); % ---------- 提取水印奇异值 ---------- Sw_extracted (S_w - S) / alpha; % ---------- 重构水印 ---------- watermark_extracted Uw * Sw_extracted * Vw; % ---------- 归一化并恢复原始尺寸 ---------- watermark_extracted mat2gray(watermark_extracted); watermark_extracted imresize(watermark_extracted, wm_size); watermark_extracted uint8(watermark_extracted * 255); % ---------- 保存提取结果 ---------- imwrite(watermark_extracted, extracted_watermark.png); % ---------- 若原始水印存在计算 NC ---------- if exist(watermark.png, file) orig imread(watermark.png); if size(orig, 3) 3 orig rgb2gray(orig); end nc_val corr2(double(orig), double(watermark_extracted)); fprintf(水印提取完成NC %.4f\n, nc_val); end % ---------- 显示结果 ---------- figure; subplot(1, 2, 1); imshow(watermark_extracted); title(提取水印); subplot(1, 2, 2); if exist(orig, var) imshow(uint8(orig)); title(原始水印); end运行之后会生成extracted_watermark.png同时在命令行打印 NC 值。NC 越接近 1说明提取水印与原始水印越相似。一般情况下 NC 在 0.95 以上肉眼几乎看不出区别如果 NC 低于 0.8基本可以判定水印提取失败。6.2 为什么提取必须依赖密钥提取脚本刚开始就load(watermark_key.mat, ...)。S是嵌入前载体图像的 DCT 系数奇异值矩阵(S_w - S) / alpha能还原水印奇异值本质上是因为嵌入过程做的是“先知道底数再算差值”。如果你在提取环境里没有密钥文件就没有办法从S_w中分离出水印信息。这一点既是限制也是安全性的来源没有密钥的人无法轻易提取或篡改水印适合用来做版权归属声明。6.3 提取结果判断标准提取水印肉眼可辨识。NC 大于 0.9说明提取质量高。如果提取水印出现大面积噪点优先检查alpha是否太小以及载体图像在嵌入后是否被压缩过。7. 功能测试与鲁棒性验证7.1 评估指标水印效果通常看两个指标指标全称作用判断标准PSNRPeak Signal-to-Noise Ratio衡量含水印图像相比原始载体图像的质量损失越高越好通常 30 dB 以上肉眼难察NCNormalized Correlation衡量提取水印与原始水印的相似度越接近 1 越好0.9 以上视为效果好PSNR 不是水印检测指标而是水印不可见性指标。NC 才直接反映“水印提取成不成功”。测试时两个指标一起看。7.2 无攻击下的嵌入提取验证先跑一遍最基本的闭环运行watermark_embed.m生成含水印图像。运行watermark_extract.m提取水印。观察 PSNR 和 NC 两个值。如果这一步 PSNR 低于 25 dB说明alpha偏大图像质量损失明显。如果 NC 低于 0.9大概率是alpha偏小水印太弱提取困难。7.3 常见攻击测试水印鲁棒性测试的核心是模拟真实场景中的图像处理过程。最常见的攻击包括高斯噪声、JPEG 压缩、中心裁剪、图像缩放等。攻击强度不同水印表现也不同所以不要只看一种攻击结果。以高斯噪声为例可以在含水印图像上叠加少量噪声再做水印提取% 高斯噪声攻击示例 attacked imnoise(watermarkedImg, gaussian, 0, 0.001); imwrite(attacked, attacked_noise.png);这里的watermarkedImg需要是 uint8 类型如果已经是 double 类型先转换。JPEG 压缩攻击示例可以通过imwrite的Quality参数控制压缩强度% JPEG 压缩攻击示例质量参数越小压缩越强 imwrite(watermarkedImg, attacked.jpg, Quality, 70); attacked imread(attacked.jpg);中心裁剪攻击示例% 中心区域置黑模拟裁剪攻击 attacked watermarkedImg; [h, w] size(attacked); attacked(round(h/4):round(3*h/4), round(w/4):round(3*w/4)) 0;缩放攻击示例% 先缩小再放大回原尺寸 attacked imresize(watermarkedImg, 0.5); attacked imresize(attacked, [h, w]);对攻击后的图像直接运行提取脚本逻辑即可观察 NC 的下降情况。7.4 批量鲁棒性测试脚本为了对比不同攻击对水印的影响可以写一个循环将所有攻击跑完输出一张结果表。把下面代码保存为test_robustness.m% test_robustness.m % DWT-DCT-SVD 水印鲁棒性批量测试 clear; clc; close all; % 读取含水印图像 watermarkedImg imread(watermarked_lena.png); if size(watermarkedImg, 3) 3 watermarkedImg rgb2gray(watermarkedImg); end watermarkedImg uint8(watermarkedImg); % 加载密钥 load(watermark_key.mat, U, V, Uw, Vw, S, alpha, wname, wm_size); % 原始水印 orig_wm imread(watermark.png); if size(orig_wm, 3) 3 orig_wm rgb2gray(orig_wm); end % 攻击列表 attackNames {无攻击, 高斯噪声, JPEG压缩70, 中心裁剪, 缩放攻击}; PSNR_list zeros(length(attackNames), 1); NC_list zeros(length(attackNames), 1); for k 1:length(attackNames) switch k case 1 attacked watermarkedImg; case 2 attacked imnoise(watermarkedImg, gaussian, 0, 0.001); case 3 imwrite(watermarkedImg, tmp_attack.jpg, Quality, 70); attacked imread(tmp_attack.jpg); case 4 attacked watermarkedImg; [h, w] size(attacked); attacked(round(h/4):round(3*h/4), round(w/4):round(3*w/4)) 0; case 5 [h, w] size(watermarkedImg); attacked imresize(watermarkedImg, 0.5); attacked imresize(attacked, [h, w]); end if size(attacked, 3) 3 attacked rgb2gray(attacked); end attacked double(attacked); % 攻击后图像相对含水印图像的 PSNR PSNR_list(k) psnr(uint8(attacked), watermarkedImg); % 提取水印 [LL_a, ~, ~, ~] dwt2(attacked, wname); LL_a_dct dct2(LL_a); [~, S_a, ~] svd(LL_a_dct); Sw_ext (S_a - S) / alpha; wm_ext Uw * Sw_ext * Vw; wm_ext mat2gray(wm_ext); wm_ext imresize(wm_ext, wm_size); NC_list(k) corr2(double(orig_wm), double(wm_ext)); end % 输出结果表 fprintf(%-16s %-16s %-16s\n, 攻击类型, PSNR(dB), NC); for k 1:length(attackNames) fprintf(%-16s %-16.4f %-16.4f\n, attackNames{k}, PSNR_list(k), NC_list(k)); end运行后可以得到每种攻击下的 PSNR 和 NC 变化趋势。强攻击下 NC 下降是正常现象对比不同alpha值下的结果才知道哪个参数更适合你的素材。8. 资源占用与运行效率观察8.1 耗时观察MATLAB 脚本的耗时主要集中在dwt2、dct2、svd和图像缩放。其中 SVD 的复杂度最高矩阵尺寸越大耗时越明显。可以在嵌入脚本中加tic/toc观察单次耗时tic; % 这里放嵌入或提取核心过程 elapsed toc; fprintf(单次处理耗时%.4f 秒\n, elapsed);以常见 512x512 灰度图像为例haar小波基下单次嵌入通常在秒级以内完成但具体耗时取决于本机 CPU 性能和 MATLAB 版本建议不要直接套用别人给出的秒数。8.2 内存占用代码中图像被转为double之后内存占用会比 uint8 大很多。512x512 的 double 矩阵大约是 2 MB如果做多层分解或大量循环内存增长会很明显。批量测试时建议每轮循环后清理无用的中间变量或者直接把图像保持在 uint8只在 SVD 前做局部转换。8.3 降低计算量的方法用haar小波不要一开始就选高复杂度小波基。载体图像先缩放到 256x256 或 512x512 做验证。水印尺寸尽量小缩放计算量更小。批量测试时关掉figure显示避免图形窗口拖慢速度。9. 常见问题与排查方法问题现象可能原因排查方式解决方案imread报错找不到图片当前工作目录错误或文件名拼写有误pwd查看当前目录dir查看文件列表使用绝对路径或把图片放到脚本同目录提示未定义函数dwt2缺少 Wavelet Toolbox命令行输入ver查看工具箱安装对应工具箱或更换 MATLAB 版本提示未定义函数psnr缺少 Image Processing Toolboxwhich psnr检查函数路径安装工具箱或自定义 PSNR 计算函数含水印图像明显花屏、噪点alpha过大导致嵌入过度查看 PSNR通常越低说明失真越大降低alpha从 0.01 开始重新测试提取水印全是噪点无法辨认alpha太小或水印本身过小过复杂检查 NC 值观察水印图复杂度增大alpha优先用二值 Logo 测试JPEG 压缩后 NC 明显下降压缩强度过大奇异值变化超出预期比较Quality90/70/50三组结果提高alpha或对载体做分块嵌入批处理脚本运行越跑越慢中间变量未清理图形窗口堆积观察内存占用每轮循环clear中间变量关闭显示提取的水印是反色或亮度异常mat2gray归一化导致检查保存前的数值范围增加二值化阈值wm_ext wm_ext 0.510. 最佳实践与使用建议10.1 调参策略不要一开始就追求高鲁棒性。先用alpha 0.02跑通闭环观察 PSNR 和 NC再逐步增大到 0.05、0.1找到“图像质量可接受且提取水印可辨认”的平衡点。鲁棒性测试最好固定其他参数只改变alpha这样结果才有可比性。10.2 水印素材选择水印图建议优先使用二值 Logo背景干净、文字清晰。如果水印是复杂灰度图提取后肉眼效果会差一些NC 的阈值判断也更严格。想做更真实的效果可以把水印设计成带简单图案的 64x64 小图减少嵌入对载体图像的影响。