
简介一份基于MATLAB的数字水印系统课程设计资源包围绕DCT离散余弦变换与小波变换DWT两种主流算法展开对比实现并配套完整的GUI交互界面。资源适合计算机、通信、自动化、电子信息等专业在校生用于课程设计、毕业设计或算法入门也可作为数字水印方向的初级进阶练习。包内共23个文件包含MATLAB源码脚本、GUI布局文件fig、测试图像bmp、预处理数据mat以及txt操作说明和doc技术文档整体压缩包19.6MB结构紧凑便于按功能模块调用与二次开发。系统支持嵌入、加噪、旋转、剪切、压缩等多种常见攻击模拟并提供PSNR、NC等评价指标用于量化比较算法的鲁棒性能够直观理解不同水印算法在抗攻击能力上的差异。下载后如需指导可联系作者远程协助运行。目前已有121人学习下载值得作为课程设计或项目演示的参考模板。1. 课程设计里数字水印系统真正的门槛课程设计选 MATLAB 做数字水印系统最常见的翻车点不在算法而在答辩演示老师顺手把含水印图像存成 JPEG再让你从 GUI 界面点一次提取结果空白一片。空域 LSB 藏的那点比特一张有损压缩图回来就散了大半界面画得再漂亮回答不上来 alpha 为什么取 0.05、提取端为什么知道水印嵌在哪个子带也一样过不了关。这个课程设计真正要打通的是两件事水印该嵌在哪个频带以及 GUI 回调之间怎么传递带水印的中间结果。前者决定了系统能不能在压缩、加噪之后仍把水印提出来后者决定了演示的时候点按钮会不会报错或者提出一张乱码。下面这套方案以离散小波变换为主线给出可复现的嵌入/提取源码、JPEG 与噪声攻击测试脚本以及 App Designer 界面整合方案。适合想交课程设计或毕设、又不满足于随便跑通的读者。读完后你手上会有一套完整的系统骨架以及一套用来定参数、应付追问的判断依据。2. DWT选型LSB、DCT与DWT在数字水印系统里的取舍2.1 三种嵌入域对比为什么课程设计首选DWT如果只看实现难度LSB 是瞬时能跑通的选择把水印的每个比特写进载体图像像素的最低位即可。问题在于 JPEG 压缩本身就是频域量化低位比特在量化表里基本被整体丢弃所以 LSB 方案在面对“压缩攻击”时基本没有还手之力。课程设计的答辩追问通常从这开始因此至少要往频域走一步。DCT 是 JPEG 的同族思想需要把图像切成 8x8 块逐一变换嵌入系数还要考虑块效应代码量上去之后调试并不容易。DWT 的好处是整图分解、天然具有多分辨率结构低频子带集中了主要能量而人类视觉对低频的微小变化又不敏感这正好满足“看不见又能抗压缩”的矛盾需求。MATLAB 里只靠 Wavelet Toolbox 的两个函数 dwt2 和 idwt2 就能完成正向和逆向变换课程设计阶段这种低编码成本非常关键。嵌入域实现难度抗JPEG压缩抗几何攻击典型用途LSB空域最低差差实验演示、信息隐藏入门DCT频域中中中与JPEG流程兼容适合看文献扩展DWT频域中低较好一般课程设计/毕业论文最常用路线上表里的“抗几何攻击”要说明一下三种方法遇到旋转、缩放都要头疼DWT 只是没有更差而已解决办法后面第 5 章会提到。课程设计选 DWT不是因为它在所有指标上碾压而是因为它在写得出代码、讲得清原理、扛得住压缩这三件事上平衡得最好。2.2 水印预处理Arnold置乱与灰度缩放直接把 logo 图像按像素铺进载体子带提取结果里会残留明显的原始图案轮廓这是因为自然图像邻域相关性太强。常见做法是嵌入前先把水印做一次位置置乱把相邻像素打散到整个画面嵌入能量在频域里分布得均匀一些提取回来时再做逆置乱恢复。Arnold 变换是经典的选择公式可以写成 [x; y] [1 1; 1 2] * [x; y] mod N其中 N 是方形水印的边长。编程实现不需要矩阵库两层循环直接映射坐标就行function img_s arnold_scramble(img, times) % 输入img应为方形灰度图或二值图 % times为置乱次数同时也是提取端需要的还原参数之一 n size(img, 1); img_s img; for k 1:times tmp zeros(size(img)); for i 1:n for j 1:n % Arnold映射新坐标由原坐标线性组合后取模 new_i mod(i j - 1, n) 1; new_j mod(i 2*j - 1, n) 1; tmp(new_i, new_j) img_s(i, j); end end img_s tmp; end end这个函数把置乱次数 times 暴露给调用方实际工程里 times 和尺寸 n 要一起保存起来提取端没有这两个值是无法还原水印的。置乱次数一般取 10 到 20 次超过一定次数会回到原点所以不要随手填一个很大的数。另外 Arnold 只适用于方形图像水印若不是方的可以先用 imresize 或 padarray 处理成方形再置乱。如果水印是灰度 logo、不是二值图建议在预处理阶段先做一次归一化im2double 之后缩放到 [0,1]保证嵌入时和 double 类型的载体在同一个数值尺度上。灰度水印能藏的信息量更大但课程设计演示时二值 logo 更好讲提取结果也更容易被老师一眼看懂。2.3 嵌入位置的确定二级DWT低频子带的嵌入公式载体不做任何处理整图直接嵌入也可以但常见做法是分解两级先对灰度图做一级 dwt2得到 LL1、LH1、HL1、HH1再对 LL1 做一级 dwt2得到 LL2。水印嵌在 LL2 上理由有三个低频能量集中、抗 JPEG 量化能力强、后续逆变换不会把水印扩散到高频细节里。嵌入公式分加性和乘性两种。加性写成 c c alpha * w理解简单、代码直观乘性写成 c c * (1 alpha * w)能量大的系数被改动的绝对量也大主观不可见性会好一点但需要小心低频系数出现负值的问题。课程设计一般用加性就足够控制 alpha 在 0.02 到 0.1 之间低于 0.02提取时信噪比不够高于 0.1图像能看出纹理变化PSNR 会掉到 35dB 以下。具体数值不是拍脑袋定的第 5 章会给一条实测曲线来定边界。提示dwt2 默认小波是 haar代码里建议显式指定 db4。haar 保留边缘更锐利但重构振铃明显db4 是 Daubechies 中长度较短的选择重建质量与运算速度之间更均衡。3. 可复现源码DWT水印嵌入、提取与攻击测试3.1 嵌入函数两级DWT的加性嵌入实现这里给出完整的嵌入函数输入是灰度载体、灰度水印和嵌入强度 alpha输出是 double 类型的含水印图像。代码在处理水印尺寸时用了 imresize避免出现“矩阵维度不匹配”的低级报错。function wm_img embed_watermark(cover, wm, alpha) % cover: 灰度图像uint8或double均可 % wm: 灰度水印uint8或double均可不要求与cover同尺寸 % alpha: 嵌入强度典型范围0.02~0.1 cover im2double(cover); wm im2double(wm); % 第一级DWT得到低频LL1和高频子带 [LL1, LH1, HL1, HH1] dwt2(cover, db4); % 第二级DWT继续对LL1分解 [LL2, LH2, HL2, HH2] dwt2(LL1, db4); % 把水印缩放到LL2相同尺寸 [r, c] size(LL2); wm_r imresize(wm, [r, c]); % 加性嵌入 LL2_wm LL2 alpha * wm_r; % 用修改后的LL2重建一级低频再重建整图 LL1_wm idwt2(LL2_wm, LH2, HL2, HH2, db4); wm_img idwt2(LL1_wm, LH1, HL1, HH1, db4); end代码逻辑分四步先把输入统一成 double避免 uint8 运算溢出接着连续两次 dwt2 分解然后缩放水印并相加最后用 idwt2 逐级恢复。alpha 乘在 wm_r 上而不是乘在 LL2 上这样水印能量跟载体自身的亮度无关调参数时行为更可控。需要注意 idwt2 后面跟的是 LH2、HL2、HH2也就是二级分解时产生的高频子带一级恢复时用的是 LH1、HL1、HH1。如果传错层级图像会重建出严重的棋盘纹理这是初学最容易踩的坑。输出一律保持 double显示时用 im2uint8 转一下即可存储时再用 imwrite 转换不要在嵌入中途就截断到 8bit。3.2 提取函数非盲提取与归一化相关系数NC提取端采用非盲方案也就是默认手里有原始载体。这样做课程设计最稳因为公式推导和代码都简单如果要做盲提取需要改成交替嵌入加相关检测的思路代码量会翻倍且稳定性依赖统计特性答辩反而不容易讲清楚。提取过程就是把嵌入过程反过来分别对含水印图和原始图做两级 DWT两者 LL2 的差值除以 alpha理论上就能还原水印。DWT 是线性变换加性嵌入在前差分提取在后噪声只来自攻击环节。function wm_ext extract_watermark(wm_img, cover, alpha) % wm_img: 含水印图double % cover: 原始载体图double非盲提取的参考 % alpha: 必须与嵌入时一致否则提取结果整体偏大或偏小 [LL1_c, ~, ~, ~] dwt2(cover, db4); [LL2_c, ~, ~, ~] dwt2(LL1_c, db4); [LL1_w, ~, ~, ~] dwt2(wm_img, db4); [LL2_w, ~, ~, ~] dwt2(LL1_w, db4); wm_ext (LL2_w - LL2_c) / alpha; wm_ext imresize(wm_ext, [size(cover, 1), size(cover, 2)]); end提取结果是一个和载体同尺寸的浮点图不能直接拿它跟原始水印比。课程设计里评价提取质量最常用的是归一化相关系数 NC公式上可以去掉均值以忽略直流偏移。下面这个实现加了均值中心化和 eps 防除零数值稳定function nc calc_nc(ref, test) a ref(:) - mean(ref(:)); b test(:) - mean(test(:)); nc sum(a .* b) / sqrt(sum(a.^2) * sum(b.^2) eps); endNC 在 0 到 1 之间通常 0.9 以上肉眼已经能看出水印轮廓。比 NC 更严格的是第 5 章要讲的 BER但先跑通 NC 就能证明系统闭环了。注意 calc_nc 的两个输入必须是同一尺寸ref 建议用置乱前或置乱后的水印保持一致否则算出来会偏低。3.3 攻击测试JPEG压缩、高斯噪声与旋转裁剪怎么模拟课程设计报告里没有攻击测试就等于没有验证鲁棒性。最常见的三个攻击场景按复杂度排序JPEG 压缩最简单imwrite 指定 Quality 再读回即可高斯噪声直接 imnoise旋转裁剪稍微麻烦一点imrotate 默认会改变图像尺寸需要保持与嵌入时一致才能提取。% JPEG压缩质量因子40数值越小压缩越狠 imwrite(im2uint8(wm_img), attack.jpg, Quality, 40); attacked im2double(imread(attack.jpg)); % 高斯噪声方差0.001相当于轻度噪点 attacked imnoise(wm_img, gaussian, 0, 0.001); % 旋转5度并裁剪保持原尺寸 attacked imrotate(wm_img, 5, bicubic, crop);三种攻击的验证方式相同把 attacked 送进 extract_watermark再和原始水印算 NC。JPEG 攻击重点关注质量因子降到多少时 NC 跌破 0.7高斯噪声要分清均值和方差的区别imnoise 第二个参数 0 是均值第三个参数才是方差旋转攻击最难处理因为像素重采样会破坏水印的同步位置crop 只能保证尺寸不能保证内容对齐。到这里三个核心函数都齐了把嵌入、提取、攻击三个函数存成独立 .m 文件给 GUI 界面预留好函数签名接下来就是界面层的事。函数签名保持输入输出清晰是前面所有步骤里对后期工程化最重要的一步。4. GUI界面整合用App Designer做成可演示的数字水印系统4.1 App Designer还是GUIDE课程设计怎么选老教程里大量用 GUIDE 写界面但新版 MATLAB 打开 GUIDE 时会看到提醒新装的环境里 GUIDE 相关组件也不再是默认推荐。App Designer 的代码是类结构控件回调自动生成属性面板可以直接改文字和范围更适合课程设计这种“要改参数、要现场演示”的场景。如果老师指定必须用 GUIDE回调逻辑是一样的只是 handles 换成 app 对象后面代码可以对应改写。我一般会直接选 App Designer理由很实际界面自动生成 .mlapp 文件里面已经带好控件初始化的方法不需要手工维护 figure 的位置和回调注册。对课程设计来说少写 30 行界面代码就多 30 行精力去调水印参数。4.2 界面布局与控件回调的读写链界面按“导入-参数-操作-结果”四个区域排左侧放“导入载体”“导入水印”两个按钮和一张预览轴中间放 alpha 滑块、分解层数下拉框和嵌入按钮右侧上下两个 UIAxes 显示结果和提取水印底部放一个文本区显示 PSNR 和 NC 数值。App Designer 里每个控件都有自动生成的名称比如滑块叫 AlphaSlider读取当前值就是 app.AlphaSlider.Value。这个值默认是 double直接传给嵌入函数即可。下拉框用 Items 属性配置 {2,3}回调里要做一次 str2double因为控件返回的是字符串。跨回调共享数据是界面层最容易出错的地方。水印图像、载体图像、嵌入后的图像都要挂在 app 对象的自定义属性上而不是局部变量。在代码视图顶部 properties (Access public) 区域声明properties (Access public) coverImg []; wmImg []; watermarkedImg []; lastAlpha 0.05; end这样嵌入按钮写完的数据提取按钮随时能读。最典型的翻车场景是用户嵌入后把 alpha 滑块从 0.05 拖到 0.1再点提取提取端拿 0.1 去还原一张按 0.05 嵌的图结果整张水印都偏暗NC 直接掉到 0.3 以下。所以嵌入成功那一刻要把当时的 alpha 存进 app.lastAlpha提取回调只认这个值滑块只负责“下次嵌入”的参数。4.3 把嵌入、提取、攻击函数接进按钮回调导入载体按钮的回调只需要做三件事弹文件选择框、读入图像、显示并保存到属性。灰度图直接存彩色图先 rgb2gray否则后续 DWT 会拿到三维矩阵报错。代码结构如下function ImportCoverButtonPushed(app, event) [file, path] uigetfile({*.png;*.bmp;*.jpg}, 选择载体图像); if isequal(file, 0) return; % 用户取消选择 end app.coverImg imread(fullfile(path, file)); if size(app.coverImg, 3) 3 app.coverImg rgb2gray(app.coverImg); end imshow(app.coverImg, Parent, app.CoverAxes); enduigetfile 返回的 file 也可能是 0说明用户点了取消不判断就直接 fullfile 会报错。这一步代码虽短但答辩现场很容易因为文件格式选错而崩溃在取消分支里直接 return 是好习惯。嵌入按钮的回调把前面的模块串起来从属性读图像从滑块读 alpha调用 embed_watermark把结果存属性并显示同时算一次 PSNR 刷新底部标签。function EmbedButtonPushed(app, event) if isempty(app.coverImg) || isempty(app.wmImg) app.ResultLabel.Text 请先导入载体和水印; return; end alpha app.AlphaSlider.Value; app.watermarkedImg embed_watermark(app.coverImg, app.wmImg, alpha); app.lastAlpha alpha; % 保存嵌入参数提取时保持一致 imshow(im2uint8(app.watermarkedImg), Parent, app.ResultAxes); p psnr(app.watermarkedImg, im2double(app.coverImg)); app.ResultLabel.Text sprintf(PSNR: %.2f dB, p); endpsnr 函数来自 Image Processing Toolbox如果没有这个工具箱可以手动算 MSE 再取对数一行公式的事10 * log10(1 / mean((a(:) - b(:)).^2))。提取按钮的回调同样套路先检查 app.watermarkedImg 是否为空再调 extract_watermark 和 calc_nc最后 imshow 到右侧轴。攻击测试建议单独做一个下拉框加一个执行按钮下拉框选择攻击类型执行按钮读出类型后调用第 3.3 节对应攻击代码。攻击后的图像要覆盖 app.watermarkedImg 吗我一般不会覆盖而是单独存在 app.attackedImg 里提取按钮上加一个复选框“从攻击后图像提取”这样既能看干净图像的水印质量又能看攻击后的演示时多一个对比层次。5. 鲁棒性验证与调参技巧用NC/BER曲线定参数5.1 批量测试alpha和JPEG质量因子画一条能答辩的曲线alpha 不该靠目测随机设。把 0.01 到 0.1 按步长 0.01 扫一遍嵌入后同时算 PSNR 和提取 NC画成两条曲线交叉区域就是推荐参数。脚本结构用一个循环最直观alphas 0.01:0.01:0.1; for i 1:length(alphas) wm_img embed_watermark(cover, wm, alphas(i)); psnr_v(i) psnr(wm_img, cover); wm_ext extract_watermark(wm_img, cover, alphas(i)); nc_v(i) calc_nc(wm, wm_ext); end % 画图时用plotyy或tiledlayout左轴PSNR右轴NC无攻击时 NC 会接近 1这条曲线主要用来找“不可见性”的边界真正决定参数的是加上 JPEG 攻击后的曲线。固定质量因子 50重复上面循环NC 开始明显下滑的 alpha 值就是最低可用强度。常见结果是 alpha 取 0.03 到 0.05 时PSNR 在 38dB 附近JPEG 质量 50 下 NC 仍能保持 0.85 以上这个区间可以直接写进设计报告。5.2 提取结果带噪怎么办二值化与BER评价受攻击后提取出的水印会蒙上一层灰白色的压缩噪声直接用 calc_nc 看数值还行但显示出来很难看。常见做法是把提取结果归一化后再做二值化二值图和嵌入前的二值水印逐像素比较用误码率 BER 来评价BER 是错误比特占总比特的比例越接近 0 越好。% 提取结果先拉伸到[0,1]再用imbinarize做全局阈值二值化 wm_norm mat2gray(wm_ext); wm_bw imbinarize(wm_norm); % 嵌入前把水印转成logical保证类型一致 % 如果用水印灰度图做嵌入提取端就走灰度评价 ber sum(wm_bw(:) ~ wm_ref(:)) / numel(wm_ref);这里 wm_ref 必须和嵌入用的水印完全一致最好是 logical 类型。如果嵌入前做的是灰度水印那么提取端也走灰度评价不要混着用。BER 低于 0.01 在课程设计里已经算优秀答辩时用 BER 曲线替代 NC 曲线会更“像论文”。imbinarize 同样来自 Image Processing Toolbox没有工具箱时可以用 graythresh 加 im2bw 等价实现。提一个容易被忽略的同步细节旋转攻击后的提取图像要先按嵌入时保存的置乱次数做逆 Arnold 置乱再与原始水印比对否则 NC 再高也对不上。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。