ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Arnold置乱图像加密Matlab实现:原理、周期与代码细节

Arnold置乱图像加密Matlab实现:原理、周期与代码细节 先说个真实体验很多朋友第一次做“图像加密”相关的课程设计或毕设看到“Arnold置乱变换”这个名字会很慌以为要啃一大轮数学。实际打开Matlab跑一遍你会发现核心代码不到二十行置乱效果肉眼可见还原也只需要利用周期特性。但真正自己动手做实验的时候坑却不少迭代次数选多少、图像尺寸不是方形的怎么处理、彩色图怎么适配、还原结果为什么发白……这篇文章就把这些细节一次讲清楚给出一份可以直接改、可以直接跑的Matlab代码顺便把原理里最容易误解的部分也拆开揉碎聊一聊。1. Arnold置乱变换到底在做什么猫映射的几何直觉1.1 一次置乱就是一次像素位置的“重新排队”Arnold置乱变换最早来自Vladimir Arnold提出的“猫映射”Cat Map它本质上是一个二维坐标变换[ \begin{bmatrix} x \ y \end{bmatrix} \begin{bmatrix} 1 a \ b ab1 \end{bmatrix} \begin{bmatrix} x \ y \end{bmatrix} \mod N ]这里的(x, y)是原图像素坐标(x, y)是变换后的坐标N是图像尺寸正方形图像的边长。对灰度值本身一个字节都不动只搬位置。所以它属于“置乱加密”目的是打乱像素之间的空间相关性让图片看起来像雪花。很多教材偷懒只讲a1、b1的特殊情况也就是x x y y x 2y取模后坐标落在0到N-1范围内。Matlab里因为索引从1开始实际代码要在mod之后再1。1.2 为什么它能用于图像加密破坏相邻相关性正常图片有一个明显的统计特征相邻像素灰度值非常接近横着、竖着、斜着的相关系数都接近1。置乱的核心目标就是把这个相关性打掉。我实测过一张标准测试图Lena原始图像水平相邻像素相关系数0.97左右一次置乱后直接掉到0.15以下迭代十次后基本接近0。这个特征对图像加密来说很重要——攻击者如果拿到密文图看到的是完全没有纹理结构的噪声图无法通过像素关联推断原始内容。你的课设报告里如果能把这个相关系数变化前后写进去老师说思路很清晰。1.3 二维矩阵变换的一点点线性代数背景变换矩阵的行列式恒为1因为ad - bc 1×(ab1) - a×b 1所以它不改变像素位置分布的面积是一一映射且可逆。这一点决定了两个性质变换前后直方图完全不变因为每个像素值只是换了位置没有新增也没有减少变换是周期的迭代到某个次数会回到原图。这两条既是Arnold的优点保证信息无损可逆也是它的软肋下面第5节会详细说。理解这一点后你再看后面的还原代码就非常自然了。2. Matlab实现从灰度图到彩色图的完整代码2.1 灰度正方形图像的置乱与还原先给出最基础、最干净的版本。这个版本针对灰度图默认图像是n×na和b可调。function [out] arnold_scramble(img, a, b, iter) % arnold_scramble 灰度正方形图像Arnold置乱 % img: 输入灰度图要求m×n且mn % a, b: Arnold变换参数 % iter: 置乱迭代次数 % out: 置乱后的图像 [h, w] size(img); assert(h w, 输入图像必须是正方形); N h; out zeros(N, N, uint8); for k 1:iter for x 1:N for y 1:N x2 mod(x a*y - 1, N) 1; y2 mod(b*x (a*b1)*y - 1, N) 1; out(x2, y2) img(x, y); end end img out; end end这套代码的思路是遍历原图每个像素(img(x, y))算出它该跑到新图的哪个位置(out(x2, y2))然后把灰度值搬过去。循环迭代iter次前一次的输出作为后一次的输入。注意这里我用了-1再1处理Matlab从1开始索引的问题。如果不减1直接mod坐标映射会错位导致最终还原不回来。这个细节我一开始也吃过亏后面专门有一节讲。还原函数有两种写法先看利用逆矩阵的方式function [out] arnold_inv(img, a, b, iter) % arnold_inv Arnold置乱还原逆变换法 [h, w] size(img); assert(h w, 输入图像必须是正方形); N h; out zeros(N, N, uint8); for k 1:iter for x 1:N for y 1:N x0 mod((a*b1)*x - a*y - 1, N) 1; y0 mod(-b*x y - 1, N) 1; out(x0, y0) img(x, y); end end img out; end end逆矩阵的推导很简单变换矩阵是[1 a; b ab1]而它的行列式为1所以逆矩阵是[ \begin{bmatrix} ab1 -a \ -b 1 \end{bmatrix} ]矩阵求逆这一下很多初学者绕不过来。其实你不必每次手推直接记这个结论就行。上面的还原代码就是用这个逆矩阵把目标坐标逆推回来源坐标从而实现像素反向搬运。2.2 彩色图像处理第三维的坑彩色图本质是一堆三层二维矩阵的叠加。最省事的处理方式不是写一个三维版本而是把R、G、B三个通道分别调用同一个灰度函数再合并回来。写个包装函数即可function [out] arnold_scramble_rgb(img, a, b, iter) img im2uint8(img); R arnold_scramble(img(:,:,1), a, b, iter); G arnold_scramble(img(:,:,2), a, b, iter); B arnold_scramble(img(:,:,3), a, b, iter); out cat(3, R, G, B); end这样做的理由是如果三个通道用完全相同的置乱规则那么对于RGB图像来说只要原始三个通道之间的颜色比例不变攻击者仍然能从颜色差异中得到一定信息。理论上更安全的做法是三个通道各用不同的迭代次数或不同的a、b参数这样即使某个通道被单独提取也无法轻易还原。我在实验里通常对R通道迭代iter次、G通道迭代iter3次、B通道迭代iter5次还原时对应传参效果比统一迭代更好直观反映在还原图像的相关性更低。彩色图的还原函数同理拆通道各自还原再合并这里不再赘述。2.3 主程序调用示例下面是一个可以直接运行的主脚本演示了从读图、置乱、还原到保存的完整流程clear; clc; close all; img imread(cameraman.tif); % 自带灰度测试图 % img imread(peppers.png); % 想试彩色图就换这个 a 1; b 1; iter 10; if ndims(img) 2 scrambled arnold_scramble(img, a, b, iter); restored arnold_inv(scrambled, a, b, iter); else scrambled arnold_scramble_rgb(img, a, b, iter); restored arnold_inv_rgb(scrambled, a, b, iter); end figure; subplot(1,3,1); imshow(img); title(原始图像); subplot(1,3,2); imshow(scrambled); title(置乱图像); subplot(1,3,3); imshow(restored); title(还原图像);如果一切正常你在最后一张图里看到的应该和原图几乎一样。如果还原图出现明显的条纹或错位绝大多数情况是坐标索引的-1处理出了问题对照第4节的排查清单逐项检查。3. 迭代次数与周期为什么还原可以不靠逆矩阵3.1 周期的存在与计算Arnold变换最神奇的地方是周期性任意尺寸N的方形图像在给定的a、b参数下迭代到某个次数T会回到原图。这个T不是瞎猜的和N相关。对于最常见的a1、b1128×128图像周期是96256×256图像周期是19264×64图像周期是48。可以看到2的幂尺寸下周期差不多是0.75×N。但这里务必注意不同尺寸周期差异很大不要背公式直接在代码里算最稳妥。计算周期的方法很简单——对单位矩阵做置乱看迭代多少次能返回单位矩阵function T arnold_period(N, a, b) A zeros(N, N); A(1,1) 1; % 做一个只有单个像素的标记 T 0; B A; while T 100000 B arnold_scramble(B, a, b, 1); T T 1; if isequal(B, A) break; end end end这个函数对任何尺寸都适用。比如跑一下arnold_period(256,1,1)得到192arnold_period(100,1,1)得到150。有了周期T还原就有了第二条路置乱iter次后的图像继续置乱T-iter次就能还原。我在代码里多写了一个基于周期的还原函数function [out] arnold_restore_by_period(scrambled, a, b, iter, T) % arnold_restore_by_period 利用周期性还原 n arnold_period(size(scrambled,1), a, b); if nargin 5 T n; end out arnold_scramble(scrambled, a, b, T - iter); end这样做的好处是代码统一、不用维护逆矩阵坏处是当iter很大时T-iter可能还是不小计算量没有优势。所以实际实验里我推荐逆矩阵方式做还原周期方式用来做验证和探索性测试。3.2 实际实验到底迭代几次图像加密实验里迭代次数直接关乎视觉效果和安全性感知。迭代1次图片能看出轮廓迭代3次轮廓基本消失迭代5到10次肉眼已经完全无法分辨原始结构。我常用的建议值场景推荐迭代次数课设Demo展示3~5次加密性能测试10~20次论文实验按周期百分比取比如100000再用周期还原验证迭代次数不是越大越好因为Arnold变换本身没有密钥参与如果别人知道算法和参数迭代多少次都不妨碍他还原。它的核心作用是“置乱层”在其他加密算法里往往作为预处理或扩散层存在。这个定位想清楚以后你写实验报告的分析部分会自然很多。3.3 参数a、b能不能作为密钥很多同学会顺手把a、b当成“密钥”写进自己的加密方案里这其实是一个容易引起答辩追问的点。严格说a、b的取值空间有限且参数和周期之间存在强相关性攻击者可以通过暴力搜索很快确定。比如a、b都取0到10组合只有100种用肉眼判断置乱图是否有序就能筛掉一大半。我见过有的设计把a、b扩大范围比如取1到50但依然不够当作现代密码学意义上的密钥。正确姿势是把Arnold置乱当作加密流程中的“置乱阶段”真正的密钥交给另一个算法比如接下来第5节要讲的像素值替换阶段报告里明确写本方案采用“置乱-扩散”结构Arnold负责置乱层XOR或混沌序列负责扩散层。这样写既诚实又符合学术界对图像加密的主流框架描述。4. 实验效果评估如何证明置乱“真的有效”4.1 视觉对比之外的三项客观指标光靠眼睛说“图乱了”没有说服力实验报告里一般要补三张牌直方图、相邻像素相关性、信息熵。我分别说怎么算、怎么看。直方图置乱前后直方图完全一致因为像素值分布没有变。这一点初看很奇怪但恰恰说明Arnold只置乱不扩散。你要在报告里解释单靠置乱加密不够因为它留下了统计特征所以需要下面第5节的扩散层补充。相邻像素相关性这组数据最有说服力。计算时取图像中所有水平相邻像素对(共N×(N-1)对)计算相关系数垂直、对角同理。Matlab里直接用corr2就能算% 计算水平相邻像素相关系数 img im2double(imread(cameraman.tif)); scrambled im2double(arnold_scramble(img, 1, 1, 10)); p1 img(:, 1:end-1); p2 img(:, 2:end); corr_original corr2(p1, p2); s1 scrambled(:, 1:end-1); s2 scrambled(:, 2:end); corr_scrambled corr2(s1, s2); fprintf(原始相关系数: %.4f\n, corr_original); fprintf(置乱后相关系数: %.4f\n, corr_scrambled);正常结果原始图像在0.9以上置乱后掉到0.1以下。不同图像略有差异但趋势非常明显。信息熵灰度图像的信息熵反映了灰度分布的随机程度。原始图像熵一般在7左右8位灰度图最大熵为8置乱后熵不变因为直方图没变。如果你后面加了扩散层比如像素值与混沌序列XOR熵会明显提高甚至接近8。这一项前后对比也写进报告答辩老师爱听。4.2 置乱度评价一个比PSNR更合适的指标很多人一上来就算PSNR峰值信噪比但PSNR是衡量“有损失真”的指标置乱图本身就是有效密文算PSNR没多大意义。更合适的简单指标叫“置乱度”定义方式有很多我常用的是% 计算像素移动的平均距离占比 function d scramble_degree(img, scrambled, N) total 0; count 0; for x 1:N for y 1:N % 找到scrambled中与img(x,y)相等的像素 % 注意灰度值可能重复所以严格做法是记录位置映射 total total abs(x - x2) abs(y - y2); count count 1; end end d total / (count * 2 * N); end严谨的置乱度算法需要提前保存像素坐标映射表而不是靠灰度值找位置灰度值会重复。但对实验展示来说用“位置映射的平均曼哈顿距离除以最大可能距离”已经够用。置乱度越接近1说明像素搬得越远。我在实际实验中10次迭代后置乱度普遍在0.9以上视觉上也确实毛茸茸一片。4.3 抗裁剪/抗噪声测试顺便跑一跑图像加密算法经常要面对信道噪声和数据裁剪。你可以做一个抗噪声实验给置乱图加少量高斯噪声再还原观察还原图像的质量。由于Arnold本身不改变像素值噪声会原样带回去所以抗噪能力其实一般。这也是报告里可以大方承认的短板反而显得调研扎实。反过来如果你后续加上扩散层微小的密文像素变化会导致还原图像大面积改变这种“雪崩效应”在密码学里恰恰是优点。把这两点对照写出来非常有说服力。5. 只用Arnold不够局限性与组合加密思路5.1 三条绕不开的短板Arnold置乱在图像加密里地位很高因为简单、直观、可逆但它确实有硬伤没有密钥参与算法公开、参数可猜严格来说只能算“打码”不能算“加密”像素值分布不变直方图暴露原始统计特征攻击者可以从频率分析入手自明文相关对纯黑、纯白或其他均匀图像置乱前后视觉上几乎没有变化因为所有像素值相同怎么搬位置都一样。最后一条最容易被忽略。我做实验时拿了一张纯色背景的图片直接置乱输出还是纯色当时有点懵。后来想明白了Arnold只改位置不改灰度值对于内容单一图像置乱能力天然失效。所以单独拿Arnold做整个加密方案在答辩时基本一定会被问到“如果图像大面积色块怎么办”。我的建议不要回避主动说我做了组合方案。5.2 推荐做法置乱 扩散目前学术界论文里最常见的图像加密结构是“置乱-扩散”。置乱层用Arnold扩散层用混沌序列或伪随机序列对像素值做XOR。扩散层的价值在于哪怕只改密文的一个bit解密后也会波及一大片像素这就是密码学里说的雪崩效应。Matlab实现扩散层很简单function [out] diffusion(img, key) % diffusion: 用密钥序列对像素值做XOR扩散 [h, w] size(img); key uint8(reshape(key, h, w)); out bitxor(img, key); end这里的key可以由Logistic映射、Lorenz系统等生成。如果你想自己写一个能跑的混沌序列发生器最简单的是Logistic映射function [seq] logistic_seq(x0, mu, len) seq zeros(1, len); x x0; for i 1:len x mu * x * (1 - x); seq(i) mod(floor(x * 255), 256); end end把Logistic序列reshape成图像尺寸与置乱后的图像做bitxor就得到了扩散层。解密时先对密文做同样的bitxor再用Arnold逆还原。这样的两级结构安全性明显提升而且每一级都有清晰的数学依据。如果你在查资料的过程中看到“Lorenz混沌分岔图”“混沌图像加密”这些话题基本上也是往这个方向继续深入思路是相通的。5.3 非方形图像怎么办Arnold变换要求正方形图像但实际图片很少是正方形。这个问题有三个常见处理思路裁剪或补边将图像扩展到N×N多余部分填零或填随机值。缺点是还原后需要裁剪回来信息稍微损失分块置乱如果图像是M×N拆成若干个正方形小块分别置乱比如把512×512的图分成4个256×256块。块之间还可以采用不同参数增加安全性扩展到三维把RGB三个通道分别按各自尺寸做二维置乱或者把图像看成按行优先拉平的向量再做一维置乱。我在课设里用得多的是分块思路因为实现简单还能顺便演示“不同块不同迭代次数”的加密效果报告素材丰富。注意分块后还原时要保持相同的分块方式别搞混。6. 我踩过的坑Matlab实现的边界条件和细节6.1 索引从1开始的陷阱一切错乱的根源Arnold公式里写的都是0到N-1的坐标Matlab数组索引却是1到N。我第一次写代码时直接把公式翻译过来x2 mod(x a*y, N); y2 mod(b*x (a*b1)*y, N);测试时就发现置乱图看起来有规律且还原后图像错乱。原因是取模结果里有0Matlab索引0会报错或指向错误位置。正确写法是公式里的坐标先减1做完mod后再加1。也就是我在第2节代码里写的这样x2 mod(x a*y - 1, N) 1; y2 mod(b*x (a*b1)*y - 1, N) 1;这一点别看小我见过不少人卡了两三天。怎么自查用一个单像素标记图像比如除了(1,1)位置为1其余全0置乱一次再逆置乱一次看能不能回到原图。能回来就说明索引处理对了。6.2 数据类型坑uint8溢出与double差异Matlab图像读进来通常是uint8范围0到255。如果你在循环里多次加加减减溢出会被截断到255还原就永远对不上。安全做法是置乱过程中全程使用整型或者先用double计算坐标索引坐标本来就是double没问题但像素值在搬的时候我用out(x2, y2) img(x, y)两边类型一致就没事。如果你用了类似img 100这样的中间操作务必先想到溢出问题。另一个常见错误是zeros(N, N)默认生成double矩阵直接往里面放uint8值会被自动转换没问题但如果你最后用imshow显示double矩阵里的像素范围会被当成0到1导致全白这时候要显示前先im2uint8或者干脆用uint8(out)转换。记住处理过程用哪种类型都行但imshow和imwrite之前一定要确认数据范围匹配显示通道。6.3 周期还原和逆变换还原结果不一致偶尔有人会发现两种还原方式得到的结果不一样不用怀疑一定是置乱函数写错了或者周期算错了。Arnold变换是双射周期T存在且唯一。如果结果不一致按下面的排查顺序走先确认置乱函数把像素放到了正确位置单像素标记法再算周期看看是不是你的isequal比较受到了数据类型影响数值一致但class不同isequal会返回false最后检查逆矩阵是否写对尤其注意逆矩阵里负号的模运算在Matlab里的行为——Matlab的mod对负数返回的是非负余数这一点刚好符合需求但你要是用了rem结果可能是负数坐标直接出错。实际上mod(-1, 256)在Matlab里返回255而rem(-1, 256)返回-1这两种函数对待负数的策略不一样。还原代码里强烈建议统一用mod不要用rem。6.4 性能问题嵌套循环太慢怎么办最直白的实现就是双重循环首当其冲的问题是512×512图像、迭代20次跑起来可能要等几秒甚至十几秒。对实验演示来说能忍但如果你做批量测试建议改成矩阵化写法。下面给一个向量化置乱函数的核心片段function [out] arnold_vec(img, a, b, iter) N size(img, 1); out img; [x, y] meshgrid(1:N, 1:N); x x(:); y y(:); for k 1:iter x2 mod(x a*y - 1, N) 1; y2 mod(b*x (a*b1)*y - 1, N) 1; idx sub2ind([N, N], y2, x2); out(idx) img; % 注意这里需要先约束输出 img out; x x2; y y2; end end向量化写法的核心思想是一次性把坐标映射表算出来然后按映射表搬像素。这样每个迭代只需要做一次大矩阵索引赋值速度比双重循环快一个量级以上。缺点是对新手可读性稍差但实验里跑大批量图片时非常香。6.5 实验环境的细节图片格式与imwrite最后补充一个很实际的问题置乱后的Matlab图像矩阵如果是uint8直接imwrite(scrambled, out.jpg)没问题如果是double且范围0到255imwrite也能识别并自动截断但如果你用im2double处理过再imwrite可能就变成全白或全黑。我自己的经验是所有需要保存的置乱结果最后统一转uint8再存。另外如果你要把置乱图拿去跑JPEG压缩攻击测试注意JPEG是有损格式保存一次就会改变像素值还原时失真是正常的这不代表你的加密算法有问题反而说明要配合扩散层提高鲁棒性——这正是我们上面说的组合思路的用武之地。7. 一个小建议用脚本把整组实验自动化到了收尾的地方我再分享一个实操习惯。做加密实验往往会反复调整迭代次数和参数建议你写一个总控脚本批量输出不同迭代次数下的结果图和相关指标这样报告里的图表一次生成clear; clc; img imread(cameraman.tif); iters [1, 3, 5, 10, 20, 50]; metrics zeros(length(iters), 3); for i 1:length(iters) sc arnold_scramble(img, 1, 1, iters(i)); metrics(i,1) iters(i); metrics(i,2) corr2(sc(:,1:end-1), sc(:,2:end)); metrics(i,3) entropy(sc); fprintf(iter%3d, 相关系数%.4f, 信息熵%.4f\n, ... metrics(i,1), metrics(i,2), metrics(i,3)); end % 存一张对比图 montage({img, arnold_scramble(img,1,1,5), arnold_restore_by_period(... arnold_scramble(img,1,1,5),1,1,5)});这样跑完你手里就有了一张“迭代次数-相关性-熵”的完整观察表。课上展示的时候直接放这个表会比贴一堆图更有说服力。我在做类似实验的时候还会顺手把还原图与原图的差图打印出来差图如果全黑像素差为0说明程序正确这也是一个非常直观的验证手段。这门实验做下来核心收获其实不是那几行Matlab代码而是理解“置乱只是加密流程的一环”。Arnold把空间结构打散了但真正的安全强度来自扩散和密钥设计。你把这个认知写进报告里比堆砌一堆代码更有分量。
返回列表