当前位置: 首页 > news >正文

ORB-SLAM2 ----- LocalMapping::SearchInNeighbors()

文章目录

  • 一、函数意义
  • 二、函数讲解
  • 三、函数代码
  • 四、本函数使用的匹配方法ORBmatcher::Fuse()
    • 1. 函数讲解
    • 2. 函数代码
  • 四、总结

一、函数意义

本函数是用于地图点融合的函数,前面的函数生成了新的地图点,但这些地图点可能在前面的关键帧中已经生成过了,这时候就需要判断是否重复生成,如果重复生成就只保留最观测观测次数最多的那一个关键帧的地图点(地图点融合)。

二、函数讲解

想要融合地图点们就要找出那些关键帧,与当前关键帧有共视关系。本函数使用的方法是,找出与当前关键帧公式关系最好的10个关键帧(单目为20个)作为一级共视关键帧,然后找出一级共视关键帧共视关系最好的5个关键帧作为二级共视关键帧。接下来的融合将从一级和二级共视关键帧进行。本函数为了保证融合的完全性和正确性,采用了正反两次融合,第一次将当前关键帧的点投影到共视关键帧中,寻求匹配和融合。第二次将共视关键帧中的地图点投影到当前关键帧中,寻求匹配和融合。这两次特征匹配使用的方法都是,半径搜索法,相对于词袋匹配的方法,此方法是精匹配。

三、函数代码

// 检查并融合当前关键帧与相邻帧(两级相邻)重复的地图点
void LocalMapping::SearchInNeighbors()
{// Retrieve neighbor keyframes// 单目情况要20个邻接关键帧,双目或者RGBD则要10个int nn = 10;if(mbMonocular)nn=20;// 获取与该关键帧连接的前N个最强共视关键帧(已按权值排序)const vector<KeyFrame*> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);// 存储一级相邻关键帧及其二级相邻关键帧vector<KeyFrame*> vpTargetKFs;// 遍历这些共视的关键帧for(vector<KeyFrame*>::const_iterator vit=vpNeighKFs.begin(), vend=vpNeighKFs.end(); vit!=vend; vit++){// 获取关键帧的指针KeyFrame* pKFi = *vit;// 排除坏点和已经融合的点if(pKFi->isBad() || pKFi->mnFuseTargetForKF == mpCurrentKeyFrame->mnId)continue;// 获取一级相邻关键帧vpTargetKFs.push_back(pKFi);// 获取索引pKFi->mnFuseTargetForKF = mpCurrentKeyFrame->mnId;// Extend to some second neighbors// 以一级相邻关键帧的共视关系最好的5个相邻关键帧 作为二级相邻关键帧const vector<KeyFrame*> vpSecondNeighKFs = pKFi->GetBestCovisibilityKeyFrames(5);// 同样的操作,获取二级相邻关键帧for(vector<KeyFrame*>::const_iterator vit2=vpSecondNeighKFs.begin(), vend2=vpSecondNeighKFs.end(); vit2!=vend2; vit2++){KeyFrame* pKFi2 = *vit2;if(pKFi2->isBad() || pKFi2->mnFuseTargetForKF==mpCurrentKeyFrame->mnId || pKFi2->mnId==mpCurrentKeyFrame->mnId)continue;vpTargetKFs.push_back(pKFi2);}}// Search matches by projection from current KF in target KFs// 将当前帧的地图点分别投影到两级相邻关键帧,寻找匹配点对应的地图点进行融合,称为正向投影融合// 初始化特征匹配器ORBmatcher matcher;// 获取当前帧匹配的地图点vector<MapPoint*> vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();// 遍历一级和二级的相邻关键帧,融合地图点for(vector<KeyFrame*>::iterator vit=vpTargetKFs.begin(), vend=vpTargetKFs.end(); vit!=vend; vit++){// 获取关键帧的指针KeyFrame* pKFi = *vit;// 将地图点投影到关键帧中进行匹配和融合;融合策略如下// 1.如果地图点能匹配关键帧的特征点,并且该点有对应的地图点,那么选择观测数目多的替换两个地图点// 2.如果地图点能匹配关键帧的特征点,并且该点没有对应的地图点,那么为该点添加该投影地图点// 注意这个时候对地图点融合的操作是立即生效的 matcher.Fuse(pKFi,vpMapPointMatches);}// Search matches by projection from target KFs in current KF// 反向匹配vector<MapPoint*> vpFuseCandidates;vpFuseCandidates.reserve(vpTargetKFs.size()*vpMapPointMatches.size());// 遍历一级和二级的相邻关键帧,// 查找当前关键帧中的候选地图点,查看它们是否与相邻关键帧中已存在的地图点匹配。// 如果匹配,则将它们加入到待融合的候选列表 vpFuseCandidates 中for(vector<KeyFrame*>::iterator vitKF=vpTargetKFs.begin(), vendKF=vpTargetKFs.end(); vitKF!=vendKF; vitKF++){// 获取指针KeyFrame* pKFi = *vitKF;// 获取匹配的地图点vector<MapPoint*> vpMapPointsKFi = pKFi->GetMapPointMatches();// 遍历这些地图点for(vector<MapPoint*>::iterator vitMP=vpMapPointsKFi.begin(), vendMP=vpMapPointsKFi.end(); vitMP!=vendMP; vitMP++){   // 获取指针MapPoint* pMP = *vitMP;// 排除不合格的点if(!pMP)continue;if(pMP->isBad() || pMP->mnFuseCandidateForKF == mpCurrentKeyFrame->mnId)continue;// 获取索引pMP->mnFuseCandidateForKF = mpCurrentKeyFrame->mnId;// 将它们加入到待融合的候选列表 vpFuseCandidates 中vpFuseCandidates.push_back(pMP);}}// 进一步融合matcher.Fuse(mpCurrentKeyFrame,vpFuseCandidates);// Update points// 获取本关键帧的地图点vpMapPointMatches = mpCurrentKeyFrame->GetMapPointMatches();// 遍历这些地图点for(size_t i=0, iend=vpMapPointMatches.size(); i<iend; i++){MapPoint* pMP=vpMapPointMatches[i];if(pMP){if(!pMP->isBad()){// 计算该地图点最具代表性的描述子pMP->ComputeDistinctiveDescriptors();// 获取其深度和法向量pMP->UpdateNormalAndDepth();}}}// Update connections in covisibility graph// 更新连接关系mpCurrentKeyFrame->UpdateConnections();
}

四、本函数使用的匹配方法ORBmatcher::Fuse()

1. 函数讲解

ORBmatcher::Fuse()函数是这个函数中调用的特征匹配的方法,本函数是一个有返回值的函数,返回值为匹配融合成功的地图点的个数。注意到函数中有两层for循环,这两个函数构成了函数的主题,第一层是遍历候选的地图点(这些地图点来源于主函数筛选出来的,准备融合的地图点),第二层是将地图点投影到别的帧,获取那个帧以该投影点为中心,以r为半径的圆内的点,找出这些点中最匹配的那个点,作为匹配成功的点,但如果最匹配的点也超出了阈值,就判定为该地图点匹配失败。匹配成功后筛选不合格的点,然后为最终融合成功的点增加观测。该函数的融合原则为如果地图点能匹配关键帧的特征点,并且该点有对应的地图点,那么选择观测数目多的替换两个地图点,如果地图点能匹配关键帧的特征点,并且该点没有对应的地图点,那么为该点添加该投影地图点。正反两次特征匹配使用的相同函数,只是函数的参数不同,即投影的方向不同。

2. 函数代码

/*** @brief 将地图点投影到关键帧中进行匹配和融合* @param[in] pKF           关键帧* @param[in] vpMapPoints   待投影的地图点* @param[in] th            搜索窗口的阈值,默认为3* @return int              更新地图点的数量*/ 
int ORBmatcher::Fuse(KeyFrame *pKF, const vector<MapPoint *> &vpMapPoints, const float th)
{// 获取相邻关键帧的旋转矩阵和平移矩阵cv::Mat Rcw = pKF->GetRotation();cv::Mat tcw = pKF->GetTranslation();// 获取相机内参const float &fx = pKF->fx;const float &fy = pKF->fy;const float &cx = pKF->cx;const float &cy = pKF->cy;const float &bf = pKF->mbf;// 获取相邻关键帧的相机中心(相机的世界坐标)cv::Mat Ow = pKF->GetCameraCenter();// 用于计数已成功融合的地图点数量int nFused=0;// 获取待融合的地图点的数量const int nMPs = vpMapPoints.size();// 遍历待融合的地图点for(int i=0; i<nMPs; i++){// 获取地图点指针MapPoint* pMP = vpMapPoints[i];// 排除不符合规定的点if(!pMP)continue;if(pMP->isBad() || pMP->IsInKeyFrame(pKF))continue;// 将地图点坐标从世界坐标系转到相机坐标系cv::Mat p3Dw = pMP->GetWorldPos();cv::Mat p3Dc = Rcw*p3Dw + tcw;// Depth must be positive// 排除深度为负的地图点if(p3Dc.at<float>(2)<0.0f)continue;// 计算图像坐标const float invz = 1/p3Dc.at<float>(2);const float x = p3Dc.at<float>(0)*invz;const float y = p3Dc.at<float>(1)*invz;const float u = fx*x+cx;const float v = fy*y+cy;// Point must be inside the image// 检查点是否在图像内if(!pKF->IsInImage(u,v))continue;// 计算右目坐标const float ur = u-bf*invz;//  获取地图点的最大和最小距离const float maxDistance = pMP->GetMaxDistanceInvariance();const float minDistance = pMP->GetMinDistanceInvariance();// 计算地图点与相机中心的距离cv::Mat PO = p3Dw-Ow;const float dist3D = cv::norm(PO);// Depth must be inside the scale pyramid of the image// 检查距离是否在有效范围内if(dist3D<minDistance || dist3D>maxDistance )continue;// Viewing angle must be less than 60 deg// 获取地图点的法向量cv::Mat Pn = pMP->GetNormal();// 检查视角条件if(PO.dot(Pn)<0.5*dist3D)continue;// 预测金字塔层级并查找特征点int nPredictedLevel = pMP->PredictScale(dist3D,pKF);// Search in a radius// 设置搜索半径const float radius = th*pKF->mvScaleFactors[nPredictedLevel];// 获取以该特征点为中心,以radius为半径搜索获取特征点const vector<size_t> vIndices = pKF->GetFeaturesInArea(u,v,radius);// 没有获取到点就返回if(vIndices.empty())continue;// Match to the most similar keypoint in the radius// 获取描述子const cv::Mat dMP = pMP->GetDescriptor();// 初始化最佳距离和索引int bestDist = 256;int bestIdx = -1;// 遍历这些点for(vector<size_t>::const_iterator vit=vIndices.begin(), vend=vIndices.end(); vit!=vend; vit++){// 获取指针const size_t idx = *vit;// 获取去畸变后的坐标const cv::KeyPoint &kp = pKF->mvKeysUn[idx];// 获取当前金字塔层级const int &kpLevel= kp.octave;// 该点层级大于预测层级或小于两个层级,就返回if(kpLevel<nPredictedLevel-1 || kpLevel>nPredictedLevel)continue;// 索引大于零则获取误差,误差超过阈值就返回if(pKF->mvuRight[idx]>=0){// Check reprojection error in stereoconst float &kpx = kp.pt.x;const float &kpy = kp.pt.y;const float &kpr = pKF->mvuRight[idx];const float ex = u-kpx;const float ey = v-kpy;const float er = ur-kpr;const float e2 = ex*ex+ey*ey+er*er;if(e2*pKF->mvInvLevelSigma2[kpLevel]>7.8)continue;}else{const float &kpx = kp.pt.x;const float &kpy = kp.pt.y;const float ex = u-kpx;const float ey = v-kpy;const float e2 = ex*ex+ey*ey;// 第一个点的尺度为5.99if(e2*pKF->mvInvLevelSigma2[kpLevel]>5.99)continue;}// 获取该点的描述子const cv::Mat &dKF = pKF->mDescriptors.row(idx);// 获取该地图点与本关键帧最佳描述子之间的距离const int dist = DescriptorDistance(dMP,dKF);// 更新最佳距离if(dist<bestDist){bestDist = dist;bestIdx = idx;}}// If there is already a MapPoint replace otherwise add new measurement// 判断最佳距离是否在阈值内if(bestDist<=TH_LOW){// 获取拥有最佳匹配距离的地图点的指针MapPoint* pMPinKF = pKF->GetMapPoint(bestIdx);// 排除不合格的点if(pMPinKF){if(!pMPinKF->isBad()){// 获取观测数量更多的那个点,作为融合后的地图点if(pMPinKF->Observations()>pMP->Observations())pMP->Replace(pMPinKF);elsepMPinKF->Replace(pMP);}}// 如果不存在地图点,则将新的观察数据添加到地图点和关键帧中else{pMP->AddObservation(pKF,bestIdx);pKF->AddMapPoint(pMP,bestIdx);}// 增加成功融合计数 nFusednFused++;}}return nFused;
}

四、总结

本函数是为创早的新地图点服务的函数,目的是减少重复的地图点数量,同时优化地图点的坐标(选择观测最多的那个帧对应的地图点保留,本身就是一种优化),我们可以看到,进入到局部见图线程以后,不再使用普通真,而全部使用关键帧,这是因为关键帧是经过筛选的,相对一一般的帧更好,而且这样做的好处还有减少计算量,只用关键帧,可以大大减少匹配次数和难度,在优化时作用更加明显,显著提高效。

相关文章:

ORB-SLAM2 ----- LocalMapping::SearchInNeighbors()

文章目录 一、函数意义二、函数讲解三、函数代码四、本函数使用的匹配方法ORBmatcher::Fuse()1. 函数讲解2. 函数代码 四、总结 一、函数意义 本函数是用于地图点融合的函数&#xff0c;前面的函数生成了新的地图点&#xff0c;但这些地图点可能在前面的关键帧中已经生成过了&a…...

给UE5优化一丢丢编辑器性能

背后的原理 先看FActorIterator的定义 /*** Actor iterator* Note that when Playing In Editor, this will find actors only in CurrentWorld*/ class FActorIterator : public TActorIteratorBase<FActorIterator> {//..... }找到基类TActorIteratorBase /*** Temp…...

【Docker】常用命令汇总

Docker 是1个开源的应用容器引擎&#xff0c;基于Go 语言并遵从 Apache2.0 协议开源。 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&#xff0c;相…...

Mybatis:CRUD数据操作之多条件查询及动态SQL

Mybatis基础环境准备请看&#xff1a;Mybatis基础环境准备 本篇讲解Mybati数据CRUD数据操作之多条件查询 1&#xff0c;编写接口方法 在 com.itheima.mapper 包写创建名为 BrandMapper 的接口。在 BrandMapper 接口中定义多条件查询的方法。 而该功能有三个参数&#xff0c;…...

【笔记】轻型民用无人驾驶航空器安全操控

《轻型民用无人驾驶航空器安全操控》 理论考试培训材料 法规部分 【民用无人驾驶航空器的分类】 1、如何定义微型、轻型无人驾驶航空器&#xff1f; 微型无人驾驶航空器&#xff0c;是指空机重量小于0.25千克&#xff0c;最大平飞速度不超过40千米/小时&#xff0c;无线电发…...

TouchGFX设计模式代码实例说明

一)Model - View - Presenter (MVP) 模式在 TouchGFX 中的应用 1)Model&#xff08;模型&#xff09;&#xff1a; 模型代表应用程序的数据和业务逻辑。例如&#xff0c;在一个简单的计数器应用中&#xff0c;模型可以是一个包含计数器当前值的类。 class CounterModel { pri…...

flink学习(7)——window

概述 窗口的长度(大小): 决定了要计算最近多长时间的数据 窗口的间隔: 决定了每隔多久计算一次 举例&#xff1a;每隔10min,计算最近24h的热搜词&#xff0c;24小时是长度&#xff0c;每隔10分钟是间隔。 窗口的分类 1、根据window前是否调用keyBy分为键控窗口和非键控窗口…...

restTemplate get请求

报错解释&#xff1a; 这个报错信息表明在使用RestTemplate进行GET请求时&#xff0c;需要提供一个请求类型&#xff08;reqType&#xff09;&#xff0c;但是传入的值为空。这通常意味着在构建请求或者调用方法时&#xff0c;没有正确设置请求的Content-Type头部&#xff0c;…...

ffmpeg 预设的值 加速

centos 安装ffmpeg 编译安装 官网获取最新的linux ffmpeg 代码 https://ffmpeg.org//releases/ mkdir -p /data/app/ffmpeg cd /data/app/ffmpeg wget http://www.ffmpeg.org/releases/ffmpeg-7.1.tar.gz tar -zxvf ffmpeg-7.1.tar.gz#安装所需的编译环境 yum install -y \…...

maven <scope>compile</scope>作用

在 Maven 项目中&#xff0c; 元素用于定义依赖项的作用范围。 元素可以有多个值&#xff0c;每个值表示不同的作用范围。其中&#xff0c;scope compile scope 是默认的作用范围&#xff0c;表示该依赖项在编译、测试和运行时都需要。 scope compile scope 的含义 1、编译时…...

Ubuntu Server 22.04.5 从零到一:详尽安装部署指南

文章目录 Ubuntu Server 22.04.5 从零到一&#xff1a;详尽安装部署指南一、部署环境二、安装系统2.1 安装2.1.1 选择安装方式2.1.2 选择语言2.1.3 选择不更新2.1.4 选择键盘标准2.1.5 选择安装版本2.1.6 设置网卡2.1.7 配置代理2.1.8 设置镜像源2.1.9 选择装系统的硬盘2.1.10 …...

反射机制了解

反射概念 了解反射背景 存在某些变量或形参的声明类型是Object类型&#xff0c;但是程序却需要调用该对象运行时类型的方法&#xff0c;该方法不是Object中的方法&#xff0c;如何解决。转到如何获取该对象运行时类型的方法。 只能运行时才能获取&#xff0c;这就用到反射。 …...

机器学习策略Ⅰ

机器学习策略Ⅰ 在构建一个好的监督学习系统时&#xff0c;通常需要确保以下四个方面&#xff1a; 系统需要在训练集上能够很好地拟合数据&#xff0c;达到某种可接受的性能水平&#xff08;如接近人类水平&#xff09;。如果训练集表现不好&#xff0c;可以使用更大的模型&…...

redis中的bigkey及读取优化

一、bigKey介绍 1、简介 在 Redis 中,Big Key(大键)指的是占用大量内存的单个键。通常,Redis 是一个高性能的内存数据库,但是当某些键变得非常大时,会带来性能上的影响。例如,大量的内存消耗、长时间的操作延迟,甚至可能导致 Redis 停止响应或崩溃。 通俗的来说,指…...

【西瓜书】支持向量机(SVM)

支持向量机&#xff08;Support Vector Machine&#xff0c;简称SVM&#xff09;。 超平面 分类学习最基本的想法就是基于训练集合D在样本空间中找到一个划分超平面&#xff0c;将不同类别的样本分开。 但能将训练样本分开的划分超平面可能有很多&#xff0c;应该努力去找到哪…...

三维渲染中顺序无关的半透明混合(OIT)(二——Stencil Route)

1、A-Buffer算法。 在谈到Stencil Route之前&#xff0c;需要先讨论A-Buffer算法。A-Buffer是一种图形学&#xff08;渲染方向&#xff09;上的用于可见面分析(Visble Surface Detection)的技术&#xff0c;是Z-Buffer的衍生方法。 Z-Buffer是用于剔除 不透明 物体的算法。假…...

(SAST检测规则-3)固定的 SessionID 缺陷详解

漏洞类型&#xff1a; 会话固定攻击&#xff08;Session Fixation Attack&#xff09; 漏洞描述&#xff1a; 会话固定攻击是利用服务器的会话管理机制存在漏洞&#xff0c;攻击者通过提前控制或预测用户的会话标识符&#xff08;Session ID&#xff09;&#xff0c;当用户登录…...

【安卓开发】【Android Studio】项目构建(Build)时报错:Integer Overflow

一、问题描述 在安卓项目中&#xff0c;构建&#xff08;Build&#xff09;失败并报错&#xff1a;xxxxx Integer Overflow&#xff08;整型溢出&#xff09;。 二、相关代码 刚开始以为是某个整数&#xff08;例如控件、java类&#xff09;不匹配造成的&#xff0c;检查如下…...

STM32主要功能

STM32 是由意法半导体&#xff08;STMicroelectronics&#xff09;推出的一系列基于 ARM Cortex-M 内核的微控制器&#xff08;MCU&#xff09;。STM32 微控制器广泛应用于嵌入式系统中&#xff0c;因其高性能、低功耗、丰富的外设接口和多种封装形式而被广泛采用。其主要功能和…...

MacOS 如何连接 Linux NFS 服务器

以 Ubuntu 为例。 Ubuntu 服务器端设置 1. 进入 root 权限&#xff0c;安装 NFS 服务&#xff1a; apt-get update apt-get install nfs-kernel-server2. 创建共享目录&#xff1a; mkdir /data chown nobody:nogroup /data chmod 777 /data3. 配置 /etc/exports 文件: vi …...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...