尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度学习+Shack-Hartmann:Matlab实现直接Zernike系数波前重建

深度学习+Shack-Hartmann:Matlab实现直接Zernike系数波前重建 简介这套基于深度学习和Shack-Hartmann波前传感器的波前重建Matlab仿真资源面向光学工程、计算机、电子信息及数学等专业的学生与科研人员用于解决传统波前重建在噪声或非理想条件下的精度与速度问题。压缩包共46个文件包含20个m文件Matlab仿真主程序、7个py文件Python辅助脚本、6个png图片结果图示以及xml、txt、md、mat等配置与数据文件整体仅1.84MB轻量易下载。已有476人浏览学习适合作为课程设计、期末大作业或毕业设计的实战参考。资源采用参数化编程关键参数可灵活更改代码注释详细附赠可直接运行的案例数据替换数据即可复用便于新手快速掌握波前传感、深度学习与Matlab仿真结合的核心流程。涵盖Shack-Hartmann波前传感器原理、深度学习模型构建与波前重建算法实现可帮助读者在实验中开展参数调优与结果分析。1. 基于深度学习和Shack-Hartmann传感器的波前重建直接映射Zernike系数自适应光学的波前重建场景里Shack-Hartmann波前传感器是使用最广的测量器件。传统做法把每个微透镜形成的光斑图先做质心提取再用区域法或模式法反演相位。这个流程在暗弱目标和强湍流下会有明显短板质心定位的亚像素误差会被后续拟合放大而且光斑形状信息基本被丢弃。我见过不少仿真项目把大量精力花在质心算法上换来的却是重建相位上的网格伪影。深度学习介入后的思路很直接把整幅光斑强度图像作为输入让卷积神经网络直接回归波前相位对应的Zernike系数跳过质心提取步骤。这个方向在波前重建研究里已经有一批工作但很多Matlab用户还是倾向于自己写脚本复现。这里按我熟悉的工程流程从传感器成像模型、仿真数据生成、网络训练到验证指标把完整的Matlab仿真路径讲清楚。适合正在做自适应光学算法验证、计算成像仿真或者毕设开题的从业者也适合刚接触深度学习想找一个物理意义明确的回归问题练手的人。2. Shack-Hartmann波前成像模型与深度学习重建的理论基础2.1 子孔径成像的几何模型光斑偏移与局部斜率Shack-Hartmann传感器的核心是一片微透镜阵列。入射波前被分割成多个子孔径每个子孔径内的局部相位畸变会将该处的子光束偏折最终在探测器上形成一个位置偏移的光斑。几何光学近似下光斑中心偏移量 (\Delta x) 与子孔径内平均波前斜率满足[ \Delta x f \cdot \frac{\partial \phi(x,y)}{\partial x}, \quad \Delta y f \cdot \frac{\partial \phi(x,y)}{\partial y} ]其中 (f) 是微透镜焦距(\phi) 是待测波前相位。这个线性关系是传统质心法的核心依据。实际仿真中每个子孔径内的光斑强度近似为二维高斯分布光斑宽度取决于衍射和微透镜尺寸。下面是一个生成高斯光斑的Matlab函数实际模拟时直接调用它往子孔径图像中写光斑。function g gaussSpot(dx, dy, sigma, subSize) % 生成一个中心偏移为(dx,dy)的高斯光斑 % subSize: 单个子孔径图像的像素尺寸 u (1:subSize) - (subSize1)/2; [X, Y] meshgrid(u, u); g exp(-((X-dx).^2 (Y-dy).^2) / (2*sigma^2)); end这个函数有三个关键参数dx和dy是光斑相对子孔径中心的偏移量单位是像素正比于局部斜率sigma是光斑高斯宽度一般取0.5到1.5像素。太大的sigma会导致相邻光斑重叠太小的sigma会引入离散采样误差。生成整幅传感器图像时每个子孔径的偏移量由该区域波前梯度均值决定。常规做法是在子孔径像素网格上用gradient()求相位梯度再乘以焦距。2.2 传统质心法重建路径与误差边界传统波前重建分为两步。第一步是质心提取在噪声背景下找到每个光斑的亚像素中心。第二步是斜率映射把质心偏移向量转换为波前相位常用zonal区域重建或modal模式重建。zonal方法使用Fried或Hudgin模型建立斜率到相位的稀疏线性方程组计算量小但对噪声非常敏感。modal方法则把相位分解为Zernike多项式基通过最小二乘拟合得到系数抗噪能力相对好但受限于所选模式的阶数和截断误差。质心法的误差边界比较清楚。当光斑位置接近探测器像素边界、信噪比低于某阈值或者光斑发生形变时质心估计的方差会显著增加。传统方法把分布在几百个像素上的光斑信息压缩成几个坐标点这本身就是一个有损过程。光斑形状异常、子孔径内出现多个峰值、暗电流和读出噪声叠加都会让压缩后的特征不可靠。我在实践中的感觉是使用传统算法时一半的调试时间都花在质心阈值和光斑尺寸参数的适配上了。2.3 深度网络如何重建波前从光斑图像到Zernike系数的回归用深度学习做波前重建本质是把光斑图像到Zernike系数看作一个高维回归问题。CNN不需要显式计算质心而是从局部感受野中学习斜率信息。它同时利用了光斑位置和光斑形状位置信息对应低阶模式形状变化和光斑间强度分布对应高阶细节。只要训练数据覆盖合理的畸变范围网络输出的Zernike系数就能直接恢复到传感器动态范围内的相位。从信号处理角度看光斑阵列图像中包含的信息量远大于质心向量。质心法只保留了一阶矩而CNN可以使用的特征包括光斑的宽度、对称性、相对强度变化以及跨子孔径的相关结构。对低阶畸变而言模型误差和质心误差没有本质区别。但到高阶模式和低信噪比场景深度学习保留的冗余信息就开始发挥作用。这也是为什么跳过质心不是简单的算法替换而是信息表征方式的转变。2.4 用Matlab做完整仿真的通用流程Matlab非常适合这类物理仿真与深度学习的交叉验证。图像处理工具箱可以方便地生成光斑图和掩膜深度学习工具箱提供现成的卷积层和训练选项而且把训练好的网络导出为C代码也比较顺畅方便后续部署到FPGA或嵌入式计算平台。常见做法是把整个仿真拆成四个独立部分波前生成、传感器图像模拟、网络训练、重建评估。下面的列表是我的标准流程每一部分对应一个独立脚本生成训练数据随机Zernike系数 - 波前相位 - Shack-Hartmann光斑图。做数据预处理裁剪、归一化、按比例划分训练集验证集测试集。定义网络并训练用imageInputLayer、卷积层、全连接层和regressionLayer组成模型。评估效果把测试集输入predict比较Zernike系数和波前位相的RMS误差。整个流程在单张GPU显卡上跑两万样本三十个epoch大约需要十几分钟。没有GPU时用CPU也能跑只是把trainingOptions里的执行环境设为cpu即可。下一章先解决数据从哪里来的问题。3. 在Matlab中生成模拟Shack-Hartmann光斑数据集并做预处理3.1 用随机Zernike系数合成波前相位数据生成常见的坑有两个基函数不标准导致重建系数无法解释以及波前幅度太大导致光斑偏移超出子孔径范围。我的做法是把波前相位限制在直径为一个单位像素的圆形光瞳内最高用到前15项Zernike模式系数标准差控制在0.2弧度左右。这样生成的RMS波前误差大约在几百分之一波长的量级光斑偏移不会超出探测器单元。下面给出一个简化但可直接运行的Zernike基函数实现覆盖Noll序号前10项足够演示用。实际项目需要更高阶模式时可以替换成你自己项目里的一份Zernike工具包。function Z zernikeCart(j, x, y) % 返回Noll序号j的Zernike多项式在笛卡尔网格上的值 % 只实现了前10项第1项为平移项 r sqrt(x.^2 y.^2); t atan2(y, x); switch j case 1, Z ones(size(r)); case 2, Z 2 * x; case 3, Z 2 * y; case 4, Z sqrt(3) * (2*r.^2 - 1); case 5, Z 2*sqrt(6) * x .* y; case 6, Z sqrt(6) * (x.^2 - y.^2); case 7, Z sqrt(8) * (3*r.^3 - 2*r) .* y; case 8, Z sqrt(8) * (3*r.^3 - 2*r) .* x; case 9, Z sqrt(8) * y .* (3*x.^2 - y.^2); case 10, Z sqrt(8) * x .* (x.^2 - 3*y.^2); otherwise, error(示例实现只支持前10个Zernike模式); end Z reshape(Z, size(x)); end这个函数逐个返回基函数值。合成波前时把随机系数和对应基函数相乘累加即可。需要注意x和y网格必须在-1到1之间并且用rho 1的手段标记圆形光瞳之外的无效点。3.2 生成完整的Shack-Hartmann光斑图生成光斑图的关键是确定每个子孔径内的平均斜率。常规做法是把波前相位划分成与微透镜阵列对应的块在每个块上计算梯度均值然后乘以焦距像素值。如果子孔径边长为subSize整张传感器的像素尺寸就是numSubApertures * subSize。下面的代码展示了主循环部分。% 参数设置 N 256; % 波前网格 numSub 16; % 每行子孔径数 subSize 16; % 子孔径像素尺寸 focalPix 40; % 以像素为单位的等效焦距 spotImg zeros(numSub*subSize); [x, y] meshgrid(linspace(-1,1,N)); phase generatePhaseWavefront(x, y); % 由Zernike系数累加得到 % 遍历子孔径 for ii 1:numSub for jj 1:numSub % 当前子孔径在相位网格上的索引范围 xIdx (jj-1)*N/numSub1 : jj*N/numSub; yIdx (ii-1)*N/numSub1 : ii*N/numSub; blockPhase phase(yIdx, xIdx); [px, py] gradient(blockPhase); % 平均斜率 - 质心偏移 dx focalPix * mean(px(:)); dy focalPix * mean(py(:)); % 生成子孔径光斑图 spotSubImg gaussSpot(dx, dy, 1.2, subSize); % 写入整幅图像 rowOffset (ii-1)*subSize; colOffset (jj-1)*subSize; spotImg(rowOffset1:rowOffsetsubSize, colOffset1:colOffsetsubSize) spotSubImg; end end这个脚本里generatePhaseWavefront就是上一小节的Zernike累加函数可以封装成单独函数。focalPix控制光斑偏移对斜率的敏感度增大它会让传感器对低阶波前误差更敏感但也更容易让光斑偏出子孔径边界。调试时可以先从focalPix 40开始观察偏移量是否在3~5个像素范围内。3.3 数据标注、归一化与训练集划分训练样本的标注是生成该图像时的Zernike系数注意要把第一项平移项去掉因为全局平移对成像质量没有影响。归一化方式采用全局均值和标准差这比逐样本归一化更稳定。下面的代码展示了批量生成训练数据的流程。numSamples 20000; tileSize 128; % 输入网络的图像尺寸 trainImg zeros(numSamples, tileSize, tileSize, 1); trainLabel zeros(numSamples, nModes-1); for s 1:numSamples coeff randn(nModes,1) * 0.2; phase synthPhase(coeff, x, y, mode); spot synthSpot(phase, numSub, subSize, focalPix); trainImg(s,:,:,1) imresize(spot, [tileSize tileSize]); trainLabel(s,:) coeff(2:end); end % 全局归一化 meanVal mean(trainImg, all); stdVal std(trainImg, 0, all); trainImg (trainImg - meanVal) / stdVal;建议把数据保存成.mat文件训练时直接加载避免每次重复生成。按8:1:1的比例划分训练、验证、测试集。验证集不参与训练但需要和训练集来自同一个分布。数据增强方面我一般只加随机噪声不做旋转或翻转因为Shack-Hartmann图像中的坐标方向是硬信息翻转会破坏梯度符号与质心偏移的对应关系。3.4 仿真数据与真实传感器数据的差异模拟数据和真实传感器数据之间存在几个常见偏差真实光斑不是完美高斯背景有杂散光微透镜间的串扰会让光斑边界变模糊。如果只是做算法验证高斯模拟完全够用。如果要做实验数据迁移见第5章末的迁移学习技巧。这里先提一个核心原则训练数据的系数范围要与实际场景匹配。动态范围太宽网络容易被极端样本干扰太窄遇到强畸变时会失效。4. 搭建和训练卷积神经网络从光斑图像到Zernike系数4.1 网络输入输出维度的约束网络的输入是一张灰度光斑图经过imresize统一成tileSize × tileSize的单通道图像。输出是nModes-1个实数对应去掉平移项后的Zernike系数。这个任务属于连续值回归所以最后一层必须是fullyConnectedLayer和regressionLayer的组合。输入层使用Normalization为none因为前面已经做过全局归一化不需要网络再重复处理。网络结构可以选择经典的小型CNN。太大的网络容易过拟合因为光斑图像的结构相对规则。我常用的结构是三组卷积加池化接两个全连接层。下面是可以直接放入layerGraph的层列表。networkLayers [ imageInputLayer([tileSize tileSize 1], Name, input, Normalization, none) convolution2dLayer(3, 32, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer(2, Stride, 2, Name, pool1) convolution2dLayer(3, 64, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer(2, Stride, 2, Name, pool2) convolution2dLayer(3, 128, Padding, same, Name, conv3) batchNormalizationLayer(Name, bn3) reluLayer(Name, relu3) maxPooling2dLayer(2, Stride, 2, Name, pool3) fullyConnectedLayer(128, Name, fc1) dropoutLayer(0.5, Name, drop) fullyConnectedLayer(nModes-1, Name, fc_out) regressionLayer(Name, reg) ];卷积核尺寸选了3×3因为光斑图上的局部梯度尺度在几个像素范围内。每次池化后特征图尺寸缩小一半到第三次池化后特征图已经是16×16大小再接全连接层参数量比较合理。如果直接用32×32的输入也可以减掉一层池化。批归一化放在卷积之后能让训练更稳定特别是数据尺度变化大的时候。4.2 训练参数配置学习率、批次、早停与损失函数回归任务的损失函数默认用均方误差。训练时选择Adam优化器初始学习率从1e-3开始。Shack-Hartmann图像的数据空间比较平滑学习率太高会震荡太低会收敛得特别慢。批量大小设为128。更大的批量会让训练更稳定但内存占用也更高。关注验证损失是否下降而不是只盯训练集准确度。options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MaxEpochs, 30, ... MiniBatchSize, 128, ... Shuffle, every-epoch, ... ValidationData, {imVal, labelVal}, ... ValidationFrequency, 20, ... L2Regularization, 1e-4, ... OutputNetwork, best-validation, ... Plots, training-progress, ... ExecutionEnvironment, auto); net trainNetwork(trainImg, trainLabel, networkLayers, options);这里最关键的是OutputNetwork设置为best-validation它会在验证误差不再降低时保存最佳模型而不是最后一次迭代的模型。ValidationFrequency设置为20表示每20次迭代计算一次验证损失。训练时观察两条曲线训练损失的下降速度、验证损失是否同步下降。如果验证损失在某个点后开始上升说明进入过拟合区间。4.3 训练不稳定时的参数调整现象直接原因调整方案损失一直不下降学习率过大或网络初始化不稳降低初始学习率到3e-4或增加前两层的批归一化训练损失下降但验证损失上升过拟合增加Dropout比例、增大L2正则系数、增加训练样本损失出现NaN梯度爆炸或数据中有NaN检查光斑图是否含NaN把Zernike系数标准差调小梯度裁剪验证误差波动剧烈验证集太小或批量不均衡增加验证样本数或使用更大的验证频率训练结束后用predict在测试集上得到预测系数。不要直接和真值比系数就完事因为系数误差需要换算成波前相位误差才有物理意义。下面一节就是验证的细节。5. 波前重建效果的验证方法与工程化技巧5.1 用RMS和相关系数验证重建质量重建效果的物理量是波前相位而不是系数。所以验证代码要把预测系数重新合成为相位图再和真实相位图逐点比较。注意必须使用同样的Zernike基函数和同样的归一化半径。下面的例子展示了完整步骤。predCoeff predict(net, testImg); predPhase zeros(N, N); for k 1:nModes-1 predPhase predPhase predCoeff(k) * zernikeCart(k1, x, y); end residual predPhase - truePhase; residual residual .* apertureMask; rmsErr sqrt(mean(residual(apertureMask).^2)); pvErr max(residual(apertureMask)) - min(residual(apertureMask)); rCorr corr2(predPhase, truePhase);掩膜apertureMask必须提前生成好并且和测试数据生成时保持一致。常见错误是直接用整个方形网格算RMS这会掺杂光瞳外无意义的拟合数值导致RMS虚高。corr2计算二维相关系数适合用来衡量相位空间分布的一致性但它对整体缩放不敏感所以只能作为辅助指标。5.2 与质心法在不同信噪比下的典型对比把深度学习方法与质心法放在同一组测试数据上对比不同信噪比下的重建效果是论文和项目汇报里常见的验证方式。在仿真条件为16×16子孔径、前10阶Zernike、系数标准差0.2弧度时典型趋势可以概括为下表。信噪比条件质心法RMS误差CNN方法RMS误差无噪声0.12 rad0.08 rad高信噪比 30dB0.18 rad0.10 rad中信噪比 20dB0.35 rad0.14 rad低信噪比 10dB0.80 rad0.22 rad表中数据来自多次仿真试验的均值具体数值会因参数不同而变但趋势一致低信噪比下质心法误差快速增大深度学习方法相对稳健。原因在于质心法把噪声在一阶矩里累积而CNN有能力忽略部分不相关像素。如果你的结果在高信噪比时CNN反而不如质心法先检查训练数据里是否包含噪声。没有噪声的纯仿真训练集网络在高信噪比下可能学到了一些对噪声敏感的伪特征。5.3 易踩的坑归一化半径不一致与掩膜加权RMS最后说一个我实际调试中踩过的坑。生成波前相位时网格坐标归一化半径是1圆形光瞳之外直接忽略。重建验证时如果使用同一套zernikeCart函数必须把预测系数和真实系数都映射到同一个半径范围。否则系数对不上RMS误差会突然变得很大。另一个坑是残余相位边缘的高频毛刺。当训练集的Zernike阶数到15以上网络输出的高阶系数往往有一些噪声。直接计算全局RMS看不出问题但相位图边缘会出现高频起伏。这时候可以用低通滤波或者去掉高阶项来做可视化但评估指标仍然要面向原始输出。建议额外输出一张残差相位彩色图用imagesc查看是否有明显的环形条纹。如果有说明训练数据中高阶模式太少或者网络容量不够增加模式数和卷积核数量通常能缓解。最后如果传感器是固定微透镜机构的真实设备迁移学习比重新训练更高效。把预训练模型的前面卷积层冻结只微调后面全连接层用几百张真实光斑图就能把模型迁移到实际系统的非均匀响应上。本文还有配套的精品资源点击获取
返回列表