MATLAB实现DWT-DCT-SVD图像数字水印嵌入提取算法
2026/9/11 15:04:23 网站建设 项目流程

这次来看一个基于 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:多分辨率分解

离散小波变换把图像分解成一个低频近似子带和三个高频细节子带,分别记为LLLHHLHHLL是原图的低频近似,包含图像大部分能量,视觉上相当于一个缩小后的原图;三个高频子带包含边缘、纹理等细节信息。

水印嵌入通常选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 三域组合的嵌入与提取流程

整个算法的正向流程可以写成:

  1. 对载体图像做一级 DWT,取LL子带。
  2. LL子带做 DCT,得到LL_dct
  3. LL_dct做 SVD,得到U * S * V'
  4. 对水印图像做 SVD,得到Uw * Sw * Vw'
  5. 用嵌入强度alpha将水印奇异值叠加到载体奇异值上:S_new = S + alpha * Sw
  6. U * S_new * V'重构 DCT 系数,再idct2回到空间域,最后idwt2重构出含水印图像。

提取时,对含水印图像重复步骤 1 到 3,得到含水印图像的奇异值矩阵S_w,然后计算:

Sw_extracted = (S_w - S) / alpha

最后用保存的UwVw重构水印:

W = Uw * Sw_extracted * Vw'

这里的SUwVwalpha就是提取水印必须的密钥。这也是非盲水印的典型特征。

4. 环境准备与前置条件

4.1 MATLAB 版本与工具箱检查

代码用到了dwt2idwt2dct2idct2svdpsnrcorr2imresize等函数,分别来自 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计算最快;db2sym4鲁棒性更好但耗时增加
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 无攻击下的嵌入提取验证

先跑一遍最基本的闭环:

  1. 运行watermark_embed.m,生成含水印图像。
  2. 运行watermark_extract.m,提取水印。
  3. 观察 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 压缩攻击示例,可以通过imwriteQuality参数控制压缩强度:

% 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 脚本的耗时主要集中在dwt2dct2svd和图像缩放。其中 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 明显下降压缩强度过大,奇异值变化超出预期比较Quality=90/70/50三组结果提高alpha,或对载体做分块嵌入
批处理脚本运行越跑越慢中间变量未清理,图形窗口堆积观察内存占用每轮循环clear中间变量,关闭显示
提取的水印是反色或亮度异常mat2gray归一化导致检查保存前的数值范围增加二值化阈值:wm_ext = wm_ext > 0.5

10. 最佳实践与使用建议

10.1 调参策略

不要一开始就追求高鲁棒性。先用alpha = 0.02跑通闭环,观察 PSNR 和 NC,再逐步增大到 0.05、0.1,找到“图像质量可接受且提取水印可辨认”的平衡点。鲁棒性测试最好固定其他参数,只改变alpha,这样结果才有可比性。

10.2 水印素材选择

水印图建议优先使用二值 Logo,背景干净、文字清晰。如果水印是复杂灰度图,提取后肉眼效果会差一些,NC 的阈值判断也更严格。想做更真实的效果,可以把水印设计成带简单图案的 64x64 小图,减少嵌入对载体图像的影响。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询