Matlab实现深度学习波前重建:从Shack-Hartmann光斑图到Zernike系数

发布时间:2026/10/25 23:30:22
Matlab实现深度学习波前重建:从Shack-Hartmann光斑图到Zernike系数

简介:面向计算机、电子信息与数学类专业学生,这套基于深度学习和Shack-Hartmann波前传感器的Matlab仿真资源,提供了从传感器光斑检测到深度网络波前重建的完整实现方案。内容涵盖Shack-Hartmann传感器原理仿真、ResUNet深度学习模型演示、降噪对比及结果可视化代码,可直接用于课程设计、期末大作业或毕业设计。压缩包共46个文件,以20个m主程序、7个py辅助脚本、6张png结果图及1个mat数据集为主,另含工程配置、说明文档和模型文件,整体仅1.84MB,轻量易运行。已有477人学习使用。代码采用参数化编程,关键参数可一键调整,注释逐段写明思路,案例数据可整体替换后直接运行;对于初学者,既能借完整流程掌握波前传感与深度学习结合方法,也可在此基础上扩展改进,是兼具可读性与实用性的参考项目。

1. 基于深度学习和Shack-Hartmann波前传感器的波前重建:一个能落地的Matlab仿真方案

拿到“基于深度学习和Shack-Hartmann波前传感器的波前重建Matlab仿真”这个题目时,大多数人第一反应是“这不该是Python的活吗,Matlab能行吗?”——我的答案是能,而且用对工具后比想象中更顺。这个仿真要解决的问题很具体:传统Shack-Hartmann波前传感器靠质心提取测子孔径斜率,但在低信噪比、光斑变形、子孔径越过孔径边缘时,质心法误差会被急剧放大;用深度学习训练一个卷积网络,让光斑图直接回归Zernike系数或波前相位,可以绕开质心这个瓶颈。这套方案尤其适合做自适应光学、眼底像差测量、激光传输仿真的研究生和工程师。我一般会在Matlab里用Deep Learning Toolbox配合自建物理模型来完成,从数据生成到训练评估是一条完整的链路。

2. 从光斑位移到Zernike系数:Shack-Hartmann重建链路与深度学习切入点

2.1 Shack-Hartmann传感器的数据链路:你手里只有一幅光斑图

Shack-Hartmann波前传感器的基本结构不复杂:入射波前经过一组微透镜阵列,每个微透镜只截取波前的一小块,并在探测器对应区域聚焦成一个光斑。如果波前是理想平面波,光斑会落在子孔径中心;如果局部波前倾斜,光斑就会偏移。光斑质心偏移量与子孔径内波前平均斜率成正比,这个关系可表示为:

sx = (1/A) ∬A (∂W/∂x) dxdy

但要注意,传感器直接输出的是探测器上的光斑强度图像,而不是斜率。传统处理链路是:先对每个子孔径区域做质心提取,得到二维斜率阵列,再做波前重建。波前重建有区域法和模式法,模式法里最常用的是Zernike模式法,把波前展开成Zernike多项式:

W(x,y) = Σ ci Zi(x,y)

关键的坑在于,质心提取是整个链路里最脆弱的一环。当子孔径内光斑信噪比低于1、光斑被像素化离散、或者光斑与相邻子孔径重叠时,质心计算会产生系统性偏差。质心法通常只统计光斑的一阶矩,光斑形状中携带的衍射信息完全没利用。这个环节一旦出错,后面用最小二乘解Zernike系数时只能得到“漂亮但错误”的拟合结果。这就是我为什么坚持在仿真阶段就把成像物理模型做到位,否则网络学到的只是数据生成器的规律,而不是波前重建能力。

2.2 深度学习为什么能切入:把“质心+重构”变成“图像到系数”回归

深度学习的切入点是把测量模型当作一个黑盒:既然输入是光斑强度图,输出是波前Zernike系数(或波前相位网格),那么就可以直接用卷积神经网络从图像回归到系数。这样省掉了质心提取、阈值设置、子孔径划分这一连串手工步骤。CNN能自动学到的特征是光斑的位移、形状变化,甚至局部光强不均匀,这些信息在传统质心法中经常被丢弃。

我一般用Zernike系数作为输出目标,而不是直接输出相位网格。原因很简单,系数维度低,一个15阶甚至30阶的系数向量就可以描述大部分眼像差和大气湍流像差,全连接层可以直接输出;而相位网格动不动就是128×128维,训练数据量需求会高一个数量级。下面这张表是这个方案里最常见的选型对比:

对比项质心+Zernike拟合深度CNN回归
抗噪能力弱,光斑一亮就偏强,可隐式学噪声分布
子孔径失效需手工掩膜自动学会忽略坏子孔径
标定成本无需要训练数据
解释性强弱,黑匣子
部署成本几乎零需要推理框架

这个对比并不是说深度学习一定更好,而是在低信噪比和大动态范围场景下,它有明显的空间。如果要复现标题里的仿真,核心工作就是先把仿真数据生成器做出来。

2.3 生成仿真数据:用随机Zernike系数造出“光斑图+系数”配对

训练深度学习模型前,得先有成对的训练样本:输入是带噪声的光斑强度图,输出是Zernike系数向量。我通常用前面30阶Zernike模式,随机生成系数,计算孔径内相位,再求梯度,按子孔径划分得到光斑偏移,叠加高斯光斑形状和泊松噪声,最后把整个子孔径阵列拼成一张图。下面是一个核心的Matlab生成函数:

function [img, coeffs] = generateSHImage(numSubApertures, pixelPerSubap, numZernike, params) % 生成单个训练样本:随机Zernike系数 -> 光斑图像 % params: 包含 kx, ky, sigma, photonCount, background 等标定参数 % 1. 随机Zernike系数,方差按模式序号衰减,模拟实际像差能量分布 c = randn(numZernike, 1) .* (0.8 .^ (0:numZernike-1)'); % 2. 生成单位圆网格上的Zernike多项式展开 N = numSubApertures * pixelPerSubap; [x, y] = meshgrid(linspace(-1, 1, N)); rho = sqrt(x.^2 + y.^2); theta = atan2(y, x); phase = zeros(N); for i = 1:numZernike Z = zernfun(i, rho, theta); % 自实现Zernike多项式函数 phase = phase + c(i) * Z; end % 3. 计算整幅相位梯度,再按子孔径区域求平均斜率 [dPh_dx, dPh_dy] = gradient(phase); spSize = pixelPerSubap; slopesX = zeros(numSubApertures); slopesY = zeros(numSubApertures); for m = 1:numSubApertures for n = 1:numSubApertures xRange = (m-1)*spSize+1 : m*spSize; yRange = (n-1)*spSize+1 : n*spSize; slopesX(m,n) = mean(dPh_dx(xRange, yRange), 'all'); slopesY(m,n) = mean(dPh_dy(xRange, yRange), 'all'); end end % 4. 用高斯光斑模型生成探测器图像,kx/ky把斜率换算成像素位移 img = zeros(N, N); [xx, yy] = meshgrid(1:N, 1:N); for m = 1:numSubApertures for n = 1:numSubApertures centerX = (m-0.5)*spSize + slopesX(m,n) * params.kx; centerY = (n-0.5)*spSize + slopesY(m,n) * params.ky; img = img + exp(-((xx-centerX).^2 + (yy-centerY).^2) / (2*params.sigma^2)); end end % 5. 加泊松噪声和背景光 img = poissrnd(img * params.photonCount) / params.photonCount + params.background; coeffs = c; end

逻辑说明:函数首先生成一组随机Zernike系数,系数方差随阶数增大而衰减,符合真实像差的能量谱。然后把相位展开到整幅像素网格上,通过gradient计算相位梯度,再对每个子孔径区域做平均,得到光斑位移量。最后用高斯核模拟光斑,经过泊松噪声和背景叠加,得到接近真实探测器输出的图像。这里最关键的是zernfun函数必须用同一个单位圆网格,否则后续重建时坐标对不上。

参数说明:kx和ky是光斑位移比例系数,单位是像素/斜率。它由微透镜焦距、波长和探测器像元大小共同决定。当微透镜焦距越长,光斑位移越灵敏,系数越大。sigma设为0.5到1.5个像素,模拟衍射极限下的光斑大小。photonCount决定光子数,训练时可以随机在1e3到1e5之间取log均匀分布,让网络对亮度变化更鲁棒。background设成0.02到0.2,代表暗电流和背景散射。这些参数直接决定CNN学到的是什么样的“传感器”,值得反复调整,而不是随便填。

3. 用Matlab搭建可复现的训练集:数据组织、CNN结构与训练选项

3.1 把样本组织成Datastore:避免一次性载入几百幅大图

写数据生成函数后,下一步是组织训练集。常见做法是写一个自定义datastore,或者将生成的图像批量存入mat文件。我更推荐用mat文件存成数组:把所有图像存成H×W×1×N的四维数组,标签存成N×K矩阵。这样trainNetwork可以直接吃数组,但缺点是N很大的时候内存占用高。用imageDatastore加augmentedImageDatastore是更好的做法,不过需要把图像写入PNG或JPG;在仿真环境里我一般直接生成两个随机数组然后喂给trainNetwork,对于几千个样本足够。

下面是一段数据集生成脚本,注意我在调用生成函数前先定义了标定参数:

% 生成训练集和验证集 rng(7); % 固定训练集随机种子 numSubA = 8; % 子孔径数 8x8 pixPerSub = 16; % 每个子孔径像素 16x16 numZ = 15; % Zernike阶数 numSamples = 5000; numVal = 500; params.kx = 30; % 斜率到像素位移比例 params.ky = 30; params.sigma = 1.0; % 光斑高斯半径 params.photonCount = 1e4; % 光子数 params.background = 0.05; % 背景光强 XTrain = zeros(pixPerSub*numSubA, pixPerSub*numSubA, 1, numSamples, 'single'); YTrain = zeros(numSamples, numZ, 'single'); for i = 1:numSamples [img, c] = generateSHImage(numSubA, pixPerSub, numZ, params); XTrain(:,:,1,i) = single(img); YTrain(i,:) = single(c(:)'); end % 验证集使用同一组参数,但固定另一随机种子 rng(42); XVal = zeros(pixPerSub*numSubA, pixPerSub*numSubA, 1, numVal, 'single'); YVal = zeros(numVal, numZ, 'single'); for i = 1:numVal [img, c] = generateSHImage(numSubA, pixPerSub, numZ, params); XVal(:,:,1,i) = single(img); YVal(i,:) = single(c(:)'); end % 输入归一化到[0,1]区间 XTrain = (XTrain - min(XTrain(:))) / (max(XTrain(:)) - min(XTrain(:))); XVal = (XVal - min(XVal(:))) / (max(XVal(:)) - min(XVal(:))); save('sh_dataset.mat', 'XTrain', 'YTrain', 'XVal', 'YVal');

逻辑说明:脚本预设8×8子孔径,每个子孔径16×16像素,所以单幅光斑图是128×128。先调用生成函数得到四维数组,再把所有图像统一归一化到0-1。使用single类型可以显著降低内存占用,5000张128×128图像的体积在500MB左右,普通机器能承受。保存为mat文件后,后续训练脚本直接load,避免每次重新生成浪费大量时间。

参数说明:5000张训练集在8×8子孔径、128×128分辨率下基本够用;如果子孔径数提高到16×16,样本数应提升到10000以上。归一化采用全局min-max会破坏绝对光强信息,但CNN可以学到相对差异;如果后续要做真实传感器迁移,建议改成按每幅图的均值/标准差做z-score,保留光斑相对亮度。标签YTrain直接使用随机Zernike系数,没做标准化。训练时用MSE损失会引导网络自适应数值范围,但为了加速收敛,也可以提前减去均值、除以标准差,最后推理时再反算。

3.2 定义CNN回归网络:卷积层、BN层与全连接层

针对这个回归任务,网络结构不需要很深。光斑图像的特征本质上是局部位置的偏移,层级太深反而会造成位置信息丢失。我一般用三层卷积提取局部特征,然后下采样,接两个全连接层输出15个系数。下面是一个Matlab层定义:

% 定义CNN层 layers = [ imageInputLayer([128 128 1], 'Name', 'input', 'Normalization', 'none') convolution2dLayer(3, 16, 'Padding', 'same', 'Name', 'conv1') batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu1') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool1') convolution2dLayer(3, 32, 'Padding', 'same', 'Name', 'conv2') batchNormalizationLayer('Name', 'bn2') reluLayer('Name', 'relu2') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool2') convolution2dLayer(3, 64, 'Padding', 'same', 'Name', 'conv3') batchNormalizationLayer('Name', 'bn3') reluLayer('Name', 'relu3') fullyConnectedLayer(64, 'Name', 'fc1') dropoutLayer(0.5, 'Name', 'dropout') fullyConnectedLayer(15, 'Name', 'fc2') % 输出与Zernike系数数量一致 regressionLayer('Name', 'output') ];

逻辑说明:输入层明确写128×128单通道图;三个卷积层都在下采样后增加通道数,用批归一化和ReLU避免深层梯度消失。max pooling把空间尺寸从128降到32×32,保留足够的位移信息。两个全连接中间加入0.5的dropout,防止对训练数据过拟合。最后一层fullyConnectedLayer(15)输出15个Zernike系数,接regressionLayer作为MSE损失。

参数说明:卷积核大小设为3×3,对于光斑这种只有十几个像素的目标足够。如果光斑位移范围超过5个像素,建议把第一个卷积核换成5×5,否则感受野不够。dropout比例0.5是默认值;当训练样本只有几百张时,我一般会调到0.7来加大正则。Matlab较新的版本(如R2026b)里可以采用深度网络设计器快速调结构,但注意训练时别忘了给数据指定'Normalization', 'none',因为inputLayer的归一化会影响最终输出。

3.3 训练选项:SGDM与验证集的选择

训练选项直接影响收敛速度和最终精度。我一般用SGDM而不是Adam,因为它在这个回归任务上的泛化更稳,虽然慢一点。训练选项代码如下:

options = trainingOptions('sgdm', ... 'InitialLearnRate', 1e-3, ... 'MaxEpochs', 40, ... 'MiniBatchSize', 32, ... 'Shuffle', 'every-epoch', ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 20, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.3, ... 'LearnRateDropPeriod', 10, ... 'Plots', 'training-progress', ... 'Verbose', false); net = trainNetwork(XTrain, YTrain, layers, options);

逻辑说明:采用SGDM,初始学习率1e-3,训练40轮。每10轮学习率乘以0.3,这是仿真任务里比较稳妥的衰减策略。验证集每20个迭代评估一次,通过损失曲线直观判断是否过拟合。Shuffle设为every-epoch,保证每个epoch内样本顺序不同,避免网络学到固定次序。

参数说明:如果把回归目标换成更难的相位网格,初始学习率应降到3e-4;换成Adam也可以,但在Matlab里SGDM通常需要更多epoch但泛化更好。MiniBatchSize取决于显存,32在4GB显存上可用,否则降到16。ValidationFrequency不宜太小,否则频繁打印验证损失会拖慢训练。训练完成后用save('sh_cnn.mat', 'net')保存网络,方便后续测试和部署。

4. 训练后的评判:别只看训练损失,要看残差波前和RMS

4.1 从训练好的网络到重建:加载模型并计算系数

训练结束后的重建很简单:用predict函数把新光斑图转换为系数。但这里有一个常见误区——直接把网络输出当作最终的波前结果。别忘记,如果训练前对Zernike系数做了标准化,那么推理时也需要用同一组均值和方差做反变换。下面是一段标准评估脚本:

% 载入网络和数据集 load('sh_cnn.mat', 'net'); load('sh_dataset.mat', 'XVal', 'YVal'); % 对验证集做预测 YPred = predict(net, XVal); % 计算系数均方根误差 coeffError = YPred - YVal; rmse = sqrt(mean(coeffError.^2, 'all')); fprintf('Coefficient RMSE: %.4f\n', rmse);

逻辑说明:predict返回尺寸是样本数×Zernike系数数。这里直接和真值YVal做差得到误差矩阵,最后取全体均方根。如果RMSE远大于训练损失,说明验证集的噪声分布与训练集不一致,要检查生成器随机种子;如果RMSE在1e-3以下,通常意味着训练充分。注意YVal本来就是随机系数,所以RMSE的绝对数值和系数尺度相关,不能在不同阶数间直接比较。

参数说明:这是“系数RMSE”,不是最终用户关心的“波前RMS”。系数RMSE低只说明系数回归准确,但Zernike高阶项对RMS贡献小,系数误差分布不同会导致完全不同的波前形状。所以下一节必须把系数映射回波前,用波前RMS做主要指标。

4.2 重建波前残差:从系数到相位网格并计算RMS波前误差

系数只是中间量,用户真正关心的是重建后的波前误差。把预测系数代入Zernike展开,就得到预测相位,与原输入相位相减得到残差波前。计算残波前RMS的Matlab代码如下:

% 重建波前并计算RMS误差 function rmsError = wavefrontRMSE(coeffTrue, coeffPred, numZ, N) [x, y] = meshgrid(linspace(-1, 1, N)); rho = sqrt(x.^2 + y.^2); theta = atan2(y, x); Wtrue = zeros(N, N); Wpred = zeros(N, N); for i = 1:numZ Z = zernfun(i, rho, theta); Wtrue = Wtrue + coeffTrue(i) * Z; Wpred = Wpred + coeffPred(i) * Z; end residual = Wtrue - Wpred; % 圆形孔径掩膜,剔除rho>1区域 mask = rho <= 1; rmsError = sqrt(mean(residual(mask).^2)); end

逻辑说明:重建波前时使用与训练生成器相同的zernfun和网格定义,否则会出现坐标不匹配。residual是残差波前,单位与Zernike系数一致。目标是把RMS压到0.1λ以下,此时Strehl比约为0.67。掩膜很重要,单位圆外的相位本身没有物理意义,不掩膜会把RMS拉高。

参数说明:N取128已经足够,如果计算速度慢可以用64。注意zernfun在这里是共用的,如果换了一套实现,必须和训练时完全一致,否则会出现系统偏差。我习惯把这个函数和生成器放在同一个目录里,作为一个公共模块引用,避免复制两份代码产生漂移。

4.3 与质心法对比:低信噪比下深度学习究竟赢在哪

我一般在报告里会做一个对比:同一组加噪光斑图,分别用传统质心+Zernike拟合和CNN重建,统计RMS误差。传统质心法的Matlab实现可以用regionprops计算子孔径质心,然后求斜率矩阵,再用最小二乘拟合Zernike系数。简化代码如下:

% 传统质心法示意 function coeffsLM = leastSquaresCentroid(img, numSubA, pixelPerSubap, numZ) % 按子孔径划分区域并计算质心 slopes = zeros(numSubA, numSubA); for r = 1:numSubA for c = 1:numSubA rowRange = (r-1)*pixelPerSubap+1 : r*pixelPerSubap; colRange = (c-1)*pixelPerSubap+1 : c*pixelPerSubap; sub = img(rowRange, colRange); [xx, yy] = meshgrid(1:pixelPerSubap, 1:pixelPerSubap); total = sum(sub, 'all'); if total > 0 cx = sum(xx .* sub, 'all') / total; cy = sum(yy .* sub, 'all') / total; else cx = pixelPerSubap/2; cy = pixelPerSubap/2; end % 归一化斜率 slopes(r, c) = (cx - pixelPerSubap/2) / pixelPerSubap; end end % 最少二乘拟合Zernike系数... end

逻辑说明:这段示意代码给出质心提取的核心。真正难的是构建从Zernike系数到子孔径平均斜率的模式矩阵,通常用单位圆网格上的Zernike偏导做积分。一旦噪声大,某个子孔径总强度太低,质心会被拉到区域中心,产生随机误差。相比之下,CNN可能已经学会忽略坏子孔径,这就是它在低信噪比下更强的根本原因。

参数说明:传统方法里子孔径失效判据常用总强度阈值,这个阈值在仿真中很容易调,但在真实传感器上很难定。这恰恰是深度学习方法能省掉的经验参数之一。如果要在论文里出对比图,建议固定同一个随机种子产生加噪光斑,让两种方法在同一输入下做比较,避免争议。

5. 波前重建仿真中的5个经典翻车点:现象、排查与处理

5.1 翻车点一:训练时OOM,图像尺寸和MiniBatchSize互相打架

现象:trainNetwork进行几百个迭代后直接弹“Out of memory on device”,或者Matlab无响应,风扇狂转。原因:128×128×64输入本身不大,真正吃显存的是卷积层的中间激活和梯度缓存,把MiniBatchSize设成128就很容易超。解决:把MiniBatchSize降到16或8,同时用'single'类型数据。如果还超,就把图像尺寸改成64×64,子孔径数用8、每个子孔径用8像素,CNN照样能学。我在Matlab R2026b上实测过,同样的网络在64×64输入下显存占用只有128×128的四分之一,训练时间反而更快。

5.2 翻车点二:系数预测错位半个口径,Zernike顺序对不上

现象:训练损失很低,但用预测系数重建出来的波前图整体倾斜或旋转,和真值相差很大。原因:zernfun的阶次顺序与训练标签不一致,或者theta的定义方向与光斑梯度方向不一致。解决:把生成器和评估函数里zernfun的输入统一,theta一律用atan2(y,x),Zernike排列固定为Noll序号。建议在生成训练集时,把第一组样本对应的系数和光斑图可视化检查一次,确认光斑偏移方向与系数符号一致。我常用的检查手段是把第n个光斑图和该样本的倾斜系数c2、c3单独画出来,看光斑整体是否朝右上方移动,这一步能避免后面所有重建结果都错位。

5.3 翻车点三:泊松噪声每次重新生成,验证集时好时坏

现象:同一个网络在两个验证集上的RMSE差异巨大,一个优秀一个完全不行。原因:最初没有在生成验证集时固定随机种子,导致两次验证集的噪声差异很大,尤其低光强时泊松噪声方差大。解决:用rng(42)固定在生成验证集之前,训练集用rng(7),保证别人复现时结果一致。如果要考核噪声鲁棒性,就固定一个测试种子,然后扫描photonCount从1e3到1e5,画一条误差随光子数变化的曲线,而不是只报一个数字。

5.4 翻车点四:网络对子孔径数变化完全失灵

现象:在8×8子孔径上训练好,换到10×10子孔径数据上预测,误差暴涨。原因:CNN的卷积核可以泛化,但全连接层要求输入结构固定,10×10子孔径产生的光斑图尺寸变了,全连接层无法处理。解决:要么固定传感器参数,要么把网络改成全卷积结构,先对子孔径做特征提取再全局池化,最后输出系数。更实际的做法是在训练时随机让外围子孔径置零,模拟遮挡,网络会学会忽略缺失区域,从而对子孔径布局变化更鲁棒。

5.5 翻车点五:损失曲线在某个epoch突然变NaN,训练白跑

现象:训练进行到十几轮,loss突然变成NaN,之后再也回不来。原因:一般是因为学习率太大、Zernike系数中出现极端值、或在归一化时除零。解决:把学习率降到3e-4,并给Zernike系数加上截断:c = max(min(c, 5), -5)。检查数据中有没有全黑的光斑图,把总强度低于某个阈值的样本跳过。养成习惯是每5个epoch保存一次checkpoint,我一般用OutputFcn写一个存档函数,即使白跑也不至于从零开始。

以上5条是我在跑这个方向时踩过最深的坑,其中第2条和第3条最隐蔽。很多初学者把loss曲线当成判断标准,其实先检查数据生成器比检查网络更重要。我的经验是:在训练前做一次“输入-输出”可视化,把随机样本的光斑图和对应的Zernike系数画出来,确认光斑偏移方向和系数符号一致,这能省掉一大部分排查时间。

6. 进阶:从仿真到真实传感器,我常用的两个迁移技巧与验证方法

6.1 把仿真模型迁移到真实传感器的两个技巧

仿真模型和真实传感器的差距主要在光斑PSF、像素响应和背景噪声。直接拿真实光斑图喂给仿真训练的网络,效果通常会打折扣。我的做法是在不改变骨干网络的前提下,在输入层后加一层可训练的3×3卷积层,用少量真实标定数据做微调。微调时固定后面所有层的学习率为0,只让这一层和最后的回归层更新。这个技巧从“深度学习和Shack-Hartmann波前传感器”的实践角度看,比全量微调更稳,因为真实数据通常只有几十组。

第二个技巧是把输出层改成子孔径斜率。如果不想放弃现成的区域法重构模块,可以让CNN输出2×numSubAperture^2的斜率向量,而不是直接输出Zernike系数。这样网络学习到的仍然是光斑图到斜率的端到端映射,下游处理可以继续用已有的矩阵求逆和模态滤波,这对不想推翻原有自适应光学系统的人来说非常实用。

6.2 用Strehl比做最终验证

只看Zernike系数RMSE容易骗自己。我会在报告里加一条Strehl曲线:对同一组测试波前,逐步增加噪声,计算重建残差波前RMS,再用Strehl ≈ exp(-(2π·RMS/λ)²)估算成像质量。当RMS从0.1λ变到0.3λ时,Strehl会从0.67掉到0.17,这条曲线比单个RMSE数字更能说明深度学习方法在什么条件下值得用。具体Matlab实现很简单,直接对wavefrontRMSE函数的结果取exp即可,不需要额外工具包。

我现在的习惯是:跑任何波前重建仿真,都先固定随机种子、生成一张包含所有子孔径光斑的可视化图,再进入训练循环。这个习惯帮我省掉了至少一半的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取