ARTICLE DETAIL

资讯详情

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

基于Matlab的MPSK图像传输系统设计与实现

基于Matlab的MPSK图像传输系统设计与实现 做通信方向仿真的人大概都有这种体会BER 曲线、星座图翻来覆去调那么久最后交上去的实验报告千篇一律。换个更有成就感的玩法把一张图像丢进 MPSK 调制系统经过加噪信道再靠接收端把图像解调、恢复出来——图像传通了没、损没损、掉没掉色一眼就能看出来。这就是标题里那个“MPSK 图像传输系统”干的事情。MPSK 全称 M-ary Phase Shift Keying也就是 M 进制相移键控常见的 BPSK、QPSK、8PSK、16PSK 都属于这个家族。用 Matlab 把图像比特流调制到符号上加噪声再判决解调整个过程把通信原理课本里最难啃的几章全部串了一遍所以它特别适合做通信原理课程设计、信号处理综合实验甚至拿来当毕业设计的基础框架。这篇文章按我自己实际调试的经验把链路设计、代码细节、参数坑都写清楚希望能帮你少走弯路。1. 项目核心思路与链路总体设计1.1 MPSK 的数学模型和图像传输的关系MPSK 的本质用一句话就能说清楚发射端用载波的 M 种不同相位来承载信息每个符号携带 k log2(M) 个比特。以 QPSK(M4) 为例一次发 2 bit相位分别取 0、π/2、π、3π/28PSK 一次发 3 bit相位间隔变成 π/416PSK 一次发 4 bit相位间隔 π/8。星座图上的点全部落在单位圆上等间距分布。为什么选相位这个维度来扛噪声因为加性高斯白噪声本质上是在理想星座点周围糊上一层二维高斯“云”它会同时扰乱幅度和相位。相比之下幅度维度的可用判决空间很局促一点噪声就能把收发幅度判错而相位维度只要相邻星座点离得足够开噪声把点挤歪了只要不越过两个星座点的“中界线”接收端就能正确判回来。这个几何直觉比背公式重要得多后面很多参数取舍都是围绕它展开的。图像和比特流的关系就更直接了。灰度图每个像素是 0 到 255 的整数对应 8 bit彩色图则有 RGB 三个通道。Matlab 读进来是一堆字节把这些字节按顺序展开就得到一串比特。再把比特按每 k 个一组拼成符号索引映射成发射相位。一张 256×256 的灰度图总共 65536 个像素也就是 524288 bit。如果拿 QPSK 来传需要 262144 个符号拿 8PSK 来传需要 174762 个符号多一点必须补零。这个数字关系看起来简单但实操中一大半的 bug 都出在“比特数不能被 k 整除”“比特顺序反了”“图像矩阵 reshape 维度对不上”这种地方。1.2 为什么用图像做测试载体而不是只画误码率曲线纯误码率仿真当然能说明系统性能但它太抽象了。误码率 0.1% 是什么概念屏幕上一条曲线往下掉很难触动人。换成图像传输之后0.1% 的误码率会变成恢复图上的零星噪点1% 的误码率会让图像出现明显细碎颗粒10% 的误码率基本就是花屏。视觉效果和数字指标之间的换算关系做过一次就再也忘不掉。图像做测试载体还有一个隐藏优势它天然是一个大数据包。通信系统最怕的不是单次传输出错而是链路中的缓冲、顺序、同步、分段机制在大数据量下露出问题。你发几百个符号手动检查每一段都能对发几十万符号时序和索引错的毛病就藏不住了。图像恰好提供了足够大的载荷能让整个收发链路在真实压力下跑一遍。另外图像可以量化评估恢复质量。除了对比肉眼效果还可以算峰值信噪比 PSNR 和结构相似性 SSIM。这让项目从“能跑”上升到“能评价”写进课程设计报告或者毕业论文里都很好看。我个人习惯把误码率、星座图、恢复图像放在同一张图里对比这样发端、信道、收端三个环节的状态一目了然。1.3 整体链路结构速览把这个系统的模块拆开看其实和一套真实数字通信收发机没有本质区别发端原始图像 → 灰度/通道处理 → 字节转比特 → 分组映射 → MPSK 符号 → 可选成型滤波 信道AWGN 加噪通过 Eb/N0 控制信噪比 收端可选匹配滤波 → 相位判决 → 符号索引 → 比特流 → 补零/截断处理 → 字节重组 → 恢复图像初级实验可以省略成型滤波器和载波同步直接在复基带完成这会省掉一大半麻烦但原理要懂。等这一版跑通再逐步把滤波器、差分编码、纠错码加进去循序渐进。2. Matlab 代码实现与关键细节2.1 第一步图像转比特流图像转比特流这一步看着简单却是全项目最容易写错顺序的地方。我的推荐写法是img imread(lena.png); if size(img,3) 3 img rgb2gray(img); % 彩色转灰度简化问题 end [imgM, imgN] size(img); imgVec img(:); % 像素 - 8bit - 比特流高位在前 bitLen numel(imgVec) * 8; dataBits reshape(de2bi(imgVec, 8, left-msb), [], 1);这里必须解释一下de2bi(imgVec, 8, left-msb)的行为它返回一个 L×8 的矩阵每一行是一个像素的 8 位二进制最左边是最高位。转置后变成 8×L再按列捋直得到的就是严格按像素顺序、每个像素高位在前排列的比特流。新手最容易犯的错是漏掉第二次转置或者把left-msb写成right-msb。顺序一旦反了像素 128二进制 10000000会被解成 1整体图像直接变成雪花噪点。我自己排查这类问题会用一个最简单的方法验证取dataBits(1:8)和第一个像素对比逐位核对一遍后面几十万比特的信任感就是这样建立的。另外要提前处理补零问题。QPSK 是每 2 bit 一组8PSK 每 3 bit 一组16PSK 每 4 bit 一组。图像总比特数是 8 的倍数但不一定能被 k 整除尤其是 8PSK。建议在映射之前就做补齐并保存原始长度origBitLen numel(dataBits); k log2(M); if mod(origBitLen, k) ~ 0 dataBits [dataBits; zeros(k - mod(origBitLen, k), 1)]; end接收端在恢复图像前用origBitLen把补的零截掉就行。2.2 第二步比特映射到 MPSK 符号把比特流按 k 个一组切成符号索引然后映射到星座点。最朴素的是自然映射symIdx reshape(dataBits, k, []).; symIdx bi2de(symIdx, left-msb); % 0 ~ M-1 txSym exp(1j * 2*pi*symIdx / M); % 单位圆上 M 个点自然映射的问题是相邻相位点对应的二进制可能差多位。比如 8PSK 里相位 0 和相位 π/4 对应的索引 0 和 1二进制是 000 和 001只差 1 bit没问题但索引 3 和 4 对应的二进制是 011 和 100差了 3 bit。如果噪声把符号从 3 推到 4判决一错就是 3 个比特全错。格雷映射Gray mapping就是为了解决这个问题让相邻星座点的二进制码只差 1 bit把符号错误的影响压到最小。Matlab 里生成格雷码很简单grayCode bitxor((0:M-1), bitshift(0:M-1, -1));例如 M4 时grayCode [0 1 3 2]相位 0、π/2、π、3π/2 对应的数据索引分别是 0、1、3、2。发射端要做的是把自然索引symIdx换成格雷索引txIdx grayCode(symIdx 1); txSym exp(1j * 2*pi*txIdx / M);解调时则反着来先判出星座索引再查逆格雷码映射表。逆映射表可以提前建好grayInv zeros(1, M); grayInv(grayCode 1) 0:M-1;后面从接收相位得到rxIdx后用dataIdx grayInv(rxIdx)就能还原数据索引再转比特。这个细节看起来只差一行但对高阶调制下的误码率影响非常明显我建议从一开始就用格雷映射。2.3 第三步加噪信道与相干解调加噪信道的实现有两条路直接用awgn()函数或者手动控制噪声方差。我强烈建议新手先用手动方式因为awgn()的 SNR 定义在复基带场景下容易把人绕晕而手动加噪的每一步都是透明的。假设符号能量归一化为 1也就是每个星座点模值为 1那么给定 Es/N0 后噪声的复功率就是EsN0dB EbN0dB 10*log10(k); % 符号信噪比 比特信噪比 每符号比特数换算 N0 10^(-EsN0dB/10); % 符号能量为1N0就是1/(Es/N0) noise sqrt(N0/2) * (randn(size(txSym)) 1j*randn(size(txSym))); rxSym txSym noise;为什么噪声实部虚部各自要除 √2因为复噪声总功率是实部功率加虚部功率每个分量各占一半。总功率是 N0实部和虚部就各是 N0/2幅度系数自然是 sqrt(N0/2)。这个细节在教科书上只是一句话但写代码时漏掉它星座图会明显比理论发散得更厉害。解调端用相位判决。对每个接收符号求相位映射到 0 到 2π然后四舍五入到最近的星座点ph mod(angle(rxSym), 2*pi); rxIdx round(ph / (2*pi/M)) 1; rxIdx(rxIdx M1) 1;这里有个边界要小心angle()返回的是-π到π如果不做mod落在负半轴的相位会直接判错。做完mod之后相位 2π 附近和 0 附近会合到一起所以要把超过 M 的索引拉回 1。判决之后按格雷逆映射还原数据索引再 de2bi 转成比特rxDataIdx grayInv(rxIdx); rxDataIdx rxDataIdx(:); % 确保是列向量 rxBits de2bi(rxDataIdx, k, left-msb); rxBits reshape(rxBits., [], 1);2.4 一张简化但完整的主流程代码把上面这些片段凑起来就是一个完整可运行的发射-信道-接收-恢复链路。下面这段代码是我平时调通 QPSK 图像传输的最小版本建议先拿它跑通再往上加模块。%% 发射端 clear; close all; clc; M 4; k log2(M); EbN0dB 10; img imread(lena.png); if size(img, 3) 3 img rgb2gray(img); end [imgM, imgN] size(img); imgVec img(:); % 像素 - 比特流高位在前 origBitLen numel(imgVec) * 8; dataBits reshape(de2bi(imgVec, 8, left-msb), [], 1); % 补零 if mod(origBitLen, k) ~ 0 dataBits [dataBits; zeros(k - mod(origBitLen, k), 1)]; end % 分组、自然索引、格雷映射 symIdx reshape(dataBits, k, []).; symIdx bi2de(symIdx, left-msb); grayCode bitxor((0:M-1), bitshift(0:M-1, -1)); txIdx grayCode(symIdx 1); txSym exp(1j * 2*pi*txIdx / M); %% 信道 EsN0dB EbN0dB 10*log10(k); N0 10^(-EsN0dB/10); noise sqrt(N0/2) * (randn(size(txSym)) 1j*randn(size(txSym))); rxSym txSym noise; %% 接收端 ph mod(angle(rxSym), 2*pi); rxIdx round(ph / (2*pi/M)) 1; rxIdx(rxIdx M1) 1; grayInv zeros(1, M); grayInv(grayCode 1) 0:M-1; rxDataIdx grayInv(rxIdx); rxBits de2bi(rxDataIdx, k, left-msb); rxBits reshape(rxBits., [], 1); rxBits rxBits(1:origBitLen); % 截掉补的零 % 比特流 - 像素 imgRec uint8(reshape(bi2de(reshape(rxBits, 8, [])., left-msb), imgM, imgN)); %% 展示 figure; subplot(1,2,1); imshow(img); title(原始图像); subplot(1,2,2); imshow(imgRec); title(恢复图像);这段代码在 M4、Eb/N010dB 的条件下恢复图肉眼基本看不出损失。跑通之后再去换 M、调信噪比、加滤波器和测试更多图像会轻松很多。3. 参数调优与结果对标3.1 Eb/N0、Es/N0 与 SNR 千万别搞混这是整个项目里最容易翻车的地方没有之一。教科书里讨论误码率标的标准横轴是 Eb/N0也就是每比特能量与噪声功率谱密度之比。但实际写代码时我们操作的是复基带符号每个符号能量是 Es而 Es k × Eb所以换算关系是Es/N0 (dB) Eb/N0 (dB) 10×log10(k)拿 QPSK 举例k2Es/N0 比 Eb/N0 高大约 3.01 dB8PSK 的 k3高约 4.77 dB16PSK 的 k4高约 6.02 dB。如果代码里把 Eb/N0 直接当成符号信噪比用等于是少加了这 3 到 6 dB结果误码率会比理论期望高出一大截星座图看起来也会发得更散。再往下还有一层坑如果你用了awgn(signal, snr, measured)这个snr参数是信号功率与噪声功率之比既不是 Eb/N0也不严格等于 Es/N0除非你手动把信号功率归一化并做好换算。我的建议是完全绕开awgn用 2.3 节手动加噪的方式每一步都明确知道自己加的是什么。下面这个表格是常用的对照关系我每次写代码前都会扫一眼调制方式k (bit/符号)10log10(k) (dB)Es/N0 Eb/N0 10log10(k)BPSK10Es/N0 Eb/N0QPSK23.01Es/N0 Eb/N0 3.018PSK34.77Es/N0 Eb/N0 4.7716PSK46.02Es/N0 Eb/N0 6.023.2 M 到底选 4、8 还是 16M 值每翻一倍频谱效率提升 1 bit/符号但代价是星座点间距变小。单位圆上均匀分布 M 个点相邻点的最小欧氏距离是d_min 2 × sin(π / M)这组数字非常直观。BPSK 的 d_min2QPSK 的 d_min≈1.4148PSK 只有 0.76516PSK 直接掉到 0.390几乎是 QPSK 的四分之一。这意味着同样强度的噪声16PSK 的判决余量小得多误码率会急剧恶化。理论误码率有助于建立“大概能到哪个量级”的直觉。高信噪比下的常用近似是Pb ≈ (2 / k) × Q(sqrt(2 × Es/N0) × sin(π/M))代码里写就是Ps 2 * qfunc(sqrt(2 * k * EbN0lin) * sin(pi/M)); Pb_theory Ps / k;这个公式在中等偏高的信噪比下已经够用了。我个人的体会是做课程设计M4QPSK是首选想展示高阶调制M8 是可控的10dB 附近能看出性能下降但图像还能辨认M16 建议只在纯误码率仿真里玩图像传输在这种信噪比下几乎是灾难现场除非你同时引入纠错编码。M每符号比特数d_min 近似直观恢复效果Eb/N010dB212.000极好几乎没有误码421.414基本无损830.765有零星噪点1640.390噪点明显图像可辨认但脏3.3 成型滤波器什么时候加初级实验不加成型滤波器直接在符号层面加噪声跑起来很快也能验证调制解调核心逻辑。但如果你把工程包交给导师或者面试官对方十有八九会问一句带宽利用率有没有考虑所以这个点必须搞清楚。真实系统为什么需要成型滤波因为矩形符号在频域的主瓣和旁瓣都很宽直接发射会浪费带宽还会干扰邻道。解决思路是用升余弦/根升余弦滤波器对基带符号做脉冲成型让频谱收敛到一个可控带宽内。滚降因子 α 越小带宽越窄但时域波形拖尾越长对定时同步的要求也越高常用值 0.22 到 0.5。问题是加了成型滤波器之后接收端必须做匹配滤波再加采样否则符号间干扰会毁掉一切。而“加采样”又引出了定时同步问题你不知道应该在每个符号的哪个采样点判决。完整解决定时同步需要 Gardner 算法或者早迟门工程复杂度一下子就上来了。我的建议是先不加滤波器把主链路跑通再单独开一节对比“加滤波理想抽样”和“无滤波”两种情况。加滤波器时发送端做 k 倍过采样、接收端匹配滤波后只在已知正确采样点抽取这样不会引入定时偏差又能直观看到匹配滤波对噪声的抑制效果。3.4 误码率与图像质量的实测对照做实验不能只看最后一张恢复图建议把误码率曲线、星座图和恢复图像放在一起看。我经常采取的做法是跑一组 Eb/N0 从 0 到 12dB 的循环每个点统计误码率同时记录一张恢复图像。实测下来几个典型视觉观感大概是这样的QPSK 在 6dB 时恢复图像会有稀稀拉拉的离散噪点肉眼能看见但图像结构完整8dB 时基本看不出问题10dB 以上误码率低于 e-4 量级视觉无损。8PSK 在 10dB 时误码率大约在 e-3 量级恢复图会有少量细碎噪点和边缘毛糙想达到和 QPSK 一样的视觉无损大概需要 14dB 以上。16PSK 在 10dB 时误码率已经到了 e-2 量级恢复图会明显花掉图像细节丢失严重。这里还要提醒一件事单次仿真长度有限误码率统计本身是有随机起伏的。比如理论上误码率 1e-6你只发了 10 万个符号大概率一个误码都测不到统计出来的误码率是 0。这不算错但别因为仿真 0 误码就以为系统真的完美了。想要更可靠的对比应该用蒙特卡洛多次取平均或者干脆固定符号数把误码率的置信区间考虑进去。4. 常见问题与排查经验4.1 恢复图像出现成片噪点或黑白块我最早调这个项目时遇到过恢复图像出现大块黑斑和花色的情况不是随机离散噪点而是一整片区域颜色错乱。这种成片损坏基本不是噪声随机误码造成的而是结构化错误大概率出在比特顺序、补零截断或者矩阵 reshape 上。排查思路很简单先不接噪声通道直接让发射符号原封不动进接收端。如果仍然花屏说明发端和收端的映射表、补零逻辑、比特流顺序里有问题。这时候把发端的前 32 个符号索引和收端的前 32 个判决索引分别打印出来逐项对比第一次出现不一致的位置就是 bug 所在。如果无噪声时完全正常只有加噪声后才花屏那再回到误码率统计和信噪比换算上去查。另一个容易出问题是补零的位置。补零要补在比特流末尾接收端截断时要用原始长度截断。如果补零补错了位置或者截断时多了一句reshape(rxBits, [], 8)把长度弄错恢复出来的图像行列会对不上表现就是图像整体扭曲而不是局部噪点。4.2 星座图整体旋转导致全错如果接收端对每个符号的相位都偏了一个固定角度比如整体转了 45 度那么所有判决都会踩到相邻星座点上误码率接近 50%图像直接花掉。这种问题的根源在于相干解调要求接收端严格知道载波的相位参考误差在零附近才行。真实系统会有载波同步环路仿真里如果没有专门建模相位参考就完全建立在“发端和收端共享同一个时钟/相位”的理想假设上。验证方法很直接把发射符号和接收符号画在同一个复数平面上或者用plot(real(corr), imag(corr), .)看接收星座图。如果发现所有点都绕原点旋转了一个固定角度就要检查是不是接收端自己加了一个额外的复数旋转或者相位判决前的处理引入了偏差。比如某些代码为了可视化方便会把接收符号乘一个 e^{jθ} 再画图后来忘了把 θ 清零。解决相位模糊的标准工程手法是做差分编码。差分编码的核心思想是不再用绝对相位传信息而是用相邻符号之间的相位差来传信息这样即使整体旋转了一个固定角度相位差依然保持不变。这个方向我在第 5 节还会展开说这里先记住结论课程设计仿真可以不处理相位模糊但要在报告里说明这是理想假设做工程演示请上差分编码。4.3 比特顺序错位让图像完全错乱图像变成雪花状乱码最常见的元凶就是 bit 顺序没对齐。de2bi和bi2de这两个函数默认是右边为最低位的right-msb模式如果你在某一端忘了加left-msb那么每个字节的高低顺序就会反转。例如像素值 128 的二进制是 10000000如果按right-msb读取就变成了 00000001也就是 1。这样一个像素差 127 个灰度级整张图怎么可能正常。我建议所有涉及 bit 顺序的代码统一用left-msb并且在发端和收端各留一行注释清晰标注“高位在前”。更稳妥的办法是写一个小测试函数输入一个已知像素值矩阵比如 255、128、64、1 的组合跑一遍“像素→比特→符号→判决→比特→像素”的全流程看是否原样返回。这个测试跑通基本就能保证顺序没有大问题。4.4 代码运行太慢怎么办图像大了之后这种问题立刻显现。比如 1024×1024 的灰度图比特数 800 多万如果用 for 循环逐个处理像素或逐个符号跑一次实验可能要几分钟到十几分钟调试起来非常痛苦。解决办法是向量化。像素转比特用de2bi批量处理比特分组用reshape符号加噪用矩阵运算判决相位用modround一整个向量搞定。在整个收发流程里真正需要循环的地方几乎为零。另外图像建议先用 256×256 的尺寸跑通全流程再换大图。如果要在多个信噪比点反复实验优先考虑外层用 for 循环、内层纯向量化而不是反过来。还有一个容易忽略的性能陷阱如果你在循环里反复调用de2bi、bi2de这类函数Matlab 每次都会有函数调用和内存分配开销。可以把映射表、逆映射表、必要的常量提前计算好循环里只做索引操作。5. 从课程设计到工程系统的升级方向5.1 差分编码解决相位模糊差分编码是 MPSK 系统从“仿真玩具”走向“能用”的第一步。思路是把信息编码在相邻符号的相位差上发送第 n 个符号时令当前相位等于前一个符号相位加上当前要传送的相位差。以 DQPSK 为例发射端维护一个状态变量lastPhase 0; for n 1:length(dataIdx) phaseDiff 2*pi*dataIdx(n)/M; lastPhase lastPhase phaseDiff; txSym(n) exp(1j*lastPhase); end接收端只需要计算相邻接收符号的相位差diff(angle(rxSym))再做判决。因为载波相位模糊是乘性旋转对相位差没有影响。代价是误码率会比绝对相位调制稍微高一点因为每两个符号之间的噪声相位差是两次噪声共同作用的结果。这个方向很值得写进报告体现你对实际系统约束的理解。5.2 加纠错编码和交织不加纠错时16PSK 在低信噪比下的图像基本没法看。但加一个简单的纠错码之后情况会大不一样。课程设计阶段你可能不需要真的写 LDPC 编解码器用分组码或者重复码也能演示思想。在 MPSK 链路上加纠错关键不是编码本身而是搞清楚“硬判决前加好还是硬判决后加好”。简单做法是发端先对图像比特流做信道编码收端解调判决出硬比特后输入译码器。如果想让性能更好接收端应该输出软信息比如相位判决的似然度量再做软判决译码。这一步会让工程复杂度上去不少但对理解链路预算和编码增益极有帮助。交织也很重要因为瞬时衰落或者突发干扰会造成连续误码而纠错码通常擅长纠正分散误码不擅长纠正连片误码。把比特顺序打乱让突发误码在译码前重新变成随机分布纠错能力会显著提升。图像语义上刚好有一个天然的交织粒度先按行扫描编码再按列传输接收端恢复的时候再做一次行列转换就能把连续误码打散。5.3 自适应调制与更完整的工程化MPSK 图像传输系统再往后走很难绕开自适应调制信道好的时候用 16PSK 或 8PSK 提高吞吐信道差的时候退回到 QPSK 甚至 BPSK 保命。这是一种很朴素又很实用的链路自适应策略。仿真的话可以设定一个感知门限比如根据接收端估计出的 SNR 判断当前信道状态再反馈给发端在下一帧调整 M。图像传输帧结构里额外加一包头用固定 BPSK 调制保证可靠性payload 再用自适应 MPSK 调制。这样系统就有了“控制面”和“数据面”的分工已经非常接近真实通信系统的架构了。工程化方面我建议把代码按模块拆函数image_to_bits(img, M)图像转比特modulate_mpsk(bits, M)调制add_awgn(tx, EbN0, k)信道demodulate_mpsk(rx, M)判决bits_to_image(bits, imgSize)恢复图像每个函数只做一件事参数用结构体传递。这样做的直接好处是后面换不同的图像、不同的 M、不同的信噪比点只需要改参数不需要动主流程调试效率翻倍。5.4 我的最终建议根据我自己踩坑的经验我强烈建议你做实验时按梯度来先 BPSK 无噪声跑通全链路确认像素到比特、比特到符号、符号到判决、比特到图像这四个转换全部无误再拿 QPSK 加 AWGN用星座图和误码率曲线验证加噪逻辑然后换 8PSK 和 16PSK体验高阶调制的性能代价最后如果时间和精力允许再上差分编码、成型滤波和纠错码。不要一上来就追求“完整的现代通信系统”那个复杂度足够让任何人吃不消。把每一步的中间结果打印出来、画出来一旦出错就知道是哪一环的问题。图片传输这个终点的好处在于任何一步实现不对最终恢复出来的图像都会用最直观的方式告诉你。说实话我自己后来做更复杂的调制和信道仿真时偶尔还会把这个图像传输链路拿出来当冒烟测试工具——它能快速证明核心收发逻辑没问题比只跑误码率曲线安心得多。你按这个路径走一遍收获的绝对不只是一份能跑的 Matlab 代码而是一整套看待通信系统的方式。
返回列表