浅析CC中的点云配准为什么效果好于PCL?

公众号致力于分享点云处理,SLAM,三维视觉,高精地图相关的文章与技术,欢迎各位加入我们,一起交流一起进步,有兴趣的可联系微信:cloudpoint9527。本文来自点云PCL博主的分享,未经作者允许请勿转载,欢迎各位同学积极分享和交流。

前言

一些小伙伴说“感觉CloudCompare中的点云配准要比PCL中的配准效果要好”,这是为什么呢?这里先说一下我的大致的理解,从算法实现上,虽然CC也是使用了ICP算法,但是在ICP基础上进行了改进,让其更具有通用性,具体实现细节咱们一会一起看看代码,改进的ICP算法采用了一些特殊的策略或优化来适应一些特定的应用场景。而PCL库提供了多种点云配准算法的实现,包括ICP(Iterative Closest Point)、NDT(Normal Distributions Transform)等,这些算法在实现和性能上可能与CloudCompare的算法有所不同,因为CloudCompare对ICP算法进行了一些默认参数的调优,以适应一般情况下的配准需求。相比较而言PCL提供了很大的灵活性,用户可以对配准算法的参数进行精细的调整。这种自由度对于专业用户可能是一项优势,但也需要用户对算法有更深入的理解。

所以说所有的点云的算法一定是根据点云的属性,比如点云的有序性,以及点云的稀疏程度,噪声大小,在调用PCL的算法的时候一定要学会调整参数进行适配,所以在实际应用中,选择合适的配准工具和参数通常需要根据具体的应用场景和数据特点进行实验和调整。

CC中的配准函数

定义 ICPRegistrationTools 类的 Register 方法 

ICPRegistrationTools::RESULT_TYPE ICPRegistrationTools::Register( 
GenericIndexedCloudPersist* inputModelCloud, 
GenericIndexedMesh* inputModelMesh, 
GenericIndexedCloudPersist* inputDataCloud, 
const Parameters& params, 
ScaledTransformation& transform, 
double& finalRMS, 
unsigned& finalPointCount, 
GenericProgressCallback* progressCb /*=0*/)

该函数的步骤我做了一些的步骤说明:

(1)首先检查点云是否为空

(2)为了更好的配准,我们肯定希望使用与用户最初定义的点数相同的数量,但是如果数量比较大,肯定影响效率,所以如果输入数据点云太大,这里进行随机采样以提高速度,这里我们注意到dataSamplingLimit默认为50000,当大于这个最大点数,调用该函数进行随机采样,同时需要对点云的权重进行重采样

data.cloud = CloudSamplingTools::subsampleCloudRandomly(inputDataCloud, dataSamplingLimit);

(3)对输入点云进行八叉树构建 ,默认是8个层级

unsigned char meshDistOctreeLevel = 8; 
找到最适合与模型八叉树级别
meshDistOctreeLevel = dataOctree.findBestLevelForComparisonWithOctree(&modelOctree); }

(4)同样对模型也需要同样的处理,点云数量,权重,以及八叉树构建

(5)计算两个点云之间的初始距离(同时也计算 CPSet)

(6)判断是否应该移除最远的点,这里需要计算数据点云的距离分布参数 

NormalDistribution N; 
  N.computeParameters(data.cloud);
  // 获取均值和方差 
  N.getParameters(mu, sigma2); 
  // 计算最大距离 
  ScalarType maxDistance = static_cast(mu + 2.5 * sqrt(sigma2));

(7)然后保留距离不太高的点云,并对重叠点云的距离进行并行排序,计算每个 point 的权重值

(8)现在已经选择了将用于配准的点云,如果使用权重,必须计算加权 RMS均方根误差,如果权重无效直接跳过。

(9)ICP 的目标就是保证平方距离和的减小(不保证距离和的减小)

(10) 迭代点云配准的过程函数 RegistrationProcedure,点云配准停止的条件如下:

if ((params.convType == MAX_ERROR_CONVERGENCE && deltaRMS < params.minRMSDecrease) || (params.convType == MAX_ITER_CONVERGENCE && iteration >= params.nbMaxIterations))
 { result = ICP_APPLY_TRANSFO; 
    break; 
}

(11)过滤平移矩阵

FilterTransformation(currentTrans, params.transformationFilters, currentTrans);

(12)计算得到转化矩阵后应用到转化成新的点云,并计算其到模型的距离

一次迭代过程的函数RegistrationProcedure注释

// 配准过程,用于计算数据点云 P 和模型点云 X 之间的变换
bool RegistrationTools::RegistrationProcedure(GenericCloud* P, // data
                                              GenericCloud* X, // model
                                              ScaledTransformation& trans,
                                              bool adjustScale/*=false*/,
                                              ScalarField* coupleWeights/*=0*/,
                                              PointCoordinateType aPrioriScale/*=1.0f*/)
{
    // 结果的变换矩阵(R 在初始化时无效,T 为 (0,0,0),s 为 1)
    trans.R.invalidate();
    trans.T = CCVector3(0, 0, 0);
    trans.s = PC_ONE;
    // 检查数据点云和模型点云是否有效,大小是否相等,且点数不少于3
    if (P == nullptr || X == nullptr || P->size() != X->size() || P->size() < 3)
        return false;
    // 计算质心
    CCVector3 Gp = coupleWeights ? GeometricalAnalysisTools::ComputeWeightedGravityCenter(P, coupleWeights) : GeometricalAnalysisTools::ComputeGravityCenter(P);
    CCVector3 Gx = coupleWeights ? GeometricalAnalysisTools::ComputeWeightedGravityCenter(X, coupleWeights) : GeometricalAnalysisTools::ComputeGravityCenter(X);
    // 特殊情况:只有3个点
    if (P->size() == 3)
    {
        // 计算第一组法线
        P->placeIteratorAtBeginning();
        const CCVector3* Ap = P->getNextPoint();
        const CCVector3* Bp = P->getNextPoint();
        const CCVector3* Cp = P->getNextPoint();
        CCVector3 Np(0, 0, 1);
        {
            Np = (*Bp - *Ap).cross(*Cp - *Ap);
            double norm = Np.normd();
            if (norm < ZERO_TOLERANCE)
                return false;
            Np /= static_cast(norm);
        }


        // 计算第二组法线
        X->placeIteratorAtBeginning();
        const CCVector3* Ax = X->getNextPoint();
        const CCVector3* Bx = X->getNextPoint();
        const CCVector3* Cx = X->getNextPoint();
        CCVector3 Nx(0, 0, 1);
        {
            Nx = (*Bx - *Ax).cross(*Cx - *Ax);
            double norm = Nx.normd();
            if (norm < ZERO_TOLERANCE)
                return false;
            Nx /= static_cast(norm);
        }


        // 现在旋转简单地从 Nx 到 Np,以 Gx 为中心
        CCVector3 a = Np.cross(Nx);
        if (a.norm() < ZERO_TOLERANCE)
        {
            trans.R = CCLib::SquareMatrix(3);
            trans.R.toIdentity();
            if (Np.dot(Nx) < 0)
            {
                trans.R.scale(-PC_ONE);
            }
        }
        else
        {
            double cos_t = Np.dot(Nx);
            assert(cos_t > -1.0 && cos_t < 1.0); // 
            double cos_half_t = sqrt((1 + cos_t) / 2);
            double sin_half_t = sqrt((1 - cos_t) / 2);
            double q[4] = {cos_half_t, a.x * sin_half_t, a.y * sin_half_t, a.z * sin_half_t};
            // 归一化四元数
            double qnorm = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
            assert(qnorm >= ZERO_TOLERANCE);
            qnorm = sqrt(qnorm);
            q[0] /= qnorm;
            q[1] /= qnorm;
            q[2] /= qnorm;
            q[3] /= qnorm;
            trans.R.initFromQuaternion(q);
        }


        if (adjustScale)
        {
            double sumNormP = (*Bp - *Ap).norm() + (*Cp - *Bp).norm() + (*Ap - *Cp).norm();
            sumNormP *= aPrioriScale;
            if (sumNormP < ZERO_TOLERANCE)
                return false;
            double sumNormX = (*Bx - *Ax).norm() + (*Cx - *Bx).norm() + (*Ax - *Cx).norm();
            trans.s = static_cast(sumNormX / sumNormP);
        }
        // 推导第一个平移
        trans.T = Gx - (trans.R * Gp) * (aPrioriScale * trans.s);
        // 现在需要在(X)平面上找到旋转
        {
            CCVector3 App = trans.apply(*Ap);
            CCVector3 Bpp = trans.apply(*Bp);
            CCVector3 Cpp = trans.apply(*Cp);


            double C = 0;
            double S = 0;
            CCVector3 Ssum(0, 0, 0);
            CCVector3 rx;
            CCVector3 rp;


            rx = *Ax - Gx;
            rp = App - Gx;
            C = rx.dot(rp);
            Ssum = rx.cross(rp);


            rx = *Bx - Gx;
            rp = Bpp - Gx;
            C += rx.dot(rp);
            Ssum += rx.cross(rp);


            rx = *Cx - Gx;
            rp = Cpp - Gx;
            C += rx.dot(rp);
            Ssum += rx.cross(rp);


            S = Ssum.dot(Nx);
            double Q = sqrt(S * S + C * C);
            if (Q < ZERO_TOLERANCE)
                return false;


            PointCoordinateType sin_t = static_cast(S / Q);
            PointCoordinateType cos_t = static_cast(C / Q);
            PointCoordinateType inv_cos_t = 1 - cos_t;


            const PointCoordinateType& l1 = Nx.x;
            const PointCoordinateType& l2 = Nx.y;
            const PointCoordinateType& l3 = Nx.z;


            PointCoordinateType l1_inv_cos_t = l1 * inv_cos_t;
            PointCoordinateType l3_inv_cos_t = l3 * inv_cos_t;


            SquareMatrix R(3);
            // 第1列
            R.m_values[0][0] = cos_t + l1 * l1_inv_cos_t;
            R.m_values[0][1] = l2 * l1_inv_cos_t + l3 * sin_t;
            R.m_values[0][2] = l3 * l1_inv_cos_t - l2 * sin_t;


            // 第2列
            R.m_values[1][0] = l2 * l1_inv_cos_t - l3 * sin_t;
            R.m_values[1][1] = cos_t + l2 * l2 * inv_cos_t;
            R.m_values[1][2] = l2 * l3_inv_cos_t + l1 * sin_t;
            // 第3列
            R.m_values[2][0] = l3 * l1_inv_cos_t + l2 * sin_t;
            R.m_values[2][1] = l2 * l3_inv_cos_t - l1 * sin_t;
            R.m_values[2][2] = cos_t + l3 * l3_inv_cos_t;


            trans.R = R * trans.R;
            trans.T = Gx - (trans.R * Gp) * (aPrioriScale * trans.s); // 更新 T
        }
    }
    else
    {
        CCVector3 bbMin;
        CCVector3 bbMax;
        X->getBoundingBox(bbMin, bbMax);
        // 如果数据点云等效于单个点(例如,在ICP过程中两个点云相距很远),我们尝试让两个点云靠近
        CCVector3 diag = bbMax - bbMin;
        if (std::abs(diag.x) + std::abs(diag.y) + std::abs(diag.z) < ZERO_TOLERANCE)
        {
            trans.T = Gx - Gp * aPrioriScale;
            return true;
        }


        // 交叉协方差矩阵,参见Besl92中的方程#24(但如果有权重,则包含权重)
        SquareMatrixd Sigma_px = (coupleWeights ? GeometricalAnalysisTools::ComputeWeightedCrossCovarianceMatrix(P, X, Gp, Gx, coupleWeights)
                                              : GeometricalAnalysisTools::ComputeCrossCovarianceMatrix(P, X, Gp, Gx));
        if (!Sigma_px.isValid())
            return false;
            
        // 转置 sigma_px
        SquareMatrixd Sigma_px_t = Sigma_px.transposed();
        SquareMatrixd Aij = Sigma_px - Sigma_px_t;
        double trace = Sigma_px.trace(); // 即 sigma_px 的对角元素之和
        SquareMatrixd traceI3(3); // 创建带有特征值等于 trace 的 I 矩阵
        traceI3.m_values[0][0] = trace;
        traceI3.m_values[1][1] = trace;
        traceI3.m_values[2][2] = trace;
        SquareMatrixd bottomMat = Sigma_px + Sigma_px_t - traceI3;
// 计算交叉协方差矩阵的下半部分
SquareMatrixd bottomMat = Sigma_px + Sigma_px_t - traceI3;
// 构建配准矩阵(参见 ICP 算法)
SquareMatrixd QSigma(4); // #25 in the paper (besl)
QSigma.m_values[0][0] = trace;
QSigma.m_values[0][1] = QSigma.m_values[1][0] = Aij.m_values[1][2];
QSigma.m_values[0][2] = QSigma.m_values[2][0] = Aij.m_values[2][0];
QSigma.m_values[0][3] = QSigma.m_values[3][0] = Aij.m_values[0][1];
QSigma.m_values[1][1] = bottomMat.m_values[0][0];
QSigma.m_values[1][2] = bottomMat.m_values[0][1];
QSigma.m_values[1][3] = bottomMat.m_values[0][2];
QSigma.m_values[2][1] = bottomMat.m_values[1][0];
QSigma.m_values[2][2] = bottomMat.m_values[1][1];
QSigma.m_values[2][3] = bottomMat.m_values[1][2];
QSigma.m_values[3][1] = bottomMat.m_values[2][0];
QSigma.m_values[3][2] = bottomMat.m_values[2][1];
QSigma.m_values[3][3] = bottomMat.m_values[2][2];
// 计算 QSigma 的特征值和特征向量
CCLib::SquareMatrixd eigVectors;
std::vectoreigValues;
if (!Jacobi::ComputeEigenValuesAndVectors(QSigma, eigVectors, eigValues, false))
{
    // 失败
    return false;
}


// 如Besl所说,最佳旋转对应于与最大特征值相关联的特征向量
double qR[4];
double maxEigValue = 0;
Jacobi::GetMaxEigenValueAndVector(eigVectors, eigValues, maxEigValue, qR);
// 这些特征值和特征向量对应于一个四元数 --> 我们获取相应的矩阵
trans.R.initFromQuaternion(qR);


if (adjustScale)
{
    // 两个累加器
    double acc_num = 0.0;
    double acc_denom = 0.0;
    // 现在推导尺度(参见 "Point Set Registration with Integrated Scale Estimation", Zinsser et. al, PRIP 2005)
    X->placeIteratorAtBeginning();
    P->placeIteratorAtBeginning();
    unsigned count = X->size();
    assert(P->size() == count);
    for (unsigned i = 0; i < count; ++i)
    {
        // 'a' 指的是数据 'A'(移动的)= P
        // 'b' 指的是模型 'B'(不动的)= X
        CCVector3 a_tilde = trans.R * (*(P->getNextPoint()) - Gp); // a_tilde_i = R * (a_i - a_mean)
        CCVector3 b_tilde = (*(X->getNextPoint()) - Gx);            // b_tilde_j = (b_j - b_mean)
        acc_num += b_tilde.dot(a_tilde);
        acc_denom += a_tilde.dot(a_tilde);
    }
    // DGM: acc_denom 不能为0,因为我们已经检查过边界框不是单个点!
    assert(acc_denom > 0.0);
    trans.s = static_cast(std::abs(acc_num / acc_denom));
}
// 推导平移
trans.T = Gx - (trans.R * Gp) * (aPrioriScale * trans.s); // #26 in besl paper, modified with the scale as in jschmidt
}


return true;
}

具体公式可以参考文章:https://graphics.stanford.edu/courses/cs164-09-spring/Handouts/paper_icp.pdf

f7ed8a046f348da4c4ff799c7d0fc306.png

其他模块链接

Point Set Registration with Integrated Scale Estimation,Znisser et al, PRIP 2005 for the scale estimation.

https://robotik.informatik.uni-wuerzburg.de/telematics/download/3dim2007/node2.html

https://en.wikipedia.org/wiki/Iterative_closest_point

https://graphics.stanford.edu/courses/cs164-09-spring/Handouts/paper_icp.pdf

Robust Point Set Registration Using Gaussian Mixture Models", B. Jian and B.C. Vemuri, PAMI 2011

以上内容如有错误请留言评论,欢迎指正交流。如有侵权,请联系删除

3a4cee749c34c604d7333a3756f08daa.png

扫描二维码

                   关注我们

让我们一起分享一起学习吧!期待有想法,乐于分享的小伙伴加入知识星球注入爱分享的新鲜活力。分享的主题包含但不限于三维视觉,点云,高精地图,自动驾驶,以及机器人等相关的领域。

分享与合作:微信“cloudpoint9527”(需要按要求备注) 联系邮箱:dianyunpcl@163.com,欢迎企业来联系公众号展开合作。

点一下“在看”你会更好看耶

d6371f99140fca2500c668bc34d939bf.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/132863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最新大麦订单生成器 大麦订单图一键生成

1、8.6全新版 本次更新了四种订单模板生成 多模板自由切换 2、在软件中输入生成的信息&#xff0c;这里输入的是商品信息&#xff0c;选择生成的商品图片&#xff0c;最后生成即可 新版大麦订单生成 四种模板图样式展示 这个样式图就是在大麦生成完的一个订单截图&#xff…

大数据毕业设计选题推荐-生产大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

内网安全-基础设施构建-cobaltstrike远控工具beacon使用

kali在CS文件目录下&#xff0c;打开终端,运行命令&#xff1a; /teamserver 192.168.77.128 123456 在windows中双击bat文件&#xff1a; 填写图下信息&#xff1a; 双击运行&#xff0c;CS上线 自查方法&#xff1a;1、kali与物理机可互通 2、物理机与windows10跳板…

[SHCTF]web方向wp

[SHCTF]web方向wp [WEEK1]babyRCE题目源码wp [WEEK1]1zzphp题目源码wp [WEEK1]ez_serialize题目源码wp [WEEK1]登录就给flag题目wp [WEEK1]生成你的邀请函吧~题目源码wp [WEEK1]飞机大战题目wp [WEEK1]ezphp题目源码wp [WEEK2]no_wake_up题目源码wp [WEEK2]MD5的事就拜托了题目…

人工智能在汽车业应用的五项挑战

在汽车行业扩展人工智能应用时需要注意的问题 随着更多企业投资于汽车人工智能 (AI) 解决方案&#xff0c;我们也愈加接近大规模部署 5 级全自动驾驶汽车。汽车行业的组织如果希望加入这场 AI 带来的颠覆性变革&#xff0c;就应该已提前考虑如何成功和大规模地将人工智能部署到…

C语言每日一题(28) 反转链表

牛客网 BM1 反转链表 题目描述 描述 给定一个单链表的头结点pHead(该头节点是有值的&#xff0c;比如在下图&#xff0c;它的val是1)&#xff0c;长度为n&#xff0c;反转该链表后&#xff0c;返回新链表的表头。 数据范围&#xff1a; 0≤n≤1000 要求&#xff1a;空间复…

LCD1602设计(1)

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

stm32超声波测距不准的解决方法(STM32 delay_us()产生1us)

首先要说明一下原理&#xff1a;使用stm32无法准确产生1us的时间&#xff0c;但是超声波测距一定要依赖时间&#xff0c;时间不准&#xff0c;距离一定不准&#xff0c;这是要肯定的&#xff0c;但是在不准确的情况下&#xff0c;要测量一个比较准确的时间&#xff0c;那么只能…

JavaScript从入门到精通系列第三十三篇:详解正则表达式语法(二)

文章目录 一&#xff1a;正则表达式 1&#xff1a; 检查一个字符串中是否有. 2&#xff1a;第二种关键表达 3&#xff1a;第三种关键表达 ​编辑4&#xff1a;第四种关键表达 5&#xff1a;第五种关键表达 6&#xff1a;第六种关键表达 二&#xff1a;核心表达二 1&am…

在程序中链接静态库

现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。 add_library(库名称 STATIC 源文件1 [源文件2] ...) link_libraries(<static lib> [<static lib>...]) 参数1&#xff1a;指定出要链接的静态库的名字 可以是全…

Postgres的级数生成函数generate_series应用

Postgres的级数生成函数generate_series应用 引用&#xff1a;http://postgres.cn/docs/12/functions-srf.html 函数文档 函数 参数类型 返回类型 描述 generate_series(start, stop) int、bigint或者numeric setof int、setof bigint或者setof numeric&#xff08;与参数类型相…

公司注册股东选择几个人合适?

创业初期很多创业者都会选择有注册有限责任公司&#xff0c;有限责任由五十个以下的股东出资设立&#xff0c;每个股东以其所认缴的出资额为限对公司承担有限责任。那么问题来了股东人数选择几个最合适呢&#xff0c;下面上海注册公司网&#xff08;www.91kaiye.cn&#xff09;…

AMD64内存属性详解

本文参考文档为AMD64 Architecture Programmer’s Manual Volume 2: System Programming&#xff0c;版本号3.41&#xff0c;这不是对原英文文档的翻译&#xff0c;但是所有内容的排版都是根据原手册的排版来的&#xff0c;如有与官方文档冲突的内容&#xff0c;以官方文档为准…

【C++破局】C++内存管理之new与deleted剖析

​作者主页 &#x1f4da;lovewold少个r博客主页 ⚠️本文重点&#xff1a;c内存管理部分知识点梳理 &#x1f449;【C-C入门系列专栏】&#xff1a;博客文章专栏传送门 &#x1f604;每日一言&#xff1a;花有重开日&#xff0c;人无再少年&#xff01; 目录 C/C的内存分配机…

向量的点积和外积

参考&#xff1a;https://www.cnblogs.com/gxcdream/p/7597865.html 一、向量的内积&#xff08;点乘&#xff09; 定义&#xff1a; 两个向量a与b的内积为 ab |a||b|cos∠(a, b)&#xff0c;特别地&#xff0c;0a a0 0&#xff1b;若a&#xff0c;b是非零向量&#xff0c;…

测量直线模组时如何降低误差?

直线模组属于高精度传动零部件&#xff0c;是机械行业中不可或缺的零部件之一&#xff0c;其具有高精度、速度快、使用寿命长等特点&#xff1b;如果直线模组的精度受损&#xff0c;则不能达到预期的使用效果&#xff0c;那么我们测量时应该如何减少误差&#xff0c;确保直线模…

【数据结构】二叉树经典例题---<你真的掌握二叉树了吗?>(第一弹)

一、已知一颗二叉树如下图&#xff0c;试求&#xff1a; (1)该二叉树前序、中序和后序遍历的结果。 (2)该二叉树是否为满二叉树&#xff1f;是否为完全二叉树&#xff1f; (3)将它转换成对应的树或森林。 (4)这颗二叉树的深度为多少? (5)试对该二叉树进行前序线索化。 (6)试对…

Vue3路由配置

目录 ​编辑 一&#xff1a;前言 二&#xff1a;配置路由 1、安装路由 2、创建各文件 1&#xff09;views 下的 index.vue 文件 2&#xff09;router 下的 index.ts 3&#xff09;App.vue 文件修改 4&#xff09;main.ts 文件修改 3、一些会遇到的报错 1&#xff09;…

【计算机毕业设计】基于微信小程序实现校园综合服务平台-芒果校园(源码+路演ppt)

项目场景&#xff1a; 这个是之前在准备比赛做的项目&#xff0c;本来拿来去参加的&#xff0c;后面因为一些原因&#xff0c;这个项目被搁置了&#xff0c;今天打开源码 好在还在&#xff0c;但当我打开的时候&#xff0c;接口发生了一些变化&#xff0c;例如 getLocation();…

Doris:多源数据目录(Multi-Catalog)

目录 1.基本概念 2.基本操作 2.1 查看 Catalog 2.2 新增 Catalog 2.3 切换 Catalog 2.4 删除 Catalog 3.元数据更新 3.1手动刷新 3.2定时刷新 3.3自动刷新 4.JDBC Catalog 4.1 上传mysql驱动包 4.2 创建mysql catalog 4.3. 读取mysql数据 1.基本概念 …