简介:面向信息光学课程学习者与MATLAB编程实践者的作业资源包,集中呈现傅里叶变换、GUI交互和衍射模拟三大光学计算主题。压缩包整体约22.71MB,内部以MATLAB脚本与函数文件为主,适合直接运行、调试并在此基础上二次开发。傅里叶变换部分利用fft2对二维图像进行频域分析,帮助理解频谱与空间域的对应关系;GUI模块演示了按钮、滑块和文本框等组件的回调设计,可用于交互式查看光学数据;衍射模拟则通过数值方法再现菲涅尔与夫琅禾费衍射过程,便于观察光斑分布和光栅效应。通过调整输入参数,还可直观观察不同孔径与波长条件下的光强分布,帮助建立定性到定量的光场分析思路。已有194人学习下载,整体内容兼顾理论验证与实践操作,既能辅助完成课程作业,也能作为图像处理、光学仿真方向的基础参考。
1. 信息光学作业.zip:一份把傅里叶光学讲透的 MATLAB 仿真全家桶
如果你在学信息光学或者数字全息,碰到的第一个坎往往不是公式推导,而是“我知道菲涅尔衍射积分长什么样,但不知道它怎么变成屏幕上那张光强图”。这份《信息光学作业.zip》就是用来填这个坑的:它用 MATLAB 写好了从标量衍射理论到数字全息再现的全套仿真脚本,拿过来跑一遍,就能把教材里的卷积、傅里叶变换、传递函数这些概念一一对应到实际画面上。适合正在做课程设计的学生,也适合需要快速搭建衍射仿真 Demo 的开发者。所有代码都遵循一条核心逻辑——把连续光学公式离散化到采样网格上,然后交给 FFT 去算。这份资源解决的正是“公式会写、代码不会写”的尴尬,而且脚本之间互相独立,能单文件运行,不用从头啃到尾。
2. 工程结构盘点:这个压缩包里到底装了什么
2.1 文件清单与目录规划
解压之后,典型的构成是一个主脚本加若干功能函数,目录里还带一份实验报告模板。按我拆过的这类课程作业来说,常见布局长这样:
info_optics_homework/ ├── main_runner.m # 主入口,按章节顺序调用各仿真 ├── fresnel_ft.m # 菲涅尔衍射——单次傅里叶变换法 ├── fresnel_conv.m # 菲涅尔衍射——卷积法 ├── fraunhofer_fft.m # 夫琅禾费衍射仿真 ├── lens_fourier.m # 透镜的傅里叶变换性质验证 ├── hologram_generate.m # 全息图生成(菲涅尔全息) ├── hologram_reconstruct.m # 全息图数值再现 ├── utils/ │ ├── grid_generate.m # 生成采样坐标网格 │ ├── aperture_rect.m # 矩形孔/狭缝 │ ├── aperture_circle.m # 圆孔 │ └── my_imshow.m # 幅度/相位统一显示工具 └── docs/ └── experiment_report.md # 实验报告模板每个 m 文件都是独立的,这样有一个好处:调试时单跑一个脚本不会污染其他结果。主脚本main_runner.m的作用是把各个仿真串起来,按顺序输出图像,方便直接对照教材章节看现象。
2.2 变量命名与数据结构约定
整个工程的命名规则很统一,读懂一个文件就能看懂全部。关键变量约定如下表所示:
| 变量名 | 含义 | 单位 / 说明 |
|---|---|---|
N | 采样点数 | 通常取 512 或 1024,必须是 2 的幂 |
L | 物理尺寸 | 单位米,表示观察平面的大小 |
dx | 空间采样间隔 | L / N |
lambda | 波长 | 单位米,默认 632.8e-9(氦氖激光) |
z | 传播距离 | 单位米 |
U0 | 物平面复振幅 | 复数矩阵,尺寸N x N |
Uz | 观察平面复振幅 | 复数矩阵,尺寸N x N |
这套命名方式几乎是信息光学仿真里的“标准方言”,因为教材里的符号就是这么定义的。用U0表示初始光场、Uz表示传播后的光场,看代码时可以直接对应到公式上,省去了翻译过程。采样点数N统一取 2 的幂,是给 FFT 服务的——MATLAB 的fft2在输入尺寸为 2 的幂时计算效率最高,这一点在涉及大矩阵运算时差别非常明显。
3. 核心模块拆解:一个衍射过程从公式到图像的完整链路
3.1 网格生成:所有仿真的地基
写衍射仿真第一件要做的事就是生成采样坐标网格,这一步错则后面全部错。工具函数grid_generate.m负责这事,核心逻辑很紧凑:
function [x, y, dx] = grid_generate(N, L) % 生成 N x N 的采样坐标网格,原点在中心 % 输入: N - 采样点数, L - 物理尺寸(m) % 输出: x, y - 网格坐标矩阵(m), dx - 采样间隔(m) dx = L / N; % 注意这里用 (0:N-1) 然后平移,而不是直接用 linspace x_1d = (-N/2 : N/2-1) * dx; [x, y] = meshgrid(x_1d, x_1d); end这段代码的关键在坐标生成方式:(-N/2 : N/2-1)生成的是对称区间,这样零频(直流分量)落在矩阵中心,和fftshift之后的频谱对齐。如果改用linspace(-L/2, L/2, N),虽然看起来差不多,但边界点会被重复计算一次,导致网格尺寸实际上变成N+1个间隔,细微的不一致会在干涉条纹仿真中放大成可见误差。这个坑我踩过,后面避坑章节还会细说。
3.2 菲涅尔衍射的两种实现:单次 FFT 与卷积法
菲涅尔衍射是信息光学的核心内容,这份作业里给出了两种经典实现。第一种是单次傅里叶变换法(S-FFT),核心代码:
function Uz = fresnel_ft(U0, lambda, z, L0, Lz) % 单次傅里叶变换法计算菲涅尔衍射 % 输入: U0 - 物平面复振幅, lambda - 波长(m), z - 传播距离(m) % L0 - 物平面尺寸(m), Lz - 观察平面尺寸(m) % 输出: Uz - 观察平面复振幅 [N, ~] = size(U0); k = 2 * pi / lambda; dx0 = L0 / N; % 物平面采样间隔 dxz = Lz / N; % 观察平面采样间隔 % 二次相位因子(球面相位近似) phase1 = exp(1i * k / (2*z) * (x.^2 + y.^2)); phase2 = exp(1i * k * z) / (1i * lambda * z) * dx0^2; % 频域坐标(对应观察平面采样间隔) fx = (-N/2 : N/2-1) / Lz; [fX, fY] = meshgrid(fx, fx); phase_f = exp(-1i * pi * lambda * z * (fX.^2 + fY.^2)); % 实现: FFT(u * phase1) .* phase_f 再乘系数 Uz = ifft2(fft2(U0 .* phase1) .* phase_f) * phase2; end逻辑说明:S-FFT 法把菲涅尔衍射积分拆成三步——先在物平面乘一个二次相位因子,然后做傅里叶变换,再在频域乘传递函数对应的二次相位因子,最后逆变换回空间域。这里的phase1对应积分核里的球面子波项,phase_f对应传递函数的抛物面近似。
参数说明:L0和Lz可以不同,这让 S-FFT 法能模拟光场传播后“扩散”的效果。实际运行时需要注意,Lz和L0的比值由lambda * z / (dx0 * L0)隐式决定,手动输入不一致的尺寸会导致输出网格的缩放不匹配。第二种是卷积法,代码更直白:
function Uz = fresnel_conv(U0, lambda, z, L) % 卷积法计算菲涅尔衍射(保持窗口尺寸不变) % 输入: U0 - 物平面复振幅, lambda - 波长(m), z - 传播距离(m), L - 公共尺寸(m) % 输出: Uz - 观察平面复振幅 [N, ~] = size(U0); k = 2 * pi / lambda; dx = L / N; % 构造卷积核:菲涅尔积分核的离散化 [x, y] = meshgrid((-N/2 : N/2-1) * dx, (-N/2 : N/2-1) * dx); h = exp(1i * k / (2*z) * (x.^2 + y.^2)) / (1i * lambda * z) * dx^2; % 频域相乘实现卷积 Uz = ifft2(fft2(U0) .* fft2(h)); end逻辑说明:卷积法的物理意义更直接——把菲涅尔积分看成“物光场与球面子波核的卷积”,在频域相乘等价于空间域卷积,一步到位。它跟 S-FFT 法的区别在于:卷积法要求物平面和观察平面的采样间隔一致,即窗口尺寸不变,适合模拟传播距离较短、光场直径变化不大的情况;S-FFT 法则可以自由设定传播后的窗口大小。
这两种方法的选用是一个经典取舍。我做仿真时通常遵循这条经验:传播距离z较小时用卷积法,因为此时衍射光斑的扩展不明显,窗口尺寸保持一致反而省事;当z较大、光场明显扩散时用 S-FFT 法,并把Lz设成L0 + lambda * z / dx0的近似值。如果两种方法在同一距离下给出明显不一致的结果,先检查网格坐标是否对齐,再检查采样间隔是否满足奈奎斯特条件。
3.3 夫琅禾费衍射:远场近似的快速实现
夫琅禾费衍射是菲涅尔衍射在传播距离足够远时的近似,教材上通常用“傅里叶变换的直接结果”一笔带过。这份作业里的实现更细:
function [Uf, xf, yf] = fraunhofer_fft(U0, lambda, z, L0) % 夫琅禾费衍射:利用傅里叶变换的缩放性质 % 输入: U0 - 物平面复振幅, lambda - 波长(m), z - 传播距离(m), L0 - 物平面尺寸(m) % 输出: Uf - 远场复振幅(已做坐标缩放), xf/yf - 输出坐标网格 [N, ~] = size(U0); dx0 = L0 / N; k = 2 * pi / lambda; % 物平面坐标(用于计算球面相位因子) [x0, y0] = meshgrid((-N/2 : N/2-1) * dx0, (-N/2 : N/2-1) * dx0); % 夫琅禾费公式:Uf(xf,yf) = exp(ikz)/(iλz) * exp[ik/(2z)(xf²+yf²)] * FFT[U0] % 其中 xf = λz fx,这里的 fx 是空间频率坐标 Uf_raw = fftshift(fft2(U0)) * dx0^2; % 频域采样间隔 dfx = 1 / L0 dfx = 1 / L0; % 坐标缩放:xf = lambda * z * fx xf = (-N/2 : N/2-1) * lambda * z * dfx; yf = xf; [Xf, Yf] = meshgrid(xf, yf); % 球面相位因子 spherical_phase = exp(1i * k / (2*z) * (Xf.^2 + Yf.^2)); const_factor = exp(1i * k * z) / (1i * lambda * z); Uf = const_factor * spherical_phase .* Uf_raw; end逻辑说明:这段代码把傅里叶变换后的频率坐标直接换算到了观察平面的空间坐标,缩放系数是lambda * z。也就是说,变换结果不需要再做一次 IFFT,而是直接对频域结果进行坐标映射,这正是夫琅禾费近似“远场就是频谱的直观表现”这句话的代码形态。
参数说明:输出坐标范围由lambda * z / dx0决定,波长越长、距离越远、物平面采样越密,远场光斑就越大。如果你用默认参数跑完后发现光斑只有几个像素,通常是把z设得太小,或者L0设得太大导致频率分辨率不足。
3.4 全息图的生成与再现
这份作业的压轴模块是数字全息。生成部分模拟了经典的离轴菲涅尔全息记录光路:
function hologram = hologram_generate(Uo, Ur, lambda, z, L) % 生成离轴菲涅尔全息图 % 输入: Uo - 物光复振幅, Ur - 参考光复振幅, lambda - 波长(m), z - 记录距离(m), L - 尺寸(m) % 输出: hologram - 全息图(强度分布) [N, ~] = size(Uo); % 菲涅尔衍射传播到记录平面 Uo_z = fresnel_ft(Uo, lambda, z, L, L); % 复用前面写的函数 % 参考光:倾斜平面波,入射角由 theta 控制 k = 2 * pi / lambda; dx = L / N; [x, y] = meshgrid((-N/2 : N/2-1) * dx, (-N/2 : N/2-1) * dx); theta = 1e-3; % 参考光与光轴夹角(rad),离轴量 Ur_phase = exp(1i * k * x * theta); % 干涉记录强度 hologram = abs(Uo_z + Ur_phase).^2; end再现部分则利用傅里叶变换的滤波性质,从全息图中提取出原始物光的复振幅:
function [Uo_recon, twin] = hologram_reconstruct(hologram, lambda, z, L) % 全息图数值再现:傅里叶变换法 % 输入: hologram - 全息图, lambda - 波长(m), z - 再现距离(m), L - 尺寸(m) % 输出: Uo_recon - 再现物光(实像), twin - 共轭像 [N, ~] = size(hologram); k = 2 * pi / lambda; % 模拟照明光再现 % 再现过程:H * R,其中 R 是参考光的共轭 % 简化处理:直接对全息图做傅里叶变换,频谱中的三个分量自动分离 H_f = fftshift(fft2(hologram)); dx = L / N; fx = (-N/2 : N/2-1) / L; [fX, fY] = meshgrid(fx, fx); % 离轴量决定频谱偏移,用频域滤波提取 +1 级(实像) % 滤波窗口半径经验值 filter_radius = 0.1 * N / L; center_x = N / 2 + round(k * theta * z / (2*pi) * N / L); % 正一级中心位置 % 生成圆形滤波掩膜 mask = zeros(N, N); [Xc, Yc] = meshgrid(1:N, 1:N); mask(((Xc - center_x).^2 + (Yc - N/2).^2) < (filter_radius * N)^2) = 1; % 提取正一级频谱并逆变换 H_filtered = H_f .* mask; Uo_recon = ifft2(ifftshift(H_filtered)); twin = ifft2(ifftshift(H_f .* circshift(mask, [0, -2*(center_x - N/2)]))); % 共轭像 end逻辑说明:离轴全息的本质是把物光和参考光的干涉条纹记录下来,再现时通过傅里叶变换让三组分量(零级、实像、共轭像)在频域分开,再用一个带通掩膜把正一级分量抠出来。代码里的mask就是那个“滤波器”,它的位置由参考光入射角决定。 参数说明:theta是离轴角,取得太小会导致频谱三个分量重叠,取得太大则条纹过密、采样不足。这个值在 1e-3 到 5e-3 弧度之间比较安全,具体由theta > 3 * lambda / dx的经验门槛约束。要注意这里在数值上为了兼顾效率和直观性,有一些工程取舍,但整体流程与教材上的光学全息完全一致,能够在 MATLAB 中得到肉眼可验证的再现像。
4. 运行环境与操作指南:把代码跑通是第一步
4.1 环境准备:版本和工具箱要求
这份作业对运行环境的要求不高,但它依赖了若干底层函数,版本太老会直接报错或得到错误结果。预处理脚本用的都是 MATLAB 基础函数,但有两个隐形门槛:
fft2/ifft2需要 MATLAB 自带的基础库,R2016a 之后这些函数对多线程支持的效率有明显差异- 推荐 R2020a 及以上版本,低版本跑
meshgrid在较大尺寸(N=1024)时内存表现较差
工具箱方面,严格来说不需要额外安装任何工具箱。但如果你作图时想用sgtitle这类排版函数,需要 MATLAB R2018b 以上。如果你用的是 Octave 这种兼容环境,fftshift和meshgrid的行为基本一致,但tiledlayout不可用,跑主脚本main_runner.m前需要把画图部分改成逐图显示的旧版写法。
4.2 快速启动步骤
跑通的流程很简短,按顺序执行即可:
# 假设你已经解压 zip 到工作目录 cd info_optics_homework matlab -nodisplay -nosplash -r "main_runner; exit"如果是在 MATLAB GUI 里操作,直接打开main_runner.m,点击“运行”按钮。主脚本会自动把utils目录加入路径,不需要手动addpath。脚本执行后,工作区里会出现几个关键变量:Uo(物平面光场)、hologram(全息图)、Uo_recon(再现像)。留意一下Uo_recon的幅度分布,应该能看到与原物相似的图案。
4.3 单文件调试的正确姿势
如果你想单独跑某一个仿真,比如只验证夫琅禾费衍射,注意不要直接运行fraunhofer_fft.m——因为它是函数文件,没有独立的执行入口。常见做法是在脚本里调用:
% 手动设置参数 N = 512; % 采样点数 L = 1e-2; % 物平面尺寸 1cm lambda = 632.8e-9; % 氦氖激光波长 z = 0.5; % 传播距离 50cm % 物平面:矩形孔 2mm x 1mm U0 = zeros(N, N); rect_x = round(N * 2e-3 / L); rect_y = round(N * 1e-3 / L); U0(N/2-rect_y/2 : N/2+rect_y/2, N/2-rect_x/2 : N/2+rect_x/2) = 1; % 调用夫琅禾费衍射函数 [Uf, xf, yf] = fraunhofer_fft(U0, lambda, z, L); % 显示强度分布 imagesc(xf*1e3, yf*1e3, abs(Uf).^2); axis image; xlabel('x (mm)'); ylabel('y (mm)');这里的U0构造方式也很典型——直接在矩阵中央填充一个矩形区域来模拟物体。注意round的使用:当物理尺寸与采样间隔的比值不是整数时,四舍五入会让孔的边界偏移不到一个像素,这在实际仿真中完全可忽略,但如果要做严格的光栅仿真,就需要用数值精确的布尔运算来构造物体矩阵。
5. 避坑实录:信息光学仿真里那些反复翻车的地方
5.1 现象:衍射结果出现整体漂移,光斑中心不在原点
原因:网格坐标生成方式不对。许多人习惯用linspace(-L/2, L/2, N)生成坐标,这样最后一个点和第一个点只差dx,从对称性上说是对的,但fft期望的输入是从零开始的序列。当你把linspace生成的坐标配合meshgrid使用时,零频位置会偏半个像素,导致整体平移。
解决:统一改用(-N/2 : N/2-1) * dx生成坐标,这样对称区间里样本总数正好是 N 个,且第一个点和最后一个点关于原点对称。这个细节我之前栽过不止一次,自从强制统一坐标生成方式后——不管写哪个仿真脚本,都从grid_generate.m拿坐标,就再也没犯过这个错。
5.2 现象:S-FFT 法和卷积法在同一参数下结果对不上
原因:两种方法对输出平面的坐标定义不同。S-FFT 法允许Lz和L0不同,输出网格变密或变疏;卷积法要求两个平面采样间隔一致。如果你只改了物理尺寸没改采样点数,两个结果的坐标缩放系数直接不一样,光斑尺寸自然不同。
解决:先确定当前传播距离属于“近场”还是“远场”。近场用小距离(比如z < 0.1 * L0^2 / lambda),统一用卷积法;远场直接用夫琅禾费公式或 S-FFT 法,并保证Lz = lambda * z / dx0的换算关系正确。我还习惯在代码里加一个断言,比较两种方法输出的总能量是否一致(误差小于 5%),如果不一致就打印警告。
5.3 现象:全息图能生成但再现像很暗,几乎看不到
原因:离轴角theta设得太小,频谱三组分量(零级、正一级、负一级)在频域上重叠,滤波时正一级信息被零级的强成分淹没。另一种可能是滤波窗口中心center_x计算错误,导致提取的是零级分量。
解决:把theta增大到 2e-3 弧度以上,并在生成全息图后先画一下fftshift(fft2(hologram))的频谱图,用眼睛确认三个亮点是否分得开。如果分不开,再调大theta或减小L(提高空间采样密度)。滤波窗口的半径也不要设成固定值,按全息图尺寸的 5%-10% 动态计算。
5.4 现象:程序不报错但内存飙满,运行卡死
原因:当N = 2048时,meshgrid生成的两个复数矩阵x和y各占 64MB,再叠加多层相位因子后工作区内存占用轻松突破 2GB。如果系统物理内存不足,MATLAB 会进入长时间的交换分区读写,看起来就是“假死”。
解决:把默认采样点数降到512。这个点数对大多数信息光学作业完全够用。如果必须用高分比率看细节,就分批计算相位因子,或者用单精度(single)数组保存中间变量,代价只是精度损失在 1e-7 量级,视觉上完全无感。
5.5 现象:光强分布出现同心圆环状伪影
原因:这通常是 FFT 固有的环绕卷积效应(wrap-around)造成的。当物平面的非零区域延伸到边缘附近时,FFT 的本质是周期性延拓,衍射计算会把从右边溢出的部分绕回左边,形成人为干涉条纹。
解决:给物平面周围加零填充。常见做法是把输入矩阵尺寸从N扩展到2N,物放在中心,边缘填充零。这能显著抑制环绕伪影。代价是计算量增加四倍,但在N = 512时依旧可以接受。我一般会把“零填充 + 裁剪”封装一个工具函数,任何仿真输入都过一遍它。
6. 二次开发方向:把作业代码改造成自己的仿真工具箱
有了这套基础代码,往上加东西并不难。最容易见效的改造是把衍射函数统一封装成标准的“输入光场 + 参数 → 输出光场”接口,然后写一个批量脚本,自动扫描多组传播距离z、波长lambda或孔径尺寸参数,把结果存成 PNG 序列,用来看参数演化的连续动画。脚本大概长这样:
% batch_diffraction.m z_list = linspace(0.05, 2.0, 20); for i = 1:length(z_list) Uz = fresnel_ft(U0, lambda, z_list(i), L, L * 1.5); imwrite(mat2gray(abs(Uz).^2), sprintf('frame_%02d.png', i)); end这个批处理思路可以直接扩展成 GUI 程序:做一个滑块控件调节传播距离,实时刷新右边的图案。MATLAB 的uislider加addlistener回调不用转 App Designer 也能搭出能用的交互界面,核心仍然调用的就是fresnel_ft。
更深入的改造方向是加物理模型——目前代码处理的是单色平面波,你可以加入高斯光束的初始相位项,或者把孔径函数从简单的矩形/圆孔换成由灰度图像加载的复杂振幅物体,比如加载一张图片作为物平面U0,看看它的菲涅尔衍射图样。这一步非常直观,会帮助你体会“图像信息经过光传播后发生了什么”。
我习惯把任何一种新想法都先跑通最小示例,再考虑扩展。这份作业代码的模块化程度比较高,加新功能时通常只需要新增一个aperture_*.m文件或修改U0的构造逻辑,不用动核心衍射算法。如果将来要适配 GPU 加速,也只需要把fft2换成gpuArray.fft2,核心公式不需要重写。希望这些改动能让你少走点弯路,毕竟仿真这事儿,跑起来永远比想出来更快验证。希望帮到你。
本文还有配套的精品资源,点击获取