资讯动态

单目RGBD实时室内三维重建:深度图、TSDF与相机位姿追踪全解析

发布时间:2026/10/26 7:41:02 来源:尧图企业网站定制
简介室内场景三维重建项目基于单目彩色与深度相机实现实时三维重建解决从二维图像序列高效生成室内三维模型的难题适合计算机视觉研究者、算法工程师以及三维重建初学者用于算法学习、复现与二次开发。资源包共652个文件压缩包大小约8.24MB主要文件类型包括Python与C/CUDA源码、C头文件、CMake与YAML构建配置、OpenGL着色器、Markdown文档及说明文本兼顾算法实现、编译脚本和辅助工具目录结构清晰便于按模块检索。目前已有108人学习浏览。项目源码完整附有流程教程可系统理解特征提取、深度数据生成点云、点云融合和实时优化等关键环节还能参考PDF等说明文档快速上手既适合入门者建立整体认知也适合研究者在此基础上扩展和适配不同应用场景如虚拟现实、机器人导航与智能家居等。这些应用对实时重建有直接需求通过本项目可快速获得可运行的基础实现与扩展思路。1. 室内场景三维重建为什么非得用单目RGBD拿着相机扫一圈就能出网格把一台 RealSense D435i 或者 Orbbec Astra 接上电脑在客厅里慢慢走一圈屏幕上实时长出一个带颜色的三维网格这就是“单目RGBD相机实现实时室内场景3D重建”最直观的形态。它要做的事可以拆成三句话用单目RGBD相机同步拿到彩色图与深度图用位姿追踪把每一帧对齐到全局坐标系再把几百帧的深度观测逐帧融合进一个三维体素场最终导出一个可编辑、可渲染的OBJ或者PLY模型。相比激光雷达扫描方案硬件成本低一个数量级相比纯RGB单目重建深度图直接给出尺度信息弱纹理白墙也不会丢跟踪。这套方案适合机器人导航、AR/VR内容制作、数字孪生数据采集也适合想在毕设里把一套完整算法链路跑通的人——它需要的不是顶配GPU而是一条清晰的流程教程和一双会看参数的眼睛。2. 单目RGBD相机选型与数据链路深度图才是实时重建的底气2.1 单目RGBD到底单在哪一个RGB镜头加一个深度传感器先说“单目”。这里说的单目指的是只有一个RGB彩色镜头并不是说整个相机只有一个镜头。深度信息由独立的深度传感器产生常见的有结构光、主动红外立体和ToF三种原理。Kinect v1是结构光通过投射红外散斑、从散斑形变反算深度RealSense D435i是主动红外立体用左右两个IR相机看同一个红外投影仪的散斑做密集匹配Kinect v2和Azure Kinect则是ToF通过测量红外光飞行时间直接得到距离。这个设计对实时三维重建非常友好。纯RGB单目重建需要靠运动初始化出尺度遇到无纹理白墙时特征点直接消失重建会断纯点云方案用激光扫描精度虽高但价格高、点云稀疏。RGBD相机用一个消费级传感器同时拿到8位纹理和16位深度深度给了明确的物理尺度纹理给了配准和贴图的信息来源正好补上对方各自的短板。选型上有三个参数要盯深度分辨率、有效量程、帧率。室内场景常用的配置是640x48030fps量程0.2~3m。D435i属于主动立体对室内自然光、关灯环境都不敏感但对纯黑物体和镜面反射仍会丢深度ToF对黑色物体稍好但近距离噪声偏大且容易受多路径干扰。要是场景里有大面积玻璃幕墙说句实在话不管选哪种相机都得换扫法靠多角度绕扫来补洞。2.2 把相机数据接进程序pyrealsense2的取流参数设置在写重建管线之前第一个坑往往是深度图和彩色图没对齐。深度图坐标系属于深度传感器彩色图坐标系属于RGB镜头两个传感器在硬件上有一段物理距离数据流出来天然是错位的。重建时如果直接把深度值贴到彩色像素上物体会出现一指宽的边缘偏移。用RealSense官方的pyrealsense2库可以一次性解决import pyrealsense2 as rs import numpy as np import cv2 pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipeline.start(config) # 关键步骤让深度图与彩色图对齐到同一坐标系 depth_sensor profile.get_device().first_depth_sensor() depth_sensor.set_option(rs.option.visual_preset, 4) # 高密度预设 align rs.align(rs.stream.color) for _ in range(30): frames pipeline.wait_for_frames() aligned_frames align.process(frames) aligned_depth aligned_frames.get_depth_frame() if aligned_depth: break depth_image np.asanyarray(aligned_depth.get_data()).astype(np.float32) color_image np.asanyarray(aligned_frames.get_color_frame().get_data()) cv2.imwrite(color.png, color_image) np.save(depth.npy, depth_image) pipeline.stop()这段代码做了三件最基本的事把深度流和彩色流都配成640x48030fps把深度图通过rs.align重投影到彩色坐标系把深度数据以float32的毫米值保存下来。align的对象是深度帧方向是rs.stream.color意思是“把深度影像对齐到彩色影像”对齐之后深度图的宽高与彩色图一致每个像素的深度值对应彩色图上同一个像素的物体。参数上有一个细节经常被忽略深度传感器有个visual_preset选项4在这里对应High Density。不同型号相机这个数字的映射不一样最稳妥的办法是调用get_option_range查一下可选值。室内重建默认选High Density是为了减少深度空洞代价是噪声略微变大后面需要用滤波压一压。2.3 深度图预处理补洞、双边滤波与飞点清理拿到深度图之后不建议直接把原始数据喂给重建算法。RealSense这类消费级传感器的深度图有三个典型问题反光、镜面、超量程位置会产生零值黑洞物体边缘的深度值会在前景和背景之间跳变形成飞点同一静止场景会有2到5厘米的时间抖动。这些脏数据进到体素融合里最终都会变成模型表面的鼓起和空洞。常见的处理顺序是把RealSense内置的空间滤波和Open3D的深度截断结合import numpy as np import open3d as o3d # 深度单位是毫米先转成米并清掉无效值 depth_mm np.load(depth.npy).astype(np.float32) depth_m np.where(depth_mm 0, depth_mm / 1000.0, 0.0) depth_o3d o3d.geometry.Image(depth_m) color_o3d o3d.io.read_image(color.png) rgbd o3d.geometry.RGBDImage.create_from_color_and_depth( color_o3d, depth_o3d, depth_trunc3.0, # 超过3米直接丢弃 depth_scale1.0, convert_rgb_to_intensityFalse, ) # 转成点云检查效果 intrinsic o3d.camera.PinholeCameraIntrinsic( o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault) pcd o3d.geometry.PointCloud.create_from_rgbd_image(rgbd, intrinsic) o3d.visualization.draw_geometries([pcd])补洞和去飞点最好的工具其实是RealSense自带的滤波器decimation filter降分辨率但保留边缘spatial filter平滑空洞temporal filter会用前面几帧深度做时域中值代价是快速移动时会有残影。我的经验是室内重建只开spatialtemporal不要开太大否则相机轻微抖动时深度会拖尾配准时会把墙面配歪。depth_trunc3.0这个值很关键。室内场景三维重建的量程需求一般就是0.2到3米再远的点噪声已经大到对重建贡献是负的丢弃它们还能顺便降低点云数量减小后续配准耗时。2.4 深度到点云的转换内参标定与单位陷阱RGBD重建的全链路里内参标定是最容易被跳过但最不能省的一步。Open3D的PrimeSenseDefault内参其实是Kinect v1时代的预设焦距和主点都是固定值和D435i的真实内参差不少。直接用预设内参也能跑但重建出来的模型会带系统性畸变墙面会有轻微的鼓包。正确做法是用相机SDK读出出厂内参或者用棋盘格做一次标定profile pipeline.start(config) color_profile profile.get_stream(rs.stream.color) intrinsics color_profile.as_video_stream_profile().get_intrinsics() print(intrinsics.fx, intrinsics.fy, intrinsics.ppx, intrinsics.ppy)拿到fx、fy、ppx、ppy之后把它填进Open3D的PinholeCameraIntrinsicfx, fy, ppx, ppy intrinsics.fx, intrinsics.fy, intrinsics.ppx, intrinsics.ppy intrinsic_o3d o3d.camera.PinholeCameraIntrinsic( intrinsics.width, intrinsics.height, fx, fy, ppx, ppy)这里还有一个单位陷阱RGBDImage.create_from_color_and_depth默认的depth_scale是1000即把米当毫米读如果你已经把深度图除以1000转成了米就必须把depth_scale设为1.0。很多人正是在这一步忘了同步结果重建出来的模型尺寸变成真实尺寸的千分之一看起来像微缩玩具。第一反应是检查点云转出来的点云应该在相机前方、尺寸合理颜色和彩色图一致。如果看到点云在相机背后或者尺寸明显异常优先检查depth_scale和深度单位。这两项出了问题后面整个重建链路都是白跑的。3. TSDF体素融合与相机追踪实时重建的最小闭环3.1 为什么选TSDF而不是把点云拼到一起最直觉的实时重建思路是每帧把深度图转成点云用相机位姿把它们拼到一块。这种“点云拼接”路线在数据量小的时候够用但扫几十秒后同一个平面被几百帧覆盖点云在表面的法线方向上会越堆越厚。TSDF把空间划分为体素网格在每个体素里存一个截断符号距离值正值表示该体素在表面前方负值表示在表面后方绝对值接近0的地方就是真实表面。每来一帧深度图只更新这帧能看见的体素距离值用加权平均的方式累积相当于在体素级别做表面融合噪声被平均掉不会有堆积问题。TSDF另一个好处是天然支持增量融合和新视角的更新。点云拼接之后想修正一个区域的几何得把那个区域重新配准、重新替换TSDF只要把新帧的深度观测投进对应体素原来不准的距离值会被新观测按权重拉回来。这也是为什么几乎所有实时重建框架——KinectFusion、ElasticFusion、BundleFusion——底层都是TSDF或其变体。Open3D里对应的是ScalableTSDFVolume它用体素哈希做稀疏存储不会把一个20平米房间的完整三维空间一次性分配成数组而是只分配被扫描覆盖到的体素块。扫描过程中体素块随着覆盖范围增大而增多这也是后面要讲的内存问题的根源。关键参数先给一张表后面还会展开参数作用室内场景推荐值voxel_length体素边长决定网格分辨率0.005~0.015mm~1cmsdf_trunc截断距离控制表面锐利程度voxel_length的3到4倍color_type是否把RGB融合进体素RGB8或None只要几何3.2 RGBD odometry用混合项算帧间位姿实时重建和离线重建的分水岭在相机追踪离线可以靠Bundle Adjustment整体优化实时只能一帧一帧往前算。最底层的方案是普通ICP用两帧深度点云找最近点估计相对位姿。普通ICP在平面场景里很吃亏因为平面内平移自由度约束不足帧间位姿会滑动。Open3D的run_rgbd_odometry用的是hybrid term同时优化几何误差和RGB灰度误差。几何项负责深度一致性光度项负责纹理一致性。白墙上的纹理虽弱但只要有一点明暗变化光度项就能补上几何缺失的约束这是纯ICP做不到的。import open3d as o3d import numpy as np intrinsic o3d.camera.PinholeCameraIntrinsic( o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault) def load_rgbd(seq): depth o3d.io.read_image(fdepth/{seq:04d}.png) color o3d.io.read_image(fcolor/{seq:04d}.png) return o3d.geometry.RGBDImage.create_from_color_and_depth( color, depth, depth_trunc3.0, depth_scale1.0, convert_rgb_to_intensityFalse, ) source load_rgbd(0) target load_rgbd(1) result o3d.pipelines.color_map.run_rgbd_odometry( source, target, o3d.pipelines.odometry.RGBDOdometryJacobianFromHybridTerm(), intrinsic, ) print(相对位姿:\n, result.transformation) print(内点率: {:.2f}.format(result.inlier_ratio))run_rgbd_odometry返回的transformation是从source到target的相对变换矩阵也就是当前帧相对于上一帧的相机运动。内点率inlier_ratio代表匹配成功的点的比例是最重要的质量信号高于0.6说明配准可靠低于0.3基本可以认为这一帧废了千万别硬融。实际项目里很少直接用这个相对位姿更常见的写法是维护全局位姿global_pose np.eye(4) for seq in range(1, len(rgbd_frames)): rel compute_odometry(load_rgbd(seq - 1), load_rgbd(seq)) global_pose_current global_pose rel global_poses.append(global_pose_current)这里有一个公认的坑矩阵乘法方向。Open3D的约定是变换矩阵右乘也就是当前全局位姿等于上一帧全局位姿乘相对位姿。顺序反了就是先平移再旋转结果会越走越飘最终模型扭曲。我处理这个问题有个习惯第一帧位姿固定为单位矩阵第二帧跑完odometry后在大场景中肉眼检查是否合理——相机前进时模型应当后退、相机左转时模型右侧内容增多不对就立刻改乘序。提示全局位姿是“相机在世界里的位姿”而TSDF的integrate需要的是“世界在相机里的位姿”两者互为逆矩阵。3.3 把位姿喂给TSDF逐帧融合与网格提取位姿追踪和TSDF融合配合起来就是一个最小可跑的实时重建闭环import open3d as o3d import numpy as np volume o3d.pipelines.integration.ScalableTSDFVolume( voxel_length0.01, sdf_trunc0.04, color_typeo3d.pipelines.integration.TSDFVolumeColorType.RGB8, ) intrinsic o3d.camera.PinholeCameraIntrinsic( o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault) global_poses [] # 存放每一帧的4x4全局位姿 for frame_id in range(len(global_poses)): rgbd load_rgbd(frame_id) volume.integrate(rgbd, intrinsic, np.linalg.inv(global_poses[frame_id])) mesh volume.extract_triangle_mesh() o3d.io.write_triangle_mesh(room_mesh.ply, mesh)这里最容易出错的是位姿方向。TSDF的integrate需要的是相机外参也就是从世界坐标变换到相机坐标的矩阵而你追踪得到的是从相机坐标变换到世界坐标的全局位姿两者正好互为逆矩阵所以要np.linalg.inv。很多人直接把全局位姿传进去出来的模型视角颠倒或完全错乱。如果模型出现在相机背后的奇怪位置第一反应就检查这个inv写了没有。extract_triangle_mesh是Marching Cubes算法把TSDF体素场中距离值为零的等值面抽成三角网格。实时管线不应该每帧都执行这一步否则CPU版本上会明显卡顿。正确的节奏是每帧只做odometry和integrate扫描结束后再一次性extract或者每隔几十帧提取一次用于可视化。3.4 网格提取的后处理从体素场到流形模型经过Marching Cubes提取的网格和你在三维软件里建的模型差得很远。最直接的问题是表面会有孤立的小碎片、退化三角形和非流形边直接导入游戏引擎会报错。这些可以通过Open3D的拓扑清理解决# 提取后立刻清理 mesh.remove_duplicated_vertices() mesh.remove_degenerate_triangles() mesh.remove_non_manifold_edges() # 按三角面连通域分组只保留最大主连通域 cluster_tris, cluster_n_tris, cluster_area mesh.cluster_connected_triangles() clusters np.asarray(cluster_n_tris) mesh mesh.remove_triangles_by_mask( np.asarray(cluster_tris) ! np.argmax(clusters)) mesh.remove_unreferenced_vertices() # 法线统一朝外 mesh.orient_normals_consistent_manifold(50)cluster_connected_triangles返回三个列表每个三角面的连通域标号、每个连通域的三角面数量、每个连通域的表面积。直接取三角面数量最大的连通域就能把扫描过程中产生的悬浮碎片、飞点产生的小孤岛丢出门外。orient_normals_consistent_manifold把相邻面法线逐步统一参数50是迭代步数相当于法线传播的范围。这一步不做的话模型渲染时光照是花的一半亮一半暗。4. 实时性瓶颈与参数调整让重建帧率从PPT到顺滑4.1 用下采样换帧率分辨率与金字塔策略实时重建的帧率瓶颈几乎总是在相机追踪这一环不在TSDF融合。640x480的深度图有30万个候选点每帧做一次RGBD odometry要迭代好几轮CPU上单帧耗时随分辨率线性上涨。我处理这个问题的标准做法是追踪前先把RGBD图像降到320x240用低分辨率跑odometry得到位姿初值融合时再用全分辨率深度图。因为TSDF融合的计算量与当前帧覆盖的体素数成正比和图像分辨率关系不大真正吃CPU的是odometry。small_color cv2.resize(color_image, (320, 240)) small_depth cv2.resize(depth_image, (320, 240)) source_small o3d.geometry.RGBDImage.create_from_color_and_depth( o3d.geometry.Image(small_color), o3d.geometry.Image(small_depth.astype(np.float32)), depth_trunc3.0, depth_scale1.0, convert_rgb_to_intensityFalse)把odometry的输入降到320x240CPU帧耗时通常能降到原来一半以下位姿精度的损失在室内场景基本无感。如果你还不确定瓶颈在哪动手前先加一段计时代码把odometry和integrate分开测import time t0 time.time() result o3d.pipelines.color_map.run_rgbd_odometry( source_small, target_small, o3d.pipelines.odometry.RGBDOdometryJacobianFromHybridTerm(), intrinsic) odom_ms (time.time() - t0) * 1000 t0 time.time() volume.integrate(rgbd_full, intrinsic, inv_pose) fuse_ms (time.time() - t0) * 1000 print(fodometry {odom_ms:.1f} ms, integrate {fuse_ms:.1f} ms)大多数情况下你会看到odometry的耗时是integrate的两倍以上。这也解释了为什么很多项目把分辨率降一半就能明显变顺真正贵的是像素匹配不是体素入库。4.2 四个必调参数体素长度、截断距离、配准距离与内点率RGBD重建的参数看似多真正决定成败的只有四个。下表是我在室内场景多次试出来的经验区间直接抄可以少走弯路参数作用经验值调错症状voxel_length体素边长室内0.01精细局部0.005太大糊小细节太小内存爆炸sdf_trunc截断距离voxel_length的3到4倍太大会膨胀太小表面漏洞max_correspondence_distance配准最近点搜索半径0.05~0.1米太小匹配不上太大飞点硬配inlier_ratio阈值位姿质量闸门大于0.5接受小于0.3拒帧低质量位姿混进来带歪模型分开说每个怎么调。voxel_length是最贵的参数从1cm压到0.5cm模型精细度提升是线性的内存和时间开销却是8倍关系所以先确认自己能不能扛住再动它。sdf_trunc的直觉是“表面要多薄才算边界”我通常跟着voxel_length走改体素就同步改截断距离四倍最稳三倍边缘更锐但容易出现细碎噪声。max_correspondence_distance是配准的搜索半径物理意义是“多大的初始误差我还能拉回来”。室内正常慢扫用0.05够用如果你是广角镜头、帧间运动大就要放宽到0.1。内点率阈值更像一个保险丝我见过不少人把0.1的低质量帧硬融进去几步之后模型彻底分层再回不来了。实时管线宁可少融几帧也不要融错一帧。4.3 CPU到GPU的实时路线选择如果你的目标是交互式实时显示CPU版Open3D管线大约在8到15fps够看但不够顺。升级路线有三个选择按性价比排序第一降体素精度到2cm配合320x240配准在CPU上能到20fps以上。代价是模型细节明显下降但如果你只是给移动机器人做导航地图这个精度正好。第二把TSDF融合放到CUDA。KinectFusion的核心就是GPU体素融合Open3D的新版本有GPU TSDF示例但编译配置比较折腾。我一般是在CPU验证参数没问题之后再迁移GPU千万不要在没调好参数的情况下去碰GPU调试成本会翻倍。第三换轻量框架。如果你想追求真正的30fps实机部署可以考虑基于CUDA的ElasticFusion类方案。但入门验证用Open3D足够先跑通流程比先追求帧率重要得多。另一个容易被忽略的实时性杀手是每帧往磁盘写图像磁盘IO会直接把管线拖垮调试时开日志实时跑时全关。5. 实时重建的5个常见问题排查从相机追踪丢失到内存爆掉5.1 相机追踪突然丢失画面抽帧错位现象扫着扫着屏幕上的模型突然不动过一两秒又跳一大截之后整个模型错位。原因最常见是扫太快帧间重叠不够ICP找不到足够的对应点其次是这一帧深度图大面积空洞。白墙、玻璃、黑色皮革沙发都是深度空洞的大宗来源。解决把扫描速度放慢到每秒钟大约移动20到30厘米保证相邻帧重叠率在70%以上。代码层面加一个质量闸inlier_ratio低于阈值就跳过融合if result.inlier_ratio 0.3: continue # 跳过这帧的integrate千万别把这种帧也喂进TSDF。错误位姿的破坏力远大于丢掉一帧的损失这个意识贯穿整个实时重建调试过程。5.2 模型糊成豆腐块锐利边缘全丢现象扫完以后桌面边缘、窗框轮廓都是一圈圆弧细节完全消失。原因两个参数背锅。voxel_length太大体素网格吞掉了低于体素精度的特征sdf_trunc太大把表面边缘的符号距离值削平了。解决把voxel_length从0.01往0.005压sdf_trunc同步改到0.02。内存如果撑不住把扫描范围限制在小区域先扫一张桌子而不是整个房间验证分辨率的影响后再扩大范围。这个坑很适合用来理解体素分辨率的概念模型的精细度上限就是体素边长体素是1cm任何小于1cm的特征都不可能被重建出来。5.3 模型上飘着大量杂物点现象模型主体还算完整但周围漂浮着一堆稀疏的杂点或细丝像毛边。原因深度图飞点没清干净。RealSense在物体边缘像素上经常给出一个既不属于前景也不属于背景的深度值这个值在数百毫秒内跳变还有深度值超过量程的部分如果没有被depth_trunc丢弃就会以噪声点的形式融进TSDF。解决在create_from_color_and_depth里把depth_trunc设为3.0或更小并开启RealSense空间滤波。最彻底的兜底是百分位截断# 每帧深度图做百分位截断去掉最远5%的深度值 valid depth_m[depth_m 0] p95 np.percentile(valid, 95) depth_m[depth_m p95] 0.0这里用百分位比固定阈值稳因为不同房间、不同距离分布下“远点”的标准不同。扫过走廊和扫过客厅同一个固定阈值效果完全不一样。5.4 绕场一周首尾不闭合累计漂移现象绕房间扫一圈第一面和最后一面墙之间出现一道明显的裂缝模型不闭合。原因这是帧间ICP累计误差的必然结果。每一帧有零点几个毫米的姿态误差几百帧后累计成几厘米的环闭合偏差。TSDF融合只做局部表面平均没有全局回环修正能力。解决慢扫减少帧间累积误差绕两圈让TSDF对同一表面有多个视角的平均如果实在要求高给管线引入回环检测或者让D435i自带的IMU参与视觉惯性里程计。IMU对旋转漂移的抑制效果特别明显但这一套就没有单纯的单目RGBD这么简单了属于后期加复杂度的路子。判断漂移来源有一个土办法扫完一圈后在首尾接缝处看模型偏移方向如果总是往同一个方向偏多半是IMU未标定或曝光突变如果是随机扰动多半是帧间重叠不足。5.5 内存一直涨到OOM现象扫到五六分钟后程序越来越卡内存占用肉眼可见地涨最后进程被杀。原因ScalableTSDFVolume的体素哈希会随着扫描覆盖空间扩大而增长。如果你扫一个30平米的空间、体素设0.5cm体素块数量会到千万级别。房间越扫越大的过程就是哈希表越撑越大的过程。解决把体素长度卡在1cm以上这是内存和精度的平衡点扫描结束先extract mesh释放volume再进入下一段重建。大空间场景可以分几段扫描每段用独立volume做局部重建之后再做拼接——不过这又绕回了位姿累积误差的问题。所以更重要的是意识室内实时重建适合单房间量级整层楼的连续大空间扫描不是这套方案的主场。6. 把网格导出去且能用的最后一公里封装修复与纹理检查扫描和网格提取完成之后直接导出的模型在一线使用中是不够的。最后一个常被我忽视、后来变成固定习惯的步骤是拿参考尺校验模型物理尺度。用一把卷尺量一下墙宽在MeshLab或Blender里量一下模型对应距离两者如果相差5%以上说明深度尺度或内参有问题这比任何肉眼判断都准。尺度没问题之后再处理纹理。TSDF融合如果开了RGB8网格顶点上带颜色但Open3D的write_triangle_mesh写OBJ时不会完整保留顶点色标准做法是导出PLY格式PLY支持每顶点RGBo3d.io.write_triangle_mesh(room_mesh.ply, mesh, write_asciiFalse)在Blender里用File导入PLY把显示设置改为顶点色就能看到带真实纹理的房间模型。如果项目方坚持要OBJ就得把顶点色烘焙成UV纹理。常见做法是在Blender里展开UV之后用顶点色作为颜色源烘焙到UV贴图不要在Open3D里自己写UV展开耗时且容易出错。坐标对齐也是最后一公里里容易被吐槽的点。扫描时相机倾斜导出的模型整个是歪的。我一般会在Blender里用地面平面做对齐选中模型底部的平面用Snap操作把游标放到地面再旋转模型让地面法线朝向Z轴。如果扫描数据里地面够平这一步可以批量处理。最后想说的是这个方向值不值得投入取决于你要什么。如果只是要一个能用、能看的室内模型CPU版Open3D加单目RGBD相机已经够了性价比很高如果你要的是30fps交互式重建体验那就要走GPU体素融合或换更重的框架复杂度立刻上一个台阶。我个人的习惯是先固定相机的自动曝光再扫描前3秒只追踪不融合让TSDF用高质量的初始帧建立表面朝向基准。这个小小的前置步骤帮我避开了很多追踪丢失的翻车也希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑