简介:一份专注于离轴全息干涉模拟与单次快速傅里叶变换重构的MATLAB实例资源,面向光学工程、数字全息及信息处理领域的初学者和研究者,也适合作为高校相关课程的配套实验。资源包内含两个可运行的m脚本、一个光场数据mat文件及一张全息图tif样例,共4个文件,总大小仅909KB,结构紧凑,便于直接运行和二次开发。已有1144人浏览学习,具备广泛参考价值。通过该实例可系统掌握光源建模、离轴干涉计算、全息图生成、快速傅里叶变换、相位恢复及反变换重构等完整流程,帮助深入理解离轴全息的记录与重建机制。无论是用于课程设计、教学演示,还是作为复杂全息算法的起点,这套代码都能提供直观、可复现的实验支撑,并为进一步研究三维成像、生物医学光学等应用打下基础。 “重构”这个词在互联网语境里已经被用得很宽泛了,代码重构、项目重构、架构重构,似乎什么东西都可以叫重构。但在我常做的数字全息方向,“重构”是一个有明确物理含义的步骤——它是把全息图里记录下来的物光波前“还原”出来的过程。这篇文章要聊的就是一个最小可跑的实例:离轴全息干涉模拟及1-FFT重构。我会带你从物面搭建、参考光干涉、条纹记录,一直走到频谱滤波和逆变换出图,每一步都给出了可以复现的代码和需要注意的坑。适合刚接触数字全息的研究生,或者想在项目里快速验证离轴记录参数的工程师。
1. 先搞清楚“1-FFT重构”到底在重构什么
1.1 全息记录的三个像是怎么来的
数字全息的物理过程并不复杂:一束物光和一束参考光在记录面上叠加,探测器记录的是两者干涉后的强度分布
I(x, y) = |O(x, y) + R(x, y)|² = |O|² + |R|² + O·R* + O*·R
这四项里,前两项分别是物光自相关和参考光强度,频率成分都落在低频区域;后两项才是关键——它们包含了物光复振幅O的完整信息(振幅和相位都编码在干涉条纹里)。所以从信息角度看,全息图不缺东西,缺的是怎么把O从这四堆混叠的信号里分离出来。
如果参考光与物光完全同轴,这四项在频域全部挤在一起,无论你怎么滤波,都只能同时拿到实像和孪生像,这就是经典的同轴全息“孪生像”问题。而离轴全息的思路很朴素:让参考光带上一个倾斜载频,把正负一级像在频谱上推到远离零级的位置,再用一个矩形窗截出来。
1.2 载频给三项频谱分了频道
设参考光是平面波 R = exp(j·2π·f_R·x),f_R = sinθ/λ 就是离轴角对应的空间频率。对全息图做傅里叶变换后,三个项的频谱中心分别位于:
- 零级(|O|² + |R|²):频谱中心在0,带宽约为物谱带宽的2倍
- +1级(O*·R):中心在 +f_R,带宽约等于物谱带宽
- -1级(O·R*):中心在 -f_R,带宽约等于物谱带宽
要想三项互不重叠,需要满足 f_R ≥ 3B,B是物光最高空间频率。这个条件对应到实验里就是:离轴角不能太小,否则一级像会被零级亮斑淹没。对应到模拟里也是一样——你在电脑里造全息图,如果角度设得不够,照样会看到三个频谱糊成一团。
1.3 为什么叫“1-FFT”
这个叫法很容易让人误以为只做一次FFT就能出图。严格说,离轴全息的傅里叶重构法是对全息图做一次FFT得到分离频谱,截取+1级窗口,再执行一次IFFT得到物光场的复振幅分布。两次变换合起来是整个流程的操作主线,和基于衍射积分的逐点重建方法相比,主要计算量集中在这两次变换上,因此业界习惯简称为1-FFT重构。优点是速度快、实现简单、物理过程直观;缺点是它本质是“频域窗选”,如果物谱带宽很宽、或离轴角不满足分离条件,重建质量会明显下降。
2. 模拟搭建前必调的参数:角度、距离、像素的换算关系
2.1 关键参数速查
模拟不能上来就写代码,先确定参数。最常见的一组教学参数如下,照着这组参数做,频谱分离和采样约束都能满足。
| 参数 | 取值 | 说明 |
|---|---|---|
| 采样点数 N | 512×512 | 太小频谱分辨率不够 |
| 像元尺寸 Δ | 5 μm | 典型CCD/CMOS像素尺寸 |
| 波长 λ | 632.8 nm | He-Ne激光,教程经典波长 |
| 记录距离 d | 0.2 m | 物面到记录面的距离 |
| 离轴角 θ | 2° | 满足频谱分离条件 |
这组参数不是随便拍的,两个约束必须同时满足:
一是频谱分离条件。我特定一个简单物面,最高空间频率大致对应20~30像素宽度的图案,物谱半宽也就20像素左右。根据 f_R ≥ 3B 的规则,2°离轴角对应的频谱偏移像素数约为
offset = N·Δ·sinθ / λ = 512 × 5e-6 × sin(2°) / 632.8e-9 ≈ 141 像素
这远超物谱半宽的3倍,一级像安全落在零级和频谱边缘之间。如果角度只有0.5°,offset约35像素,很可能就压进零级亮斑里。
二是采样条件。干涉条纹的空间频率不能超过探测器奈奎斯特频率,即条纹周期不能小于2个像素。极限角度满足 sinθ_max = λ / (2Δ),对上述参数就是约3.63°。所以2°是一个靠中间、有余量的值。超过3.5°再加大角度,干涉条纹就开始欠采样,频谱会被折叠到边缘,反而坏事。
2.2 物面设计:加不加随机相位差很多
物面直接用一个简单图形就行,关键是别造一个理想得不像话的物体。真实物体表面是粗糙的,光照上去会产生漫散射。模拟时我习惯在振幅图案上叠一层小幅随机相位:
- 随机相位幅度太小(比如0.01π),散斑效应不明显,重构结果过于完美;
- 随机相位幅度太大(比如π),物频谱会展得很宽,离轴条件就难满足了。
推荐用0.1π~0.2π,既能模拟真实的漫射物体,又不至于让频谱宽度失控。振幅图案就用两个矩形拼成一个“Π”形,重构后能不能看出这个形状,就是判断算法是否成功的最直观依据。
2.3 传播用角谱法:三行代码,比菲涅尔近似省心
有的教程偷懒直接在记录面构造物光场,干涉倒是能做,但物理过程不完整,后续想讨论离焦、传播距离对重构的影响就没有抓手。我建议用**角谱法(ASM)**把物面传播到记录面,在频域乘一个传播因子
H(fx, fy) = exp( j·k·d·sqrt(1 - (λ·fx)² - (λ·fy)²) )
角谱法的好处是只要采样条件满足,传播距离内都是严格的,不额外引入菲涅尔近似的误差。代码就三行,何乐不为。
2.4 完整的离轴全息模拟代码
下面是模拟记录端的完整代码,跑完会得到一张数字全息图 I 以及频谱图:
import numpy as np import matplotlib.pyplot as plt # ========== 基本参数 ========== N = 512 # 采样点数 pixel = 5e-6 # 像元尺寸,单位m wl = 632.8e-9 # 波长,单位m d = 0.20 # 物面到记录面距离,单位m theta = 2.0 # 离轴角,单位度 theta_r = np.deg2rad(theta) f_r = np.sin(theta_r) / wl # 参考光空间频率 k = 2 * np.pi / wl # 空间坐标 x = (np.arange(N) - N // 2) * pixel XX, YY = np.meshgrid(x, x) # ========== 物面构建 ========== # “Π”形振幅物体,加0.15π随机相位模拟粗糙表面 obj_amp = np.zeros((N, N)) obj_amp[170:350, 170:210] = 1.0 obj_amp[170:350, 260:300] = 1.0 obj_amp[170:220, 170:300] = 1.0 obj_phase = 0.15 * np.pi * np.random.rand(N, N) u0 = obj_amp * np.exp(1j * obj_phase) # ========== 角谱传播到记录面 ========== fx = np.fft.fftfreq(N, pixel) FX, FY = np.meshgrid(fx, fx) fz2 = np.sqrt(np.maximum(0, 1 - (wl * FX)**2 - (wl * FY)**2)) H = np.exp(1j * k * d * fz2) U = np.fft.ifft2(np.fft.fft2(u0) * H) # ========== 离轴参考光 ========== R = np.exp(1j * 2 * np.pi * f_r * XX) # ========== 干涉记录 ========== I = np.abs(U + R)**2 # ========== 可视化 ========== S = np.fft.fftshift(np.fft.fft2(I)) plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.imshow(I, cmap='gray', vmin=0, vmax=I.max()*0.8) plt.title('hologram') plt.subplot(1, 2, 2) plt.imshow(np.log10(np.abs(S) + 1e-6), cmap='gray') plt.title('spectrum') plt.show()跑完这段,全息图是一团密集条纹,肉眼看不出物体;频谱图则是中心一个亮斑、两侧各一个亮斑。看到这种三个亮斑的频谱图,说明离轴条件已经成立,可以进入重构环节了。
3. 1-FFT重构实现:截窗、搬频、逆变换三步走
3.1 频谱图上到底在看什么
频谱图上,中央亮斑是零级像,包含物光自相关和参考光强度,属于无用信息;左右两个对称亮斑就是正负一级。我前面参数推导过,2°离轴角对应141像素的偏移,用代码验证一下:
offset = int(round(f_r * N * pixel)) print(offset) # 141所以+1级亮斑中心就在 (N/2 + offset, N/2),也就是(397, 256)附近。实际因为物体图案有宽度,一级像的频谱不是单点,而是一片以该坐标为中心的弥散斑。这就是物频谱,截它的矩形窗口大小就代表了你能保留多少物体细节。
3.2 窗函数选择与代码实现
核心重构代码如下,矩形窗提取+1级,平移到频谱中心,再IFFT:
# ========== 1-FFT重构 ========== S = np.fft.fftshift(np.fft.fft2(I)) offset = int(round(f_r * N * pixel)) center_x = N // 2 + offset center_y = N // 2 half_w = 45 # 窗半宽,按物谱实际大小调整 filtered = np.zeros_like(S) filtered[N//2 - half_w:N//2 + half_w, N//2 - half_w:N//2 + half_w] = \ S[center_y - half_w:center_y + half_w, center_x - half_w:center_x + half_w] recon = np.fft.ifft2(np.fft.ifftshift(filtered)) amp_recon = np.abs(recon)这里最关键的一步是把截出来的频谱“搬回中心”。为什么要搬?因为全息图的一级项本身带有参考光载频,不搬回去直接IFFT,空间域出来的结果会残留高频条纹。搬回中心在数学上等价于在空间域乘上参考光的共轭相位因子,也就是把载频“解调”掉,从而得到干净的空间域物像。这个细节新手最容易漏,漏了之后图像会叠加一层斜条纹,还以为是噪声。
3.3 振幅像和相位像的区别
重构出来的recon是复数数组。取模amp_recon,得到的就是物光场的振幅分布,可以看到“Π”形图案。这是强度像,也是数字全息里最常用的展示方式。
但要注意一点:如果你截取的是+x方向的亮斑,重构出来的是物光的共轭像O*;取-x方向的亮斑才是物光本身。对振幅像来说二者等价,因为|O*|=|O|;但如果你后续要做定量相位测量,相位符号会相反,必须留意自己截的是哪个方向。这是很多初学者做了很久相位重构才发现数据反了的原因。
另外,由于物面本身叠了0.15π的随机相位,振幅像上会看到一些散斑颗粒感,这是正常现象。不是算法出错,而是物面模型的粗糙度在重建结果中的自然体现。想要更平滑的重建像,可以把随机相位幅度降低或设置随机种子来复现。
4. 实测里逃不掉的三个坑:混叠、振铃、量化
4.1 离轴角太小,频谱叠成一片怎么判断
我建议你把theta改成0.5°再跑一遍,看看会发生什么。频谱图上,一级像和零级的边缘会不同程度地交叠。重构出来的图像就是中心被一块亮斑压住,边缘能看到图案轮廓,但对比度差到没法用。
判断标准很简单:看频谱图中一级亮斑和中央零级之间是否存在一条明显的“暗带”。只要有暗带,就说明分离条件成立;如果一级亮斑像是从零级亮斑边缘长出来的,角度就不够。
但也不能无限加大角度。前面算过极限角度约3.63°,超过这个值后条纹欠采样,一级像频谱会与另一侧的一级像或频谱边缘折叠。实际使用把这个范围放在1°到3°之间最合适。到了真实系统里,如果相机噪声大,推荐离轴角比理论最小值再留40%左右的余量,因为噪声会让一级像边缘的弱频率成分更难分辨。
4.2 矩形窗带来的振铃:什么时候需要换平滑窗
矩形窗最直接,但代价是窗边缘的频谱被突然截断,空间域会产生Gibbs振铃,表现是重构图像边缘出现明暗交替的振荡条纹。物体细节越丰富、高频成分越强,振铃越明显。图案尺寸小或者只关心低频形貌时,矩形窗完全够用。
如果想抑制振铃,把矩形窗换成二维汉宁窗就行了:
from numpy import hanning, outer w = hanning(2 * half_w) w2d = outer(w, w) filtered[...] = S[center_y-half_w:center_y+half_w, center_x-half_w:center_x+half_w] * w2d平滑窗以牺牲一定的频谱分辨率为代价换取旁瓣抑制。half_w不变时,汉宁窗的主瓣比矩形窗宽,表现为重建像边缘更柔和、细节略微软。我的经验是:如果只是看形貌和存在性,矩形窗即可;如果要抠边缘细节或者后续做定量分析,优先平滑窗,同时把half_w略微加大来补偿主瓣展宽。
4.3 模拟中加8bit量化,提前暴露相机的动态范围问题
实验相机输出的是量化后的整数灰度,常见8bit或12bit。模拟时如果一直用float64做到底,你会得到一个“过于干净”的结果。可以把干涉强度量化到8bit再重构,感受一下动态范围的影响:
I_quant = (I / I.max() * 255).astype(np.uint8) S_q = np.fft.fftshift(np.fft.fft2(I_quant.astype(float)))量化之后,零级亮斑的强度没怎么变,但暗区的条纹对比度被压到了一个灰度级别以内,重建像的散斑颗粒会更明显,物体边缘轮廓会变淡。原因在于离轴载频把物光信息搬到了高频区域,低比特量化对高频小幅起伏的记录精度远低于低频大幅度成分。模拟中加这一步,能让你提前知道实验系统的动态范围是否够用,而不是等拍完片子才发现重构结果发闷。
5. 从模拟走向实验时的几个衔接心得
仿真跑通之后,真正搭光路拍全息图,最大的感受是“模拟里没建模的噪声,在实验里全都冒出来了”。首当其冲是参考光的不均匀性:模拟里R是一个理想的单位振幅平面波,实验里激光束是高斯型的,参考光本身就有强度起伏,这会让频谱上的零级亮斑扩宽,分离条件变得更苛刻。处理办法是把参考光扩束后只取中心区域相对平坦的部分,或者在重构时先在记录面做背景减除。
另一个容易被忽略的是振动。离轴干涉对光程差极其敏感,空气流动、桌面振动都会让条纹模糊。模拟里无论怎么调参数都不会出现这个问题,但到了实验里,一台光学隔振平台和足够长的稳定时间,往往比算法优化更有效。
如果你想把这套模拟更贴近真实实验,我会建议按这个顺序做扩展:先给记录强度加泊松噪声模拟光子噪声,再考虑参考光高斯包络,最后把探测器量子效率的不均匀性用一个空间缓变因子乘上去。每一步加进去之后,用第3章的重构代码重新跑一遍,看看一级像频谱还能不能干净分离。我在实际项目里的体会是:模拟和实验的差距,最常出现在“认为参考光是理想的”这个假设上。把模拟做得越接近真实的非理想参考光,你从仿真里得到的参数结论就越能直接迁移到实验台上。这个方向可以持续深入,也是数字全息从演示迈向工程应用时必须走通的一步。
本文还有配套的精品资源,点击获取