简介:面向通信与数字图像处理交叉方向的毕业设计资源,以MATLAB实现FSK与PSK调制解调系统为主线,覆盖数字图像处理基础、MATLAB编程、调制解调原理、系统建模与误码率仿真,适合通信工程、电子信息类本科生毕业设计参考。压缩包共8个文件,包含2个M源码、2个FIG界面文件、2张运行截图及1份PDF课题介绍,整体仅514KB,文件结构紧凑,便于直接运行和二次修改。已有111人学习下载。资料既提供FSK/PSK核心算法实现,也配有仿真界面与运行效果图,可快速对照验证不同调制方式的波形和误码性能;PDF部分还涉及课题思路与案例赏析,能帮助理解从图像信号到数字调制传输的扩展应用。
1. 为什么一份“数字图像处理”毕设包里躺着fsk.m和psk.m
收到同学发来的压缩包时,我以为会看到一堆图像滤波和分割算法,结果解压后先跳出来的是psk.m、fsk.m、psk.fig、fsk.fig和两张运行截图。标题写的是“MATLAB数字图像处理毕业设计”,核心内容却是FSK与PSK调制与解调系统。这种组题方式在毕设资源里其实很常见:图像不一定是被分析的对象,而是被当成“要传输的二维信源”。先把一张图变成比特流,再走FSK/PSK调制、AWGN信道、解调和误码统计,最后把比特流重新拼回图像。对通信原理课程设计、或者想把“图像传输”相关方向写进毕设的人来说,这个包的参考价值比单独的图像算法大得多。后面所有章节都会围绕这套仿真链路展开,包括图像信源怎么接进去、两个调制模块的参数怎么调、误码率曲线怎么画,以及fig界面跑不起来的常见坑。
2. 图像到比特流:数字图像处理与FSK/PSK调制与解调系统的信源接口
2.1 数字图像处理在这套仿真里的真实位置
这套资源里并没有出现边缘检测、形态学处理等经典图像算法。图像处理在这里只承担信源编码前的准备工作:读入、灰度化、二值化、串行化。你可以把它理解成“任意数据调制解调仿真”的输入部分,图像只是让传输结果肉眼可见。MATLAB做这件事非常顺手,图像在内存里本来就是矩阵,矩阵按列展开就是一维向量,一维向量里的0/1可以直接送给FSK或PSK的调制器。
下表列出的函数是这个资源里最可能被用到的图像处理基础函数,它们共同构成了通信仿真前端的标准流程。
| 函数 | 输入到输出 | 在通信链路中的作用 |
|---|---|---|
imread | 路径字符串 → H×W×C矩阵 | 读入图像,建立信源 |
rgb2gray | H×W×3 → H×W | 去掉色彩信息,只保留灰度 |
imbinarize | H×W → logical矩阵 | 生成0/1比特,降低传输数据量 |
reshape | H×W → (H×W)×1列向量 | 二维信号变一维,供调制器使用 |
imshow | 矩阵 → 图形窗口 | 接收端还原显示,便于答辩展示 |
注意imbinarize输出的是logical类型,如果直接送去算BER,很多函数会把它当布尔量处理。常见做法是加一层double(),把true/false转成1/0。
2.2 图像转比特流的可复用片段
下面是典型的图像预处理代码,放在FSK/PSK调制之前使用。先别直接拿大图跑仿真,图像分辨率越高,比特数越大,MATLAB跑蒙特卡洛仿真会非常慢。
% image_to_bits.m % 把图片变成0/1比特序列,供FSK/PSK调制器使用 imgRaw = imread('lena_small.png'); % 先用小图验证链路 if size(imgRaw, 3) == 3 imgGray = rgb2gray(imgRaw); % 彩色转灰度 else imgGray = imgRaw; % 本身就是灰度图 end imgBin = imbinarize(imgGray); % Otsu全局二值化 bits = double(imgBin(:)); % 按列展开成列向量 fprintf('图像尺寸 %dx%d,比特总数 %d\n', size(imgBin,1), size(imgBin,2), length(bits));逻辑说明:rgb2gray只在图像有三个通道时调用,避免灰度图重复转换。imbinarize默认使用Otsu方法计算全局阈值,在没有通信工具箱的旧版MATLAB中,可以换成imgBin = imgGray > 128。imgBin(:)是按列读取矩阵,这是MATLAB的内存排列习惯;如果希望按行扫描图像,写bits = double(reshape(imgBin', [], 1))。
拿到bits之后,后续的FSK/PSK调制只需要把它作为信息序列送入。接收端解调得到等长的0/1向量,再reshape回原来的H×W矩阵,就能用imshow直接看恢复效果。这一步是整个资源里图像处理和调制解调的连接点。
2.3 为什么老师会接受这种组题方式
通信系统的信源在现实中可以是语音、视频或图像。用图像做信源,接收端恢复后可以直接用imshow对比原图,比只看一串BER数字直观得多。整体链路可以描述为:图像预处理 → 比特流 → FSK/PSK调制 → AWGN信道 → 解调 → 比特流 → 图像重建。答辩时如果老师问“图像处理体现在哪”,你可以回答接收端图像重建前的灰度恢复和二值化去噪,这些都可以作为后续扩展点。
但有一点需要说清楚:资源里的fsk.m和psk.m大概率只包含调制解调主体,图像预处理通常需要你自己从外部读入并接好。测试时建议先用32×32的二值图验证完整链路,再逐步换大图。这样既能快速定位调制解调问题,也方便给老师演示中间波形。
3. FSK调制与解调的频率间隔选择:参数调整与误码率对照
3.1 为什么FSK的核心参数是频率间隔
FSK把信息放在载波频率里。以2FSK为例,两个频率分别是f1和f2,符号周期为T,频率间隔Δf = |f1 - f2|。要让两个符号在接收端能够被可靠区分,需要满足正交条件。实际仿真中最常见的取法是Δf = 1/T,也就是等于符号速率Rs。MSK/GMSK这类连续相位FSK可以把频率间隔压缩到1/(2T),代价是解调复杂度上升。很多物联网芯片中,FSK解调器和LoRa解调器共用射频前端,频率间隔由寄存器配置,这和你在MATLAB里调整freqsep是同一件事。
| 调制方式 | 频率间隔常见取值 | 带宽利用率 | 典型场景 |
|---|---|---|---|
| BFSK | Rs = 1/T | 较低 | 低速率遥测、工业无线 |
| MSK/GMSK | 1/(2T) | 中等 | GSM、蓝牙 |
| FSK/LoRa混合前端 | 寄存器配置 | 可变 | LPWAN网关 |
不要随意把freqsep设成很小。频率间隔过小时,两条频谱重叠,解调器很难判定当前符号对应哪个频率。频率间隔过大虽然容易区分,但占用更多带宽,不满足实际系统的频谱效率要求。
3.2 fsk.m核心仿真链路与参数含义
fsk.m里最可能出现的函数组合是fskmod和fskdemod,它们都来自MATLAB通信工具箱。下面这段代码可以直接替换掉资源里的主循环,作为2FSK的基线版本。
% demo_fsk.m % 2FSK调制/解调环路,验证freqsep对BER的影响 clear; clc; rng(7); M = 2; % 2FSK freqsep = 100; % 频率间隔(Hz),通常取符号速率整数倍 nsamp = 16; % 每个符号采样点数 Fs = 400; % 系统采样率(Hz),必须大于freqsep Nbits = 20000; data = randi([0 M-1], Nbits, 1); txSig = fskmod(data, M, freqsep, nsamp, Fs); SNR = 12; % 采样点信噪比,不是Eb/N0 rxSig = awgn(txSig, SNR, 'measured'); rxData = fskdemod(rxSig, M, freqsep, nsamp, Fs); ber = mean(rxData ~= data); fprintf('freqsep=%d Hz, SNR=%d dB, BER=%g\n', freqsep, SNR, ber);逻辑说明:fskmod要求输入数据是0到M-1范围的整数列向量,输出的是复基带调制信号。freqsep是频率间隔,单位Hz;nsamp是每个符号的采样点数;Fs是整个系统的采样率。采样率至少要大于freqsep,否则两个频点在离散频谱上混叠,后面无论怎么调都救不回来。awgn在这里加的是采样点信噪比,不是误码率曲线里常用的Eb/N0,下一章会专门讲换算关系。
3.3 不依赖工具箱的手写FSK调制
如果运行环境的MATLAB没有通信工具箱,fskmod会直接报Undefined function。这时可以用复指数手写一个2FSK调制器,原理是让符号0和符号1分别对应不同的复基带频率。
% 手写BFSK复基带信号生成 tvec = (0:nsamp-1).'/Fs; % 单个符号的时间采样列向量 freqs = [0 freqsep]; % 符号0和符号1对应的偏移频率 sig0 = exp(1j*2*pi*freqs(1)*tvec); sig1 = exp(1j*2*pi*freqs(2)*tvec); txManual = zeros(Nbits*nsamp, 1); for k = 1:Nbits if data(k) == 0 txManual((k-1)*nsamp+1:k*nsamp) = sig0; else txManual((k-1)*nsamp+1:k*nsamp) = sig1; end end逻辑说明:这里每个符号独立生成完整的复指数波形,频率间隔就是偏移频率差。注意这种写法在符号切换点相位可能不连续,频谱旁瓣会偏高,但用于学习原理完全足够。如果后续需要相位连续FSK,可以按照CPFSK的思路,在符号边界保持相位累积。解调时可以用两个相关器分别与sig0、sig1做匹配,比较输出能量即可。
建议在调试阶段把时域波形和功率谱都画出来,确认频率切换肉眼可见:
figure; plot(real(txSig(1:4*nsamp))); title('FSK时域波形'); figure; pwelch(txSig, hamming(256), [], [], Fs); title('FSK功率谱');时域波形里应该清晰看到两种频率交替。功率谱上应该有两个明显的谱峰。如果只有一个峰,说明freqsep太小,或者输入数据全是同一个符号。这类检查比直接看BER更快。
4. PSK调制与解调的相位映射:BPSK/QPSK仿真与星座图验证
4.1 相位域的信息携带方式
FSK靠频率区分符号,PSK靠相位。BPSK把比特0映射到0相位,比特1映射到π相位;QPSK把每2个比特映射到四个星座点。常见格雷映射为:00→π/4,01→3π/4,11→5π/4,10→7π/4。格雷码的好处是相邻星座点只差1比特,相位解调出错时多数情况下只错1个比特,等效误比特率更低。
| 调制方式 | 每符号比特数 | 星座点数 | 解调方式 | 理论误比特率 |
|---|---|---|---|---|
| BPSK | 1 | 2 | 相干解调 | Q(sqrt(2Eb/N0)) |
| QPSK | 2 | 4 | 相干解调 | 约等于BPSK |
| 8PSK | 3 | 8 | 相干解调 | 高于BPSK/QPSK |
4.2 psk.m的典型代码与星座图验证
psk.m里通常使用pskmod和pskdemod这对函数。下面代码在符号级仿真,不涉及载波和采样,重点看星座图。
% demo_psk.m % BPSK/QPSK调制解调与星座图 clear; clc; rng(10); M = 4; % 2=BPSK,4=QPSK data = randi([0 M-1], 50000, 1); txSym = pskmod(data, M, 0, 'gray'); % 0相位偏移,格雷映射 SNRdB = 10; % 符号信噪比,这里没有过采样 rxSym = awgn(txSym, SNRdB, 'measured'); rxData = pskdemod(rxSym, M, 0, 'gray'); [~, ser] = symerr(data, rxData); fprintf('M=%d, SNR=%d dB, SER=%g\n', M, SNRdB, ser); if M == 4 scatterplot(rxSym, 1, 0, 'bx'); hold on; scatterplot(txSym, 1, 0, 'ro'); legend('接收','发送'); end逻辑说明:pskmod的输入同样是0到M-1的整数向量,返回值是复基带符号序列,没有波形,因此awgn的SNR相当于符号能量与噪声能量比。第四个参数'gray'指定格雷映射,能有效降低误比特率。symerr返回符号错误数和错误率,第一输出是错误个数,所以用~丢弃。星座图上发送点应当聚成M个标准位置,接收点围绕发送点形成云团;SNR越低云团越扩散,云团互相重叠时误码率会快速上升。
4.3 手写BPSK完整链路:看出载波同步的影响
如果工具箱不可用,可以自己实现一个实信号的BPSK调制解调,这也是课程设计最常见的形式。
% 手写BPSK:实载波调制 + 相干解调 Fs = 8000; fc = 1000; nsamp = 40; tvec = (0:nsamp-1)/Fs; bits = randi([0 1], 10000, 1); s = 2*bits - 1; % 0变成-1,1变成+1 tx = reshape(s * cos(2*pi*fc*tvec), [], 1); rx = awgn(tx, 8, 'measured'); rxMat = reshape(rx, nsamp, []); % 每列对应一个比特 y = sum(rxMat .* cos(2*pi*fc*tvec'), 1) / nsamp; bitsRx = double(y > 0); ber = mean(bitsRx ~= bits'); fprintf('BPSK手写解调BER=%g\n', ber);逻辑说明:发送端每个比特用一个完整周期的余弦载波表示,s * cos(...)得到的是Nbits×nsamp矩阵,再按列展开成波形。接收端把波形按每nsamp个采样点切回矩阵,与本地载波做相关积分,最后按正负判决。这里隐含了一个重要前提:本地载波必须和发送载波同频同相。如果相位偏了π,解调结果会整体反转,y > 0的判决条件也要反过来。实际PSK系统需要载波同步,常见方案是发送导频序列,或用Costas环进行相位锁定。
4.4 从FSK改到PSK的关键点
把fsk.m改成psk.m时,最核心的变化是参数从freqsep换成phasoffset。FSK对载波频率漂移有一定容忍度,PSK对相位误差更敏感,所以PSK仿真一定要看星座图,而不是只看BER。如果收到的星座图整体旋转了一个固定角度,说明本地载波和发送端存在固定相偏。可以先估计旋转角,再对接收符号做相位补偿,或者改用差分PSK规避相位模糊。这个知识点在答辩时被问到的概率很高。
5. AWGN信道中的误码率曲线:Eb/N0换算与蒙特卡洛仿真参数
5.1 为什么不能直接拿awgn的SNR当Eb/N0
很多同学在MATLAB里直接把awgn的SNR当作Eb/N0,画出的BER曲线和理论值对不上。原因在于awgn加在采样点上,而Eb/N0定义在比特上。对于每符号带k个比特、每符号nsamp个采样的复基带信号,线性域关系是:
采样点SNR = (k / nsamp) × (Eb/N0)
转换成dB形式:
SNR_dB = Eb/N0_dB + 10×log10(k) − 10×log10(nsamp)
提示:如果仿真在符号级进行,没有过采样即nsamp=1,则SNR_dB等于Es/N0_dB,这时才可以把SNR直接当符号信噪比使用。
这就是为什么有时fskmod生成的信号经过awgn加噪声,BER总是低得离谱。比如每符号16个采样,Eb/N0=4dB时,awgn的SNR大约要设成4 + 0 - 12 = -8dB。直接在awgn里填8dB,等效Eb/N0高达20dB以上,当然一个误码都看不到。
5.2 可复制的BER仿真脚本
下面给出一个完整的2FSK误码率仿真脚本,直接运行即可画出一条与理论曲线接近的BER图。重点看Eb/N0的换算方式。
% ber_fsk_simulation.m % 2FSK非相干检测误码率仿真 vs 理论 clear; clc; rng(99); M = 2; freqsep = 100; nsamp = 16; Fs = 400; Nbits = 2e5; % 每个Eb/N0点发送比特数 EbN0dB = 0:2:10; berSim = zeros(size(EbN0dB)); for idx = 1:length(EbN0dB) % 复基带信号,k=log2(M),nsamp个采样点/符号 snrdB = EbN0dB(idx) + 10*log10(log2(M)) - 10*log10(nsamp); data = randi([0 M-1], Nbits, 1); txSig = fskmod(data, M, freqsep, nsamp, Fs); rxSig = awgn(txSig, snrdB, 'measured'); rxData = fskdemod(rxSig, M, freqsep, nsamp, Fs); berSim(idx) = mean(rxData ~= data); end berTheory = 0.5*exp(-0.5*10.^(EbN0dB/10)); % 非相干BFSK理论值 figure; semilogy(EbN0dB, berSim, 'o-', EbN0dB, berTheory, 's-'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER'); legend('仿真','理论');逻辑说明:log2(M)是每符号比特数,2FSK就1;nsamp必须和调制时一致。fskdemod在这里认为是非相干检测,所以理论曲线用了0.5*exp(-0.5*Eb/N0)公式。如果你的psk.m是相干检测,理论表达式应该换成0.5*erfc(sqrt(10.^(EbN0dB/10)))。脚本每个点固定发20万比特,在10dB附近误码数可能不到10个,曲线有抖动是正常的。要得到更平滑的曲线,可以把Nbits提高到1e6,但仿真时间也会明显增加。
5.3 仿真统计到多少个误码才可信
误码率仿真不是发固定比特数,而是要让错误计数达到一定门限。工程上常见做法是至少收集100个误码,这样BER的置信区间才比较窄。可以用一个while循环动态增加仿真数据量。
% 每个Eb/N0点动态发送,直到误码数达到100或达到最大比特数 errors = 0; trials = 0; minErrors = 100; maxTrials = 2e6; while errors < minErrors && trials < maxTrials dataChunk = randi([0 1], 50000, 1); % 调制、加噪声、解调,得到chunkErrors errors = errors + chunkErrors; trials = trials + length(dataChunk); end ber = errors / trials;这个循环的好处是高Eb/N0下不会浪费时间,低Eb/N0下也不会因为样本太少而波动太大。实际调试时,可以通过tic/toc看看每个SNR点要跑多久,便于评估Nbits取多少合理。
| 仿真参数 | 推荐设置 | 原因 |
|---|---|---|
| 每个SNR点比特数 | 1e4~1e6 | 信噪比越高需要越多比特 |
| 最少误码数 | 100 | 保证BER统计精度 |
| Eb/N0范围 | 0~10dB | 覆盖从10^-1到10^-5的区间 |
6. 跑通fsk.fig和psk.fig:GUI回调、工具箱依赖与排错清单
6.1 让.fig和.m正确联动
资源包里的fsk.fig、psk.fig大概率是GUIDE创建的界面。直接双击fig文件通常只能看到界面外壳,按钮没有反应,因为回调函数写在同名的.m文件里。正确做法是在MATLAB命令行运行guide('fsk.fig')打开编辑器,或者直接运行fsk.m。如果打开时报错提示不是有效的GUIDE文件,可能是新版App Designer保存的界面,用appdesigner打开。
6.2 从Slider回调到坐标轴重绘
fig界面里最常改的是频率间隔和SNR滑杆。回调函数里必须调用guidata保存handles,否则其他函数拿不到新值。
function freqsepSlider_Callback(hObject, eventdata, handles) val = get(hObject, 'Value'); set(handles.freqsepEdit, 'String', num2str(val)); guidata(hObject, handles); refreshFskPlot(handles); end逻辑说明:hObject是触发回调的滑杆控件,Value是当前滑杆位置。同步文本框方便用户看到精确值。guidata把更新后的handles写回界面数据总线,后面的按钮回调才能读到最新参数。refreshFskPlot是自定义函数,里面用handles.axes1指定绘图区域,避免plot画到新弹出的figure中。
6.3 检查工具箱依赖的三种方式
如果代码里用了fskmod、pskmod、scatterplot、symerr,需要确认通信工具箱是否存在。在命令行执行:
which fskmod which pskmod license('test', 'Communication_Toolbox')which如果返回带路径的.m或.p文件,说明函数可用。如果返回字符串包含“built-in”或者直接报错,说明没有安装对应工具箱。license返回1表示工具箱已授权,0表示没有。没有工具箱时,用手写调制解调方案,代码参考第3.3节和第4.3节。
6.4 高频坑与定位手段
- 运行
fsk.m提示Undefined function 'fskmod':通信工具箱缺失,换手写方案。 - 界面弹出来了但点按钮没反应:直接运行
.m文件而不是只打开fig。 - 误码率怎么跑都是0:
awgn的SNR没有按Eb/N0换算,检查nsamp。 - 解调结果BER在0.5附近:先怀疑数据对齐。发送端如果做过滤波或时延,接收端需要用
xcorr找最佳采样点。 - 图像解调后黑白反了:BPSK存在相位翻转,判决门限需要取反,或者把恢复比特全部
~一下。
最后给一个实操作:在命令行执行dbstop if error,然后运行一次fsk.m。程序会在出错前一行停下来,这时候查看rxSig和demodData的尺寸,多数波形长度不匹配的问题一眼就能看出来。
本文还有配套的精品资源,点击获取