
简介使用NeRF对手机拍摄的物体进行三维重建的毕业设计项目主要面向计算机相关专业毕业生与三维重建入门者可直接用于毕业设计、课程设计或期末大作业。项目经导师指导并获98分评价代码经严格调试确保可运行完整覆盖手机图像输入、COLMAP相机位姿估计、NeRF模型训练、渲染路径生成与可视化输出全流程。资源共31个文件以21个Python源码文件为核心另含5个字节码文件、2个文本说明及效果展示图片/动图压缩包仅5.37MB目录结构清晰便于按模块学习。Python脚本涵盖核心算法实现以及数据加载、位姿计算、渲染路径生成、结果展示等配套工具同时支持LlFF、DTU等常见三维重建数据集。目前已有359人学习下载适合需要快速搭建三维重建实验、参考完整毕设代码框架或进行项目实战练习的读者。1. 别再拿手机随便拍一圈了NeRF重建的成败在拍照之前很多人拿到 NeRF 相关的三维重建项目第一反应是“用手机对着物体转一圈拍视频然后丢给网络训练”。这个思路方向没错但真正跑过一次就会明白NeRF 本身能处理的光照和视角变化远比想象中脆弱手机拍摄带来的运动模糊、卷帘快门效应、自动白平衡漂移再加上物体表面反光和背景杂乱随便一拍的数据集往往会让训练出来的辐射场变成一团带重影的半透明雾。NeRF 的数学本质是用一个 MLP 去拟合 5D 坐标到颜色和密度的映射它对输入图像的位姿精度极其敏感而手机照片恰恰缺少精确的相机内参和外参。因此标题里“手机拍摄”四个字才是这个项目的核心难点它不是把现成的 NeRF 代码跑通就完事而是要建立一套从拍摄、预处理、位姿解算到网格提取的完整流程。这个项目适合两类人一类是刚入门神经渲染、想用手里现成设备做物体级重建的工程师另一类是已经在用传统多视图立体MVS或 SLAM 做重建、想对比 NeRF 路线和显式几何路线差异的研究者。下面按我自己做这类项目时的顺序展开。2. 从手机到训练集COLMAP 位姿估计与数据筛选的实操细节NeRF 训练需要三样东西图像序列、每张图像对应的相机内参、每张图像对应的相机到世界坐标系的变换矩阵。手机照片没有 EXIF 里直接可用的位姿信息所以第一关是跑 Structure-from-MotionSfM。最常见的做法是用 COLMAP它把特征提取、匹配、稀疏重建和捆绑调整打包成一条命令行链路能直接输出 NeRF 训练框架如 Nerfstudio 或 Instant-NGP需要的 transforms.json 格式。但 COLMAP 对输入图像的质量和重叠率有隐性要求不是随便丢进去就能跑出可用位姿的。2.1 拍摄环节的硬性约束固定焦距、固定曝光、充足重叠在按下快门前先把手机相机切换到专业模式锁定对焦距离和曝光时间关闭 HDR 和夜景增强。原因在于 NeRF 假定场景是静态的且光照一致自动曝光会让同一物体的颜色在不同视角下发生偏移MLP 会把这些偏移当成真实辐射场的一部分去拟合结果就是渲染视角变化时出现色块漂移。环绕拍摄时每相邻两帧之间的角度差控制在 10 度以内确保任意两张相邻图像有 70% 以上的视觉重叠区域。物体放在一个纹理丰富的环境中拍摄比如报纸、键盘、木纹桌面这样 COLMAP 的特征提取器能匹配到足够多的特征点。一个常见的误区是拍摄纯色背景——特征匹配会直接失败稀疏点云会变得非常稀疏甚至分层。物体本身如果是反光材质金属、陶瓷、光滑塑料需要在表面贴哑光喷剂或用柔光箱打散高光否则 COLMAP 的特征点会集中在高光边缘导致位姿解算出漂移。手机拍摄还有一个容易被忽略的物理问题近距离拍摄时镜头畸变很明显尤其是广角端。COLMAP 的相机模型参数里SIMPLE_RADIAL 和 OPENCV 两种畸变模型可以表达径向畸变但如果拍摄距离太近且物体占画面比例过大畸变参数会和位姿参数耦合在一起捆绑调整就不稳定。我的做法是先拍一组标定板照片用 OpenCV 的 calibrateCamera 预先算出畸变系数再用--ImageReader.camera_model OPENCV把内参和畸变系数写进 COLMAP 的数据库。2.2 COLMAP 特征提取与匹配的推荐参数COLMAP 提供了 GUI 和命令行两种操作方式批量处理时用命令行更高效。特征提取阶段SIFT 的特征点数上限可以适当调高因为手机图像分辨率通常有 1200 万像素以上默认的 8192 个特征点可能不够覆盖整个物体表面。匹配阶段有两个可选策略暴力匹配exhaustive和顺序匹配sequential。对手机环绕拍摄的视频帧序列sequential matcher 更好因为它只匹配时间上相邻的帧速度更快且误匹配更少前提是你按拍摄顺序给图像编号。# 特征提取增加每张图的最大特征点数 colmap feature_extractor \ --database_path $PROJECT/database.db \ --image_path $PROJECT/images \ --ImageReader.single_camera 1 \ --ImageReader.camera_model OPENCV \ --SiftExtraction.max_num_features 16384 \ --SiftExtraction.estimate_affine_shape 1 # 顺序匹配窗口大小设为 20 表示只匹配相邻 20 帧 colmap sequential_matcher \ --database_path $PROJECT/database.db \ --SequentialMatching.overlap 20 \ --SiftMatching.guided_matching 1 # 稀疏重建输出到 sparse 目录 mkdir -p $PROJECT/sparse colmap mapper \ --database_path $PROJECT/database.db \ --image_path $PROJECT/images \ --output_path $PROJECT/sparse \ --Mapper.ba_global_function_tolerance 1e-6参数说明--ImageReader.single_camera 1告诉 COLMAP 所有图像来自同一台物理相机强制共享同一套内参这既减少了待优化参数个数也让位姿解算更稳定。--SiftExtraction.estimate_affine_shape 1让 SIFT 检测更适应视角变化对环绕拍摄的倾斜视角帮助明显。--SequentialMatching.overlap 20控制匹配窗口窗口越大召回率越高但误匹配也会增多。--SiftMatching.guided_matching 1开启引导匹配用几何约束筛选误匹配代价是耗时增加约一倍但位姿精度提升明显。mapper 阶段的--Mapper.ba_global_function_tolerance是捆绑调整的收敛阈值默认 1e-4 在某些场景下会提前停止迭代导致位姿精度不够改成 1e-6 后重建质量有可见提升。2.3 稀疏点云质量评估什么样的结果不能用于 NeRF 训练跑完 mapper 后用 COLMAP GUI 打开sparse/0目录下的 project.ini检查稀疏点云的三维分布。合格的稀疏重建应该有这几个特征点云均匀包裹物体表面没有明显的大块空洞相机位置绕物体形成球状或环状分布且有高度上的变化相邻相机的光轴夹角平滑过渡没有交叉或跳变。如果点云出现长条状漂移或相机位姿跑到物体内部说明图像匹配有问题需要回到拍摄环节补拍或删帧。另外要留意重建出的相机数量。假设拍了 200 帧COLMAP 只成功注册了 120 帧说明有 80 帧因为运动模糊或重叠不足而失败。此时需要删除未注册图像对应的文件然后在 mapper 阶段对这些失败帧做--Mapper.ba_refine_focal_length 0之类的调整或者干脆重拍。训练 NeRF 时未注册的图像不会参与损失计算但它们的存在会干扰数据集加载脚本所以处理干净。3. NeRF 训练前的数据转换与手机拍摄场景的敏感参数调优COLMAP 输出的稀疏重建结果不能直接喂给 NeRF 训练脚本需要转换成各个 NeRF 框架要求的 JSON 格式。Nerfstudio 提供了ns-process-data命令能自动完成从图像目录到 transforms.json 的转换。但默认参数是按相机阵列拍摄设计的用在手机环绕拍摄上需要调整下采样倍数和中心裁剪。3.1 用 ns-process-data 生成带掩码的训练数据手机拍摄的图像分辨率通常在 3000 像素以上直接训练 NeRF 会导致 MLP 的采样网络过大显存爆炸且收敛极慢。常见做法是把图像缩放到 800 到 1600 像素之间。缩放倍数选择要看物体在画面中的占比物体占画面 70% 以上可以用 1600 像素占比小就缩到 800 像素因为背景区域不会对物体几何有贡献反而浪费采样。ns-process-data里的--sfm-tool colmap参数会重新跑一遍 COLMAP如果你已经手动跑过可以直接用--skip-colmap跳过然后通过--data指定 COLMAP 输出目录。# 从已生成的 COLMAP 结果直接转成 nerfstudio 格式 ns-process-data \ --data $PROJECT/raw_images \ --output-dir $PROJECT/ns_data \ --skip-colmap \ --sfm-tool colmap \ --max-image-size 1200 \ --percent-radius 0.4 # 如果拍摄时物体周围有可分离背景先手动抠图生成 mask 目录再执行 ns-process-data \ --data $PROJECT/raw_images \ --output-dir $PROJECT/ns_data_mask \ --skip-colmap \ --max-image-size 1200 \ --mask-dir $PROJECT/masks \ --percent-radius 0.4--percent-radius 0.4的含义是只保留以图像中心为圆心、半径占图像短边 40% 区域内的像素参与训练。手机拍摄物体时物体边缘通常靠近画面边缘边缘处的镜头畸变和景深虚化最严重这些区域作为训练样本会给 NeRF 传递矛盾信息。裁剪掉边缘后训练更稳定且收敛速度更快。--max-image-size控制图像长边的最大像素数1200 是准确性和显存占用之间的平衡点。如果使用带掩码的方式NeRF 只在掩码区域内计算颜色损失背景颜色的干扰被彻底消除这对后续提取网格尤其重要因为背景区域的密度场会被直接清零。3.2 训练迭代次数与采样策略Instant-NGP 和 Nerfstudio 的取舍训练 NeRF 有两条主流路线一条是用 Nerfstudio 里的 nerfacto 模型它集成了场景压缩和外观嵌入对光照变化有一定鲁棒性适合手机拍摄这类曝光不完全一致的数据另一条是用 Instant-NGP即 ngp 框架它用多分辨率哈希编码代替了原始 NeRF 的位置编码训练速度快一到两个数量级。我的建议是先用 Instant-NGP 跑一个低分辨率的快速版本验证位姿质量再用 Nerfstudio 的 nerfacto 做正式训练因为 nerfacto 的损失函数里包含了畸变损失distortion loss能抑制漂浮物。# 用 nerfstudio 训练 nerfacto 模型 ns-train nerfacto \ --data $PROJECT/ns_data \ --experiment-name phone_recon \ --max-num-iterations 30000 \ --pipeline.model.background-color white \ --pipeline.datamanager.camera-optimizer.mode off \ --viewer.quit-on-train-completion True # 训练过程中实时查看 PSNR ns-train nerfacto \ --data $PROJECT/ns_data \ --max-num-iterations 30000 \ --logging.local-writer.enable True \ --logging.local-writer.logger-level INFO参数说明--pipeline.model.background-color white把背景设为白色这比默认的黑色有更好的数值稳定性因为白色背景对应密度为零且颜色为 1 的均匀区域避免 MLP 在无限远的背景区域浪费容量去拟合黑色。--pipeline.datamanager.camera-optimizer.mode off是关键选项——Nerfstudio 默认会额外优化相机位姿来补偿 COLMAP 的误差但手机数据集的位姿误差往往带有系统性偏差比如旋转矩阵的微小错位让网络继续优化这些位姿可能把内参和外参耦合错误关闭后稳定性更好。30,000 次迭代在单张 RTX 3090 上大约需要 40 到 60 分钟如果使用 1200 像素输入每帧采样 4096 条光线总样本量足够覆盖物体表面。Instant-NGP 的训练参数里影响最大的是aabb_scale和num_samples。aabb_scale定义了场景包围盒的缩放范围如果物体实际只占包围盒的一小部分密度场的大部分空间是空的训练效率极低。手机拍摄的场景通常物体居中aabb_scale4是个合理起点。如果重建结果里物体被截断说明包围盒没有包住整个物体调大到 8如果物体周围漂浮大量烟雾状点云说明包围盒过大调小到 2。# Instant-NGP 训练使用园形路径的 images 作为输入 ./build/testbed --mode nerf \ --scene $PROJECT/ns_data/transforms.json \ --save_snapshot $PROJECT/snapshot.msgpack \ --n_steps 20000 \ --aabb_scale 4 \ --num_samples 512--num_samples 512指每条光线上的采样点数量数值越高对薄壁结构的还原越精确但显存占用和训练时间线性增长。对手机拍摄的实物体512 足够了超过 1024 后提升不明显。训练过程中可以打开 testbed 的 GUI 实时查看渲染结果如果某个视角出现白色噪点说明该区域的密度场还没收敛需要继续训练如果出现半透明重影大概率是位姿误差此时回过头检查 COLMAP 的稀疏点云更有效率。3.3 损失曲线怎么看PSNR 不是唯一指标训练日志里会输出 PSNR峰值信噪比和 SSIM结构相似性。在 1200 像素输入下PSNR 达到 25 以上说明颜色拟合基本到位但 PSNR 高不代表几何正确——均匀颜色的物体表面比如白色石膏像颜色损失很小但几何可能是一团平面。因此训练完成后不要只盯着 PSNR要在 viewer 里旋转视角重点观察物体轮廓边缘是否锐利、细薄结构如叶子、耳机线是否有断裂。另一个常用指标是渲染深度图在 Nerfstudio viewer 里切换到 depth 通道如果深度图在物体边缘出现锯齿状跳变说明密度场的锐利度不够需要增加采样点数或提高图像分辨率。4. 从辐射场到可用的三维网格密度阈值、Marching Cubes 与纹理导出NeRF 训练完后得到的是一个连续密度场它本质上是一个隐式几何表示要用于游戏引擎或 3D 打印必须提取成显式的三角网格。最常见的做法是 Marching Cubes 算法在密度场中提取等值面但有两点需要根据手机拍摄数据的特点做调整。4.1 用 nerfstudio 导出网格poisson 与 marching_cubes 的区别Nerfstudio 提供了ns-export命令支持两种网格提取方式poisson和marching_cubes。poisson 方法先对密度场采样生成点云再用泊松重建算法生成封闭网格适合物体底部有支撑面的场景但会平滑掉细小结构marching_cubes 直接在体素网格上提取等值面对细节保留更好但容易产生非流形边。手机拍摄物体通常结构复杂度中等我一般先用 marching_cubes 提取再用网格后处理工具清理。# 用 marching_cubes 方式导出 PLY 网格 ns-export poisson \ --load-config $PROJECT/outputs/phone_recon/nerfacto/20250201_123456/config.yml \ --output-dir $PROJECT/mesh \ --target-num-faces 100000 \ --num-samples-per-ray 512--target-num-faces 100000控制输出网格的面数。100,000 面对于中等复杂度的物体足够面数过少会丢失细节面数过多在后处理阶段会拖慢操作。注意--num-samples-per-ray要和训练时一致或更大因为导出网格时每个光线上的采样点数决定了密度场的采样分辨率低于训练值会导致网格表面出现空洞。4.2 密度阈值的物理意义太高会薄、太低会肿Marching Cubes 提取等值面时需要指定一个密度阈值。NeRF 的密度sigma值范围通常在 0 到 50 之间但它不是一个有物理单位的量而是和训练时的采样步长相关。阈值选得过高提取的网格表面会向内收缩细薄结构直接被切断阈值选得过低表面向外膨胀物体看起来像被吹了气。一个实用的调试方法是先导出两个版本的网格——一个用默认阈值一个阈值减半——然后在 MeshLab 里叠加对比。如果默认版本出现大量破洞减半版本又明显膨胀说明密度场在物体表面附近的变化不够陡峭需要回到训练阶段把--pipeline.model.distortion-loss-mult调高比如 0.01 调到 0.05这个损失项会惩罚密度场的非稀疏分布让表面更锐利。4.3 手机拍摄物体的纹理导出与 MeshLab 清理流程网格导出后纹理信息并没有丢失。Nerfstudio 可以把颜色烘到 UV 展开后的纹理图上但默认导出的网格只有顶点颜色面数多时顶点色会显得模糊。正确的做法是在 MeshLab 里执行参数化trivial parameterization并重新采样纹理。操作步骤是导入 PLY 网格使用Filters - Remeshing, Simplification and Reconstruction - Quadric Edge Collapse Decimation把面数降到 50,000 左右然后Filters - Texture - Parametrization trivial per-triangle最后导出 OBJ 格式。这一步会生成一张 4096x4096 的纹理图手机拍摄的材质颜色基本能还原。MeshLab 里还有一个必做操作用Filters - Cleaning and Repairing - Remove Duplicate Vertices和Remove Unreferenced Vertices清理网格。Marching Cubes 提取的网格经常有重复顶点和孤立碎片这些碎片会造成渲染时的黑色闪烁。5. 用 mask 和对比测试验证重建精度并避免三个高频翻车点项目做完不代表结束你需要一套验证方法来判断重建结果到底能不能用同时也给后续做 3DGS 或 SLAM 融合留好对比基准。5.1 定量验证预留 10% 图像做测试集训练 NeRF 时不要用全部图像求 PSNR。在 ns-process-data 阶段手动留出 20 到 30 帧不参与训练把它们放到另一个目录训练完后在 Nerfstudio viewer 里渲染这些未见过视角的图像计算渲染图和真实照片的 PSNR 和 SSIM。如果 PSNR 低于 20说明模型过拟合了训练视角典型原因是训练图像太少少于 50 帧或拍摄轨迹单一只在一个平面内绕圈。此时增加俯拍角度重新采集比盲目增加迭代次数更有效。5.2 直观验证在 MeshLab 里对比正面照片真实物体重建是否精确有一个土办法把导出的网格渲染图和你手机拍的某张正面照片叠在一起半透明对比轮廓。网格的边缘线应该和照片里的轮廓基本重合偏差超过 5 个像素就说明位姿或密度场有问题。更专业的做法是用 CloudCompare 计算导出网格和 COLMAP 稀疏点云之间的倒角距离这个指标能反映几何误差的绝对量级一般控制在物体尺寸的 1% 以内。5.3 高频翻车点背景割裂、过曝高光、窄视角覆盖最后列三个我反复踩过的坑。第一手机 HDR 关闭不彻底图像亮部和暗部的色调映射曲线不一致导致 NeRF 渲染结果出现偏色块。第二物体表面有镜面高光时COLMAP 的匹配点集中在高光处稀疏点云局部密集但整体稀疏重建网格出现凹陷。第三只绕物体水平一圈拍没有俯仰角度网格顶部会是一个洞或者一片混乱平面因为 NeRF 对未观测到的区域只会预测低密度。针对第三个问题一个补救技巧是不用补拍而是在提取网格后手动裁剪掉未覆盖区域再用 MeshLab 的Close Holes补洞虽然细节是编造出来的但整体形状可接受。至于最终把 NeRF 结果用于三维重建后续的纹理简化或模型轻量化OBJ 格式配合 50,000 面以内的网格足够支撑大部分日常用途。本文还有配套的精品资源点击获取