ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

压缩感知图像加密的Matlab实现:测量矩阵充当密钥的完整解析

压缩感知图像加密的Matlab实现:测量矩阵充当密钥的完整解析

压缩感知这几年在图像加密领域真的很火,尤其是"测量矩阵同时充当密钥"这种思路,用一套流程把压缩采样和加密合二为一。我在一个图像传输项目中试过用Matlab实现这种混合算法,踩了一堆坑之后总算把流程跑通了,这篇就专门聊聊完整的实现路径和那些文档里不会写清楚的细节。适合正在做压缩感知图像加密方向研究、或者想把手头加密算法升级为"压缩+加密一体"方案的读者,内容偏工程落地,原理也会顺带讲透。

1. 为什么图像压缩加密要"掺和"压缩感知

1.1 传统加密与压缩感知加密的本质区别

传统的图像加密流程通常是这样的:先压缩(JPEG、JPEG2000这类),再把压缩后的比特流交给AES、DES这类对称加密算法处理。整个链路是"压缩在前、加密在后",各干各的。这种做法的最大问题是:压缩后的数据存在明显的结构特征,流量分析可以轻易判断出源数据是图片、视频还是文本,安全性和效率是分开算的两笔账。

压缩感知(Compressed Sensing,简称CS)把这事儿从根本上改掉了。它基于一个核心思想:如果信号在某个变换域(比如小波域、DCT域)是稀疏的,那么只需要少量线性测量值就能完整重构原始信号。用公式表达就是:

y = Φx = ΦΨs

其中x是原始图像信号,Ψ是稀疏基,s是稀疏系数,Φ是测量矩阵,y是最终拿到的测量值。关键点在于,这里的Φ如果由密钥控制生成,那么测量过程本身就是加密过程。不知道密钥、拿不到正确的Φ,攻击者拿到的y就是一堆毫无意义的数值,连重构的入口都找不到。

这和传统加密有着本质区别。传统加密是在已知明文结构的前提下打乱数据;压缩感知加密则是不让你知道"数据该怎么组织"。前者是密码学层面的混淆和扩散,后者是信号处理层面的隐藏和压缩,两者结合的混合算法,安全性自然叠加。

1.2 测量矩阵当作密钥:这个想法到底可不可行

很多人第一反应是:测量矩阵的维度动辄几百乘几千,当密钥用会不会太大了?密钥管理怎么搞?

这里就要引入一个关键设计:测量矩阵不需要完整存储和传输。只要让矩阵由一组混沌系统的初始值和控制参数决定,那么真正需要保密的就只是这几个数。接收方拿到同样的初始值,用相同的生成规则重建测量矩阵,就能完成重构。

这个做法相当于把"密钥空间"建立在混沌系统的初值敏感性上。混沌系统有个很著名的特性:初值差一点点,迭代出来的序列很快就完全分叉。拿经典的Logistic映射来说:

x(n+1) = μ * x(n) * (1 - x(n))

当μ在3.9附近、x0取某个小数时,系统进入混沌状态。只要x0变化哪怕10^(-15),迭代十几次之后序列就完全不同。这个特性天然适合做密码系统:密钥空间可以做到极大,且生成的测量矩阵具有类似随机的性质。

从我实际测试的情况看,想用测量矩阵当密钥,最关键的不是"可不可行",而是怎么保证生成的矩阵满足压缩感知重构条件。也就是常说的RIP性质(有限等距性质)和测量矩阵与稀疏基之间的不相关性。这一点决定了重构效果好不好,下面专门展开。

2. 密钥控制测量矩阵的三种典型构造方案

2.1 基于混沌序列的部分哈达玛矩阵

这是我最早尝试的方案,也是入门最容易理解的一种。思路是先构造一个完整的n阶哈达玛矩阵H(元素只有+1和-1,天然正交),然后根据混沌序列的排序索引,从里面挑出m行组成测量矩阵。

为什么选哈达玛矩阵?因为它和大多数稀疏基(DCT、小波)都有很好的不相关性,理论上有较强的RIP保证。Matlab里可以直接用函数生成哈达玛矩阵,配合混沌序列做行选择。

function Phi = HadamardChaoticMeasure(m, n, key) % key = [x0, mu],Logistic混沌映射的初值和参数 % 从n阶哈达玛矩阵中按混沌序挑选m行作为测量矩阵 x0 = key(1); mu = key(2); % 生成足够长的混沌序列用于排序 len = n * 2; x = zeros(1, len); x(1) = x0; for t = 2:len x(t) = mu * x(t-1) * (1 - x(t-1)); end [~, idx] = sort(x(1:n)); % n必须是2的幂,或者用阿达玛矩阵的变形 H = hadamard(n); Phi = H(idx(1:m), :); % 列能量归一化 Phi = Phi / sqrt(m); end

这里有个实战中的小坑:Matlab的hadamard函数要求n必须是2的幂次(或者能被4整除的数),当图像分块尺寸不满足条件时会直接报错。我的处理办法是把图像分块成16×16或32×32的小块,每块单独做压缩感知处理,这样n的取值正好是256或者1024,完全满足条件。

2.2 基于Logistic混沌序列直接构造测量矩阵

第二种方案更"激进":不用现成的正交基,直接用混沌序列的递推值构成矩阵元素。常见做法是先生成一段混沌序列,然后按行填入矩阵。

function Phi = LogisticSensingMatrix(m, n, key) x0 = key(1); mu = key(2); % 跳过前面暂态过程,避免初值附近的弱混沌段 burn_in = 500; x = x0; for t = 1:burn_in x = mu * x * (1 - x); end % 生成 m*n 个混沌值构成测量矩阵 total = m * n; seq = zeros(1, total); seq(1) = x; for t = 2:total x = mu * x * (1 - x); seq(t) = x; end Phi = reshape(seq, m, n); % 零均值化 + 单位方差归一化 Phi = Phi - mean(Phi(:)); Phi = Phi / std(Phi(:)); end

注意代码里的burn_in环节。混沌系统在开始迭代的前几百步,序列和初值的关联还比较"紧",翻译成大白话就是:如果你直接取前面几个数做矩阵元素,可能出现两把不同的密钥生成的矩阵前几行几乎一样的情况。这就等于变相缩小了密钥空间。我通常丢弃前500次迭代结果,这个值也可以当作可调参数。

这种方案的优点是灵活:m和n可以任意指定,不用像哈达玛方案那样受维度约束。缺点是矩阵的正交性变差了,重构时会引入额外的噪声。从我的实验数据看,在压缩比(m/n)为0.5时,重构PSNR通常比哈达玛方案低1到2dB。所以在这个方案之上,我一般会加一步Gram-Schmidt正交化,代价是多花一点计算时间,但对重构质量的提升非常明显。

2.3 基于循环矩阵与密钥扩展的构造方案

第三种方案是工程里用得比较多的:构造一个循环矩阵,只需要生成第一行元素,其余行由第一行循环移位得到。这种矩阵的好处是乘法可以用FFT加速,对大尺寸图像的压缩感知处理非常友好。

密钥在这里怎么用?核心思路是用混沌序列生成第一行的元素值,同时用另一个独立的混沌流决定循环移位的步长和方向,相当于把密钥拆成了两个部分。第一行密钥控制"矩阵内容",移位密钥控制"行排列结构",两层嵌套比单层方案更稳。

function Phi = CirculantChaoticMeasure(m, n, key) % 生成 m x n 循环测量矩阵,密钥包含两个混沌初值 row_key = key(1:2); shift_key = key(3:4); first_row = zeros(1, n); x = row_key(1); mu = row_key(2); for k = 1:n x = mu * x * (1 - x); first_row(k) = -1 + 2 * (x > 0.5); % 二值化 end Phi = zeros(m, n); for i = 1:m Phi(i, :) = circshift(first_row, [0, i-1]); end % 行内归一化 Phi = Phi / sqrt(n); end

这个方案在重构质量上和哈达玛方案接近,因为循环矩阵保留了很好的结构性质,同时规避了维度限制。我后来在测试大尺寸图像时基本都换成了这种方案。

三种方案对比下来,可以给一个比较直观的结论,方便你做选择。

方案正交性维度灵活性密钥空间计算复杂度适用场景
哈达玛+混沌选行高较差(受限)中低小尺寸分块图像
Logistic直接构造低(需正交化)高大中需要灵活维度的场合
循环矩阵+双密钥较高高大中(可FFT加速)大尺寸图像/实时处理

3. 压缩感知加密算法的Matlab完整实现拆解

3.1 整体流程和模块划分

一套完整的图像压缩加密混合算法,从上往下分成六个模块,对应Matlab里的六个函数文件。这样拆的好处是每个环节都可以单独调试,出了问题也不用在几百行代码里翻来翻去。

主流程 main_CSEncrypt.m | |-- 分块与稀疏化:ImagePatch + DWT2 |-- 测量矩阵生成:ChaoticSensingMatrix(按密钥key1) |-- 压缩采样与加密:y = Phi * x_block |-- 混沌置乱/扩散处理(密钥key2) |-- 反置乱/扩散处理 |-- OMP重构 + IDWT2合成图像

主流程的核心逻辑大概是这样的:

%% 主流程 main_CSEncrypt.m clear; close all; img = imread('lena.bmp'); % 测试图像 img_gray = double(rgb2gray(img)); % 转灰度并转double % 密钥定义 key1 = [0.3132, 3.91]; % 测量矩阵混沌密钥 key2 = [0.7311, 3.87]; % 像素置乱混沌密钥 % 分块参数 blockSize = 32; [m, n] = size(img_gray); blocksRow = floor(m / blockSize); blocksCol = floor(n / blockSize); cropImg = img_gray(1:blocksRow*blockSize, 1:blocksCol*blockSize); % 定义稀疏基:小波基 [Psi, ~] = wavedec2(cropImg, 3, 'db1'); % 这里仅示意,实际逐块处理 Psi = @(x) reshape(x, [], 1); ...

3.2 测量矩阵生成与稀疏采样模块

测量矩阵生成模块可以直接用第二节里写的LogisticSensingMatrix函数。需要补充一个细节:测量矩阵应该作用于稀疏系数,而不是原始像素值。很多初学者在这里搞混,导致重构效果惨不忍睹。

正确的顺序是这样的:先对图像块做二维离散小波变换得到稀疏系数矩阵s,然后把s拉成一维向量(长度n),再用测量矩阵Φ去采样:

function y = CompressedSampling(imgBlock, Phi) % imgBlock: blockSize x blockSize的原始图像块 % Phi: m x n 测量矩阵,n = blockSize^2 [C, S] = wavedec2(imgBlock, 2, 'db1'); s_vec = C(:); % 稀疏系数向量,长度n y = Phi * s_vec; % 观测向量,长度m end

这里使用db1小波做2层分解,稀疏度表现不错。实际测试中发现,小波基的选择对重构质量影响很大。db1简单快速,但如果图像纹理复杂,建议换成db4或sym8,压缩率不变的情况下重构PSNR能提高0.5dB左右。

加密的表现形式就在这个y上。因为Φ是由密钥key1生成的混沌控制矩阵,同一个图像块、不同key1,得到的y完全不同。传输或存储时只保留y和密钥,攻击者没有key1就还原不出中间的s_vec,更别说图像本身了。

3.3 像素置乱与扩散辅助加密模块

只有测量矩阵当密钥还不够,因为压缩感知是线性测量,它对选择明文攻击有一定抵御能力,但对已知明文攻击仍可能暴露结构信息。我的方案是在测量值y上再做一层混沌置乱和扩散,把密钥拆成两组:

第一组密钥key1负责测量矩阵,第二组密钥key2负责测量值的置乱和异或扩散。这样即便攻击者猜测到了测量矩阵的结构,也拿不到最终传输的密文。

function y_enc = ChaoticScramble(y, key2) % 针对测量值序列的混沌置乱 + 扩散加密 x0 = key2(1); mu = key2(2); len = length(y); % 生成混沌序列用于置乱 x = zeros(1, len); x(1) = x0; for k = 2:len x(k) = mu * x(k-1) * (1 - x(k-1)); end [~, order] = sort(x); % 置乱 y_permuted = y(order); % 扩散:当前密文值依赖前一个密文值 y_enc = zeros(1, len); y_enc(1) = mod(y_permuted(1) + floor(x(1)*255) + 1, 256); for k = 2:len y_enc(k) = mod(y_permuted(k) + floor(x(k)*255) + y_enc(k-1), 256); end end

这个扩散步骤很重要。它把测量值序列的统计相关性打散,密文直方图会变得非常均匀,几乎看不出任何结构信息。解密时只需要把顺序反过来:先逆扩散,再逆置乱,然后才能进入压缩感知重构。

3.4 OMP重构算法的Matlab实现

在接收端,拿到正确的密钥重建Φ和Ψ之后,就要从测量值y还原稀疏系数s_vec。其中最常用、最稳定的算法是正交匹配追踪(OMP)。它的核心思路是每次迭代挑出与残差相关性最强的原子,逐步逼近原信号。

function s_rec = OMPRecovery(y, Phi, Psi, sparsity) % 正交匹配追踪算法 % y: m x 1 测量值 % Phi: m x n 测量矩阵 % Psi: n x n 稀疏基矩阵 % sparsity: 稀疏度估计值 A = Phi * Psi; % 感知矩阵 r = y; % 残差 support = []; % 支撑集 s_rec = zeros(size(A, 2), 1); for iter = 1:sparsity % 计算相关性并选择最匹配的原子 corr = abs(A' * r); [~, idx] = max(corr); support = union(support, idx); % 用最小二乘法更新当前估计值 s_temp = zeros(size(A, 2), 1); AtA_inv = (A(:, support)' * A(:, support))^(-1); s_temp(support) = AtA_inv * A(:, support)' * y; % 更新残差 r = y - A(:, support) * s_temp(support); s_rec = s_temp; % 若残差足够小,提前退出 if norm(r) < 1e-6 break; end end end

很多人会忽略一点:OMP算法需要预先知道稀疏度sparsity。实际工程里我们不会提前知道图像在某个小波分解下的稀疏系数个数,所以一般会设置一个上限,比如取n的20%到30%。这个值偏大会引入噪点,偏小会丢失细节。我通常的做法是取m/4作为sparsity的初值,然后做一轮重构、看重构误差是否收敛,再微调。

重构完成后,把s_rec重新排列成原稀疏系数矩阵的结构,再用waverec2做逆小波变换,就能得到解密还原的图像块。

4. 仿真实验:重构质量、压缩率与安全性验证

4.1 实验参数配置与测试结果

我的测试环境是Matlab R2022a,机器配置很普通,主要是内存16G。测试图像用的是标准Lena图、Cameraman图和Brain扫描图,统一归一到0到1区间,然后按32×32分块,测量矩阵采用"哈达玛+混沌选行"方案,稀疏基选用db4小波做3层分解。

关键参数如下表:

参数取值
图像尺寸256×256
分块尺寸32×32
压缩比(测量率)0.25 / 0.4 / 0.6
混沌系统Logistic映射
密钥1初值0.3132, μ=3.91
密钥2初值0.7311, μ=3.87
稀疏基db4, 3层分解
重构算法OMP,sparsity=0.3×n

在压缩率为0.4的条件下,Lena图重构的PSNR约为30.8dB,SSIM指数0.91,Cameraman约为29.6dB。图像细节部分肉眼基本分不出差别,但边缘锐利度有轻微损失,这是压缩感知算法本身的通病,不是加密环节引入的误差。

如果把压缩率提升到0.6,PSNR能到34dB以上,主观视觉几乎无损。如果压缩率压到0.25,图像整体结构能保持,但纹理细节会有比较明显的模糊。所以实际选用压缩率时,得先想清楚应用场景是"节省带宽优先"还是"图像质量优先"。

4.2 密钥敏感性测试:差一位小数都解不开

这一项是我最看重的。把密钥key1从0.3132改成0.3132000001,其余参数保持不变,然后做完整解密流程。

结果是:解密图像完全是一张噪声图,PSNR不到10dB。进一步看,即使两个密钥的混沌序列在前几十步迭代中还高度一致,到了测量矩阵的下半部分也开始出现大范围差异,最终重构失败。

这是混沌密码系统最值得信任的地方:密钥敏感性极高。我实际测试了10^(-10)到10^(-15)量级的初值扰动,结果都一样——解密崩溃。密钥空间粗略估算一下,假设初值精度是10^(-15),μ的取值范围在3.6到4.0之间,两个密钥合起来大约是10^30量级的搜索空间,暴力破解基本不现实。

4.3 密文统计特征与抗攻击能力

图像加密做得好不好,除了"看起来乱",还要看统计特征。我分别计算了原始图像和加密测量值序列的直方图、相邻元素相关系数和信息熵。

指标原始图像像素值加密后测量值序列
水平方向相邻元素相关系数0.968-0.003
信息熵(8bit)7.427.98
直方图分布极不均匀几乎均匀

加密后相关系数从0.968直接掉到接近0,说明像素/测量值之间的线性相关性被打散,直方图变成近似均匀分布,信息熵接近理论最大值8bit。这些都是抗统计分析的硬指标。

额外的攻击场景我也测试过:裁剪攻击。把加密后数据任意裁掉一段,解密出来的图像会出现部分区域完全模糊、其他区域仍可辨认的现象。这个特性很有意思——压缩感知本身把全图信息"铺开"到了所有测量值里,所以局部裁剪不会导致整图失效,只是影响整体质量。对一些对容错要求高的传输场景,反而是好事。

5. 实测中的坑与调优经验

5.1 测量矩阵归一化处理不当,重构质量直接跳水

第一批实验我遇到过很诡异的现象:明明是同一套代码,改了几个矩阵生成细节,重构PSNR从30dB掉到15dB。排查了很久,最后发现问题是测量矩阵没有做列归一化。

压缩感知重构算法对观测值y的幅值极其敏感。如果Φ的某些列模长特别大(比如混沌值恰好集中在0.9以上),对应位置的系数在采样时会被放大,而OMP算法又依赖相关性排序来找支撑集,幅值失真直接破坏了排序逻辑,导致选错原子。

解决办法很简单:每一次生成完Φ之后,对每一列做L2范数归一化。归一化之后最好再拿A = ΦΨ 的最大列相关性做一次检查,如果列相关系数超过0.9,就应该换一组密钥重新生成矩阵。这个检查逻辑可以融合到密钥生成模块里,形成"生成-检查-重试"的闭环。

5.2 混沌序列的暂态效应和有限精度陷阱

用计算机实现混沌系统有一个绕不开的问题:浮点数的有限精度会导致混沌序列周期退化。Logistic映射在64位双精度下,差不多迭代几千次之后,序列就可能进入周期循环,不再是真正的混沌。当图像比较大、分块比较多的时候,一块图像需要几千个混沌值,多块共用同一条混沌序列,就有可能出现序列周期重叠。

我实际的应对策略是两件事并行做。第一,每生成一个测量矩阵之前,都重新用密钥加上当前块的序号做一个扰动,保证每块用的混沌序列都不同。第二,在需要非常长序列的项目里,把Logistic映射换成四维超混沌系统,比如用Chen超混沌系统,四个初值同时参与序列生成,周期问题会缓解很多。

而且浮点数的有限精度问题还带来了一个工程坑:Matlab在Windows和Linux上对浮点运算的舍入处理可能略有差异,导致同一把密钥在不同平台上生成的测量矩阵不完全一致。这意味着加密端和解密端最好在相同版本、相同操作系统上运行。如果确实要跨平台,建议把混沌序列值量化成整数后再参与矩阵生成,尽量把平台差异的影响隔离开。

5.3 稀疏度估计和OMP迭代次数对性能的拉扯

前面提到sparsity参数影响重构质量,实际测试中的变化趋势是这样的:

sparsity估计值重构PSNR (dB)单次重构耗时(s)
0.1×n26.90.11
0.25×n30.40.25
0.3×n30.80.31
0.5×n26.30.52

看出来没有?不是稀疏度设得越大越好。当sparsity接近0.5×n时,OMP算法会去拟合大量本底噪声,重构质量反而下降,耗时还翻了一倍。这里最合理的取值区间是0.2n到0.35n之间,具体数值和图像复杂度直接相关。

如果觉得逐个试参太麻烦,可以换一种思路:用OMP的残差变化做自适应终止。每次迭代计算一次残差能量,如果前后两轮残差下降量不足初始残差的1%,就自动停止。这样既不用估计稀疏度,也能避免过拟合,代码改动量也不大。

5.4 并行化和内存管理:处理大图像时的实战调整

256×256的图像按32×32分块,一共64块,串行处理时间还过得去。但如果换成1024×1024的图像,分块数变成1024块,每块都要做小波分解、采样、加密、解密、重构,串行执行的耗时几乎不可接受。

我的优化思路是两层:先用parfor把分块循环并行起来,再把测量矩阵生成模块中可复用的随机索引表预生成好,避免每块都重新跑一遍混沌迭代。实测下来,4核机器上总耗时能降到串行版本的60%左右。

遇到的另一个问题是内存暴涨。OMP算法里需要对感知矩阵A做多次乘积,A是m×n的稠密矩阵,当n=4096(64×64分块)时,A占了128MB左右内存,再乘上并行处理的4个worker,内存直接到1GB以上。解决方案是从"M隐式化"入手:不显式构造A = ΦΨ,而是预先计算好Ψ,并在每次A'*r的运算里拆成两步(先算Φ',再算小波逆变换),这样内存占用能减少一个数量级。

6. 算法安全性边界与可扩展方向

6.1 这个方法在什么场景下会被"破解"

客观说一句,没有任何加密方案是绝对安全的。压缩感知加密算法也有它的攻击面和适用边界。

如果攻击者同时知道密钥生成规则和测量矩阵的维度,理论上可以发起选择明文攻击。具体来说,攻击者构造一个已知的稀疏信号x0,加密后得到y0,通过分析y0与x0的关系,有可能逆向推算出Φ的内部结构。因此实际系统的密钥更换频率不能太低。

另一个短板在混沌系统的参数空间。Logistic映射的μ值如果靠近周期窗口(比如3.75附近混沌带很窄),生成的序列可能退化到周期状态,攻击者可以通过统计方法识别出这种模式。所以μ的取值要避开已知的周期窗口,我常用的做法是μ固定在[3.9, 3.95]区间内。

6.2 从"学术demo"走向"工程落地"的三个方向

这套算法目前在我的项目里已经跑通了,但要真正用于实时视频加密传输,还有三个方向值得继续深挖。

第一个方向是重构算法的轻量化。OMP虽然稳定,但单次重构耗时较高。可以换成迭代硬阈值算法(IHT)或者近似消息传递算法(AMP),速度能快很多,代价是重构质量略降。对于实时视频这种质量要求不极端的场景,明显是划算的。

第二个方向是测量值与加密流的结合方式。目前是"先压缩采样、再加密置乱",两步走得通但仍然分开。更进一步的做法是把密钥直接嵌入测量矩阵的行排列方式中,让压缩和加密从数学上彻底耦合,这样密钥空间会进一步扩大。

第三个方向是多图像联合压缩加密。如果同时传输多张图像,可以把多张图的测量值组合成一个高维张量,再利用混沌系统做三维置乱,这样可以充分利用不同图像之间的统计冗余,进一步压缩传输带宽。

我在实际使用中最深的体会是:这套混合算法最大的价值不是"多了一重加密",而是让带宽受限的加密通信场景有了全新的思路——加密不再额外增加数据量,反而与压缩共享同一套数学框架。把测量矩阵的生成逻辑写稳、把混沌序列的精度问题处理干净之后,整套系统跑起来是相当省心的。如果有正在做类似方向的朋友卡在重构质量或者密钥设计上,可以试试我在第三节给的那套模块拆分方式,按模块逐个排查,问题通常很快就能定位。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进