ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

多焦点全息图生成算法:GS与加权GS的Python实现

多焦点全息图生成算法:GS与加权GS的Python实现 简介这是基于空间光调制器SLM生成均匀多焦点阵列的MATLAB设计脚本面向从事光学成像、光镊、微加工或生物医学光子学研究的工程师与科研人员。程序以多焦点光学理论为核心通过调制SLM相位分布在焦平面上产生间隔均匀、强度一致的多焦点光场适用于并行光学操控、激光加工及多光子成像等场景。资源共1个文件为.m脚本压缩包仅1KB代码简洁聚焦便于二次开发与嵌入现有光学仿真流程。脚本内容涵盖傅里叶光学中的衍射计算、SLM相位调制函数设计以及焦点分布的可视化处理可帮助读者从算法层面理解多焦点生成的原理并可直接修改参数适配不同物镜与波长条件。该资源已吸引218人学习浏览适合具备基础光学和MATLAB知识、希望快速搭建多焦点生成算法的入门及进阶用户。通过研读代码可掌握将目标光场反演为SLM相位图的关键思路为后续开展光镊阵列并行操控或激光并行加工实验提供算法参考。1. 为什么需要自己写 generateMultiFoci多焦点生成到底在解决什么问题做光镊阵列、多焦点激光加工或者光遗传刺激时一个常见需求是把入射光束变成几十个位置可控、亮度一致的光点。商用计算全息模块能出图但实验里往往需要实时改坐标、调权重甚至把生成过程嵌进闭环反馈这时候自己写一个 generateMultiFoci_Mutifocal 就显得自然。这个标题字面上是一个函数名但它背后是一个典型的纯相位全息图计算问题给定目标焦点列表算出空间光调制器SLM每个像素应该加载的相位值。工程上遇到这类需求最常见也最可靠的做法是迭代傅里叶算法也就是 Gerchberg-SaxtonGS及其变种。下面几条会先讲透“为什么不能简单叠加”再给一份可复现的 Python 实现最后把焦点均匀性、衍射效率和 SLM 加载的坑逐个拆开。爱动手的工程师读完就能把多焦点生成跑进自己的光路脚本里。2. 从单焦点到多焦点generateMultiFoci 的数学模型与GS算法2.1 焦点位置和相位梯度的关系在傅里叶透镜系统里SLM 放在前焦面CCD 或样品放在后焦面。入射平面波经过 SLM 相位调制后透镜等效于一次二维傅里叶变换。如果只需要一个焦点SLM 上加载的相位是一个线性光栅焦点坐标由光栅的空间频率决定。设焦点坐标为(cx, cy)像素间距为pixel_pitch那么需要的相位梯度与(cx, cy)成正比。换句话说单个焦点就是一个“倾斜平面”相位。多个焦点呢从数学角度理想的全息图相位应该是若干倾斜平面相位的叠加结果。但这里有一个容易踩的误区很多人直接把多个线性相位相加再取相位角得到的并不是有效多焦点。原因在于叠加后的振幅不再均匀而 SLM 只能做纯相位调制振幅起伏全部丢失结果只有部分方向的焦点能正常产生其它焦点被严重抑制。所以不能靠解析叠加必须依赖迭代优化算法。2.2 标准 GS 算法的迭代步骤Gerchberg-Saxton 算法是生成多焦点全息图最常用的迭代方法。核心思路是在两个平面之间交替施加约束在 SLM 平面强制振幅为常数 1在焦平面强制振幅等于目标振幅。每一步都用傅里叶变换在两个平面之间切换并用当前相位更新下一个平面。# 用伪代码表达标准GS主循环 slm_field exp(i * random_phase) # 初始随机相位 for _ in range(n_iter): focal_field fftshift(fft2(ifftshift(slm_field))) # 焦平面约束保留相位振幅替换为目标振幅 focal_field target_amplitude * exp(i * angle(focal_field)) slm_field ifftshift(ifft2(fftshift(focal_field))) # SLM平面约束保留相位振幅强制为1 slm_field exp(i * angle(slm_field))这个循环里fft2扮演透镜ifft2扮演反向传播。target_amplitude是目标强度模板的平方根因为模板描述的是光强而这里要约束的是电场振幅。标准 GS 对单个焦点收敛很快几十次迭代就能达到衍射极限附近但对多个焦点会出现能量分配不均匀焦点越多越明显。通常超过 8 个焦点后亮暗差别肉眼可见。2.3 均匀性问题的出路加权 GS加权 GSWeighted GS, WGS在标准 GS 基础上给每个焦点增加一个动态权重。迭代过程中统计当前每个焦点的峰值强度与期望强度比较然后调整该焦点在目标振幅里的占比。这样偏暗的焦点会在下一次迭代里被分配更多能量偏亮的焦点则被削弱。权重更新通常写成w_i w_i * (t_i / p_i)^alpha其中t_i是第 i 个焦点的期望相对强度p_i是当前实际峰值强度alpha是每次更新的松弛因子。标准 GS 可以看作 WGS 里alpha0的特例即权重永远不变。下表是两者在 4 个焦点、迭代 100 次时的一个典型对比实际数值会随焦点间距变化但趋势稳定。指标标准 GSWGS均匀性1 - RMS/均值0.82 左右0.95 以上衍射效率偏低略高对焦点数量的敏感性敏感稳健单次迭代计算量低增加一次峰值统计WGS 的代价只是每个焦点区域多一次邻域最大值查询对 512x512 的图像来说可以忽略。这也是为什么在真实光路上我更倾向把 WGS 作为默认实现而不是一开始就用标准 GS。3. 用 numpy 把 generateMultiFoci 跑通最小实现与参数表3.1 构造目标强度模板焦点数、位置与半径GS 算法的输入不是焦点坐标列表而是一幅和目标分辨率相同的强度图像。每个焦点可以建模为一个二维高斯峰。高斯峰的半径radius很关键太窄时离散采样损失峰值太宽时相邻焦点重叠。经验上取 3 到 5 像素比较稳妥。下面的函数负责构造模板。import numpy as np def build_target(shape, foci_coords, radius3, weightsNone): shape: (rows, cols)与SLM分辨率一致 foci_coords: [(cx0, cy0), (cx1, cy1), ...]像素坐标 radius: 高斯半径单位像素 weights: 各焦点亮度权重默认全1 ys, xs np.mgrid[0:shape[0], 0:shape[1]] template np.zeros(shape, dtypenp.float32) for i, (cx, cy) in enumerate(foci_coords): w 1.0 if weights is None else weights[i] gauss w * np.exp(-((xs - cx) ** 2 (ys - cy) ** 2) / (2 * radius ** 2)) template gauss template 1e-6 # 避免零振幅区域 return template / template.max()注意xs是列坐标cy是行坐标不要写反。模板归一化到最大值为 1可以防止迭代过程中振幅尺度漂移。如果实验里某个焦点需要更亮把对应weights调大即可。3.2 完整的 GS 实现一个函数出相位图把标准 GS 封装成一个函数是这类脚本里最常见的形态。下面这份代码可以直接复制只要传入目标模板就能得到一张[0, 2π)的相位图。def generateMultiFoci_Mutifocal(shape, target, n_iter50, verboseFalse): 生成多焦点纯相位全息图。 shape: SLM 分辨率 target: 由 build_target 生成的目标强度模板 n_iter: 迭代次数 verbose: 每10次打印一次残差 返回: phase_map范围 [0, 2π) rng np.random.default_rng(42) slm_field np.exp(1j * rng.uniform(0, 2 * np.pi, sizeshape)) target_amp np.sqrt(target) # 强度转振幅 for i in range(n_iter): focal np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(slm_field))) focal target_amp * np.exp(1j * np.angle(focal)) slm_field np.fft.ifftshift(np.fft.ifft2(np.fft.fftshift(focal))) slm_field np.exp(1j * np.angle(slm_field)) if verbose and i % 10 0: intensity np.abs(np.fft.fftshift( np.fft.fft2(np.fft.ifftshift(slm_field)))) ** 2 residual np.mean(np.sqrt((intensity - target) ** 2)) print(fiter {i}, residual {residual:.4f}) return np.angle(slm_field) % (2 * np.pi)代码中的fftshift和ifftshift成对出现目的是让零频位于图像中心保证焦点坐标(cx, cy)和模板像素位置直接对应。target_amp取平方根是因为模板是强度傅里叶变换操作的对象是复振幅。迭代结束后对相位取模是为了匹配 SLM 的 0 到 2π 相位范围。n_iter50是实践中的默认值。前 20 次迭代残差下降最明显50 次后基本进入平台期。如果你需要赶时间30 次也能用如果焦点数超过 20 个可以加到 100。一次迭代只做两次 FFT512x512 分辨率下耗时毫秒级所以实时调整坐标完全来得及。3.3 关键参数表这里写死的值都要由光学系统定参数默认值说明与调整经验shape(512, 512)必须等于 SLM 分辨率否则像素映射错位radius3约为艾里斑半径的一半太小则采样不足太宽则焦点重叠n_iter50观察残差曲线超过 100 次收益微弱背景项1e-6防止目标区域为零导致振幅替换失效随机种子42固定种子便于复现和前后对照一个经常被忽略的约束radius不能大于相邻焦点间距的一半。若两个焦点相距 10 像素radius最多 5否则高斯峰连成一片算法会把能量集中到两焦点中间最终形成拉长的光斑。这个规则在排错时优先检查。4. 参数调优与常见坑焦点均匀性、衍射效率、迭代数怎么设4.1 均匀性不够在GS迭代里加入WGS权重反馈当实测多个焦点亮度参差通常不是光路问题而是标准 GS 对能量分配偏心。切换到 WGS 时只需要在迭代循环里维护一个与焦点数相同的权重数组。核心逻辑是在焦平面强度计算出来后对每个焦点邻域做峰值统计再更新权重。# 循环外初始化 weights np.ones(len(foci_coords)) # 每次迭代取得focal后执行 intensity np.abs(focal) ** 2 for idx, (cx, cy) in enumerate(foci_coords): # 取3x3邻域的峰值避免单像素噪声 peak intensity[int(cy)-1:int(cy)2, int(cx)-1:int(cx)2].max() ratio target[int(cy), int(cx)] / peak weights[idx] * ratio ** 0.7 # alpha0.7 # 重新构造目标振幅 new_target np.zeros_like(target) for idx, (cx, cy) in enumerate(foci_coords): gauss np.exp(-((xs - cx) ** 2 (ys - cy) ** 2) / (2 * radius ** 2)) new_target weights[idx] * gauss new_target np.sqrt(new_target / new_target.max())这里的alpha0.7是我常用的安全值。alpha越大均匀性反馈越强但可能会导致某个焦点权重过冲出现闪烁。如果你发现均匀性在 0.9 以下先把alpha从 0.5 逐步往上调每次增加 0.1观察峰值波动。配合 3.1 节的weights参数可以把目标亮度差异也一并纳入迭代。4.2 迭代数不是越多越好看残差曲线GS 算法的残差曲线通常是一条快速下降然后平坦的曲线。打开verboseTrue每 10 次迭代记录残差如果 50 次后还在明显下降就要检查模板设置而不是继续加迭代数。最常见的问题是焦点坐标发生镜像翻转。因为fftshift使用不对称会导致实际焦点出现在目标位置的对角方向。判断方法很简单生成相位图后用 5.1 节的方法做一次模拟传播计算实际峰值坐标与foci_coords的偏差。如果偏差接近图像尺寸的一半说明fftshift次数不对称。4.3 衍射效率低的三个方向衍射效率是焦点总能量占入射总能量的比例。多焦点生成的效率通常在 60% 以上如果低于 50%按优先级排查相位量化、零阶残光和模板过宽三个问题。检查项典型现象处理方式相位量化SLM灰度位深导致的暗纹量化到8位用np.round(phase / (2*np.pi) * 255) / 255 * 2*np.pi零阶残光中心出现未调制亮点给中心区域加一个空白掩膜或者在相位图上叠加中心相位闪烁模板过宽焦点间串扰明显减小radius保证焦点间距大于 2 倍半径相位量化这一步常被新手跳过。SLM 驱动板通常以 8 位灰度加载把计算出的浮点相位量化到 256 级会引入少量噪声但衍射效率损失通常在 5% 以内。相比之下模板过宽造成的能量分散可以轻松吃掉 20% 的效率所以每次改完模板先做模拟验证再上光路。5. 把 generateMultiFoci 用到实际光路验证方法与技巧5.1 用模拟验证函数检查均匀性和位置把相位图重新通过 FFT 传播到焦平面是最快的回归测试手段。下面这个函数可以直接帮你评估生成的相位图好不好。def validate_multi_foci(phase_map, shape, foci_coords, radius3): field np.exp(1j * phase_map) intensity np.abs(np.fft.fftshift( np.fft.fft2(np.fft.ifftshift(field)))) ** 2 peaks [] for cx, cy in foci_coords: region intensity[int(cy)-2:int(cy)3, int(cx)-2:int(cx)3] peaks.append(region.max()) peaks np.array(peaks) uniformity 1 - peaks.std() / peaks.mean() return intensity, peaks, uniformity如果均匀性低于 0.9先回头检查模板半径和焦点间距不要急着换 SLM。若峰值坐标偏离目标超过 1 像素多半是坐标中心标定问题。5.2 SLM 加载前的量化与边缘处理相位图不能直接用浮点数塞给 SLM驱动板需要灰度值。8 位 SLM 的映射关系是灰度 0 对应 0 相位灰度 255 对应 2π。同时注意 numpy 数组的坐标原点在左上角而很多 SLM 面板的原点在左下角加载时需要做垂直翻转。另一个细节是有效区域外的像素统一置为灰度 0避免面板边缘反射产生杂散光。5.3 在光路上快速对齐焦点坐标真实光路里模板坐标和实际样品坐标之间通常有一个缩放系数。最省事的标定方法是先用单焦点模式在已知间距的标记物上打出两个光点测出像素间距与实际距离的比值然后把所有多焦点坐标乘上这个系数。后续如果发现整体偏移可以在foci_coords里减去一个公共偏移量而不是重新迭代生成相位图。这个技巧能让多焦点全息图在几分钟内完成对准而不是反复烧坏样品。本文还有配套的精品资源点击获取
返回列表