信息光学作业实操:从衍射仿真到数字全息的完整闯关指南

信息光学作业实操:从衍射仿真到数字全息的完整闯关指南 简介一份面向信息光学课程作业的MATLAB实践资源包适合光学工程、电子信息等专业的本科生及编程学习者。压缩包约22.71MB以MATLAB脚本与GUI相关代码为主要内容重点覆盖傅里叶变换、衍射数值模拟与交互式界面设计三大部分。在傅里叶变换部分借助fft2函数实现二维数据快速频域转换帮助理解图像频谱与空间结构的关系衍射模拟部分涉及菲涅尔衍射与夫琅禾费衍射的数值求解能够展示光波通过障碍物后的光强分布GUI部分通过按钮、滑块等控件与回调函数搭建可调节参数的光学分析界面。已有190人学习这份资源既可作为信息光学作业的参考实现也可作为科研入门与算法练习的素材。通过运行和调试这些代码学习者既能直观观察光学现象也能掌握MATLAB在光学仿真中的典型用法实现理论与编程能力的同步提升。 看到信息光学作业.zip这个文件名你可能觉得它不过是期末提交的一个普通压缩包。但真正把这份作业从头做到尾的人都知道这个包里面装着整门课最硬核的三座山衍射场的数值仿真、空间滤波光路的物理图像、还有数字全息里永远处理不干净的伪影。我前前后后花了两周才把整套作业跑顺中间推翻过三次方案踩的坑比学到的公式还多。这篇就以我完成这份作业的真实过程为线索从压缩包目录结构讲到核心仿真代码再到那些只有动手才能发现的细节和参数选择给正在被信息光学折磨的同学一份可以照着做的完整路线。1. 压缩包里的世界一份完整作业应该怎么组织先说一个很多人不在意的点作业包怎么组织直接影响批改老师愿不愿意认真看你的东西也直接影响你自己两周后还能不能看懂。信息光学这门课的作业几乎从来不是单一内容细数下来基本都包含衍射计算、空间滤波、全息重建这几大块有的老师还会加一道光学传递函数或相干成像的思考题。把这些内容全部平铺在压缩包根目录六个脚本、二十张图、一份报告挤在一起文件名还是未命名1.png这种包连你自己三天后打开都头疼。我最后交上去的目录结构长这样信息光学作业/ ├── 01_衍射仿真/ │ ├── angular_spectrum.py # 角谱法传播 │ ├── fresnel_fft.py # 菲涅尔单次FFT │ ├── params.json # 实验参数统一存放 │ └── figures/ │ ├── rectangular_slit.png │ └── fresnel_check.png ├── 02_空间滤波/ │ ├── spatial_filtering.py │ └── figures/ │ ├── lowpass.png │ ├── highpass.png │ └── directional.png ├── 03_数字全息/ │ ├── hologram_sim.py │ └── figures/ │ ├── hologram.png │ └── reconstruction.png ├── report/ │ ├── 实验报告.md │ └── figures/ └── README.md每个实验独立成目录代码、参数、出图放在一起README写明运行方式、Python版本和依赖库这样无论谁打开压缩包都能在五分钟内复现你的全部结果。报告里我也建议按原理推导一页纸、参数表、结果图、分析与故障记录这个顺序写而不是把网上抄来的大段理论堆在前面真正有用的实验参数反而藏在最后。我强烈建议在每个实验脚本的头部放一段参数表波长、采样间隔、点数、传播距离、孔径尺寸、离轴角。这一步看似多余却是后面所有诶我这个图怎么成这样了排查的起点。我做过一个很笨的对比实验把同一套代码分别放在有参数表和没有参数表的版本里隔一周再跑有参数表的版本半小时就恢复出全部结果没有的那个我硬是又推了一遍才想起来当初用的是多少纳米波长。很多奇怪的仿真结果最后都能追溯到某个参数抄错或单位写反。2. 衍射仿真里的三个暗礁衍射仿真是信息光学作业的标配但大部分人第一步就栽在实现方式上想直接对菲涅尔衍射积分做双重循环硬算。看一眼公式就能理解为什么这是死路——菲涅尔衍射积分本质是对物平面复振幅做带二次相位因子的傅里叶变换积分核里同时含有(x-ξ)²和(y-η)²两项。如果用双重循环直接算256×256的物面意味着256×256个观察点各自做256×256次复数累加运算量在O(N⁴)量级本地跑一次几分钟打底。等把物面加到512×512这个方案基本宣告报废。所以仿真首选必然是FFT但FFT带来三个教科书上提得少、实际却决定生死的细节。2.1 角谱法最不容易出错的通用方案角谱法的思路是在频域里做乘法把物平面复振幅做二维FFT得到角谱乘上自由空间传递函数再逆FFT回来。核心就几行import numpy as np def angular_spectrum(obj, dx, wavelength, z): 自由空间衍射传播角谱法。dx为采样间隔(m)z为传播距离(m)。 ny, nx obj.shape fx np.fft.fftfreq(nx, ddx) fy np.fft.fftfreq(ny, ddx) fx, fy np.meshgrid(fx, fy) k 2 * np.pi / wavelength # 传递函数每个平面波分量沿z传播时累积的相位 H np.exp(1j * z * np.sqrt(k**2 - (2*np.pi*fx)**2 - (2*np.pi*fy)**2)) U np.fft.fft2(obj) * H return np.fft.ifft2(U)这段代码里有几个值得留意的点。fftfreq的d参数填的是以米为单位的采样间隔得到的是真正的空间频率cycles/m而不是第几个像素这决定了后面所有物理量换算都不会出错。根号内出现负数时对应的倏逝波分量在大距离下指数衰减实际可以清零不过对常规作业场景影响不大。 提示如果你在傅里叶光学里看到传递函数 H exp(jkz·sqrt(1-(λfx)²-(λfy)²))的写法和上面这段是等价形式只是把k和波长约分了一下别被两种写法绕晕。为什么我推荐角谱法做主力方案因为它输出面的采样间隔和物平面完全一致画图、对比、量尺寸都省心。而直接套菲涅尔积分公式的单次FFT法输出面坐标会随传播距离变化初学者在这里必然翻车下面细说。2.2 菲涅尔单次FFT法的坐标变换陷阱菲涅尔衍射的傅里叶变换形式长这样U(x,y) exp(jkz)/(jλz) · exp[jk(x²y²)/(2z)] · F{ U0(ξ,η) · exp[jk(ξ²η²)/(2z)] }关键就在这个F{·}上。傅里叶变换的频域变量恰好是 fx x/(λz)也就是说观察面的物理坐标藏在频率坐标里。这意味着你用fftfreq得到的频率坐标必须再换算回观察面坐标换算关系是 dx_out λz / (N·dx_in)。我算个真实数字给你看波长632.8nm物面采样间隔10μmN512传播距离z20cm那么输出面像素尺寸就是 632.8×10⁻⁹×0.2 / (512×10⁻⁵) ≈ 24.7μm。也就是说输出图像比输入放大了接近2.5倍横轴刻度必须用24.7μm去标而不是沿用10μm。很多人仿真结果看起来对了一写报告的坐标轴单位就露馅根源就在这。如果老师没有强制要求某种方法我建议直接用角谱法绕开这个坑如果作业要求对比菲涅尔法那就单独写一个坐标换算函数把dx_out跟着结果一起返回别让换算代码散落在画图脚本里。2.3 菲涅尔传递函数法的距离上限另一种常见写法是直接在频域乘菲涅尔近似传递函数 H_F exp(-jπλz(fx²fy²))再逆FFT。它的实现最简单但有个很多人不知道的致命约束z必须满足大概 z ≤ N·dx²/λ 的条件否则传递函数的相位在相邻采样点之间的变化超过π奈奎斯特采样定理被破坏输出满是环形伪影。还是用刚才那组参数N512dx10μmλ632.8nm算一下 N·dx²/λ 512×(1e-5)²/6.328e-7 ≈ 8.1cm。传播距离一旦超过8cm这个方法的输出就开始不可靠。很多作业里的衍射距离是几十厘米甚至几米直接套这个方法会得到一堆看起来像干涉条纹的鬼东西实际上全是采样不够造成的混叠。遇到这种情况别怀疑物理先查参数换角谱法再试一次。2.4 出图前的两个小习惯一个习惯是强度图要看|U|²而不是|U|。物理上探测器记录的是光强是复振幅的模平方很多人顺手画了abs(U)量级和对比度全不对。另一个是画图前想清楚做不做fftshift关心频谱比如做空间滤波就要在频率平面中心观察必须先fftshift关心衍射场空间分布就用ifft2的输出别画完才发现中心偏移。还有一个容易被忽略的问题衍射场边缘的环绕伪影。FFT默认输入是周期的当传播距离较大时孔径边缘的衍射波会从图像一侧卷到另一侧。解决办法是在物面周围补一圈零填充比如把512×512填成1024×1024再传播做完裁掉边缘。这是我调试时最常做的事几乎每次都能明显改善边缘质量。3. 4f系统与空间滤波把图像处理做在光路上空间滤波实验考察的核心是透镜的傅里叶变换性质。一句话总结当物放在透镜前焦面时透镜后焦面上的复振幅分布就是物的二维傅里叶变换坐标映射关系为 fx x_f/(λf)其中x_f是后焦面上的物理坐标f是焦距。这就是4f系统的基本原理——第一个透镜把物变到频域第二个透镜再把频域变回空域中间放一个滤波器就完成了频域滤波。说白了光学系统里的4f结构和你在计算机里先FFT、乘掩模、再IFFT是一回事只是多了光学元件的物理约束。3.1 仿真时如何设计滤波器仿真实现通常不真的去模拟两个透镜而是直接对输入图像做FFT在频谱上乘一个掩模再逆FFT回来。关键在坐标换算。假设真实光路参数是焦距f100mm、波长632.8nm你在频域里想要截止频率ν_c 15.8周/mm对应光谱面上的孔径半径1mm换算到数字频谱坐标时要先把图像做fftshift得到中心对齐的频谱再用频率坐标找半径。直接看代码更直观def spatial_filter(image, mask_typelowpass, cutoff15.8, dx10e-6, wavelength632.8e-9, f0.1): ny, nx image.shape fx np.fft.fftshift(np.fft.fftfreq(nx, ddx)) fy np.fft.fftshift(np.fft.fftfreq(ny, ddx)) fx, fy np.meshgrid(fx, fy) freq np.sqrt(fx**2 fy**2) # 单位周/m if mask_type lowpass: mask (freq cutoff).astype(float) elif mask_type highpass: mask (freq cutoff).astype(float) elif mask_type directional: # 例滤除竖直条纹对应的水平高频分量 mask np.ones_like(freq) mask[np.abs(fx) cutoff] 0 spec np.fft.fftshift(np.fft.fft2(image)) out np.fft.ifft2(np.fft.ifftshift(spec * mask)) return np.abs(out)**2, mask这里最容易犯的错就是把掩模直接建在没有fftshift的频谱上导致滤掉的是角落里那半个频谱而不是中心。记住两个shift要成对出现构建掩模前先fftshift频谱乘完掩模后再ifftshift回去才能交给ifft2。另外如果你做的是物理孔径仿真cutoff这个值最好从 r_ap/(λf) 算出来别随便拍一个数不然报告里写参数时会自相矛盾。3.2 三种滤波实验足够撑起整份报告低通滤波把高频细节砍掉图像变模糊但噪声减少这对应真实光路里在频谱面放一个小孔光阑高通滤波挡住低频背景只保留边缘信息对应放一个挡中央的不透明圆片图像边缘会变得非常锐方向滤波专门对付周期条纹比如采样照片里常见的莫尔纹它的频谱能量集中在某个方向的狭长区域把那个方向频带挖掉就能恢复干净图像。这三个实验写在一个spatial_filtering.py里跑完就能出三张对比图写报告绰绰有余。3.3 为什么重建结果跟输入长得不完全一样很多同学仿真4f系统后疑惑理论说4f系统是恒等成像为什么我恢复出来的图跟原图有差别这里要分清两种情况。理想4f系统两个无限大理想透镜确实严格成立但你在频谱面加入孔径滤波后就有了新的现象——孔径边缘对频谱的突然截断会引起Gibbs振铃图像边缘会出现明暗交替的细纹。这不是bug是傅里叶变换的固有性质报告里把这个现象解释清楚反而是加分项。另一个常见现象是去掉DC分量后整体变暗、对比度反转这是高通滤波的正常结果不是采样问题。4. 数字全息重建零级像、孪生像和采样率三方打架全息实验是信息光学作业的重头戏也是最容易让人崩溃的部分。全息的本质是把物光波的相位信息编码进干涉条纹让物光O和参考光R在记录面干涉得到强度分布I |O|² |R|² O·R* O*·R前两项是零级项DC背景第三项包含物光的复振幅信息第四项是共轭项孪生像。重建时把记录的全息图乘上参考光R*再做一次衍射传播回到物平面就能看到原始像和孪生像同时出现中间还夹着一个刺眼的DC亮斑。4.1 离轴角怎么选一个不等式搞定这三个项在频谱面上各占一块区域零级项带宽最宽约2BB是物体本身的最大空间频率真实像带宽B孪生像带宽B。要让它们不重叠参考光的空间频率 f_ref sin(θ)/λ 必须满足 f_ref 3B同时为了让干涉条纹本身不混叠还要满足 f_ref B 1/(2dx)。两个不等式合起来就限定了你的采样间隔dx不能太大。这解释了数字全息为什么必须用足够细的探测器像元也解释了仿真里随便选一个离轴角就全糊的原因。4.2 仿真代码与三个最常见的翻车现场仿真分两步先模拟记录再模拟重建。def simulate_hologram(obj, dx, wavelength, z_rec, theta_deg): # 物光传播到记录面 U_obj angular_spectrum(obj, dx, wavelength, z_rec) # 参考光以离轴角 theta 入射的平面波这里取 x 方向倾斜 theta np.deg2rad(theta_deg) x np.arange(obj.shape[1]) * dx x, y np.meshgrid(x, x) k 2 * np.pi / wavelength R np.exp(1j * k * x * np.sin(theta)) I np.abs(U_obj R) ** 2 # 记录全息图 return I, U_obj def reconstruct(I, dx, wavelength, z_rec, theta_deg): # 乘参考光恢复物光再反向传播 theta np.deg2rad(theta_deg) x np.arange(I.shape[1]) * dx x, y np.meshgrid(x, x) k 2 * np.pi / wavelength R np.exp(1j * k * x * np.sin(theta)) U_rec angular_spectrum(I * R, dx, wavelength, -z_rec) return U_rec翻车现场一是重建距离和记录距离对不上图像会带严重的二次相位看起来像失焦模糊。现场二是离轴角给得太大干涉条纹密度超过奈奎斯特极限频谱直接折叠重建结果里全是摩尔纹唯一的解法是降低角度或缩小采样间隔。现场三最气人一切参数都对但重建图中央的DC亮斑亮得惊人物体信息淹没在背景里。我的处理经验是先用log刻度显示重建强度把DC削掉再看旁边区域或者更简单重建前先在全息图频谱里把零级项对应的小范围置零再乘参考光传播对比度会立刻好很多。提示数字全息仿真里重建距离必须等于记录距离是铁律。有的人为了看清晰像去调z结果怎么调都是糊的就是没搞清楚这个基本约束。4.3 仿真参数怎么选不容易翻车我后来稳定使用的一组参数是波长632.8nmN1024dx3.45μm模拟常见CMOS像元尺寸记录距离z0.05m离轴角2°。这个组合下物体带宽B大概在几十周/mm量级f_ref sin2°/632.8nm ≈ 5.5e4周/m折合55周/mm恰好落在大于3B和小于1/(2dx)≈145周/mm的安全区间。先把这组参数跑通再往大角度或长距离试探能少走很多弯路。5. 让我少熬两个通宵的实验习惯最后分享几个纯粹从实战中总结出来的习惯这些不属于任何实验指导书但真的能救命。参数留痕这件事再怎么强调都不过分。每个实验脚本顶部放一个字典或注释块记录波长、采样间隔、N、所有距离、孔径尺寸、角度。别以为当时记得住等你在三个实验之间来回调参数第二天再看就全忘了。更狠一点的做法是准备一个背锅记录文本每次跑出奇怪结果就记一行参数是什么、现象是什么、改了什么。这份记录写到报告里就是现成的故障分析章节比临场回忆准确得多。先用解析解校准仿真。正式跑复杂物体之前先拿矩形单缝或圆孔这种有解析衍射公式的物体做一次仿真把仿真得到的暗纹位置跟理论值对比。矩形缝衍射的暗纹位置在 x mλz/a圆孔的Airy斑第一暗环在 1.22λz/D。这一步能一次性验证坐标换算、波长单位、采样间隔全都没错比对着复杂结果苦思冥想高效一百倍。我第二次做这份作业时半小时就把这条校准流程跑完了之后再没出现方向对但尺寸错的低级问题。报告里放故障分析而不是只放成功图。我第一次交作业时只贴了漂亮的结果图批语是过程太少。后来把每次失败的截图和参数一起贴进报告说明为什么出错、怎么排查的反而得了高分。信息光学是一门非常依赖数值细节的课能把伪影讲清楚比贴十张完美图片更能证明你懂。毕竟伪影背后往往是采样定理、坐标映射、相位处理这些最核心的知识点。不要等到交作业当天才跑全流程。所有代码分模块单测都通过后找一天从头到尾按README跑一遍确认一键出图。我最后一次提交前就是因为之前一直分开调试结果合并图片时发现某张图的坐标轴单位没换算连夜又改了一版。提前一天做全流程复现能避开所有类似的低级事故。说到底信息光学作业.zip这个文件名承载的是对傅里叶变换和衍射物理的理解程度。代码能跑起来只是及格线真正拉开差距的是那些参数之间的约束关系你是否真的在意。这份作业做完之后我再看任何光学仿真代码第一反应都是先查它的采样设置和相位处理——这个习惯大概比作业分数本身值钱得多。本文还有配套的精品资源点击获取