简介:数字水印系统设计是基于MATLAB的图像加密与保护方向的优秀毕业设计成果,适合高校计算机、通信、自动化、电子信息等专业学生用于毕设、课设或项目立项参考。资源共45个文件,包含29个MATLAB脚本(.m)、12个GUI界面文件(.fig)、1份PDF设计报告、说明文档及测试图片(bmp/j2k),压缩包大小39.81MB,结构清晰,便于对照学习。已有90人学习浏览。压缩包内代码完整、经过运行测试,功能稳定,可直接复现数字水印嵌入与提取流程,也可基于现有代码修改实现其他图像加密功能。配套设计报告和介绍文档能帮助理解算法逻辑与实现细节,适合从入门到进阶的读者,遇到配置或运行问题可联系作者获得远程指导。
1. 数字水印系统在毕设里到底做什么
数字水印的毕设最容易翻车的点,不是代码跑不通,而是把水印理解成在图像角落贴一个半透明logo。答辩时老师问一句“你是可见水印还是不可见水印?能抵抗JPEG压缩吗?”很多人就卡住了。真正意义上的数字水印系统,目标是让一段信息嵌入到宿主图像后,人眼分辨不出差异,而提取端能从图里把这段信息完整捞出来——埋得越隐蔽,抗攻击能力越强,系统才越完整。这篇文章围绕基于MATLAB的DWT-SVD方案,把数字水印的选型、嵌入算法、提取与攻击评测、水印本身的置乱加密完整讲一遍,全部代码可以直接在MATLAB R2021a及以上版本运行,适合正在写图像加密与保护方向毕设、又想让代码和报告都站得住的读者。
2. 空间域到变换域:水印算法的选型依据
水印写在图像的什么位置,决定了这套系统能扛住什么操作。选型用一句话概括:空间域做不了抗压缩的水印,变换域可以,而DWT加SVD的组合在代码量、提取效果、论文可写性之间是最平衡的选择。这一章把LSB、DCT、DWT、DWT+SVD四个方案按答辩递进顺序过一遍,顺便把“低频嵌入到底会不会破坏画面”这个经常被写反的点掰清楚。
2.1 空间域LSB为什么先被放弃
LSB的做法是把水印比特写进像素的最低有效位。改动一个像素的灰度值只差1,人眼完全察觉不到,嵌入容量还大,用bitset几行代码就能写完。但它天然怕三类操作:JPEG有损压缩会直接改掉低位量化误差,均值滤波会把最低位当成噪声抹平,哪怕只是把图像重新保存一遍,都有可能引起比特翻转。所以LSB在数字水印论文里基本只适合当对照组,用来衬托变换域方法的鲁棒性——想做“LSB对比实验”的毕设,这一步是最省力的实验设计。
另外有一个细节容易被答辩追问:LSB提取时需要密钥来标记哪些像素嵌入了水印。如果采用固定顺序嵌入,攻击者拿到代码就能逐位扫描出来。算法本身不应该保密,保密的是密钥,空间域方案把这个道理讲得最清楚,作为选型铺垫很合适。
2.2 DWT+SVD:变换域组合的做法
DWT即离散小波变换,MATLAB里用dwt2一行就能对图像做一层分解,输出四个尺寸减半的子带:LL低频近似、LH水平细节、HL垂直细节、HH对角细节。四个子带里LL集中了图像绝大部分能量,也是JPEG压缩、缩放、滤波这些操作下手最轻的地方,所以水印写在LL里存活率最高。
2.2.1 低频子带为什么值得嵌
能量集中在LL意味着两件事。第一,压缩和滤波算法舍不得丢弃低频,水印能跟着图像主要内容一起被保留下来;第二,低频改动会直接影响主体结构,视觉质量损失比高频更明显。很多教程只写前半句,说“人眼对低频不敏感”,这是把话说反了。低频承载图像的主体轮廓和明暗分布,是视觉最先捕捉的内容,真正容易被忽略的是高频细节的细微变化。DWT方案对透明性的控制不靠“选一个看不见的子带”,而是靠嵌入强度sf,这是后面调参最容易迷惑的地方。
2.2.2 SVD的稳定性从哪来
SVD把矩阵分解成USV',S是对角矩阵,对角线上的奇异值按大小降序排列,对应图像在这个方向上的能量权重。图像受到噪声、压缩、轻微旋转等扰动时,左右奇异向量U、V会跟着变化,但奇异值相对稳定,这就是SVD在图像处理里常被称作“代数特征”的原因。嵌入水印时只改S这一条对角线,U、V完全不碰,重构出来的图像不会有结构性偏移;提取时拿含水印图的奇异值矩阵和原图的奇异值矩阵相减,剩下的残差就是水印奇异值。稳定、可逆、容易讲,这三个特点让DWT+SVD成了毕设最高频的搭配。
2.3 为什么毕设普遍选DWT+SVD而不是DCT
DCT是JPEG压缩的核心步骤,抗JPEG压缩天然占优,单独用DCT也能写完整个水印系统。但DCT通常需要先把图像切成8×8的块,每个块单独变换,处理时还要考虑量化步长和块效应,代码量明显大。DWT在MATLAB里只需要dwt2和idwt2两个函数,SVD也只是一次svd调用,总代码量比DCT小一个量级,出错环节也少。
| 方案 | 嵌入域 | 抗JPEG压缩 | 实现成本 | 论文可写性 |
|---|---|---|---|---|
| LSB | 空间域 | 弱 | 极低 | 低,适合做对照 |
| DCT | 频域(分块) | 中 | 中 | 中 |
| DWT | 频域(子带) | 中 | 低 | 中 |
| DWT+SVD | 频域+矩阵域 | 较强 | 中 | 高,理论层次多 |
论文可写性这一项经常被低估。选定DWT+SVD之后,“空间域嵌入→频域嵌入→矩阵奇异值嵌入”本身就是一条完整的算法演进线,报告的方法章节按这个脉络写,天然有递进关系,比只写一个单方案好写得多。答辩时老师问“你为什么不选DCT”,你能说出分块处理和全图处理的差别,这个深度足够了。
3. 用MATLAB实现DWT-SVD数字水印嵌入与参数标定
整个嵌入流程只有四步:灰度化、DWT取LL子带、对LL做SVD、修改奇异值后逆变换。这套流程不仅能当毕设主算法,也是图像处理大作业里最稳的骨架。下面的代码保存成embed.m,宿主图和水印图放在同一目录即可运行。
3.1 最小可运行嵌入代码
% embed.m —— DWT-SVD 数字水印嵌入 host = imread('cameraman.tif'); % 宿主图像,可换成自备灰度图 host = im2gray(host); % 统一灰度,防止通道数不一致 host = im2double(host); % 转到[0,1],SVD数值更稳 wm = imread('watermark.png'); % 水印图像,建议二值图 wm = im2gray(wm); wm = im2double(wm); wm = imresize(wm, size(host) / 2); % 与LL子带同尺寸:一层DWT边长减半 [LL, LH, HL, HH] = dwt2(host, 'db1'); % 一层Haar小波分解 [U, S, V] = svd(LL); % 宿主低频子带做SVD [Uw, Sw, Vw] = svd(wm); % 水印做SVD,Uw/Vw提取时要用 sf = 0.05; % 嵌入强度,3.2节细说 S_w = S + sf * Sw; % 修改奇异值 LL_w = U * S_w * V'; % 重构低频子带 host_wm = idwt2(LL_w, LH, HL, HH, 'db1'); % 反变换回图像 imwrite(host_wm, 'watermarked.png'); % 保存为PNG,避免有损格式逻辑说明:这20行代码完成了“低频→SVD→加水印→重构”全链路。dwt2用的db1即Haar小波,它正交、代码最短,适合首版流程;svd分解后U、V保留图像几何结构,S只有对角线有值,S加sf*Sw仍然是对角阵,所以LL_w的构造在数学上是合法的。idwt2的四个参数顺序与dwt2输出顺序对应,LL、LH、HL、HH不能传错,这是这类代码最常见的低级错误。imresize是关键,水印必须缩放到与LL子带一样大,否则后面两个奇异值矩阵尺寸对不上。
参数说明:'db1'可以替换成'sym4'、'bior3.7'等高阶小波,高阶小波频域局部性更好,但对复杂背景的改善不明显,反而要求嵌入端和提取端必须使用同一小波基,多一个容易出错的变量。sf是可调范围大约在0.01到0.3之间的核心旋钮,决定水印埋多深。im2gray在MATLAB R2021a及之后版本可用,老版本需要改成rgb2gray并自行处理灰度判断。
3.2 嵌入强度sf怎么调
sf决定水印嵌入幅度。太大会让图像出现可感知的纹理噪声,太小则提取时差值淹没在数值误差里。通用经验是先跑0.05把整条链路打通,再扫参数。下表是256×256像素宿主、128×128像素水印测出的典型区间,换图后具体数值会有浮动,趋势不变:
| sf | 含水印图PSNR(dB) | 视觉感受 | JPEG质量50下提取NC |
|---|---|---|---|
| 0.01 | 约50 | 完全不可辨 | 约0.70 |
| 0.05 | 约42 | 极难察觉 | 约0.90 |
| 0.10 | 约36 | 局部有轻微噪声 | 约0.95 |
| 0.20 | 约30 | 明显可见 | 约0.98 |
这里的本质是一对矛盾:sf越大,水印在压缩、噪声里存活率越高,但图像越容易露馅;0.05附近是多数论文默认的甜点区。建议写一个for循环让sf从0.01扫到0.2,把每条曲线的NC结果画在同一张图上贴进报告,这个图比任何文字描述都有说服力,老师也很难在实验充分性上挑毛病。
提示:攻击实验里JPEG压缩的NC最容易出现波动。imwrite写jpg的quality参数在50以下时,量化误差已经接近水印幅值,NC掉得快是正常现象,这不是算法bug。
3.3 水印尺寸与宿主图的约束
水印和LL子带的尺寸关系是硬约束:一层DWT后LL的边长等于宿主边长的一半。imresize可以把任意尺寸水印拉齐到LL尺寸,但水印尽量用方形,因为svd的完整分解USV'要求方阵;非方阵能分解,但S变成矩形矩阵,与S加sf*Sw的尺寸对不上,还得手动裁剪,报告里也很难解释。
内容上的约束同样重要。宿主尽量选纹理适中的自然图像,大面积平涂的图像(比如纯色背景占一半的截图)SVD后奇异值非常集中,同样sf下不同区域嵌入不均匀;水印首选黑白二值图,提取后做一次阈值二值化就能肉眼判断成功与否。灰度水印也能嵌,但提取结果会有轻微串扰,需要调大sf,这会牺牲宿主图质量,非必要不用。
4. 数字水印提取、攻击测试与鲁棒性评估
嵌入做完,剩下的是提取算法和攻击测试。这一章先把提取代码给全,再给一套标准攻击脚本和评价指标。做攻击测试的意义在于你要能回答“你的水印系统在什么条件下失效”,这是答辩的必问题。
4.1 非盲提取流程的MATLAB实现
本方案的提取属于非盲提取:提取端必须知道原图LL的SVD信息(U、S、V)和原水印的Uw、Vw。Uw、Vw在嵌入端算好,可以存成mat文件当作密钥;原图的U、S、V也可以一起保存。提取时只需要对含水印图像做一次DWT和SVD,拿奇异值做差还原。
% 保存提取所需的密钥信息 save('wm_keys.mat', 'Uw', 'Vw'); save('host_svd.mat', 'U', 'S', 'V'); % extract_wm.m —— 提取函数,可放在脚本文件末尾(R2016b+支持局部函数) function wm_rec = extract_wm(host_wm, U, S, V, Uw, Vw, sf) [LL_w, ~, ~, ~] = dwt2(host_wm, 'db1'); % 含水印图的一层DWT [~, S2, ~] = svd(LL_w); % 对含水印低频做SVD S_wm = (S2 - S) / sf; % 反解水印奇异值 wm_rec = Uw * S_wm * Vw'; % 用原水印左右向量重构 end逻辑说明:提取的核心等式是S2约等于S加sf乘Sw,两边同时减去原图奇异值S再除以sf,得到水印的奇异值矩阵S_wm。这个矩阵仍然只有对角线有值,要还原成完整图像,必须乘上原水印的Uw和Vw,因为SVD分解出的左右奇异向量才代表水印图像的空间结构。提取端的输入参数里没有原图,只有原图的U、S、V和原水印的Uw、Vw,这就是密钥。
一个特别容易踩的坑是先把S2取diag再构造对角阵。svd返回的奇异值矩阵本来就是对角阵,直接矩阵减法即可;先降维再构造对角阵,矩阵维度容易搞乱。保持矩阵运算,不要中途降维。
4.2 四类攻击模型与NC阈值判定
攻击脚本集中放在一个文件里,每次攻击后调用extract_wm再算NC。NC是归一化相关系数,取值越接近1说明提取出的水印越接近原始水印,0.7以上通常认定提取有效。
wnoise = imnoise(host_wm, 'gaussian', 0, 0.001); % 高斯噪声 wsp = imnoise(host_wm, 'salt & pepper', 0.01); % 椒盐噪声 imwrite(host_wm, 'tmp.jpg', 'quality', 50); % JPEG压缩 wjpeg = im2double(imread('tmp.jpg')); % 重新读回 wcrop = host_wm; wcrop(1:64, 1:64) = 0; % 裁剪四分之一 wrot = imrotate(host_wm, 15, 'bilinear', 'crop'); % 旋转15度 calc_nc = @(a, b) sum(a(:) .* b(:)) / sqrt(sum(a(:).^2) * sum(b(:).^2)); attacks = {wnoise, wsp, wjpeg, wcrop, wrot}; names = {'gaussian', 'salt_pepper', 'jpeg50', 'crop', 'rotate15'}; for i = 1:numel(attacks) wm_tmp = extract_wm(im2double(attacks{i}), U, S, V, Uw, Vw, sf); fprintf('%s: NC=%.4f\n', names{i}, calc_nc(wm, wm_tmp)); end一段256×256宿主图在sf=0.05下的典型结果如下:
| 攻击方式 | 参数 | 提取NC范围 |
|---|---|---|
| 高斯噪声 | 方差0.001 | 0.90~0.95 |
| 椒盐噪声 | 密度0.01 | 0.90~0.96 |
| JPEG压缩 | quality=50 | 0.85~0.92 |
| 裁剪 | 左上角1/4置零 | 0.80~0.88 |
| 旋转 | 15度crop | 0.55~0.70 |
旋转是五种攻击里最难扛的,因为图像旋转后行列对齐关系被破坏,DWT子带已经不是原来的坐标体系。想在报告里解决这个问题,需要在提取前做一次旋转校正,这一步可以作为改进方向写进论文的展望段,比硬扛旋转要诚实得多。
4.3 用PSNR和NC给系统打分
嵌入质量用PSNR衡量,提取质量用NC衡量,两个指标组合起来才是完整评价体系。PSNR可以不依赖工具箱自算:
mse_val = mean((host(:) - host_wm(:)).^2); psnr_val = 10 * log10(1 / (mse_val + eps));代码说明:host和host_wm都是[0,1]范围的double图,信号峰值取1;eps防止mse完全为零时log10的参数为0。PSNR超过40dB人眼基本看不出差异,低于30dB就能明显看到噪声。报告里建议把PSNR和NC放进同一张表,因为只看NC容易忽略透明性——sf=0.2时NC接近1但图像已经坏了,两个指标一起呈现才说明系统设计合理。
5. 给水印本身加密:Arnold置乱与密钥管理
前面流程里的水印是明文嵌入,提取出来是什么样就是什么样。版权保护场景中,水印通常是logo或编号,明文嵌入等于把身份信息直接暴露在图上,任何拿到提取算法的人都能读出内容。Arnold置乱是解法:嵌入前先把水印打乱成噪声图,提取后只有持有密钥的人才能还原。
5.1 Arnold变换的周期性
Arnold变换也叫猫映射,对坐标做一次线性模运算:
x' = mod(x+y, N) y' = mod(x+2y, N)
其中N是图像边长,x、y从0开始计数。一次变换把每个像素搬到新位置,肉眼看到的图像立刻变成雪花噪声。这个变换最实用的性质是周期性:迭代足够多次后图像会回到原样。256×256图像用标准矩阵计算的周期是192,迭代192次等于没变换。
| 边长N | 8 | 16 | 32 | 64 | 128 | 256 |
|---|---|---|---|---|---|---|
| 周期T | 6 | 12 | 24 | 48 | 96 | 192 |
周期值不用查表,用一个脚本就能算:
function T = arnold_period(N) A = [1 1; 1 2]; P = eye(2); T = 0; while true P = mod(A * P, N); T = T + 1; if isequal(P, eye(2)) break; end end end周期计算里A乘P就是对每个坐标应用同一线性变换,循环直到单位矩阵说明回到了原点。这段代码跑一次是毫秒级,写进论文还能顺带展示离散动力系统的周期性概念。
5.2 双密钥水印系统:置乱后再嵌入
置乱只需要正向迭代,还原利用周期性反着转即可,完全不用实现逆变换:
iter = 10; % 密钥1:加密轮数 wm_scrambled = arnold_scramble(wm, iter); % 用wm_scrambled替换第3章的wm继续嵌入流程 % 提取后还原 wm_rec_raw = extract_wm(host_wm, U, S, V, Uw, Vw, sf); wm_rec = arnold_scramble(wm_rec_raw, arnold_period(128) - iter);这里arnold_period(128)中的128是水印边长,调水印尺寸时必须跟着改,忘记这步是最常见的还原失败原因。双向密钥中,iter是显式密钥,Uw和Vw是隐式密钥;只知道其中一组,拿不到原始水印内容。
function out = arnold_scramble(img, iter) [h, w] = size(img); assert(h == w, 'Arnold置乱要求方形图像'); out = img; for t = 1:iter tmp = zeros(size(img)); for i = 1:h for j = 1:w ni = mod(i-1 + (j-1), h) + 1; nj = mod((i-1) + 2*(j-1), w) + 1; tmp(ni, nj) = out(i, j); end end out = tmp; end end坐标减一取模是对应从0开始的映射公式,加一是回到MATLAB的一基索引。双重循环处理128×128水印迭代几十次是毫秒级,根本不需要向量化。
5.3 参数化的密钥空间
固定矩阵[1 1;1 2]时密钥只有iter,爆破难度取决于周期,256边长最多192种可能。把映射参数化成x'=x+ay、y'=bx+(a*b+1)*y的形式,a、b也都成为密钥,密钥空间从192扩展到192×a×b,三个参数合起来足够支撑答辩场上的“安全性分析”问题。
值得注意,a、b变化后周期不再是固定值,提取还原时仍要调用arnold_period重新算T。另外,Arnold置乱有一个硬约束:水印必须是方阵。如果水印本身是矩形,先用padarray补零成方形,提取还原后再把补零部分裁掉,这一步的操作结果要写进报告,防止老师追问时临时解释不清。
6. 答辩演示与数据验证的实战技巧
6.1 实验表里填什么数据才能自洽
报告里的实验表格建议按两个维度组织:sf变化维度填PSNR和NC两列,攻击类型维度只填NC,两张表覆盖全部结论。生成数据时用for循环批量跑,不要手工抄数。原始图、含水印图、提取水印三张图用subplot并排放进报告,嵌入端和提取端的结果一起展示。subplot代码只有三行,但视觉冲击力比大段原理描述强得多。
6.2 现场演示的固定顺序
演示顺序固定为:跑一遍嵌入脚本生成含水印图,切换窗口展示PSNR数值;紧接着运行攻击脚本,展示JPEG压缩后的图像;最后运行提取脚本,让NC值直接打在命令行。不要现场改参数,不要现场绘图,演示版本用固定种子和固定sf。如果想把加密模块加进演示,在水印嵌入前多一行arnold_scramble,提取后多一行反置乱,观众看到的“噪声图→还原图”过程非常直观。
6.3 有损格式自检
最后做一个简单自检:把含水印图分别存成PNG和JPEG再提取一次。PNG无损,NC应基本不变;JPEG有损,NC会有明显下降。这个对比能快速暴露一条数据链路问题——如果保存环节就把水印破坏了,后面所有攻击测试数据都对不上。验证顺序放整个流程最后,这一张对比图还能顺便证明系统对无损和有损格式的差异表现。把水印明文嵌入和Arnold置乱后嵌入各跑一遍,对比两组NC数据,你会在数据里找到比理论更深的理解。
本文还有配套的精品资源,点击获取