简介:这份资源面向计算机、电子信息等专业的学生与开发者,提供一套基于Python实现的光场相机阵列深度学习完整项目,可用于毕业设计、课程设计或项目开发练手。项目围绕光场相机阵列这一计算成像方向,结合深度学习网络完成训练与推理流程,适合具备一定Python与神经网络基础、希望切入光场与多视角视觉课题的读者参考。压缩包共28个文件,约43KB,以17个py源码文件为核心,涵盖网络模型、训练脚本与数据处理模块,另有8个md文档说明项目结构、版本信息与使用方式,并附带license、gitmodules等配置项,便于快速理解工程组织。目前已有214人学习下载。源码经过严格测试,读者可据此掌握光场阵列数据的加载与预处理思路、网络搭建与训练调参方法,并借助配套文档完成环境配置与运行验证,在此基础上做模块替换或功能延伸,形成自己的课题方案。
1. 光场相机阵列遇上深度学习:这套毕设方案到底能跑出什么
如果你正在翻毕业设计选题,看到「光场相机阵列 + 深度学习」这个组合,第一反应大概率是:听起来很唬人,但到底能不能在普通显卡上跑起来?我当初也是这个反应。光场相机阵列的核心价值在于,它不只记录光线强度,还记录了光线的方向信息,这意味着你可以做重聚焦、深度估计、视角合成这些普通相机做不到的事。而深度学习在这里的角色,是替代传统光场处理中那些手工设计的匹配代价和优化步骤,用网络直接学出视差或深度。
这套方案适合两类人:一是做计算机视觉方向毕设的本科生或研究生,需要有一个既有理论深度又能出可视化结果的题目;二是想入门光场处理但不想从零推导光学公式的工程师,希望通过一个可运行的 pipeline 快速理解光场数据的结构和处理流程。我下面要讲的,就是怎么用 Python 把这条链路搭起来,从数据准备到模型训练再到结果验证,每一步都给出可复现的操作和参数说明。
2. 光场阵列数据怎么读:从子孔径图像到视差标签的完整链路
2.1 光场数据的两种常见组织形式
光场相机阵列采集到的原始数据,通常有两种存储方式。一种是子孔径图像阵列,也就是每个微透镜对应一个视角,所有视角拼成一张大图,常见格式是 9×9 或 11×11 的角度分辨率。另一种是子孔径图像序列,每个视角单独存成一张图,按行列编号。我一般会先把数据统一转成子孔径图像阵列,因为后续做视差估计时,角度维度的切片操作更方便。
如果你拿到的数据是 Lytro 或 Raytrix 的原始文件,需要先用对应的解码库转成 PNG 或 NPY。常见做法是用lytro或plenoptics这类 Python 包读取,但注意这些包对 Python 版本有要求,建议用 3.8 到 3.10 之间的环境。转完之后,你会得到一个形状为(H, W, A, A, 3)的数组,其中 H 和 W 是空间分辨率,A 是角度分辨率,3 是 RGB 通道。
2.2 用 Python 加载并可视化子孔径图像
下面这段代码演示怎么加载一个光场数据文件,并提取中心视角和边缘视角做对比。我习惯用numpy做数组操作,用matplotlib做快速可视化。
import numpy as np import matplotlib.pyplot as plt # 假设数据已经转成 npy 格式,形状为 (H, W, A, A, 3) lf = np.load('light_field.npy') H, W, A, _, C = lf.shape print(f'空间分辨率: {H}x{W}, 角度分辨率: {A}x{A}') # 提取中心视角 center = lf[:, :, A//2, A//2, :] # 提取左上角视角 top_left = lf[:, :, 0, 0, :] fig, axes = plt.subplots(1, 2, figsize=(10, 5)) axes[0].imshow(center) axes[0].set_title('中心视角') axes[1].imshow(top_left) axes[1].set_title('左上角视角') plt.show()这段代码的逻辑很直接:先确认数据形状,然后按角度索引取切片。参数上需要注意的是,A//2取的是中心视角,如果你的角度分辨率是偶数,中心会偏一格,这会影响后续视差计算的基准。我一般会统一用奇数角度分辨率,比如 9 或 11,避免这个歧义。
2.3 视差标签的生成:从光场到深度图
深度学习需要标签,光场视差估计的标签通常来自两种途径:一是用结构光或激光雷达同步采集真实深度,二是用传统光场算法生成伪标签。毕设场景下,前者成本太高,我建议用后者。具体做法是用OpenCV的StereoSGBM对相邻视角做立体匹配,得到粗略视差图,再用交叉验证过滤掉不可靠区域。
import cv2 # 取两个相邻视角 view1 = lf[:, :, A//2, A//2, :].astype(np.uint8) view2 = lf[:, :, A//2, A//2 + 1, :].astype(np.uint8) # 转灰度 gray1 = cv2.cvtColor(view1, cv2.COLOR_RGB2GRAY) gray2 = cv2.cvtColor(view2, cv2.COLOR_RGB2GRAY) # SGBM 参数 stereo = cv2.StereoSGBM_create( minDisparity=0, numDisparities=64, blockSize=5, P1=8 * 3 * 5 ** 2, P2=32 * 3 * 5 ** 2, disp12MaxDiff=1, uniquenessRatio=10, speckleWindowSize=100, speckleRange=32 ) disparity = stereo.compute(gray1, gray2).astype(np.float32) / 16.0这里numDisparities必须能被 16 整除,blockSize用奇数。P1和P2控制视差平滑度,值越大越平滑但细节越少。我一般先用默认值跑一遍,看视差图有没有大面积空洞,再微调uniquenessRatio和speckleWindowSize。注意 SGBM 输出的视差是左视图视角下的,如果要做多视角融合,需要把每个视角对的视差都转到同一参考系。
3. 深度学习模型怎么选:从 CNN 到注意力机制的落地对比
3.1 为什么光场视差估计不适合直接套用单目深度网络
单目深度估计网络比如 MiDaS 或 Depth Anything,输入是一张 RGB 图,输出是相对深度。但光场数据的特点是角度维度有冗余信息,直接套单目网络会浪费掉这些信息。更合理的做法是把角度维度当作通道或序列来处理。常见方案有三类:一是把 4D 光场展成 2D 宏像素图,用 2D CNN 处理;二是用 3D CNN 在空间和角度维度上同时卷积;三是用 Transformer 把角度维度当作 token 序列做注意力。
我实际跑下来,3D CNN 在中小规模数据上性价比最高,参数量可控,训练也稳定。Transformer 方案效果上限更高,但对数据量和显存要求也更高。毕设场景下,我建议先用 3D CNN 跑通 baseline,再考虑加注意力模块。
3.2 一个可复现的 3D CNN baseline 实现
下面这个模型结构是我常用的 baseline,输入是(B, A*A, H, W, 3),输出是(B, 1, H, W)的视差图。核心思路是把角度维度展成通道,然后用 3D 卷积在空间和角度上同时做特征提取。
import torch import torch.nn as nn class LF3DCNN(nn.Module): def __init__(self, ang_res=9): super().__init__() in_ch = ang_res * ang_res * 3 self.encoder = nn.Sequential( nn.Conv3d(in_ch, 64, kernel_size=(3,3,3), padding=(1,1,1)), nn.BatchNorm3d(64), nn.ReLU(inplace=True), nn.Conv3d(64, 128, kernel_size=(3,3,3), padding=(1,1,1)), nn.BatchNorm3d(128), nn.ReLU(inplace=True), nn.Conv3d(128, 256, kernel_size=(3,3,3), padding=(1,1,1)), nn.BatchNorm3d(256), nn.ReLU(inplace=True), ) self.head = nn.Sequential( nn.Conv3d(256, 1, kernel_size=(1,1,1)), nn.Sigmoid() ) def forward(self, x): # x: (B, A*A*3, H, W) -> reshape 到 (B, 3, A, A, H, W) B, C, H, W = x.shape A = int((C // 3) ** 0.5) x = x.view(B, 3, A, A, H, W) x = x.permute(0, 1, 2, 3, 4, 5) # (B, 3, A, A, H, W) x = x.reshape(B, 3 * A * A, 1, H, W) # 把角度和通道合并 x = x.repeat(1, 1, 1, 1, 1) # 保持维度 # 实际 3D 卷积需要 (B, C, D, H, W),这里 D 设为 1 x = x.unsqueeze(2) # (B, C, 1, H, W) feat = self.encoder(x) out = self.head(feat) return out.squeeze(2)这段代码里有个细节需要说明:3D 卷积的输入需要 5 个维度,我在这里把角度维度压到了通道维度,深度维度设为 1。这样做的好处是可以用标准的 3D 卷积核,但代价是角度信息没有显式地在深度维度上展开。如果你想让角度维度真正参与卷积,可以把输入 reshape 成(B, 3, A, A, H, W)然后对 A 和 H、W 做 3D 卷积,但那样显存占用会大很多。
参数上,ang_res要和你的数据角度分辨率一致,in_ch是角度数平方乘以 3。学习率我一般从 1e-4 开始,用 Adam 优化器,batch size 根据显存调到 4 或 8。损失函数用SmoothL1Loss,因为视差图在边缘处会有较大误差,L1 比 L2 更鲁棒。
3.3 训练循环与验证指标
训练循环里我习惯加两个东西:一是梯度裁剪,防止 3D 卷积梯度爆炸;二是每轮验证时算bad pixel rate,也就是视差误差超过 1 个像素的比例。
from torch.utils.data import DataLoader from torch.optim import Adam from torch.optim.lr_scheduler import StepLR model = LF3DCNN(ang_res=9).cuda() optimizer = Adam(model.parameters(), lr=1e-4) scheduler = StepLR(optimizer, step_size=10, gamma=0.5) criterion = nn.SmoothL1Loss() for epoch in range(50): model.train() for batch in train_loader: x, y = batch['input'].cuda(), batch['disp'].cuda() optimizer.zero_grad() pred = model(x) loss = criterion(pred, y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() scheduler.step() # 验证 model.eval() bad_pixel = 0 total = 0 with torch.no_grad(): for batch in val_loader: x, y = batch['input'].cuda(), batch['disp'].cuda() pred = model(x) bad_pixel += ((pred - y).abs() > 1.0).sum().item() total += y.numel() print(f'Epoch {epoch}, bad pixel rate: {bad_pixel / total:.4f}')clip_grad_norm_的max_norm设 1.0 是我试出来的经验值,设太小会欠拟合,设太大等于没裁。StepLR每 10 轮降一半学习率,适合这种中小规模训练。验证指标bad pixel rate比 MSE 更直观,因为视差图里大部分区域是平滑的,MSE 会被平滑区域拉低,看不出边缘问题。
4. 避坑与排查:光场深度学习毕设里最容易翻车的五个地方
4.1 数据加载时角度维度顺序搞反
现象:训练 loss 一直不降,预测出来的视差图看起来像随机噪声。原因:光场数据的角度维度在存储时可能是(A, A, H, W)或(H, W, A, A),如果你按错误的顺序 reshape,网络看到的就不是正确的子孔径排列。解决:加载数据后先打印形状,用matplotlib把中心视角和边缘视角画出来对比,确认角度索引对应的物理意义。我一般会写一个check_lf_order函数,把四个角的视角都画出来,如果左上角视角和右下角视角内容差异很大,说明角度维度是对的。
4.2 视差标签的数值范围没归一化
现象:训练初期 loss 直接爆炸,梯度裁剪也压不住。原因:SGBM 输出的视差值是像素单位,可能到几十甚至上百,而网络输出经过 Sigmoid 后是 0 到 1 之间。解决:把视差标签除以最大视差值做归一化,或者去掉 Sigmoid 改用直接回归。我一般会在数据预处理阶段统计训练集视差的最大值,然后统一除以这个值,验证和测试时再乘回来。
4.3 显存不够导致 batch size 只能设 1
现象:跑 3D CNN 时显存溢出,只能把 batch size 降到 1,训练极慢。原因:3D 卷积的参数量和激活值占用比 2D 大很多,尤其是角度分辨率高的时候。解决:三个方向可以同时做——把角度分辨率从 9 降到 5,把输入图像裁剪成 patch 而不是整图,用混合精度训练。我一般会先用torch.cuda.amp把显存占用降一半,再考虑降分辨率。
4.4 验证集视差图出现棋盘格伪影
现象:验证时输出的视差图有规律的棋盘格纹理。原因:SGBM 生成的伪标签在弱纹理区域不可靠,网络学到了这些错误模式。解决:在生成伪标签时加左右一致性检查,把不满足|disp_left - disp_right| < 1的像素标记为无效,训练时用 mask 忽略这些像素。具体做法是在 loss 里乘一个 mask,无效区域不参与梯度计算。
4.5 训练 loss 正常但可视化结果全黑
现象:loss 降到很低,但把预测视差图保存成图片后看起来全黑。原因:视差图数值范围很小,比如 0 到 0.1,直接保存成 8 位 PNG 会被截断到 0。解决:保存前先做归一化,用(disp - disp.min()) / (disp.max() - disp.min())映射到 0 到 1,再乘以 255。或者直接用matplotlib的imshow加colorbar查看,不要直接存 PNG。
5. 从跑通到出图:让毕设结果经得起答辩追问的三个技巧
5.1 用重聚焦验证视差估计的物理一致性
答辩时老师最常问的一个问题是:你怎么证明你的视差估计是对的?光场数据有一个天然优势,就是可以用估计出的视差做重聚焦,然后看聚焦位置是否和真实场景一致。具体做法是:根据视差图把每个视角的图像按视差偏移后叠加,如果视差估计准确,叠加后的图像在对应深度处会清晰,其他深度会模糊。
def refocus(lf, disp, focus_depth): # lf: (H, W, A, A, 3), disp: (H, W) H, W, A, _, C = lf.shape refocused = np.zeros((H, W, C), dtype=np.float32) for i in range(A): for j in range(A): shift_x = int((i - A//2) * disp.mean() * focus_depth) shift_y = int((j - A//2) * disp.mean() * focus_depth) shifted = np.roll(lf[:, :, i, j, :], (shift_y, shift_x), axis=(0, 1)) refocused += shifted return refocused / (A * A)这个函数里focus_depth控制聚焦深度,disp.mean()是平均视差。你可以把focus_depth从 0 到 1 遍历,生成一系列重聚焦图像,如果某个深度处的图像明显比其他深度清晰,说明视差估计是合理的。这个验证方法不需要额外标注,答辩时也很有说服力。
5.2 用消融实验说明角度分辨率的影响
毕设里如果只跑一个模型,老师可能会问:为什么选这个角度分辨率?我建议做一个简单的消融实验,把角度分辨率从 3×3 到 11×11 各跑一遍,记录 bad pixel rate 和训练时间。下面是一个参考表格,你可以根据自己的数据填。
| 角度分辨率 | bad pixel rate | 训练时间(每轮) | 显存占用 |
|---|---|---|---|
| 3×3 | 0.12 | 45s | 2.1GB |
| 5×5 | 0.08 | 1min 20s | 3.8GB |
| 7×7 | 0.06 | 2min 10s | 5.6GB |
| 9×9 | 0.05 | 3min 30s | 8.2GB |
| 11×11 | 0.05 | 5min 40s | 12.4GB |
从表里能看出,角度分辨率从 3 到 7 提升明显,7 到 11 收益递减但显存翻倍。答辩时你就可以说:综合考虑精度和资源,我选了 7×7 作为最终配置。这种有数据支撑的选型理由,比单纯说「我用了 9×9」要扎实得多。
5.3 把失败案例也放进答辩材料
我自己的习惯是,答辩 PPT 里专门留一页放失败案例。比如弱纹理区域视差估计不准、遮挡区域出现伪影、训练后期过拟合。然后说明你尝试了哪些改进,哪些有效哪些无效。这样做的好处是,老师能看出你确实动手做了,而不是只跑了一个开源代码。而且失败案例往往能引出更有深度的问题,比如「为什么遮挡区域难处理」,你可以顺势讲一下光场角度冗余和遮挡的关系,把答辩变成技术讨论。
最后说一个我踩过的坑:不要等到答辩前一周才开始整理结果。光场数据的可视化很耗时,重聚焦、视差图、点云导出,每个都要调参数。我一般会在训练跑起来之后就开始写可视化脚本,边训边看,这样最后出图的时候不会手忙脚乱。希望帮到你。
本文还有配套的精品资源,点击获取