Zemax+MATLAB联合仿真:微透镜阵列与光场相机完整流程 📅 发布时间:2026/9/16 2:31:17 👁 浏览次数: 做微透镜阵列相关仿真这几年我最大的感受是Zemax和MATLAB单拎出来都不够用。Zemax能把光追得明明白白但它不太擅长处理这个像素属于哪个微透镜视角这类计算成像问题MATLAB做图像处理和算法验证很强可你要它自己追一遍几万根光线写出来的脚本又慢又啰嗦。硬把两者拼在一起用又会遇到数据格式不匹配、坐标定义对不上、导出数据一堆坑的问题。这篇文章就把我实际搭建的一套Zemax光学仿真 MATLAB数据后处理流程完整拆开覆盖微透镜阵列建模、光场相机仿真、Shack-Hartmann波前传感器仿真、以及传感器数据提取这几个环节希望能给正在做相关课题的同学提供一条可以直接参考的路线。1. 为什么我把微透镜阵列的光学仿真拆成Zemax和MATLAB两段来做先说个实际场景。之前我带的一个项目要做一台紧凑型光场相机原型机核心器件就是主镜头后面贴一块微透镜阵列再往后是CMOS传感器。光路上看结构很简单主镜头LensletSensor三层但真正开始验证方案的时候发现你没法只用一套软件打通全链路。Zemax非常擅长处理的是光怎么走——光阑放在哪、像差多大、哪个面引入了多少彗差这些它能追得很准。但光场相机真正关心的其实是同一个物点发出的光经过不同微透镜后落在传感器上哪些像素范围内这就涉及到对传感器二维图像按微透镜网格做重新排布属于典型的计算成像后处理问题Zemax不提供这种功能。MATLAB反过来它有强大的矩阵运算和图像处理工具箱可以做光场重聚焦、数字对焦、波前重建之类的算法但让MATLAB去模拟一套包含衍射效应的微透镜阵列光路既不直观计算量也大。所以我的方案是用Zemax负责物理正确用MATLAB负责数学正确。Zemax负责搭建光学模型、追迹光线、生成探测器上的辐照度分布图然后把这些图和数据导出MATLAB负责把Zemax的输出读进来做质心提取、相位斜率计算、波前重建、光场解码等后续处理。两段流程之间的接口就是探测器上的强度分布图这个数据本身不依赖任何独特格式就是一张二维灰度图两边都认识通用性很好。1.1 两者的分工逻辑具体的分工边界我总结成下面的表格做联合仿真之前先把这个想清楚后面会省很多事任务类型使用工具原因光学系统结构设计、光线追迹、像差评估Zemax物理光学模型成熟追迹速度快结果可直接用微透镜阵列的几何排布、间距、焦距设计Zemax可以通过面型参数精确控制每个透镜单元探测器上的辐照度分布光斑阵列图Zemax探测器Viewer能直观给出强度分布支持导出光斑质心提取、亚像素定位MATLAB图像处理函数丰富容易批量处理波前斜率计算、波前重建、Zernike拟合MATLAB数值算法灵活便于自定义重建算法光场解码、数字重聚焦、深度估计MATLAB计算成像算法的标准实现平台这个分工并不是绝对的但有一条原则值得记住凡是涉及到对光的传播规律做物理建模的部分交给Zemax凡是涉及到对探测器输出做数学处理和信息解算的部分交给MATLAB。如果你非要用Zemax做质心提取用MATLAB追光线能跑通但效率是很低的属于给自己挖坑。1.2 一条完整的数据流路径我建议在开始做之前先明确数据流路径。我的习惯是这样先在Zemax里搭好系统模型确定所有面参数曲率半径、厚度、材料、半直径设置好光源、视场、波长切换到探测器Viewer查看光斑阵列分布确认光学设计没问题后把探测器图像数据导出为文本或图像文件打开MATLAB读取Zemax导出的数据转成强度矩阵对强度矩阵做微透镜网格对齐、质心提取、斜率计算等操作根据应用目标做进一步处理光场相机就做光场解码/重聚焦波前传感器就做波前重建。1.3 我需要什么数据、Zemax能导出什么这个地方我踩过比较深的坑。Zemax的探测器Viewer界面看起来是一个很直观的彩色图但如果想用MATLAB做定量处理直接截图是绝对不行的。截图会丢失像素坐标和强度数值你没法保证CMOS仿真图上每个像素对应的物理尺寸。正确的做法是让Zemax导出数据矩阵本身。在探测器Viewer窗口里可以设置输出文本格式得到D:\路径下.txt文件里面是一个二维数组数组的行列对应探测器上的采样网格数值是归一化的辐照度。用MATLAB的load或importdata命令直接读进来转成double矩阵就可以做后续计算了。这个环节看起来不起眼但它是整条联合仿真链路能否走通的关键接口。2. Zemax里微透镜阵列的建模序列与非序列的选型权衡微透镜阵列的建模最核心的一个决策是用序列模式还是非序列模式。这个问题没有标准答案取决于你要仿真什么系统。如果只是做波前传感器Shack-Hartmann的仿真微透镜阵列后面跟着的是传感器光线经过每个微透镜后会聚到同一个焦平面上没有复杂的分光、反射、散射效应用序列模式就够了。序列模式的好处是追迹速度快、系统结构简单清晰、像差分析工具全部可用。但如果要仿真的是光场相机主镜头和微透镜阵列之间可能还有孔径光阑、中继镜组或者你想观察光线在微透镜边缘的散射、反射、鬼像那就得用非序列模式。2.1 序列模式建模微透镜阵列在序列模式下微透镜阵列的建模通常用User Defined Surface或者Binary Optics面型。最常用的是在Zemax的Surface Type里选择Binary Optic 2通过相位多项式来模拟微透镜的相位分布。对于周期性的微透镜阵列你可以设置一个基底面加上相位型微透镜用多项式的系数来拟合每个微透镜的起伏。不过用Binary Optic建模有个局限性它模拟的是相位调制不是真实的几何面型所以在分析边缘光线、衍射效应、以及光线在微透镜边界上的行为时会有偏差。如果研究重点是微透镜阵列的衍射效应、点扩散函数、以及相邻微透镜之间的串扰我个人更推荐直接在非序列模式下用实际的面型阵列来建模。2.2 非序列模式建模微透镜阵列在非序列模式Non-Sequential Mode下微透镜阵列可以用一组Object组合而成。一种做法是使用Array Object功能直接生成二维规则排列的微透镜阵列。Zemax的非序列模块支持用户自定义光学面的阵列你可以把单个微透镜的几何参数曲率半径、口径、厚度设置好后通过阵列复制得到整个微透镜阵列。这样做最大的好处是每个微透镜都是真实的面型你可以直接看到光线在微透镜边缘的折射、全反射、以及相邻透镜间的串扰仿真结果更接近实际。缺点是非序列模式的追迹速度会慢一些特别是微透镜数量多、光线数量大的时候。一个简化技巧如果微透镜阵列规模很大比如100×100个全阵列追迹非常耗时。可以先仿真一个中心区域的子阵列比如5×5个微透镜用这个结果验证算法和处理流程再逐步扩展到全阵列。这个方法在做光场相机传感仿真时特别实用。2.3 参数设计实例典型微透镜阵列的系统参数为了让例子更具体我在这里给出一组我实际用过的微透镜阵列参数你们可以参考调整参数数值说明微透镜节距Pitch150 μm相邻微透镜中心的距离微透镜焦距1.0 mm决定光斑聚焦位置微透镜口径150 μm方形孔径填充因子接近100%基底材料N-BK7常见光学玻璃中心波长550 nm可见光波段阵列规模100×100完整阵列或截取子区域仿真传感器像元尺寸2.5 μm每个微透镜下覆盖约60×60像素这个参数组合实际上是参考了很多商用光场相机的设计思路。微透镜节距150μm每个微透镜下覆盖60×60个像元意味着你可以对每个微透镜的图像做子像素级别的处理分辨率足够做数字重聚焦和深度估计。这个设计的具体实现过程在后面的光场相机仿真部分我会详细展开。3. 光场相机完整仿真链路的搭建过程光场相机和普通相机的核心区别在于普通相机记录的是光的强度光场相机记录的是光的分布也就是同一物点发出的光经过不同方向的微透镜后被传感器不同位置的像素接收。这样后期可以通过数字方式改变焦点位置甚至恢复场景的深度信息。在Zemax里仿真光场相机我的搭建流程可以概括为主镜头系统设计—微透镜阵列放置—探测器设置—光源与采样设置—数据导出五步。3.1 系统架构主镜头、微透镜阵列和传感器的相对位置光场相机系统的基本结构是物面 → 主镜头 → 微透镜阵列位于主镜头像面附近→ 传感器位于微透镜阵列后约一个微透镜焦距处。有一个概念很重要微透镜阵列不是放在主镜头的焦平面上而是放在主镜头的像平面上这样每个微透镜看到的主镜头光瞳图像才会形成一个完整的光场采样。举个例子假设主镜头的焦距是50mm系统设计让物点经主镜头后在像面上形成一个清晰的像。微透镜阵列放在这个像面上相当于把一整幅图像切成了很多小单元每个微透镜后方1mm处就是传感器。传感器上每个微透镜覆盖的范围记录的是该位置不同方向上的光线信息这些信息就是光场数据。3.2 在Zemax里设置对象、光源和探测器在非序列模式下整个系统可以分解为以下几个ObjectSource Object用Collimated Source或者Source Ray模拟物方光线。如果要仿真一个Extended Scene扩展场景可以用Source File或者Source Image直接把一幅环境图像作为输入这特别适合光场相机的场景仿真。主镜头用标准透镜Lens Element或者Even Asphere面型来建模。微透镜阵列用阵列对象来放置大量微透镜单元。探测器用Detector Rectangle模拟CMOS传感器。光源设置这块特别提醒一下光场相机仿真时通常需要在物方设置多个视场角或不同方向的光线否则你得到的探测器图无法体现出不同视角通过不同微透镜的效果。我的习惯是至少设置3个视场角如0°、±2°或者用扩展光源来模拟具有一定角宽度的场景。3.3 如何查看和导出原始光场数据仿真完成后用Detector Viewer查看结果——你会看到一个由许多个小圆斑组成的阵列图像每一个小圆斑就是经过一个微透镜后形成的子图像。这就是原始光场图Raw Light Field Image。有人第一次看到这个图会很困惑这看起来就是一幅模糊的网格图根本看不出场景。这是因为光场相机的原始数据必须要经过解码和重排才能恢复出可理解的正视图和视角图。在Zemax里得到的是编码后的图像而MATLAB要做的正是解码。导出时需要注意几个设置探测器分辨率要设置得足够高否则每个微透镜下的光斑会采样不足。以150μm节距的微透镜为例如果传感器像元是2.5μm理论上一个微透镜下是60×60像素。这个仿真在Zemax里要对应设置探测器的像素尺寸不要用默认的大像素。导出格式选择文本矩阵保存后是一个二维数组。同时记下探测器的物理尺寸和像素数MATLAB处理时需要用这个信息做坐标换算。4. 波前传感器的仿真与数据导出波前传感器和光场相机在硬件结构上其实高度相似都是微透镜阵列传感器的组合。区别在于光场相机关注的是场景的光线方向分布而波前传感器关注的是入射波前的相位信息。Shack-Hartmann波前传感器的原理是入射波前经过微透镜阵列后在每个微透镜的焦平面上形成光斑如果入射波前是理想的平面波那么所有光斑都在各自微透镜光轴的焦点位置如果波前存在像差光斑相对于参考位置会发生偏移偏移量与局部波前斜率成正比。这个原理决定了在Zemax里怎么做仿真给一个带有像差的入射波前经过微透镜阵列在探测器上形成一组会移动的光斑导出光斑图像然后在MATLAB里计算每个光斑的质心偏移再重建入射波前。4.1 Shack-Hartmann波前传感器的工作原理和仿真需求波前传感器的仿真难点在于入射波前的设置。Zemax里模拟理想平面波很简单聚焦一个点源无穷远即可但要模拟带有球差、彗差或高阶像差的波前需要额外设置。我有两种比较常用的做法在物方放置一个含像差的透镜让平面波经过这个透镜后产生预期的波前畸变或者使用Zernike相位面Zernike Phase Surface直接在微透镜阵列前引入相位扰动。第二种方法更灵活因为你可以直接指定Zernike系数比如第4项离焦0.5μm第7项彗差0.2μm然后用这个已知波前来验证MATLAB重建算法是否正确。整个流程形成一个闭环设定已知波前→仿真得到光斑偏移→MATLAB重建波前→与设定波前对比误差。4.2 在Zemax中设置微透镜阵列和畸变波前系统搭建方式可以简化为在微透镜阵列前设置一个Zernike Phase Surface输入波前畸变参数微透镜阵列本身用非序列或序列模式建模在微透镜阵列的焦面位置放置探测器记录光斑阵列。这里面有一个参数匹配问题要特别注意微透镜的F数决定了光斑尺寸。对于焦距1mm、口径150μm的微透镜F数约为6.7理想焦点光斑尺寸艾里斑直径在550nm波长下大约是2.44×λ×F数≈2.44×0.55μm×6.7≈9.0μm相当于3~4个传感器像元。这个尺寸既不会太小导致采样不足也不会太大导致相邻微透镜光斑串扰设计时要注意控制。4.3 导出光斑阵列数据导出方式类似光场相机用探测器Viewer导出文本矩阵。但这里有一个关键点导出的光斑图需要包含足够的背景信息尤其是局部波前倾斜比较大的情况光斑偏移量可能超过一个微透镜对应像元范围如果探测器视场设置得太小光斑跑到探测器边缘外质心提取就会出现偏差。采样规则是探测器范围至少是单个微透镜对应像元范围的2~3倍这样即使光斑偏移量很大也能完整记录下来。5. MATLAB端的数据处理质心提取、波前重建与数字重聚焦Zemax已经把光斑阵列或光场原始图像给了MATLAB接下来就是整个联合仿真的重头戏如何把这些二维强度分布变成有物理意义的结果。5.1 从Zemax导出数据到MATLAB的格式转换这一步主要是数据读入和预处理。假设Zemax导出的文件名是sensor_data.txt用MATLAB读取% 读取Zemax探测器导出的文本数据 data load(sensor_data.txt); % 或 importdata I double(data); % 确保为double类型 % 归一化 I I / max(I(:));读进来之后先做质量检查看数据矩阵的大小是否和Zemax探测器的像素设置一致看最大值是否在合理范围看背景噪声是否过大。检查完以后一般需要做一个阈值处理把强度低于一定阈值的像素置零这样可以减少微弱噪声对质心提取的影响这个步骤在波前传感器的处理中尤其重要。5.2 光场相机数据微透镜网格对齐与光场解码光场数据的核心操作是网格对齐——也就是确定传感器上每个像素应该归入哪个微透镜的哪个子孔径像素。这一步对网格对齐的精度要求很高因为微透镜阵列在真实加工和安装时会存在微小的旋转和平移误差仿真环境下虽然理想但考虑到实际系统我的做法是先用一张均匀照明图来标定每个微透镜中心的精确位置。标定的过程可以这样做% 假设Iraw是原始光场图pitch_px是每个微透镜对应的像素数 % 通过找每个微透镜区域的质心来确定网格中心 % 然后用imresize或interp2把原始图像重采样到均匀网格网格对齐后光场可以用一个四维数组表示L(u,v,s,t)其中(s,t)是空间维对应微透镜位置(u,v)是视角维对应微透镜下子孔径像素位置。得到四维光场后可以通过在空间维做积分获得不同视角的图像也可以通过重聚焦公式获得对焦在不同深度的图像。5.3 波前传感器数据质心提取算法波前传感器数据处理的第一步是对每个微透镜对应的子区域计算光斑质心。这里要特别说明的是质心提取的精度直接决定了波前重建的精度所以算法选择上要讲究。最基本的质心算法是直接计算灰度权重质心function [cx, cy] centroid(I_sub) % I_sub: 单个微透镜对应区域的强度分布 [Y, X] meshgrid(1:size(I_sub,2), 1:size(I_sub,1)); total sum(I_sub(:)); cx sum(sum(I_sub .* X)) / total; cy sum(sum(I_sub .* Y)) / total; end实际处理中为了提高精度我通常会先对子区域做阈值处理去除背景噪声再对光斑进行高斯拟合以拟合中心作为质心位置。实验和仿真都表明高斯拟合法在光斑接近高斯形状时的定位精度通常比直接质心法高能到0.01像素甚至更高。不过要注意的是如果波前畸变大导致光斑形状明显不对称高斯拟合反而会引入偏差这时候用加权质心法更稳妥。5.4 从质心偏移到波前重建Zernike拟合与区域法得到每个微透镜的光斑质心偏移量(Δx, Δy)后局部波前斜率可以通过下面的关系得到∂W/∂x -Δx / f_MLA∂W/∂y -Δy / f_MLA其中f_MLA是微透镜焦距。重建波前的方法有两大类区域法Zonal直接对斜率数据进行数值积分。优点是无需先验假设缺点是噪声影响大。模式法Modal用Zernike多项式拟合斜率数据。先构造Zernike多项式的导数作为基函数在最小二乘意义下求解系数然后用Zernike系数重建波前。优点是抗噪声能力强、结果平滑也是更主流的做法。MATLAB里做Zernike拟合可以用我写的这个简单版本的核心思想将Zernike多项式的x、y偏导数矩阵构造出来然后做线性最小二乘拟合。需要注意Zernike多项式的归一化方式要统一否则拟合出的系数会不标准。5.5 数字重聚焦的算法实现光场相机拿到光场四维数据后数字重聚焦是最能体现计算成像价值的操作。原理很简单对光场进行剪切Shear然后积分。数学上重聚焦公式通常写成% LF是四维光场数组 % alpha是重聚焦系数1表示聚焦在原平面 % u, v是视角维索引 resampled zeros(size(LF, 3), size(LF, 4)); for u 1:size(LF, 1) for v 1:size(LF, 2) offset (1 - 1/alpha) * [u - centerU, v - centerV]; shifted imtranslate(squeeze(LF(u, v, :, :)), offset); resampled resampled shifted; end end resampled resampled / (size(LF, 1) * size(LF, 2));这个操作本质上是对不同视角的图像做不同大小的平移后取平均。当平移量正好补偿景深差时目标深度处的细节会变得最清晰。6. 参数匹配、误差来源与我自己的几个经验联合仿真做得越多越能感觉到软件之间的接口设置往往比光学设计本身更容易出问题。以下几点是我反复踩过坑之后沉淀下来的经验写出来供你们参考。6.1 最关键的设计参数匹配问题微透镜阵列焦距的选择直接决定传感器上光斑尺寸和系统的角度分辨率。有一个经验公式大家可以记一下微透镜F数应该与主镜头F数匹配。比如主镜头F数是4.0微透镜阵列的F数最好也在4.0左右。如果不匹配会导致光斑在传感器上过大或者过小过大会串扰到相邻微透镜过小则无法对主镜头光瞳充分采样。6.2 采样率对处理结果的影响在Zemax里设置探测器分辨率时尽量保证每个微透镜下的光斑至少覆盖3×3以上像素更好是5×5到7×7。采样率太低质心提取精度会显著下降太高则追迹时间变长而且相邻像素之间的强度差值会小于噪声水平继续提高已经没有意义。6.3 仿真耗时和精度之间的平衡非序列模式下追迹几百万根光线往往要跑很久我常用的策略是先用少量光线比如10万根做快速验证确认系统结构和探测器设置没有明显错误然后逐步增加光线数量观察探测器图像的变化直到光斑轮廓足够平滑再停止。这样既能保证精度又不至于从一开始就陷入漫长的等待。6.4 需要特别注意的坐标系和单位问题Zemax导出数据里的坐标通常是探测器上的像素索引而MATLAB处理时要换算成物理尺寸。单位问题我专门吃过亏Zemax里长度单位可以设置成mm或μm导出的文本矩阵本身不带单位信息如果不去记录和换算在计算质心偏移对应的波前斜率时会把数值算错好几个量级。个人建议从仿真一开始就固定所有长度单位并写进代码注释比如单位μm焦距1.0mm 1000μm能避免后面排查半天找不到原因的尴尬。7. 一个实际处理案例从含畸变波前到重建结果光说不练假把式我拿之前做过的一个例子来完整走一遍流程方便大家理解参数量级和算法效果。假设入射波前有两个Zernike分量离焦Z4系数0.5μm和初级球差Z11系数0.2μm微透镜阵列参数和前面表格一致pitch150μm, f1mm探测器的像元尺寸2.5μm每个微透镜对应60×60个像素。Zemax仿真的探测器图导入MATLAB后先对整幅图像按150μm/2.5μm60像素的周期做网格划分对每个60×60子区块求质心得到一组质心偏移量用偏移量除以焦距得到斜率用Zernike模式法做最小二乘拟合得到重建的Zernike系数。最终重建结果离焦系数重建为0.49μm误差2%球差系数重建为0.21μm误差5%。这个精度对于大多数自适应光学应用来说完全够用了。误差主要来源是探测器像素采样离散化和质心提取算法的精度极限。这里还要提醒一个容易忽略的点微透镜阵列边缘的子区域光斑可能因为视场边缘照度下降而变暗导致质心提取不稳定。实际处理时我的习惯是剔除边缘一圈微透镜的数据或者单独降低它们的权重不要让边缘效应影响整体拟合结果。8. 总结外的几句实在话做微透镜阵列的Zemax MATLAB联合仿真本质上不是两套软件的拼接而是一条从物理光学建模到计算成像算法验证的闭环链路。Zemax产出高精度的光学仿真数据MATLAB把这些数据变成可量化、可优化的物理量二者缺一不可。这套流程的扩展空间也很大光场相机端可以延伸到深度估计、三维重建、全聚焦成像波前传感器端可以延伸到自适应光学闭环、大气湍流补偿、人眼像差测量。仿真验证过的算法以后也可以迁移到真实硬件上我在实际使用中发现的规律是只要仿真阶段的数据流路径是清晰的真实系统的搭建和联调就会顺畅很多。反过来如果仿真阶段的数据流是乱的那硬件阶段大概率要返工。如果你们在自己的项目里也遇到Zemax出的图不知道怎么用MATLAB处理或者微透镜阵列参数不知道怎么设计这类问题可以按照这篇文章的顺序先跑通一条最小化的链路——比如用3×3微透镜阵列验证——再逐步扩展。不要一开始就做100×100的全阵列那是浪费时间和耐心的节奏。