19f2096432a20.png

1. 项目速览:把实时三维重建搬到嵌入式设备上 🚀

本项目面向室内静态场景实时三维重建,核心思路是用 Zora P1 嵌入式开发板与 Astra Pro RGB-D 相机组成实时采集端,再结合 InfiniTAM v2 的体素融合重建框架,实现静态室内环境的实时建模。项目还设计了 WiFi 数据传输模块,让嵌入式端负责采集,计算能力更强的上位机负责实时重建,达到“前端轻装上阵,后端认真干活”的效果。

项目文件夹中提供了三份代码:网络传输客户端、网络传输服务端,以及不使用网络、仅依靠 Zora P1 完成实时重建的版本。也就是说,这不是单一路线,而是“板端自力更生”和“板端采集 + 上位机重建”两种路线都试过了,工程味很足。

项目要素 内容
来源 第一届 3D 视觉创新应用竞赛参赛选手路荣丰
相机型号 Astra Pro
支持设备 Zora P1 开发板
核心框架 InfiniTAM v2、OpenNI、OpenGL/GLUT、OpenCV、Socket、CUDA/CPU
主要输出 实时渲染预览、深度重建结果、STL 网格模型
源代码链接 3DReconstruction_Indoor-static-scene

2. 这套系统到底怎么搭?一前一后,分工明确 🧩

19f209fcd02de.png

项目报告与代码共同指向两种部署方案。第一种方案直接在 Zora P1 上采集并重建,优点是独立性强,缺点是算力和内存压力大;第二种方案通过 WiFi 把深度数据发给上位机处理,牺牲一点网络传输复杂度,换来更好的重建效率与显示体验。

方案 数据在哪里采集 重建在哪里完成 特点
板端直跑 Zora P1 + Astra Pro Zora P1 简洁独立,但速度约 2 帧/秒
WiFi 分体式 Zora P1 + Astra Pro 上位机 更适合实时展示与较复杂计算
离线/文件模式 本地文件 上位机 便于调试 InfiniTAM 流程

对应到源码目录,可以这样理解:

目录 角色 说明
InfiniTAM_v2 (wifi_client) 采集客户端 OpenNI 读取 Astra Pro 深度帧,并通过 socket 发送
InfiniTAM_v2(wifi_server) 重建服务端 接收深度帧,送入 InfiniTAM 重建并显示/导出
InfiniTAM_v2(only_Zora) 单机板端版本 不走网络,在嵌入式端直接采集与重建
OpenNI-Linux-Arm64-2.3.0.65 相机 SDK 面向 Arm64 的 OpenNI 运行环境

3. 技术路线:InfiniTAM 负责“长出”三维模型 🧠

InfiniTAM 的核心是体素化的实时三维重建。简单说,它把空间划分成许多小体素,把每一帧深度图融合进一个 TSDF 体素场里;随着相机移动,系统不断估计当前位姿、融合新深度、再通过 raycasting 渲染出当前模型。听起来像魔法,其实是严肃的几何计算,只是它确实有点像“用深度图喂出一个 3D 世界”。

阶段 输入 处理 输出
采集 Astra Pro 深度帧 OpenNI 读取 640×480 深度图 原始深度图
跟踪 当前深度图、上一帧模型 ICP/深度跟踪估计相机位姿 新相机姿态
融合 深度图、相机姿态 TSDF 体素融合 稠密场景模型
渲染 体素场 Raycasting 生成预览图 实时重建画面
导出 体素场 Marching Cubes 生成网格 mesh.stl

核心循环在 ITMMainEngine::ProcessFrame() 中非常清晰:先把原始 RGB-D 数据构造成 view,然后跟踪、融合、渲染,三步一气呵成。

// 节选自 ITMMainEngine.cpp:InfiniTAM 单帧处理主流程
void ITMMainEngine::ProcessFrame(
    ITMUChar4Image *rgbImage,
    ITMShortImage *rawDepthImage,
    ITMIMUMeasurement *imuMeasurement)
{
    if (imuMeasurement == NULL)
        viewBuilder->UpdateView(&view, rgbImage, rawDepthImage,
                                settings->useBilateralFilter,
                                settings->modelSensorNoise);
    else
        viewBuilder->UpdateView(&view, rgbImage, rawDepthImage,
                                settings->useBilateralFilter,
                                imuMeasurement);

    if (!mainProcessingActive) return;

    trackingController->Track(trackingState, view);

    if (fusionActive)
        denseMapper->ProcessFrame(view, trackingState,
                                  scene, renderState_live);

    trackingController->Prepare(trackingState, view, renderState_live);
}

4. 嵌入式采集端:Astra Pro 负责看,Zora P1 负责搬 📷

19f20a2f8c212.png

在 wifi_client 版本中,OpenNI 引擎被改造为带 socket 的采集模块。它启动 Astra Pro 的深度流,读取深度帧,将深度数据复制到 InfiniTAM 的 rawDepthImage,同时把同一帧转成 OpenCV Mat 并发给服务端。

// 节选自 OpenNIEngine.cpp:读取 Astra Pro 深度帧并通过 socket 发送
if (depthAvailable)
{
    const openni::DepthPixel* depthImagePix =
        (const openni::DepthPixel*)data->depthFrame.getData();

    memcpy(depth, depthImagePix,
           rawDepthImage->dataSize * sizeof(short));

    cv::Mat image(480, 640, CV_16SC1, depth);

    if (ni_socket.transmit(image) > 0)
    {
        // depth frame transmitted
    }
    else
    {
        printf("transmit failed");
    }
}

报告中也说明,当前官方 OpenNI 模块尚未成功读取 RGB 信息,因此本项目阶段主要使用深度图完成重建,彩色纹理渲染没有完全实现。这一点很关键:它不是“彩色模型没写”,而是相机数据链路里的 RGB 获取还未打通。

5. WiFi 传输:把深度图拆包送到上位机 📡

19f20a4ae9bc9.png

项目的网络模块没有搞复杂协议,而是用了 TCP socket 传输固定尺寸的深度图。深度帧是 640×480、CV_16SC1,被拆成 2 个包发送。每个包里包含一段深度数组和一个 flag,服务端根据 flag 判断帧是否收完整。

传输字段 数值/类型 作用
图像宽度 640 与 Astra Pro 深度帧一致
图像高度 480 与 Astra Pro 深度帧一致
图像类型 CV_16SC1 单通道 16 位深度
分包数 PACKAGE_NUM = 2 降低单次发送数据块大小
端口 6666 服务端监听端口

客户端连接上位机的 IP 与端口,然后逐包发送深度数据。

// 节选自 SocketMatTransmissionClient.cpp:客户端建立 TCP 连接
int SocketMatTransmissionClient::socketConnect(const char* IP, int PORT)
{
    if ((sockClient = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return -1;

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);

    if (inet_pton(AF_INET, IP, &servaddr.sin_addr) <= 0)
        return -1;

    if (connect(sockClient,
                (struct sockaddr*)&servaddr,
                sizeof(servaddr)) < 0)
        return -1;
}
// 节选自 SocketMatTransmissionClient.cpp:深度图拆成两包发送
for (int k = 0; k < PACKAGE_NUM; k++)
{
    int num1 = IMG_HEIGHT / PACKAGE_NUM * k;

    for (int i = 0; i < IMG_HEIGHT / PACKAGE_NUM; i++)
    {
        short *ucdata = image.ptr<short>(i + num1);
        for (int j = 0; j < IMG_WIDTH; j++)
            data.buf[i * IMG_WIDTH + j] = ucdata[j];
    }

    data.flag = (k == PACKAGE_NUM - 1) ? 2 : 1;
    send(sockClient, (char *)(&data), sizeof(data), 0);
}

服务端负责监听、接收并重组图像。这个部分很像快递分拣:包裹到了不算完,还得按顺序拼回一整张深度图。

// 节选自 SocketMatTransmissionServer.cpp:服务端监听并接收客户端连接
int SocketMatTransmissionServer::socketConnect(int PORT)
{
    int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(server_sockfd,
         (struct sockaddr *)&server_sockaddr,
         sizeof(server_sockaddr));

    listen(server_sockfd, 5);
    sockConn = accept(server_sockfd,
                      (struct sockaddr*)&client_addr,
                      &length);
}
// 节选自 SocketMatTransmissionServer.cpp:接收两包并恢复为深度图
cv::Mat img(IMG_HEIGHT, IMG_WIDTH, CV_16SC1, cv::Scalar(0));

for (int i = 0; i < PACKAGE_NUM; i++)
{
    int pos = 0;
    while (pos < sizeof(recvBuf))
    {
        int len0 = recv(sockConn,
                        (char *)(&data) + pos,
                        sizeof(recvBuf) - pos,
                        0);
        pos += len0;
    }

    int num1 = IMG_HEIGHT / PACKAGE_NUM * i;
    for (int j = 0; j < IMG_HEIGHT / PACKAGE_NUM; j++)
    {
        short *ucdata = img.ptr<short>(j + num1);
        for (int k = 0; k < IMG_WIDTH; k++)
            ucdata[k] = data.buf[j * IMG_WIDTH + k];
    }
}

6. 上位机重建端:收到深度图后,立刻喂给 InfiniTAM 🖥️

在 wifi_server 版本中,服务端程序先启动 socket 监听,再创建图像源。这里的 ImageFileReader 已不再单纯读本地文件,而是被改造成“从网络接收深度图,再变成 InfiniTAM 输入”的桥梁。

// 节选自 InfiniTAM.cpp:服务端启动 socket,再创建重建输入源
SocketMatTransmissionServer socketMat;

if (socketMat.socketConnect(6666) < 0)
{
    return 0;
}

CreateDefaultImageSource(imageSource, imuSource,
                         socketMat,
                         arg1, arg2, arg3, arg4);

ImageSourceEngine.cpp 中的 loadIntoCache() 接收图像,把 OpenCV Mat 中的深度值写入 ITMShortImage,随后 InfiniTAM 主循环就能像读取普通深度帧一样处理它。

// 节选自 ImageSourceEngine.cpp:网络深度图写入 InfiniTAM 缓存
cv::Mat image_receive(480, 640, CV_16SC1);

if (engine_socketMat.receive(image_receive) > 0)
{
    Vector2i newSize(640, 480);
    cached_depth->ChangeDims(newSize);

    short *ucdata = image_receive.ptr<short>(0);
    for (int j = 0; j < 480 * 640; ++j)
    {
        cached_depth->GetData(MEMORYDEVICE_CPU)[j] = ucdata[j];
    }
}

7. 为什么是静态场景?因为相机动,场景最好别跟着闹 🪑

InfiniTAM 这类深度融合方法默认场景相对静止。系统通过连续深度帧估计相机姿态,并把新的深度观测融合到同一个场景模型里。如果场景里的人、椅子、箱子大范围移动,系统可能会把“移动物体的不同位置”都融合进去,最后模型看起来就会有重影。室内静态场景,就是这套算法比较舒适的工作区。

条件 对系统的影响 建议
场景静止 深度融合稳定 推荐使用
相机缓慢移动 ICP 跟踪更容易收敛 推荐使用
相机快速晃动 位姿估计容易失败 尽量避免
大面积动态物体 模型可能出现重影 采集时保持场景稳定
弱几何区域 深度跟踪约束不足 增加扫描角度

8. 体素融合:不是存一堆点,而是在空间里“填体素” 🧱

19f20a7dee893.png

InfiniTAM 的 ITMDenseMapper::ProcessFrame() 展示了典型的体素融合流程:先根据当前深度图分配可见体素块,再把深度观测融合进场景。如果开启 swapping,还可以在 CPU/GPU 之间交换体素数据,用于更大场景。

// 节选自 ITMDenseMapper.cpp:分配体素块并融合深度帧
void ITMDenseMapper<TVoxel,TIndex>::ProcessFrame(
    const ITMView *view,
    const ITMTrackingState *trackingState,
    ITMScene<TVoxel,TIndex> *scene,
    ITMRenderState *renderState)
{
    sceneRecoEngine->AllocateSceneFromDepth(
        scene, view, trackingState, renderState);

    sceneRecoEngine->IntegrateIntoScene(
        scene, view, trackingState, renderState);

    if (swappingEngine != NULL)
    {
        swappingEngine->IntegrateGlobalIntoLocal(scene, renderState);
        swappingEngine->SaveToGlobalMemory(scene, renderState);
    }
}

系统默认设置中,场景体素参数和跟踪器也很有信息量:trackerType = TRACKER_ICP,说明位姿估计主要基于深度 ICP;voxelSize = 0.005f,代表体素分辨率为 5 mm 量级。分辨率越细,模型越精致,但内存和速度也会“当场加班”。

// 节选自 ITMLibSettings.cpp:场景参数与跟踪器设置
ITMLibSettings::ITMLibSettings(void)
    : sceneParams(0.02f, 100, 0.005f, 0.2f, 3.0f, false)
{
#ifndef COMPILE_WITHOUT_CUDA
    deviceType = DEVICE_CUDA;
#else
    deviceType = DEVICE_CPU;
#endif

    useSwapping = false;
    useApproximateRaycast = false;
    useBilateralFilter = false;

    trackerType = TRACKER_ICP;
}

9. 实时显示与网格导出:能看,也能存 🧊

19f20ab25e276.png

UI 层使用 OpenGL/GLUT 显示重建结果、原始深度图和渲染视图。按键 w 可以导出 mesh.stl,项目目录中也确实存在生成后的 STL 文件,其中 wifi_server 版本的 mesh.stl 约 75 MB,说明上位机方案已经产出较完整的网格结果。

结果文件 位置 大小
mesh.stl InfiniTAM_v2(wifi_server)/cmake-build-relwithdebinfo 约 75 MB
mesh.stl InfiniTAM_v2(only_Zora)/InfiniTAM/cmake-build-relwithdebinfo 约 39 MB
mesh.stl InfiniTAM_v2 (wifi_client)/InfiniTAM/cmake-build-relwithdebinfo 约 39 MB
// 节选自 UIEngine.cpp:按 w 导出 STL 网格
case 'w':
    printf("saving mesh to disk ...");
    uiEngine->SaveSceneToMesh("mesh.stl");
    printf(" donen");
    break;

10. 工程环境:别小看这些依赖,少一个都可能不开心 🧰

报告中列出的部署环境包括上位机 Ubuntu 18.04、嵌入式端 armbin、CMake 3.2 及以上、OpenNI、OpenGL、libpng、librealsense 等。源码的 CMake 配置也显示,项目针对不同端使用了不同 OpenNI 路径:客户端面向 Arm64 OpenNI,服务端面向 x64 OpenNI。

环境项 用途
Ubuntu 18.04 上位机运行环境
armbin 嵌入式端系统环境
OpenNI Astra Pro 深度流读取
OpenGL/GLUT 实时可视化窗口
CMake 工程构建
CUDA/CPU InfiniTAM 计算后端
Socket WiFi 数据传输
# 节选自 CMakeLists.txt:客户端面向 Arm64 OpenNI
set(INC_DIR
    /home/orbbec/Desktop/InfiniTAM-infinitam_v2/
    OpenNI-Linux-Arm64-2.3.0.65/include)

link_libraries(libOpenNI2.so)
link_libraries(liborbbec.so)
link_libraries(libOniFile.so)

11. 技术亮点:它不是炫技,是把链路跑通了 ✨

亮点 解读
嵌入式采集 Zora P1 与 Astra Pro 组成轻量前端
WiFi 分体架构 采集与重建解耦,便于上位机承担重计算
InfiniTAM 移植 将成熟体素融合框架用于室内静态重建
深度 ICP 跟踪 依靠深度几何估计相机位姿
STL 导出 重建结果可保存为网格模型
性能测量 多处加入时间统计,定位耗时瓶颈

12. 当前局限:它会建模,但还没练成完全体 🛠️

项目报告和源码都显示,项目已经实现了基本实时重建,但仍有几个明确边界。最主要的是:嵌入式端内存较小,速度约 2 帧/秒;RGB 读取与彩色纹理未完全实现;没有使用 IMU,因此更适合相机缓慢移动、场景静止的重建条件。

局限 表现 后续方向
嵌入式算力有限 板端速度约 2 帧/秒 优化分辨率、体素尺度、CUDA/CPU 后端
RGB 未完全打通 重建结果缺少彩色纹理 完善 OpenNI RGB 读取与配准
未接入 IMU 快速移动时鲁棒性不足 加入 IMU 辅助位姿预测
交互未完善 板端按钮/控制未完成 增加嵌入式端控制界面
网络传输简单 固定分包,缺少完整校验 增加帧号、校验和、重传策略

13. 后续优化路线:让小板子跑得更稳,让模型长得更像 🧭

19f20ad6b12c2.png
优化方向 具体做法 收益
网络协议增强 加入帧号、时间戳、校验和 降低丢包错帧风险
RGB-D 对齐 同步读取 RGB 与 Depth,并做外参配准 支持彩色重建
IMU 融合 将 IMU 预测作为跟踪初值 提升快速移动稳定性
动态分辨率 根据网络与算力调整深度图尺寸 平衡帧率与精度
体素参数调优 调整体素大小、截断距离和最大权重 优化速度与模型细节
板端交互 增加按钮或轻量控制界面 方便独立运行

14. 总结:这是一次“让嵌入式设备参与三维重建”的扎实尝试 🎯

这个项目的价值不只在于完成了一个室内静态场景重建 demo,更在于把 RGB-D 相机、Zora P1 嵌入式采集、WiFi 深度传输、InfiniTAM 实时融合、STL 网格导出串成了一条完整工程链路。它既验证了板端直接重建的可行性,也展示了“嵌入式采集 + 上位机重建”这种更实用的协同方案。

如果说传统 PC 端重建像是在实验室里慢慢打磨模型,那么这个项目更像是把三维重建带出工位,让一块小板子也参与到空间建模中。它还不完美,但方向很清楚:让实时三维重建更轻、更灵活,也更接近未来的移动式空间感知应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注