基于图像的深度学习+MVS三维重建全流程 可远程部署,可定制 点云pcl,c++,matlab开发,基于图像三维重建,点云算法开发 只需要提供摄的图像,即可生成完整的三维模型(大小场景均可)

上周去爬了个浙西的小众山,拍了快200张照片,本来想整个3D模型给朋友当纪念,结果找了一圈要么要几十万的专业扫描设备,要么在线工具抠抠搜搜只能弄个指甲盖大的小挂件,折腾了两天才捣鼓出这套能用的基于图像的深度学习+MVS三维重建流程——说白了就是只需要拍一堆照片,不管是手机拍的日常照还是无人机航拍的大场景,都能生成完整的3D模型,而且能远程部署、按需定制,用的工具都是C++、Matlab配合PCL点云库,全是能落地的东西,不是实验室里飘着的论文方案。

基于图像的深度学习+MVS三维重建全流程 可远程部署,可定制 点云pcl,c++,matlab开发,基于图像三维重建,点云算法开发 只需要提供摄的图像,即可生成完整的三维模型(大小场景均可)

先给大伙捋一遍完整流程:拍照片→批量预处理→深度学习特征匹配→MVS生成深度图→点云优化→网格重建→纹理映射,每一步都能改参数定制,下面边唠边插点实际用的代码。

第一步:先把照片捋顺(Matlab批量预处理)

你手机拍的照片肯定乱七八糟的:有带镜头畸变的,有分辨率不一样的,还有糊掉的。我写了个Matlab脚本批量处理,把这些破事都干了:

% 批量处理拍摄的图像,去畸变+统一分辨率
function batch_preprocess_img(src_dir, dst_dir, camera_param)
    if ~exist(dst_dir, 'dir'), mkdir(dst_dir); end
    img_list = dir(fullfile(src_dir, '*.jpg'));
    
    for i = 1:length(img_list)
        img_path = fullfile(src_dir, img_list(i).name);
        img = imread(img_path);
        % 用提前标定好的相机内参去畸变,手机镜头自带畸变,不去的话匹配会乱
        undist_img = undistortImage(img, camera_param.Intrinsics);
        % 统一缩放到1920x1080,大场景的话缩更小点也行,避免内存炸了
        resized_img = imresize(undist_img, [1080, 1920]);
        imwrite(resized_img, fullfile(dst_dir, sprintf('proc_%04d.jpg', i)));
    end
end

这段代码没啥花活,就是帮你把照片都整成一个模子——要是你不知道相机内参,用Matlab自带的相机标定工具箱拍个棋盘格就行,5分钟就能搞定,比网上那些在线标定工具靠谱多了。

第二步:聪明的特征匹配(C+++ONNX深度学习)

传统的SIFT匹配不仅慢,还容易把山上的石头和旁边的树认错,我用了LoFTR这个预训练模型,比SIFT聪明一百倍,而且能转成ONNX格式在C++里跑,不用装Python环境,方便打包部署。

// C++ 加载LoFTR ONNX模型做特征匹配
#include <onnxruntime_cxx_api.h>
#include <opencv2/opencv.hpp>

int main() {
    // 初始化ONNX运行环境,线程开8个适合服务器跑
    Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "loftr_matcher");
    Ort::SessionOptions session_options;
    session_options.SetIntraOpNumThreads(8);
    auto session = Ort::Session(env, "loftr_outdoor.onnx", session_options);

    // 读取两张预处理后的图像,转成灰度图加快推理
    cv::Mat img1 = cv::imread("proc_0001.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("proc_0002.jpg", cv::IMREAD_GRAYSCALE);
    cv::resize(img1, img1, {640, 480});
    cv::resize(img2, img2, {640, 480});

    // 这里省略把图像转成ONNX需要的张量代码,说白了就是归一化+转成通道在前的格式
    // 跑推理拿到匹配的特征点,然后用RANSAC过滤掉错误的匹配对
    // ...(省掉的代码都是些杂活,核心就是跑模型拿匹配结果)

    return 0;
}

为啥要用这个?我之前用COLMAP自带的匹配器跑景区照片,跑了3小时还一堆错配,换了LoFTR之后只花了40分钟,而且错误匹配少了90%,说白了就是省时间还省心。

第三步:点云整容手术(PCL点云优化)

MVS生成的原始点云简直没法看:有一堆噪点(比如风吹动的树叶留下的杂点),还有太密的地方电脑根本跑不动,这时候就轮到PCL出场了,我写了段C++代码做去噪、下采样和法线估计:

// PCL 点云预处理:去噪+下采样+法线估计
#include <pcl/io/pcd_io.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>

int main() {
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
    // 加载MVS生成的原始点云文件
    if (pcl::io::loadPCDFile<pcl::PointXYZRGB>("mvs_output.pcd", *cloud) == -1) {
        PCL_ERROR("Couldn't read the point cloud file!\n");
        return (-1);
    }

    // 第一步:去掉离群点,比如那些孤零零的噪点
    pcl::StatisticalOutlierRemoval<pcl::PointXYZRGB> sor;
    sor.setInputCloud(cloud);
    sor.setMeanK(50); // 每个点看周围50个邻居
    sor.setStddevMulThresh(1.0); // 偏离标准差1倍以上的就删掉
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZRGB>);
    sor.filter(*cloud_filtered);

    // 第二步:体素下采样,把太密的点云弄稀疏,不然大场景跑不动
    pcl::VoxelGrid<pcl::PointXYZRGB> vg;
    vg.setInputCloud(cloud_filtered);
    vg.setLeafSize(0.01f, 0.01f, 0.01f); // 1cm的体素,小场景用0.005,大场景用0.05都行
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_downsampled(new pcl::PointCloud<pcl::PointXYZRGB>);
    vg.filter(*cloud_downsampled);

    // 第三步:估计法线,后面生成网格需要这个来贴纹理
    pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> ne;
    ne.setInputCloud(cloud_downsampled);
    pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>());
    ne.setSearchMethod(tree);
    pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
    ne.setKSearch(10);
    ne.compute(*cloud_normals);

    // 保存处理好的干净点云
    pcl::io::savePCDFileASCII("clean_cloud.pcd", *cloud_downsampled);
    return 0;
}

这段代码就是给点云做美容:先把脸上的痘痘(噪点)挤掉,再把脸瘦下来(下采样),最后给脸做个发型(法线),不然后面生成的网格就是黑乎乎的一团,啥也看不清。

第四步:远程部署和定制

这套流程我打包成了Docker镜像,不管是阿里云还是腾讯云,一键就能部署,甚至可以写成REST API,别人不用装任何环境,只要上传照片就能拿到3D模型——我用FastAPI写了个demo接口,大概长这样:

from fastapi import FastAPI, UploadFile, File
import subprocess
import os

app = FastAPI()

@app.post("/reconstruct")
async def reconstruct(files: list[UploadFile] = File(...)):
    save_dir = "upload_imgs"
    os.makedirs(save_dir, exist_ok=True)
    for i, file in enumerate(files):
        with open(f"{save_dir}/img_{i}.jpg", "wb") as f:
            f.write(await file.read())
    
    # 调用我们的C++重建脚本,支持传参数自定义精度
    subprocess.run(["bash", "run_reconstruct.sh", save_dir, "--leaf-size=0.02"])
    return {"download_url": "https://your-server.com/output/model.obj"}

定制化的空间也很大:要是你做工业零件检测,就把滤波参数调严一点,去掉更多杂点;要是你做无人机航拍的大场景,就加上分块重建的逻辑,不然几百张航拍图直接跑内存直接炸;甚至可以把生成的模型直接导入Unity或者Unreal里做可视化,这些都能改。

最后唠两句

我上周用这套流程跑了那座山的200张照片,服务器上跑了大概20分钟,生成的模型细节还挺全的,路边的石头、树上的枝桠都能看清,不管是小到钥匙扣,大到整个景区,都能搞定。要是你也有照片想要生成3D模型,或者想要定制这套流程,随时来唠~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐