
图像加密这个方向随便一搜就能看到一堆论文标题但“基于傅立叶变换和相位掩膜”这个组合几乎是本科毕设和研究生课题里常青树一样的存在。标题里说的“傅立叶变换”就是我们熟悉的傅里叶变换叫法不同而已而相位掩膜对应的就是光学加密里最经典的双随机相位编码技术。我当年刚接触这个课题时被一堆光路图和复数矩阵搞得晕头转向后来把原理逐行对照Matlab代码才真正吃透。这篇文章就把思路、数学原理、完整可运行的代码、以及我踩过的坑一次性摊开讲适合正在做图像加密方向作业或毕设的同学也适合想快速理解DRPEDouble Random Phase Encoding原理的研究者。1. 项目整体思路为什么偏偏是傅里叶变换加相位掩膜1.1 传统图像加密的痛点我们平时说的图像加密很多是拿像素位置打乱或者异或一串混沌序列。这类方案做起来快但有个致命弱点密文往往还是“一张图”像素分布可能有统计特征破解者只要做灰度直方图分析、差分攻击大概率能猜到加密规律。更重要的是纯置乱的算法只改变像素位置或灰度值完全没有利用光学相位这一维度。光学图像加密则是把信息调制到光波的振幅和相位两个维度上加密后的密文是一坨复振幅数据直接可视化就是纯噪声人眼什么都看不出来常规的频率分析手段也难提取有效信息。但这种光学实验需要激光器、透镜、空间光调制器环境振动会影响干涉搭建起来费时费力。好在我们可以用Matlab在数字域做等价的仿真用fft2模拟透镜的傅里叶变换用随机相位矩阵模拟物理掩膜把整个光路“跑”在电脑里。这样既保留光学的抗攻击思想又极大降低复现门槛。1.2 双随机相位编码DRPE到底做了什么DRPE的框架可以用一句话概括在输入平面放一个随机相位掩膜在频谱平面放另一个随机相位掩膜两次相位调制中间夹一次傅里叶变换。原始图像先乘第一个随机相位掩膜让空域的相位信息彻底随机化做傅里叶变换后乘第二个随机相位掩膜把频谱也随机化最后逆傅里叶变换得到复数密文。解密就是“反着走”密文做傅里叶变换乘第二个相位掩膜的复共轭再逆傅里叶变换最后乘第一个相位掩膜的复共轭取实部还原。为什么要用复共轭因为相位掩膜本质上是相位因子exp(iθ)乘以它等于给信号叠加一个相位角θ而乘以它的共轭exp(-iθ)正好把这个相位角抵消就像锁和钥匙的凹凸互相咬合。1.3 为什么选择Matlab做数字实现Matlab做这个课题简直是为矩阵运算量身定做的。整个核心加密过程就三个运算逐元素点乘、fft2、ifft2每行代码都对应物理光路里的一个器件。比如im .* key1对应空间光调制器加载相位掩膜fft2(A)对应透镜聚焦完成傅里叶变换cipher ifft2(B)对应输出面的逆变换。所以代码可读性很强调起来也直观。相比用C或者PythonMatlab的好处在于内置的图像读写、PSNR计算、imshow可视化接口处理复数矩阵也特别自然。你不需要额外实现二维离散傅里叶变换也不用担心复数类型转换的细节这对算法研究来说足够了。2. 核心原理拆解傅里叶变换和相位掩膜是怎么配合的2.1 傅里叶变换在加密里的角色二维离散傅里叶变换把图像从空域(x,y)变换到频域(u,v)公式是F(u,v) Σ Σ f(x,y) * exp(-i2π(ux/M vy/N))这个变换把图像信息重新分布到不同频率成分上。低频集中在频谱中心高频分布在边缘。一旦图像的频谱被随机相位调制每个频率分量的相位都变得不可预测逆变换回去后空域信息自然被打散成噪声。你可以把傅里叶变换想象成一个“信息混合器”图像每一个像素的能量其实都会被分配到所有频谱点上因此之后任何一个频谱点的扰动都会扩散到还原图像的全局。这也是DRPE能抵抗局部裁剪攻击的根本原因。Matlab的fft2默认把零频放在左上角也就是低频角。这并不影响加密因为加密用的随机相位掩膜是全域均匀分布的不会因为频谱位置而削弱安全性。如果你只是为了观察频谱可以用fftshift把零频移到中心再取对数显示。2.2 相位掩膜的“随机”到底意味着什么理论上相位掩膜是一个相位值在[0,2π)均匀分布的透明薄片每个位置的相位都是独立随机变量。数字仿真时直接生成复数矩阵key exp(2i * pi * rand(M,N));这里的rand(M,N)生成0到1之间均匀分布的随机矩阵乘上2π变成相位区间。exp(2ipi...)就会得到一个单位幅度的复数每个像素的相位在圆上均匀分布。随机相位的作用可以直观理解图像乘上这个复矩阵后每个像素的复振幅方向都不一样整体平均后相互抵消原始结构的自相关消失。这是加密强度的重要来源。如果相位不是随机而是周期性排列的比如正弦条纹那解密就很容易被破解。所以在实验中务必确认rand生成的矩阵统计独立不要用常见种子一把梭。2.3 加解密数学上的闭环设原始图像为I空间域相位掩膜为R1频域相位掩膜为R2加密密文C为C ifft2( fft2(I .* R1) .* R2 )解密时对C做fft2得到加了R2调制后的频谱乘conj(R2)抵消再做ifft2得到I.*R1最后乘conj(R1)抵消。数学上fft2(C) fft2( ifft2( fft2(I.R1) .R2 ) ) fft2(I.R1) .R2于是 fft2(C) .* conj(R2) fft2(I.*R1)再ifft2得到I.*R1乘conj(R1)得到I。注意到加密用了ifft2做最后一步所以解密的第一步必须用fft2这是配套的。如果反了恢复图像会翻转或者出现共轭镜像。浮点运算会带来极小的虚部所以最后取real。3. Matlab代码实现与参数选择3.1 写一个通用的加解密函数直接给出一段能跑的代码核心逻辑被我封装成两个函数。加密函数支持外部传入密钥也支持自动生成密钥。这样做有个好处调试时你可以固定同一个密钥方便对比结果正式实验时可以直接用自动生成的随机密钥。function [cipher, key1, key2] drpe_encrypt(im, key1, key2) % 输入: im 灰度图像, double类型, 范围[0,1] % 输出: cipher 复数密文, key1 空间域相位掩膜, key2 频域相位掩膜 [M, N] size(im); if nargin 2 || isempty(key1) key1 exp(2i * pi * rand(M, N)); end if nargin 3 || isempty(key2) key2 exp(2i * pi * rand(M, N)); end % 空间域调制 A im .* key1; % 傅里叶变换到频谱面 B fft2(A) .* key2; % 逆傅里叶变换得到复数密文 cipher ifft2(B); end3.2 解密函数function recovered drpe_decrypt(cipher, key1, key2) % 输入: cipher 复数密文, key1/key2 与加密时完全一致的密钥 % 输出: recovered 恢复图像, double类型, 范围[0,1] % 在频域抵消第二个随机相位掩膜 C fft2(cipher) .* conj(key2); % 逆变换回空间域后抵消第一个随机相位掩膜 A ifft2(C) .* conj(key1); % 取出实部并裁剪到合法灰度范围 recovered real(A); recovered max(0, min(1, recovered)); end解密函数最后一行max(0,min(1,...))非常关键因为浮点误差可能让恢复值出现小于0或大于1的像素点不裁剪会直接导致imshow时显示异常。3.3 主脚本读图、加密、解密、评价%% 加载一张灰度测试图 im im2double(rgb2gray(imread(cameraman.tif))); %% 加密 [cipher, key1, key2] drpe_encrypt(im); %% 解密正确密钥 recovered drpe_decrypt(cipher, key1, key2); %% 可视化 figure(Name,加密结果); subplot(1,3,1); imshow(im, []); title(原始图像); subplot(1,3,2); imshow(abs(cipher), []); title(密文幅度); subplot(1,3,3); imshow(angle(cipher), []); title(密文相位); figure(Name,解密结果); subplot(1,2,1); imshow(recovered, []); title(正确密钥解密); subplot(1,2,2); imshow(im, []); title(原始图像); %% 计算PSNR psnr_val psnr(recovered, im); fprintf(恢复图像PSNR %.4f dB\n, psnr_val);如果你的Matlab里没有cameraman.tif可以随便读入一张自己的图片但记得先用im2double和rgb2gray转为灰度图。也可以直接创建合成图像im zeros(256,256); im(80:180,100:120)1; im(100:140,40:220)1;这样更方便复现。3.4 几个关键参数的选择图像尺寸方面fft2不要求必须是2的幂所以任意尺寸都能运行。但大尺寸图像会让密钥矩阵也变大占用大量内存。测试阶段建议先用256×256跑起来快出现问题也好定位。相位掩膜类型方面标准DRPE用的是连续随机相位exp(2ipirand(M,N))。如果为了简化密钥存储可以二值化相位只生成0或π的相位掩膜但安全性会下降。实际课题建议保留完整随机相位密钥空间足够大。密钥保存方面千万不要只保存随机种子。Matlab的rand在不同机型和版本上的算法可能不同只保存seed会导致解密端无法复原。正确做法是用save函数把key1和key2存成.mat文件。下面是一段保存密钥的代码save(drpe_keys.mat, key1, key2); cipher_save cipher; save(cipher.mat, cipher_save);4. 实验效果与分析怎么判断加密算法好不好4.1 加密结果的直观可视化加密后密文是复数展示时通常分成幅度谱和相位谱。在正确代码下abs(cipher)和angle(cipher)看起来都应该是均匀的随机噪声。如果幅度谱里还隐约能看出来原始图像的轮廓要怀疑随机相位掩膜是否真的“随机”。一个常见错误是生成的随机矩阵太小或者相位掩膜只乘在了实部而忘了取复数。另一个检查点是原始图像如果有大面积纯黑背景密文幅度可能整体偏低但仍然不应出现明显的形状轮廓。4.2 正确密钥下的恢复质量用正确密钥恢复的PSNR通常能达到300dB甚至更高这表示几乎无失真。为什么不是无穷大因为double浮点运算有有限精度但肉眼绝对看不出区别。如果你恢复后PSNR只有20dB甚至更低不要怀疑浮点误差大概率是算法逻辑出错了可能fft2和ifft2的顺序反了可能忘记取real也可能密钥在传递过程中被截断或重排。我建议先在小尺寸图像上验证闭环解密后计算max(abs(recovered(:)-im(:)))这个误差应该在1e-14量级。如果远大于这个量级说明加密解密不完全对偶。4.3 密钥敏感性测试一个优秀的加密算法必须对密钥极度敏感。我们可以给key1人为添加一个微小扰动例如在相位上叠加一个标准差为0.01的随机噪声再解密看看。Matlab里这样操作% 给key1的相位加微小扰动 phase angle(key1); phase_noisy phase 0.01 * randn(size(phase)); key1_wrong exp(1i * phase_noisy); recovered_wrong drpe_decrypt(cipher, key1_wrong, key2); figure; imshow(recovered_wrong, []);你会看到恢复图像完全是一片空白噪声原始信息荡然无存。这是DRPE的典型特性每一个相位掩膜像素都参与了所有输出像素的贡献单个密钥错误会通过傅里叶变换扩散到全图。这个实验也常被写进论文里的“密钥敏感性分析”小节。4.4 抗裁剪和抗噪声测试考虑一下密文在信道传输中被截断或者叠加噪声的场景。把密文中心区域置零再解密观察恢复质量。因为信息在频域是扩散的局部密文损失虽然会造成振铃但主要轮廓往往依然可辨。这比空域加密的“局部像素损坏导致局部信息丢失”要优雅得多。给密文加高斯噪声时PSNR会明显下降但图像内容仍可能保持可读。这类实验可以用循环改变噪声强度画一条PSNR随噪声方差变化的曲线用作论文仿真图。以下是一个抗裁剪测试脚本cipher_attack cipher; cipher_attack(80:180, 80:180) 0; % 裁剪中心区域 recovered_attack drpe_decrypt(cipher_attack, key1, key2); figure; imshow(recovered_attack, []);5. 常见问题与排查技巧实录5.1 解密图像出现了镜像翻转或者旋转这个问题几乎每个做DRPE的人都会碰到根源在于正逆傅里叶变换没有形成正确对偶。标准流程是加密最后用ifft2那么解密第一步就必须用fft2如果两边都用了同一个方向的变换输出就会像把矩阵行列反转一样产生镜像效果。检查一下自己的代码是不是加密用了ifft2解密却用了ifft2或者加密用了fft2解密却用了fft2。统一改成成对的fft2/ifft2组合即可。5.2 复数密文怎么保存和显示Matlab里密文是复数矩阵imshow不能直接显示复数。通常用imshow(abs(cipher),[])显示幅度用imshow(angle(cipher),[])显示相位。但如果你要保存密文用于后续实验千万别用imwrite去存幅度图那样相位信息就丢了永远无法解密。正确做法是用save存成.mat文件或者分别保存实部和虚部两个矩阵。我在实际项目中就是吃过这个亏论文图片做了但数据文件只存了幅度后续想复现实验还得重新跑一遍。5.3 密钥应该保存矩阵而不是种子很多初学者图省事在加密端写了rng(42)然后在解密端又写rng(42)以为这样密钥就一致。这非常危险。不同Matlab版本对rand算法的处理不同甚至同一版本下一旦你之前有别的随机数调用干扰了rand的序列解密端生成的密钥就不一样。最稳妥的方案是直接把key1和key2矩阵保存为.mat文件解密时load进去用。我在Ubuntu和Windows双机协作时只传种子从来没有成功对通过改成传.mat文件一次就通了。5.4 恢复图像整体偏暗或者出现灰度偏移图像偏暗通常是归一化问题。原始图像乘上相位掩膜后幅度可能有缩放解密后实部范围应该回到0附近但浮点误差叠加可能导致整体偏暗。这时候检查在显示前有没有做im2double归一化。如果原始图像是uint8im2double会转换到[0,1]但如果你直接拿uint8参与复数乘法Matlab会先转成double但值域是[0,255]解密后数值范围大很多最后display时不见得有问题但PSNR计算会出错。所以统一套路是一读图就im2double处理完再裁剪到[0,1]。5.5 图像尺寸不是2的幂时速度很慢Matlab的fft2对任意尺寸都能算但非2的幂尺寸会走更复杂的混合基算法速度明显变慢。测试阶段无所谓但如果你要对512×512以上的图做大量敏感性分析建议先padding到最近的2的幂尺寸。注意padding后生成的密钥是填充尺寸的解密恢复后要把padding区域裁掉再显示原始大小。密钥保存时也要记录原始尺寸否则裁剪无法还原。% padding示例 [M, N] size(im); M2 2^ceil(log2(M)); N2 2^ceil(log2(N)); im_pad zeros(M2,N2); im_pad(1:M,1:N) im; % 加密 im_pad, 解密后取前M,前N5.6 解密后虚部太大怎么办正确密钥下恢复图像的虚部应该是接近0的小数值比如1e-15量级。如果你发现虚部和实部一个量级说明解密流程错了常见原因包括密钥不匹配、变换顺序错乱、或者密文在保存后被修改过。这时候不要直接用real掩盖问题先打印max(abs(imag(A(:))))看看虚部大小。如果很大最好从头检查加解密代码。最后再分享一点个人体会。我最初做这个课题总觉得自己“理论上懂”但一调试就暴露问题尤其是复数运算时脑子里还想着实数图像。后来我把光学4f系统的光路图画在纸上给每个器件标注对应的Matlab运算再一步步对照思路立刻清晰了。实际项目中最坑的永远不是数学公式而是矩阵维度不匹配、密钥丢失、范围溢出这些小事。建议拿到代码先跑通cameraman再尝试换自己的图片、做密钥敏感性测试和抗裁剪测试当你看到“错一个相位全图皆噪”的现象你对DRPE的理解才算真正到位。希望这篇整理能帮你在图像加密这条路上少走几步弯路。