简介面向波前重建研究的Matlab仿真代码将深度学习与Shack-Hartmann波前传感器相结合专为计算机、电子信息工程、数学等专业大学生与研究生进行课程设计、期末大作业和毕业设计而打造。压缩包约1.84MB共46个文件主体是20个Matlab脚本另有7个Python辅助脚本、6张结果示意图、1个.mat数据文件以及README说明文档同时包含pyc编译文件、XML配置与工程文件目录结构清晰便于分别查看源码、图片、模型与数据。代码采用参数化编程注释详细关键参数可灵活调整支持Matlab 2014/2019a/2024a附带案例数据可直接运行替换数据即可适配不同波前检测场景。仿真覆盖数据预处理、模型训练、相位恢复与误差评估等关键环节目录细分为仿真主程序、网络模型、结果可视化等模块配合Python脚本可进一步扩展网络对比实验帮助使用者从数据加载到评估逐步理解深度学习在光学波前重建中的实际作用。目前已有476人学习适合需要结合深度学习与光学测量完成课设、毕设的读者快速上手。1. 波前重建怎么从光斑图像变成深度学习的回归问题在自适应光学和光束质量诊断场景里Shack-Hartmann波前传感器几乎是标配一组微透镜阵列把入射波前切分成若干子孔径每个子孔径在相机上形成一个光斑波前畸变表现为光斑质心相对参考位置的偏移。传统流程是先提质心再解斜率方程最后用Zernike多项式拟合出波前相位。这套链路在光斑信噪比高、子孔径无遮挡时非常稳定但一到近场强闪烁、低信噪比或者子孔径边缘被遮挡的场景质心提取误差会被模式拟合矩阵放大重建结果出现明显的高频伪影。我做过几次实验后最直观的感受是质心这一步是瓶颈而深度学习恰好可以把“从光斑图直接到Zernike系数”当做一个图像回归问题来解跳开质心提取。本文要讲的就是在Matlab里搭建这样一套仿真流程物理模型生成带噪声的Shack-Hartmann光斑图用卷积神经网络直接回归波前Zernike系数再评估重建误差。内容覆盖数据生成、网络设计、训练配置和评估指标全程用Matlab深度学习工具箱完成不需要额外安装Python环境。适合正在做自适应光学仿真、或者想把手头传统波前重建算法替换成深度学习方案的工程师参考。2. Shack-Hartmann波前传感器的测量模型与Zernike系数表示2.0.1 子孔径光斑偏移与波前斜率的数学关系Shack-Hartmann传感器把入瞳划分成NxN个子孔径每个子孔径近似看作一个独立的采样单元。设第(i,j)个子孔径内的平均波前斜率为(∂W/∂x, ∂W/∂y)则光斑质心偏移量为Δx f * (∂W/∂x) / (2π) * λ / (2π) ???更准确的公式是质心偏移量Δx (λ / 2π) * (∂W/∂x) * (f / r)其中f是微透镜焦距r是子孔径半径。实际操作中我们常用标定好的比例系数K把像素偏移转换为斜率。这个K的标定精度直接决定了传统方法的系统误差。传统波前重建的G矩阵方法把斜率向量S与Zernike系数向量a通过重建矩阵G联系起来a G * S。G矩阵可以在仿真中预先计算生成各阶Zernike多项式在子孔径位置的理论斜率再求伪逆。这个方法的局限在于当子孔径光斑出现坏点、饱和或者背景不均时S的估计误差会被G中的高阶项放大导致重建波前出现振铃。2.0.2 为什么选择Zernike系数作为深度学习输出Zernike多项式在圆形孔径上是正交基低阶项对应离焦、像散、彗差等经典像差物理意义明确。把深度学习模型设计成“输入光斑图、输出Zernike系数”本质上是训练一个回归网络来逼近从原始图像到模式系数的非线性映射。相比直接回归二维相位图输出维度从NxN降到几十维训练数据需求小得多收敛也更快。当然直接回归相位图也有优势高频细节保真度高。但工程实践中Zernike系数输出更容易与现有自适应光学控制系统对接因为控制回路需要的本来就是把波前误差投影到校正器模态上。下表对比了两种输出表示输出表示维度训练难度与AO系统对接适用场景Zernike系数10~30维低直接闭环校正、模式控制二维相位图64x64以上高需要模式分解高分辨率波前诊断2.0.3 卷积神经网络如何拟合光斑到系数的映射CNN善于提取局部空间特征。Shack-Hartmann光斑图中每个子孔径光斑的偏移方向和大小以局部模式存在多个子孔径的偏移趋势又构成全局模式。卷积层通过滑动窗口逐层提取这些特征池化层扩大感受野全连接层把高维特征映射到Zernike系数。这里的关键是输入图像的归一化方式光斑图的整体亮度变化不能影响输出所以在数据生成时就要做强度归一化和背景减除。网络输入可以选择拼接的子孔径光斑图整幅相机图像或者将各子孔径裁剪后按固定顺序堆叠成多通道输入。前者让网络自己学习子孔径间的空间关系后者更利于控制输入尺寸。我一般选择前者因为保留了微透镜阵列的几何排布信息网络能自行学习到相邻子孔径的斜率连续性约束。3. 用Matlab生成带噪声的Shack-Hartmann光斑图数据集3.0.1 物理仿真模型的关键参数要用深度学习就必须有大量标注数据。在Matlab里生成Shack-Hartmann光斑图的方式是先定义波前相位W(x,y)然后用傅里叶光学计算每个子孔径的焦点光斑。具体分为三步生成Zernike相位屏、按子孔径网格采样、计算远场光斑并加噪声。每个子孔径的远场光斑可以用FFT近似% 参数定义 N_sub 8; % 8x8子孔径阵列 pixel_per_sub 32; % 每个子孔径的像素数 lambda 632.8e-9; % 波长 632.8nm f_lens 5e-3; % 微透镜焦距 5mm r_sub 0.5e-3; % 子孔径半径 0.5mm image_size N_sub * pixel_per_sub; k 2 * pi / lambda; % 生成随机Zernike系数前10阶不含平移 zern_num 10; zcoeffs randn(zern_num, 1) * 0.3; % RMS在0.3弧度左右 % 生成圆域内的Zernike相位屏 [x, y] meshgrid(linspace(-1, 1, image_size), linspace(-1, 1, image_size)); r_grid sqrt(x.^2 y.^2); theta_grid atan2(y, x); phase zeros(image_size); for n 1:zern_num z zernike_poly(n, r_grid, theta_grid); % 自实现函数 phase phase zcoeffs(n) * z; end这段代码的核心思路是先生成连续波前相位再离散采样。注意Zernike多项式生成函数zernike_poly需要按Noll索引顺序实现可以从简单的高斯几何公式推得多项式表。系数幅值也直接影响光斑质心偏移量需要根据子孔径内波前斜率换算避免偏移量超出子孔径边界。3.0.2 从相位屏到子孔径光斑的传播计算拿到连续相位后对每个子孔径独立计算光斑。假设子孔径内的相位近似为线性倾斜则光斑形状近似为艾里斑平移。更精确的做法是用菲涅尔衍射公式% 对每个子孔径生成光斑图像 sub_image zeros(pixel_per_sub, pixel_per_sub); sub_phase phase((i-1)*pixel_per_sub1 : i*pixel_per_sub, ... (j-1)*pixel_per_sub1 : j*pixel_per_sub); % 夫琅禾费近似下的光斑 |FFT(孔径函数 * exp(i*相位))|^2 aperture zeros(pixel_per_sub); center pixel_per_sub / 2; for u 1:pixel_per_sub for v 1:pixel_per_sub dist sqrt((u-center)^2 (v-center)^2); if dist pixel_per_sub * r_sub / (2 * r_sub) aperture(u,v) 1; end end end sub_phase sub_phase .* aperture; field aperture .* exp(1i * sub_phase); spot abs(fftshift(fft2(field))).^2; spot spot / max(spot(:)); % 峰值归一化这段代码用FFT近似夫琅禾费衍射。参数说明r_sub / (2 * r_sub)的分母有点冗余实际是计算子孔径半径占子孔径像素数的比例直接写成pixel_per_sub / 2内判断距离小于等于pixel_per_sub / 2即可。光斑的散斑大小与像素采样率有关建议每个子孔径至少32x32像素否则离散误差会影响质心精度。3.0.3 噪声模型与数据增强仿真数据与真实数据的差距主要在噪声分布上。相机噪声包含光子散粒噪声泊松分布和读出噪声高斯分布。Matlab里模拟% 散粒噪声光强服从泊松分布 photon_count spot * 1e5; % 光子数比例系数 noisy_spot poissrnd(photon_count); % 读出噪声高斯分布标准差为满阱的1% read_noise 0.01 * max(noisy_spot(:)) * randn(size(noisy_spot)); noisy_spot noisy_spot read_noise; noisy_spot max(noisy_spot, 0); % 背景不均低频余弦背景 [xbg, ybg] meshgrid(1:pixel_per_sub, 1:pixel_per_sub); background 0.05 * (1 cos(2*pi*xbg/pixel_per_sub)) .* ... (1 cos(2*pi*ybg/pixel_per_sub)); noisy_spot noisy_spot background * mean(noisy_spot(:)) * 0.1;噪声参数的设置直接影响模型泛化能力。光子数比例系数从1e4到1e6变化覆盖不同亮度条件读出噪声标准差取最大强度的0.5%到2%。背景不均匀是为了模拟真实光学系统中的暗电流和杂散光影响。实际使用时还要加随机平移和旋转增强让网络对安装误差不敏感。4. 用Matlab深度学习工具箱训练CNN波前重建模型4.0.1 数据集组织与datastore构建训练数据需要整理成输入图像和输出Zernike系数的对应关系。Matlab中用imageDatastore加载图片不方便同时读取标签推荐用arrayDatastore或自定义transform。一个简单方式是直接保存为mat文件每个样本包含input_image和zcoeffs两个数组。% 生成训练数据并存储 num_samples 5000; data_cell cell(num_samples, 2); for idx 1:num_samples [img, coeffs] generate_sh_sample(); % 调用之前的数据生成函数 data_cell{idx, 1} img; data_cell{idx, 2} coeffs; end % 转换为数据存储 X cellfun((c) c{1}, data_cell, UniformOutput, false); Y cellfun((c) c{2}, data_cell, UniformOutput, false); X cat(4, X{:}); % 堆叠成 HxWxN数组 Y cat(2, Y{:}); % 划分训练集、验证集 [train_idx, val_idx] dividerand(num_samples, 0.8, 0.2); X_train X(:, :, :, train_idx); Y_train Y(train_idx, :); X_val X(:, :, :, val_idx); Y_val Y(val_idx, :);说明cat(4, X{:})把单张图片堆叠成四维数组Y的维度是样本数x输出维度。输入图像尺寸建议统一为64x64或128x128太大增加计算量太小损失子孔径细节。4.0.2 网络结构适合小数据集的轻量CNNZernike系数回归不需要太深的网络层数过多反而容易过拟合。推荐结构4个卷积块每个含卷积、批归一化、ReLU、池化 两个全连接层。以64x64输入为例layers [ imageInputLayer([64 64 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) maxPooling2dLayer(2, Stride, 2, Name, pool3) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu_fc1) dropoutLayer(0.3, Name, dropout) fullyConnectedLayer(10, Name, fc2) % 输出10个Zernike系数 regressionLayer(Name, output) ];网络设计考虑第一层不做归一化是因为输入光斑图的灰度范围已经在数据生成阶段统一。 dropout层放在全连接层之间抑制过拟合这对5000样本量来说很重要。卷积核大小3x3是兼顾感受野和参数量的平衡选择池化步长2让特征图逐层减半。4.0.3 训练选项与收敛性判断训练配置是深度学习仿真里容易被忽略的部分。回归任务不要用默认的交叉熵损失这里用MSE回归层默认。学习率选择影响收敛速度初始学习率0.001每20轮衰减0.1倍。options trainingOptions(adam, ... MaxEpochs, 80, ... InitialLearnRate, 1e-3, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.1, ... LearnRateDropPeriod, 25, ... MiniBatchSize, 64, ... ValidationData, {X_val, Y_val}, ... ValidationFrequency, 10, ... Plots, training-progress, ... OutputNetwork, best-validation, ... Shuffle, every-epoch);参数含义OutputNetwork设为best-validation表示在验证集误差最小时保存模型防止最后一轮过拟合覆盖最优权重。MiniBatchSize设为64需要显存足够若显存不足则减半。训练过程中关注验证损失是否持续下降若验证损失在15轮后不降反升说明学习率偏大或dropout率偏低。Shuffle每个epoch打乱训练集顺序避免周期性数据导致梯度震荡。训练结束后查看训练进度图若训练损失与验证损失差距大说明过拟合增加dropout率、增大样本数或减小网络宽度。若两者都高则网络容量不足需要加卷积层或全连接层宽度。5. 评估与推理从测试集RMS到Strehl比验证重建精度5.0.1 测试集评估指标与Matlab实现训练完成后需要在独立测试集上评估。评估不能只看MSE因为Zernike系数的每个分量权重不同——低阶像差系数误差对波前RMS的影响远大于高阶。我用两个指标波前RMS误差和Strehl比估算。% 加载测试数据 [test_img, test_coeffs] generate_sh_samples(500); pred_coeffs predict(net, test_img); % 计算RMS误差在圆域内积分波前差的平方 normalized_rms zeros(size(pred_coeffs, 1), 1); for i 1:size(pred_coeffs, 1) diff_phase reconstruct_phase(pred_coeffs(i,:) - test_coeffs(i,:)); normalized_rms(i) sqrt(mean(diff_phase(:).^2)); end % Strehl比估算 strehl exp(-normalized_rms.^2); % 以弧度为单位适用于小像差代码说明reconstruct_phase函数根据Zernike系数在网格上重建相位图。Strehl比公式在RMS小于1弧度时近似有效超过时需要使用Maréchal公式的完整形式。注意归一化RMS的单位是弧度如果相位是波数单位需要换算。5.0.2 与传统G矩阵方法的对比实验为了让评估更有说服力我通常在同一测试集上跑传统质心G矩阵方法作为基线。传统方法的质心提取用Matlab的regionprops计算每个子孔径光斑质心再代入预先算好的G矩阵。对比时需要注意传统方法的质心提取要加阈值处理避免噪声引起的虚假质心。% 传统质心法对比 centroids zeros(N_sub*N_sub, 2); for sub_idx 1:N_sub*N_sub sub_img extract_subimage(test_img(:, :, sub_idx)); % 阈值化后求质心 threshold 0.3 * max(sub_img(:)); bw sub_img threshold; stats regionprops(bw, sub_img, WeightedCentroid); centroids(sub_idx, :) stats.WeightedCentroid; end slopes (centroids - ref_centroids) * scale_k; trad_coeffs G_matrix * slopes(:);这段对比代码的关键是阈值的选择阈值太低会引入背景噪声太高则丢失光斑边缘信息通常0.3到0.5倍峰值比较可靠。G矩阵的生成需要模拟系统参数与训练数据的仿真参数一致否则对比不公平。5.0.3 误差来源分析与向量化评估脚本如果深度学习重建在低阶项表现好但高阶项误差大一个常见原因是训练数据中高阶Zernike系数的幅值太小网络没有足够信号去学习。可以在数据生成时对不同阶系数设置不同的幅值范围低阶项给±0.5弧度高阶项给±0.1弧度避免网络只学到低阶模式。评估脚本建议写成函数输入网络和测试数据目录输出各阶系数的RMSE向量和总RMS误差便于后续调参时对比。我发现一个实用技巧把预测系数与真实系数的散点图按阶数分别绘制能快速看出网络是否对所有Zernike阶次都有预测能力而不是只对前几阶有效。6. 提升波前重建精度的三个实战技巧6.0.1 混合损失函数把相位梯度也加进约束纯MSE损失优化的是系数误差但系数误差经过Zernike多项式展开后某些模式的空间分布差异会被平滑。一个有效的改进是在回归层之外增加自定义损失项约束重建相位图的梯度误差。实现方式是在end-to-end训练中不直接用预定义网络而是用dlnetwork自定义训练循环在损失函数里加上梯度惩罚项。% 自定义损失MSE λ * 梯度误差 pred_phase reconstruct_phase_from_coeffs(pred_coeffs, grid_x, grid_y); [gx, gy] gradient(pred_phase); [tx, ty] gradient(true_phase); grad_loss mean((gx - tx).^2 (gy - ty).^2); total_loss mse_loss 0.1 * grad_loss;系数λ通常取0.1到0.3过大的梯度惩罚会压制系数幅值。这个技巧对重建相位细节有明显改善特别是像散和彗差这类空间分布复杂的模式。额外开销是每次迭代都要做一次相位重建和梯度计算训练时间增加约20%。6.0.2 输入表示改进子孔径强度图堆叠很多人在输入处理上直接把整幅相机图像送入网络但这会让卷积核同时跨多个子孔径区域混合了不同孔径的信息。我测试过一种更优的输入把每个子孔径裁剪出来并归一化然后按子孔径位置排列成多通道输入——相当于把空间维度变成通道维度。这样每个卷积核只处理单个子孔径内的特征网络的参数量可以适当减少。实际对比中这种输入表示在64x64输入下比整幅图方法提升了约8%的RMS精度代价是需要预先完成子孔径位置标定。若系统存在子孔径旋转或缩放误差则该方法需要额外做配准。6.0.3 提高数据效率加入锯齿边缘的坏点修复真实Shack-Hartmann图像经常出现坏点——某些子孔径的微透镜损坏或遮挡导致光斑缺失。传统方法只能对该子孔径的数据置为无效深度学习可以学习从周围子孔径推断缺失信息。具体做法是在数据增强阶段随机将子孔径光斑遮盖mask掉遮盖比例在5%到20%之间让网络学会利用邻域子孔径的斜率连续性来补全信息。实测将坏点子孔径比例提升到15%后网络仍能保持无坏点情况下约90%的重建精度而传统方法在15%坏点情况下基本无法正常拟合G矩阵。这个技巧的实现很简单在生成光斑图数据时随机选择某个子孔径直接置零即可。配合Zernike系数的物理先验低阶系数占主导能够有效提升系统的鲁棒性验证方法是在测试集上与不加mask处理的模型分别计算最大可容忍坏点比例。本文还有配套的精品资源点击获取