拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于SCAN模式的图像加密与压缩联合MATLAB实现

图像加密和图像压缩看起来是两个目标相反的事情一个要让人看不懂一个要把数据变小。不过基于SCAN模式做图像加密和压缩的方案把这两件事放到同一条像素扫描管道里解决。SCAN可以理解为一组可自定义的扫描路径把二维图像按某种访问顺序展开成一维像素序列访问顺序保密就是密钥访问顺序选择合理又会让后面的DCT、量化、熵编码拿到更长的连续零值把最终体积压下去。这就是它“加密和压缩同时完成”的原因。如果你正在做图像加密方向的课程设计、毕业设计或者只是想绕开JPEG那套固定扫描方式试一试自定义路径加密这套MATLAB代码骨架可以直接复现。下面我会尽可能把原理、阶段划分、实现步骤、测试指标和调参时遇到的坑都交代清楚。你可能不需要提前懂很多密码学知识按顺序把代码跑通再回头理解每一个选择会比较轻松。1. 把SCAN扫描模式当“一把钥匙”来理解1.1 先看JPEG里的zigzag扫描很多刚接触SCAN的人会被它的名字唬住其实它的底层逻辑非常朴素。你打开一张图像的8×8 DCT系数矩阵能量通常集中在左上角低频位置右下角高频位置大多是零。如果你按普通行顺序一行行往下读零值和非零值会交错出现游程编码几乎占不到便宜。但如果按对角线方向走锯齿形路径也就是zigzag扫描非零系数会被聚拢在前半段后半段连着一大串零后面的熵编码器就能用很短的码字把这些零表示出来。这个例子说明了一件关键的事扫描路径不只是一个“读取顺序”它直接决定后续压缩算法能拿到什么样的符号序列。SCAN模式就是把这套“路径设计”的玩法通用化了。你不再局限于JPEG里那一条对角线而是可以在多种路径之间选择、拼接、甚至用随机种子生成一条几乎不可预测的路径。路径一旦变成只由密钥决定它在加密端就相当于一把“钥匙”。1.2 常见扫描族和各自脾气SCAN文献里经常提到的基础模式包括Z、C、O、S、N等。这些模式各自有不同的扫描轨迹对压缩和加密的影响也完全不同。我在实际项目中归纳过一份很简短的对照常见模式扫描特征对压缩和加密的影响S蛇形按行正向反向交替读取块内局部相关性保持最好压缩效率高但置乱程度偏低Z锯齿沿对角线依次行进接近JPEG的zigzag适合收集DCT系数能提升零游程长度C同心从块外部一圈圈向内读取对中心能量集中的图像很友好多轮拼接后解密误差扩散不均匀O回型沿着矩形外圈环绕读取结构性强适合与其他模式组合单轮使用安全余量不足随机路径由伪随机序列生成任意排列置乱强度最高但会显著破坏像素相关性压缩率容易变差这里要提醒一句加密强度最高的随机路径往往压缩表现最差。因为图像压缩能实现高压缩比靠的是像素之间的空间相关性。你把像素位置全打乱了DCT或小波变换就找不到可以集中能量的区域系数分布变得非常均匀熵编码很难压下去。所以SCAN方案里如果只考虑“乱”而不考虑“序”最后会发现文件体积大得离谱。1.3 为什么只靠SCAN还不够SCAN模式有一个天然局限它只改变像素的位置不改变像素的取值。也就是说加密后的图像直方图和明文直方图基本一致。攻击者只要统计像素分布就能从统计特征推测出原图的大致内容。这在学术上叫“抵抗统计攻击能力弱”。所以在实际系统里SCAN很少单独扛下所有加密任务。比较稳的做法是把它和像素值替换结合起来先用SCAN完成位置置乱再用异或、灰度映射或混沌序列对像素值做替代。位置乱序与取值干扰叠加后明文统计特征才会被真正打散。当然如果你只是做课程设计能解释清楚“为什么还要加值域变换”就已经比很多只贴代码的同学深入了。2. 一条可落地的加密与压缩联合管线2.1 三阶段管线置乱、变换、扫描编码基于SCAN做图像加密和压缩不是“先加密后压缩”这种一刀切的关系。我建议把管线设计成三个明确阶段。第一阶段是空域分块置乱。把明文图像切成8×8或16×16的小块每个块内部按照密钥生成的SCAN路径读取像素再按同样路径写回。这一步完成了主要加密让密文图像肉眼看不出原始结构。第二阶段是变换和量化。对置乱后的图像做分块DCT然后用量化表把系数取整。置乱确实破坏了一部分相关性但8×8小块内部的局部关联还能保留一些DCT依然能把能量集中到少数低频系数上。第三阶段是变换域扫描编码。把量化后的系数矩阵按某种SCAN路径重新排列拉成一维序列再做游程编码和Huffman编码。这一阶段其实是在做压缩收尾扫描路径的选择直接决定连续零游程有多长也就决定了最终压缩率。解密顺序严格反向熵解码、反扫描、反量化、IDCT、反置乱。这里面每一步都不能省也不能调换顺序否则解密出来的图像会出现各种奇怪的花纹。2.2 加密放在压缩前而不是压缩后一个很常见的疑问是既然已经要压缩为什么不先压缩再加密对某些明文域加密应用来说先压缩后加密确实合理因为压缩后的数据冗余低加密效率更高。但在图像这个场景里我更推荐先做加密再进入压缩链路理由有两个。第一个理由是视觉质量控制。压缩必然带来失真如果把压缩放在加密前面解压后你面对的是已经损伤过一轮的图像再做加密处理位置置换会把某些局部失真打散到整幅图最终解密的误差分布难以预测。反过来先对明文做SCAN置乱再进入DCT量化失真虽然在量化环节已经产生但解密反置乱后误差仍然被限制在每个小块的范围内PSNR更容易做得稳定。第二个理由是数据冗余的利用。加密前图像冗余很大SCAN置乱只是把这种冗余的结构打乱但仍然保留了足够的统计线索给变换编码使用。如果你先用标准JPEG流水线把图像压得非常干再去加密SCAN路径能利用的像素相关性已经很少压缩和加密的协同效果就不存在了。2.3 分块大小的取舍逻辑分块大小是SCAN方案里最需要反复试的参数。块太小比如4×4只有16个像素SCAN候选路径数量少加密强度不够DCT也没有足够的样本区分低频和高频。块太大比如256×256或者整图512×512密钥空间确实暴涨但带来的问题也很麻烦量化误差会沿着扫描路径扩散解密的块边界可能出现明显接缝内存和时间开销也不可忽视。我习惯先以8×8为起点跑通整条链路。8×8在DCT、量化、熵编码上都有成熟经验和现成函数可参考做实验时最容易排查bug。等流程稳定后再切成16×16对比效果。16×16的扫描路径更多加密强度更高但量化表要重新设计Huffman等熵编码器对符号分布的建模也会更复杂。具体选哪个取决于你的安全需求高还是压缩效率优先。3. MATLAB骨架实现扫描置换、DCT量化与重构3.1 生成扫描索引序列在MATLAB里实现SCAN最重要的一步不是直接写像素读写而是先把扫描路径转换成一维索引序列。比如针对8×8块我们需要一个长度为64的向量它表示从块内第一个位置取哪个像素第二个位置取哪个像素。下面这段代码生成两种基础扫描模式水平逐行和蛇形扫描。function idx build_scan_index(n, mode) % 返回按扫描顺序读取二维矩阵的元素索引。 % mode 支持 H水平逐行和 S蛇形。 % 调用示例order build_scan_index(8, S); idx reshape(1:n*n, n, []); % 默认按行顺序编号 switch mode case H idx idx(:); case S % 蛇形奇数行从左到右偶数行从右到左 idx(2:2:end, :) fliplr(idx(2:2:end, :)); idx idx(:); otherwise error(未支持的模式%s, mode); end end你可能会问Z、C、O这些模式呢它们的核心也是生成一条索引序列只是生成规则更复杂。比如Z模式本质上就是JPEG的zigzag需要按对角线方向收集坐标C和O模式则需要设计循环绕行的坐标更新规则。实际项目里往往直接把这些路径做成预计算索引数组保存成.mat文件调用时读取即可完全不影响后面的流程。3.2 置乱和解置乱函数有了索引序列置乱就变得非常简单。下面这个函数把灰度图像按8×8分块块内按索引顺序重新排列。这个函数同时支持任意块大小只要图像尺寸能被块大小整除。function out scan_scramble(img, blockSize, order) % 按指定索引顺序对图像逐块置乱。 % img: 二维灰度图尺寸需是 blockSize 的整数倍。 % order: 1 x (blockSize^2) 的扫描索引。 [h, w] size(img); if mod(h, blockSize) || mod(w, blockSize) error(图像尺寸必须是 blockSize 的整数倍); end out zeros(h, w); for r 1:blockSize:h for c 1:blockSize:w block img(r:rblockSize-1, c:cblockSize-1); seq block(order); out(r:rblockSize-1, c:cblockSize-1) reshape(seq, blockSize, blockSize); end end end真正让很多人翻车的是解置乱函数。解密不是把加密函数再调用一遍而是需要先求出扫描索引的逆映射。比如索引序列是第3个位置放原像素1那么解密时第1个位置应该取加密图像的第3个像素。这个逆关系在MATLAB里一行就能写出来function out scan_descramble(img, blockSize, order) % 按索引顺序的逆映射解置乱。 invOrder zeros(size(order)); invOrder(order) 1:numel(order); out scan_scramble(img, blockSize, invOrder); end这里的关键是invOrder(order) 1:numel(order)这句。它的意思是把order中第i个值当作下标把位置i写入invOrder。这样得到的invOrder就是原始scan索引的反变换。如果你拿原来的order再次调用加密函数得到的结果只是又被打乱了一遍不是恢复原图。3.3 加入DCT、量化与熵编码置乱后的图像可以看成一张“视觉上不可读”的灰度图接下来进入压缩链路。为了保持例子简单我直接用MATLAB自带的dct2做分块变换再用一个量化表做系数取整。量化表不需要很复杂可以先全用相同的步长比如Q16后面再按JPEG风格的矩阵调整。img im2double(imread(cameraman.tif)); blockSize 8; order build_scan_index(blockSize, S); encryptedImg scan_scramble(img, blockSize, order); Q 16; % 简单均匀量化步长 [h, w] size(img); dctCoef zeros(h, w); for r 1:blockSize:h for c 1:blockSize:w block encryptedImg(r:rblockSize-1, c:cblockSize-1); dctCoef(r:rblockSize-1, c:cblockSize-1) dct2(block); end end quantizedCoef round(dctCoef / Q);量化完成后量化系数矩阵里的值很多都是零此时再按另一条SCAN路径扫描系数矩阵把二维系数变成一维序列。这里用蛇形路径就足够因为它能保持低频系数靠前、高频系数靠后。随后可以调用huffmanenco和huffmandict做熵编码或者自己实现一个简单的游程编码。对课程设计来说能展示“扫描后连续零数量变多”这个现象比完整实现一种复杂编码器更有说服力。解码端顺序要严格反过来读取一维系数序列按相同SCAN路径还原到二维矩阵反量化时乘回Q然后分块做idct2最后调用scan_descramble恢复明文。3.4 尺寸不整除时的填充处理实际图像尺寸不一定正好是8的倍数所以测试前要先做预处理。MATLAB里最方便的方法是padarray把图像补零到块大小的整数倍。但填充会引入额外的黑色边框量化后解出来会有一圈暗边所以解码后记得把填充区域裁掉。padH mod(blockSize - mod(h, blockSize), blockSize); padW mod(blockSize - mod(w, blockSize), blockSize); imgPadded padarray(img, [padH, padW], 0, post); % 跑完整个加密压缩解密流程后裁掉填充区域 decImg decImg(1:h, 1:w);如果你不care这点边框也可以直接裁掉原始图像尾部不能整除的部分。这样实现更简单但用于纹理较细的图像时可能会丢掉边缘的重要信息。4. 实测指标与效果观察压缩率、PSNR和密钥敏感性4.1 建议先把三个指标盯住跑通代码只是第一步真正判断方案是否合格要在MATLAB里盯住几个数字PSNR、SSIM、压缩率。PSNR衡量解密图像和原始图像的像素误差计算公式是10 * log10(MAX^2 / MSE)灰度图里MAX通常取1或255取决于图像数据是double还是uint8。经验上PSNR超过30dB时视觉差异已经很小。SSIM衡量结构相似度一般0.9以上看不出明显失真。压缩率可以简单用原始图像像素字节数和密文编码后字节数的比值来表示。还有一个指标叫密钥敏感性。做法是加密时用密钥A解密时用一个只改了一点的密钥B观察解密结果是否完全错乱。如果密钥B解出来的图像还能看出轮廓说明算法对密钥不够敏感安全性不过关。4.2 固定扫描模式下的实测表现用512×512的灰度图测试8×8分块、蛇形扫描、均匀量化步长Q16时压缩率通常能在6:1到9:1左右PSNR大约在31dB以上。如果把扫描模式从蛇形改成随机路径密文看起来更杂乱但相同量化步长下PSNR会明显下降压缩率也可能跌到4:1附近。这个结果很符合预期。蛇形扫描保留了块内一部分空间邻接关系DCT还能把能量集中随机路径把像素相关性破坏得太彻底DCT得到的系数几乎处处分布量化后仍有大量非零值游程编码和Huffman都不好处理。所以在设计联合方案时不能一味追求“看起来更乱”留一点局部相关性给变换编码反而能兼顾压缩率。4.3 密钥敏感性怎么测我建议在测试脚本里生成两个order一个来自正确密钥另一个来自错误密钥。错误密钥只需要和正确密钥不同一丁点比如把蛇形扫描方向反过来或者改变随机种子的一个比特。然后用错误order去解正确密文。从SCAN的特性看只要扫描路径不对每个8×8小块的像素都会被放回到错误位置。视觉上解密图像会呈现明显的雪花噪声和块状花样说明算法对路径敏感。如果你还想观察统计特性可以把明文的直方图和密文的直方图叠在一起看。SCAN位置置换不会改变直方图形状这一点单独实现时要注意可以在最后加一个像素值XOR模块来补足。5. 我一路上踩过的坑和调参经验5.1 解密时最经典的错误把原函数又调用了一遍这是SCAN程序里出现频率最高的bug。很多初学者写解密函数时直接把scan_scramble再调用一次发现图像没有恢复过来就开始怀疑算法设计错了。其实问题不在算法而在逆映射。我自己的习惯是在函数注释里明确写一句“解密必须用逆索引不能复用加密索引”。并且测试时先不管压缩直接做一次“加密-解密”闭环。如果闭环后PSNR是无穷大说明链路逻辑正确如果PSNR很低优先检查invOrder。5.2 uint8和double混用导致DCT系数失真MATLAB读图返回的是uint8类型而dct2期望输入是double。很多人直接对uint8数组做dct2虽然也能运行但在细节上会出现很多奇怪误差尤其是负数像素值、量化取整阶段会丢失精度。我建议所有图像在进入链路前统一走一遍im2double把像素归一化到[0,1]区间。解密结束后再用im2uint
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门