VINS-Mono/Fusion与OpenCV去畸变对比
VINS中没有直接使用opencv的去畸变函数,而是自己编写了迭代函数完成去畸变操作,主要是为了加快去畸变计算速度
本文对二者的结果精度和耗时进行了对比
VINS-Mono/Fusion与OpenCV去畸变对比
- 1 去畸变原理
- 2 代码实现
- 2.1 OpenCV去畸变
- 2.2 VINS去畸变
- 3 二者对比
1 去畸变原理
opencv去畸变操作由cv::undistortPoints实现
VINS去畸变由PinholeCamera::liftProjective实现(以针孔相机为例)
二者均采用了迭代求解,通过多次迭代逼近真值。其中cv::undistortPoints方法中默认迭代5次,并计算每次重投影误差是否小于阈值,VINS去畸变方法只设置了迭代8次。
二者均输入像素坐标,输出归一化坐标。
2 代码实现
2.1 OpenCV去畸变
opencv去畸变操作由cv::undistortPoints实现,代码在opencv-3.4.13/modules/imgproc/src
undistortPoints首先处理了输入参数,主要实现部分调用cvUndistortPointsInternal
void undistortPoints( InputArray _src, OutputArray _dst,InputArray _cameraMatrix, InputArray _distCoeffs,InputArray _Rmat, InputArray _Pmat, TermCriteria criteria)
void undistortPoints( InputArray _src, OutputArray _dst,InputArray _cameraMatrix,InputArray _distCoeffs,InputArray _Rmat,InputArray _Pmat,TermCriteria criteria)
{Mat src = _src.getMat(), cameraMatrix = _cameraMatrix.getMat();Mat distCoeffs = _distCoeffs.getMat(), R = _Rmat.getMat(), P = _Pmat.getMat();int npoints = src.checkVector(2), depth = src.depth();if (npoints < 0)src = src.t();npoints = src.checkVector(2);CV_Assert(npoints >= 0 && src.isContinuous() && (depth == CV_32F || depth == CV_64F));if (src.cols == 2)src = src.reshape(2);_dst.create(npoints, 1, CV_MAKETYPE(depth, 2), -1, true);Mat dst = _dst.getMat();CvMat _csrc = cvMat(src), _cdst = cvMat(dst), _ccameraMatrix = cvMat(cameraMatrix);CvMat matR, matP, _cdistCoeffs, *pR=0, *pP=0, *pD=0;if( !R.empty() )pR = &(matR = cvMat(R));if( !P.empty() )pP = &(matP = cvMat(P));if( !distCoeffs.empty() )pD = &(_cdistCoeffs = cvMat(distCoeffs));cvUndistortPointsInternal(&_csrc, &_cdst, &_ccameraMatrix, pD, pR, pP, criteria);
}
static void cvUndistortPointsInternal( const CvMat* _src, CvMat* _dst, const CvMat* _cameraMatrix, const CvMat* _distCoeffs, const CvMat* matR, const CvMat* matP, cv::TermCriteria criteria)
static void cvUndistortPointsInternal( const CvMat* _src, CvMat* _dst, const CvMat* _cameraMatrix,const CvMat* _distCoeffs,const CvMat* matR, const CvMat* matP, cv::TermCriteria criteria)
{CV_Assert(criteria.isValid());double A[3][3], RR[3][3], k[14]={0,0,0,0,0,0,0,0,0,0,0,0,0,0};CvMat matA=cvMat(3, 3, CV_64F, A), _Dk;CvMat _RR=cvMat(3, 3, CV_64F, RR);cv::Matx33d invMatTilt = cv::Matx33d::eye();cv::Matx33d matTilt = cv::Matx33d::eye();CV_Assert( CV_IS_MAT(_src) && CV_IS_MAT(_dst) &&(_src->rows == 1 || _src->cols == 1) &&(_dst->rows == 1 || _dst->cols == 1) &&_src->cols + _src->rows - 1 == _dst->rows + _dst->cols - 1 &&(CV_MAT_TYPE(_src->type) == CV_32FC2 || CV_MAT_TYPE(_src->type) == CV_64FC2) &&(CV_MAT_TYPE(_dst->type) == CV_32FC2 || CV_MAT_TYPE(_dst->type) == CV_64FC2));CV_Assert( CV_IS_MAT(_cameraMatrix) &&_cameraMatrix->rows == 3 && _cameraMatrix->cols == 3 );cvConvert( _cameraMatrix, &matA );if( _distCoeffs ){CV_Assert( CV_IS_MAT(_distCoeffs) &&(_distCoeffs->rows == 1 || _distCoeffs->cols == 1) &&(_distCoeffs->rows*_distCoeffs->cols == 4 ||_distCoeffs->rows*_distCoeffs->cols == 5 ||_distCoeffs->rows*_distCoeffs->cols == 8 ||_distCoeffs->rows*_distCoeffs->cols == 12 ||_distCoeffs->rows*_distCoeffs->cols == 14));_Dk = cvMat( _distCoeffs->rows, _distCoeffs->cols,CV_MAKETYPE(CV_64F,CV_MAT_CN(_distCoeffs->type)), k);cvConvert( _distCoeffs, &_Dk );if (k[12] != 0 || k[13] != 0){cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], NULL, NULL, NULL, &invMatTilt);cv::detail::computeTiltProjectionMatrix<double>(k[12], k[13], &matTilt, NULL, NULL);}}if( matR ){CV_Assert( CV_IS_MAT(matR) && matR->rows == 3 && matR->cols == 3 );cvConvert( matR, &_RR );}elsecvSetIdentity(&_RR);if( matP ){double PP[3][3];CvMat _P3x3, _PP=cvMat(3, 3, CV_64F, PP);CV_Assert( CV_IS_MAT(matP) && matP->rows == 3 && (matP->cols == 3 || matP->cols == 4));cvConvert( cvGetCols(matP, &_P3x3, 0, 3), &_PP );cvMatMul( &_PP, &_RR, &_RR );}const CvPoint2D32f* srcf = (const CvPoint2D32f*)_src->data.ptr;const CvPoint2D64f* srcd = (const CvPoint2D64f*)_src->data.ptr;CvPoint2D32f* dstf = (CvPoint2D32f*)_dst->data.ptr;CvPoint2D64f* dstd = (CvPoint2D64f*)_dst->data.ptr;int stype = CV_MAT_TYPE(_src->type);int dtype = CV_MAT_TYPE(_dst->type);int sstep = _src->rows == 1 ? 1 : _src->step/CV_ELEM_SIZE(stype);int dstep = _dst->rows == 1 ? 1 : _dst->step/CV_ELEM_SIZE(dtype);double fx = A[0][0];double fy = A[1][1];double ifx = 1./fx;double ify = 1./fy;double cx = A[0][2];double cy = A[1][2];int n = _src->rows + _src->cols - 1;for( int i = 0; i < n; i++ ){double x, y, x0 = 0, y0 = 0, u, v;if( stype == CV_32FC2 ){x = srcf[i*sstep].x;y = srcf[i*sstep].y;}else{x = srcd[i*sstep].x;y = srcd[i*sstep].y;}u = x; v = y;x = (x - cx)*ifx;y = (y - cy)*ify;if( _distCoeffs ) {// compensate tilt distortioncv::Vec3d vecUntilt = invMatTilt * cv::Vec3d(x, y, 1);double invProj = vecUntilt(2) ? 1./vecUntilt(2) : 1;x0 = x = invProj * vecUntilt(0);y0 = y = invProj * vecUntilt(1);double error = std::numeric_limits<double>::max();// compensate distortion iterativelyfor( int j = 0; ; j++ ){//在这里判断if ((criteria.type & cv::TermCriteria::COUNT) && j >= criteria.maxCount)break;if ((criteria.type & cv::TermCriteria::EPS) && error < criteria.epsilon)break;double r2 = x*x + y*y;double icdist = (1 + ((k[7]*r2 + k[6])*r2 + k[5])*r2)/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2);if (icdist < 0) // test: undistortPoints.regression_14583{x = (u - cx)*ifx;y = (v - cy)*ify;break;}double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x)+ k[8]*r2+k[9]*r2*r2;double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y+ k[10]*r2+k[11]*r2*r2;x = (x0 - deltaX)*icdist;y = (y0 - deltaY)*icdist;if(criteria.type & cv::TermCriteria::EPS){double r4, r6, a1, a2, a3, cdist, icdist2;double xd, yd, xd0, yd0;cv::Vec3d vecTilt;r2 = x*x + y*y;r4 = r2*r2;r6 = r4*r2;a1 = 2*x*y;a2 = r2 + 2*x*x;a3 = r2 + 2*y*y;cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4;vecTilt = matTilt*cv::Vec3d(xd0, yd0, 1);invProj = vecTilt(2) ? 1./vecTilt(2) : 1;xd = invProj * vecTilt(0);yd = invProj * vecTilt(1);double x_proj = xd*fx + cx;double y_proj = yd*fy + cy;error = sqrt( pow(x_proj - u, 2) + pow(y_proj - v, 2) );}}}double xx = RR[0][0]*x + RR[0][1]*y + RR[0][2];double yy = RR[1][0]*x + RR[1][1]*y + RR[1][2];double ww = 1./(RR[2][0]*x + RR[2][1]*y + RR[2][2]);x = xx*ww;y = yy*ww;if( dtype == CV_32FC2 ){dstf[i*dstep].x = (float)x;dstf[i*dstep].y = (float)y;}else{dstd[i*dstep].x = x;dstd[i*dstep].y = y;}}
}
2.2 VINS去畸变
void
PinholeCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const
{double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u;double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d;//double lambda;// Lift points to normalised planemx_d = m_inv_K11 * p(0) + m_inv_K13;my_d = m_inv_K22 * p(1) + m_inv_K23;if (m_noDistortion){mx_u = mx_d;my_u = my_d;}else{if (0){double k1 = mParameters.k1();double k2 = mParameters.k2();double p1 = mParameters.p1();double p2 = mParameters.p2();// Apply inverse distortion model// proposed by Heikkilamx2_d = mx_d*mx_d;my2_d = my_d*my_d;mxy_d = mx_d*my_d;rho2_d = mx2_d+my2_d;rho4_d = rho2_d*rho2_d;radDist_d = k1*rho2_d+k2*rho4_d;Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d;Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d;inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d);mx_u = mx_d - inv_denom_d*Dx_d;my_u = my_d - inv_denom_d*Dy_d;}else{// Recursive distortion modelint n = 8;Eigen::Vector2d d_u;distortion(Eigen::Vector2d(mx_d, my_d), d_u);// Approximate valuemx_u = mx_d - d_u(0);my_u = my_d - d_u(1);for (int i = 1; i < n; ++i){distortion(Eigen::Vector2d(mx_u, my_u), d_u);mx_u = mx_d - d_u(0);my_u = my_d - d_u(1);}}}// Obtain a projective rayP << mx_u, my_u, 1.0;
}
3 二者对比
在相机坐标系下随机生成了 20 个观测点,并将其归算到归一化坐标系下作为真值。
#include <iostream>
#include <vector>
#include <random>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>
#include <chrono>#include "Camera.h"using namespace std;int main()
{// 随机数生成 20 个 三维特征点int featureNums=20;default_random_engine generator;vector<cv::Point2f> pts_truth; //归一化真值vector<cv::Point2f> uv_pts; //像素坐标vector<cv::Point2f> cv_un_pts, vins_un_pts; //归一化坐标for(int i = 0; i < featureNums; ++i){uniform_real_distribution<double> xy_rand(-4, 4.0);uniform_real_distribution<double> z_rand(8., 10.);double tx = xy_rand(generator);double ty = xy_rand(generator);double tz = z_rand(generator);Eigen::Vector2d p(tx/tz, ty/tz);Eigen::Vector2d p_distorted;distortion(p, p_distorted); //归一化坐标畸变p_distorted+=p;pts_truth.push_back(cv::Point2f(p(0), p(1)));cv::Point2f uv(fx*p_distorted(0)+cx, fy*p_distorted(1)+cy); //投影到像素坐标uv_pts.push_back(uv);}//OpenCV去畸变,输入像素坐标,输出归一化坐标chrono::steady_clock::time_point cv_t1 = chrono::steady_clock::now();cv::undistortPoints(uv_pts, cv_un_pts, K, distCoeffs);chrono::steady_clock::time_point cv_t2 = chrono::steady_clock::now();double cv_time = chrono::duration_cast<chrono::duration<double,milli>>(cv_t2-cv_t1).count();cout<<"OpenCV"<<endl;cout<<"used time: "<<cv_time/cv_un_pts.size()<<"ms"<<endl;cout<<"pixel error: "<<GetResidual(cv_un_pts, pts_truth)<<endl;//VINS去畸变chrono::steady_clock::time_point vins_t1 = chrono::steady_clock::now();liftProjective(uv_pts, vins_un_pts);chrono::steady_clock::time_point vins_t2 = chrono::steady_clock::now();double vins_time = chrono::duration_cast<chrono::duration<double, milli>>(vins_t2-vins_t1).count();cout<<"VINS"<<endl;cout<<"used time: "<<vins_time/vins_un_pts.size()<<"ms"<<endl;cout<<"pixel error: "<<GetResidual(vins_un_pts, pts_truth)<<endl;return 0;
}
输出结果
给出了每个观测点的平均去畸变耗时和像素坐标系下的重投影误差。
VINS所采用的去畸变算法耗时更少,重投影误差平均值更小,opencv方法与其相差一个数量级。
相关文章:

VINS-Mono/Fusion与OpenCV去畸变对比
VINS中没有直接使用opencv的去畸变函数,而是自己编写了迭代函数完成去畸变操作,主要是为了加快去畸变计算速度 本文对二者的结果精度和耗时进行了对比 VINS-Mono/Fusion与OpenCV去畸变对比1 去畸变原理2 代码实现2.1 OpenCV去畸变2.2 VINS去畸变3 二者对…...

jmx prometheus引起的一次cpu飙高
用户接入了jmx agent进行prometheus监控后,在某个时间点出现cpu飙高 排查思路: 1、top,找到java进程ID 2、top -Hp 进程ID,找到java进程下占用高CPU的线程ID 3、jstack 进程ID,找到那个高CPU的线程ID的堆栈。 4、分析堆…...
Android 虚拟 A/B 详解(六) SnapshotManager 之状态数据
本文为洛奇看世界(guyongqiangx)原创,转载请注明出处。 原文链接:https://blog.csdn.net/guyongqiangx/article/details/129094203 Android 虚拟 A/B 分区《AAndroid 虚拟 A/B 分区》系列,更新中,文章列表: Android 虚拟分区详解(一) 参考资料推荐Android 虚拟分区详解(二…...

Python快速入门系列之一:Python对象
Python对象1. 列表(list)2. 元组(tuple)3. 字典(dict)4. 集合(set)5. 字符串(string)6. BIF (Built-in Function)7. 列表、集合以及字…...

【博客626】不同类型的ARP报文作用以及ARP老化机制
不同类型的ARP报文作用以及ARP老化机制 1、ARP协议及报文 2、不同类型的ARP报文作用 3、ARP工作原理 4、ARP老化机制 5、Linux ARP老化机制 ARP状态机: 在上图中,我们看到只有arp缓存项的reachable状态对于外发包是可用的,对于stale状态的…...

nacos discovery和config
微服务和nacos版本都在2.x及之后。1、discovery用于服务注册,将想要注册的服务注册到nacos中,被naocs发现。pom引入的依赖是:yml配置文件中:2、config用于获取nacos配置管理->配置列表下配置文件中的内容pom引入的依赖是&#…...
【算法数据结构体系篇class06】:堆、大根堆、小根堆、优先队列
一、堆结构1)堆结构就是用数组实现的完全二叉树结构2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆4)堆结构的heapInsert与heapify操作5)堆结构的增大ad…...
试题 算法提高 最小字符串
资源限制内存限制:256.0MB C/C时间限制:2.0s Java时间限制:6.0s Python时间限制:10.0s问题描述给定一些字符串(只包含小写字母),要求将他们串起来构成一个字典序最小的字符串。输入格式第一行T,表示有T组数据。接下来T…...

已解决ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘
已解决from radiomics import featureextractor导包,抛出ImportError: cannot import name ‘featureextractor‘ from ‘radiomics‘异常的正确解决方法,亲测有效!!! 文章目录报错问题报错翻译报错原因解决方法联系博…...

乡村振兴研究:全网最全指标农村经济面板数据(2000-2021年)
数据来源:国家统计局 时间跨度:2000-2021年 区域范围:全国31省 指标说明: 部分样例数据: 行政区划代码地区年份经度纬度乡镇数(个)乡数(个)镇数(个)村民委员会数(个)乡村户数(万户)乡村人口(万人)乡村从业人员(万人…...

C语言中用rand()函数产生一随机数
在C语言中如何产生一个随机数呢?用rand()函数。 rand()函数在头文件:#include <stdio.h>中,函数原型:int rand(void);。rand()会返回一个范围在0到RAND_MAX(32767)之间的随机数(整数&…...
关于系统架构
1.系统架构分类: C/S架构 B/S架构 2.C/S架构 Client / Server(客户端 / 服务器) 特点:需要安装特定的客户端软件。 C/S架构的系统优点和缺点: 优点: 1)速度快(软件中数据大部分都是集成到客户端当中,很少量的数据从服…...
LeetCode 1237. 找出给定方程的正整数解
原题链接 难度:middle\color{orange}{middle}middle 2023/2/18 每日一题 题目描述 给你一个函数 f(x,y)f(x, y)f(x,y) 和一个目标结果 zzz,函数公式未知,请你计算方程 f(x,y)zf(x,y) zf(x,y)z 所有可能的正整数 数对 xxx 和 yyy。满足条件…...

【ArcGIS Pro二次开发】(5):UI管理_自定义控件的位置
新增的自定义控件一般放在默认的【加载项】选项卡下,但是根据需求,我们可能需要将控件放在新的自定义选项卡下,在自定义选项卡添加系统自带的控件,将自定义的按钮等控件放在右键菜单栏里以方便使用,等等。 下面就以一…...

学习OpenGL图形2D/3D编程
环境:WindowsVisual Studio 2019最流行的几个库:GLUT,SDL,SFML和GLFWGLFWGLAD库查看显卡OPENGL支持情况VS2019glfwgladopenGL3.3顶点着色器片段着色器VAO-VBO-(EBO)->渲染VAO-VBO-EBO->texture纹理矩阵matrix对图形transfor…...
2023美赛思路 | A题时间序列预测任务的模型选择总结
2023美赛思路 | A题时间序列预测任务的模型选择总结 目录 2023美赛思路 | A题时间序列预测任务的模型选择总结基本介绍数据描述任务介绍时序模型基本介绍 这道题分析植被就行,主要涉及不同植被间的相互作用,有竞争有相互促进,我查了下“植物科学数据中心”和“中国迁地保护植…...

PHP教材管理系统设计(源代码+毕业论文)
【P003】PHP教材管理系统设计(源代码论文) 设计方案 本系统采用B/S结构,所有的程序及数据都放在服务器上,终端在取得相应的权限后使用Web页面浏览,录入,修改等功能。在语言方面使用PHP语言,在…...

nps内网穿透工具
一、准备一台有公网ip的服务器 https://github.com/ehang-io/nps/releases 在这个地址下载服务端的安装包,centos的下载这个 上传到服务器上。 二、然后解压,安装,启动 [rootadministrator ~]# tar xzvf linux_amd64_server.tar.gz [roo…...
webpack打包时的热模块替代配置以及source-map
1.HMR 在devServer当中添加hot:true 热模块化功能 含义:当其中有一个文件发生变化的时候,那么就会被重新打包一次,极大的提高了构建速度 A.样式文件:可以使用HMR功能,因为在style-loader当中实现了 B.js文件:默认不能使用HMR功能…...

Seata架构篇 - TCC模式
TCC 模式 概述 TCC 是分布式事务中的两阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel)。Try:对业务资源的检查并…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...