(01)ORB-SLAM2源码无死角解析-(64) BA优化(g2o)→闭环线程:Optimizer::OptimizeSim3→Sim3变换优化

news/2024/5/17 15:45:08/文章来源:https://blog.csdn.net/weixin_43013761/article/details/127373225

本人讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析-接如下:
(01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/123092196
 
文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证{\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证}文末正下方中心提供了本人联系方式,点击本人照片即可显示WX官方认证
 

一、前言

上一篇博客对建图线程:Optimizer::LocalBundleAdjustment→局部建图位姿与地图点优化进行了详细的讲解。那么接下来要讲解的,就是闭环线程中的 Optimizer::OptimizeSim3→Sim3变换优化。该函数实现于 src/Optimizer.cc 文件中,被 src/LoopClosing.cc 文件中的 LoopClosing::ComputeSim3() 调用。如果当前关键帧,与某一候选关键帧匹配时,则会计算两帧之间的 Sim3 变换 gScm(候选关键帧到当前帧的Sim3变换),如果感觉不太熟悉的朋友,可以回顾一下这篇博客:
(01)ORB-SLAM2源码无死角解析-(55) 闭环线程→计算Sim3:总体流程讲解ComputeSim3()

获得候选关键帧到当前帧的Sim3变换gScm之后,然后再利用两帧之间的匹配关系,对gScm进行优化,首先看下图:
在这里插入图片描述
KF1代表当前关键帧,KF2代表匹配成功的候选关键帧。g2oS12 表示 KF2→ KF1 的Sim变换(g2oS12 KF1→ KF2 的Sim变换)。大家需要注意,闭环线程中调用 LoopClosing::ComputeSim3() 时,还未进行地图点融合。也就是说KF1的关键点KP1与KF1的关键点KP2匹配,但是他们对应的地图点未必相同。

简单看一下 Optimizer::OptimizeSim3() 中相关参数的介绍:

/*** @brief 形成闭环时固定(不优化)地图点进行Sim3位姿优化* 1. Vertex:*     - g2o::VertexSim3Expmap(),两个关键帧的位姿*     - g2o::VertexSBAPointXYZ(),两个关键帧共有的MapPoints* 2. Edge:*     - g2o::EdgeSim3ProjectXYZ(),BaseBinaryEdge*         + Vertex:关键帧的Sim3,MapPoint的Pw*         + measurement:MapPoint在关键帧中的二维位置(u,v)*         + InfoMatrix: invSigma2(与特征点所在的尺度有关)*     - g2o::EdgeInverseSim3ProjectXYZ(),BaseBinaryEdge*         + Vertex:关键帧的Sim3,MapPoint的Pw*         + measurement:MapPoint在关键帧中的二维位置(u,v)*         + InfoMatrix: invSigma2(与特征点所在的尺度有关)* * @param[in] pKF1              当前帧* @param[in] pKF2              闭环候选帧* @param[in] vpMatches1        两个关键帧之间的匹配关系* @param[in] g2oS12            两个关键帧间的Sim3变换,方向是从21       * @param[in] th2               卡方检验是否为误差边用到的阈值* @param[in] bFixScale         是否优化尺度,单目进行尺度优化,双目/RGB-D不进行尺度优化* @return int                  优化之后匹配点中内点的个数*/

核心:\color{red} 核心:核心: 若 KP1 与 KP2 匹配,那么显然最理想的Sm3变换为: ①KP2→g2oS12=KP1;②KP1→g2oS21=KP2;
 

二、顶点与边

顶点

Sim优化中,涉及到顶点类类型为 VertexSim3Expmap,VertexSBAPointXYZ。其在前面博客中已经进行了详细介绍,这里就不再重复讲解了。

Sim优化中,主要的边为 EdgeSim3ProjectXYZ,与 EdgeInverseSim3ProjectXYZ。先来看看 EdgeInverseSim3ProjectXYZ,其与前面的 EdgeSE3ProjectXYZ 十分相像,仅仅是把欧式变换SE3更替成了Sim变换。其主要变化为其 virtual void computeError() 函数,实现于Thirdparty\g2o\g2o\types\types_seven_dof_expmap.h 文件中,如下:

    void computeError(){const VertexSim3Expmap* v1 = static_cast<const VertexSim3Expmap*>(_vertices[1]);const VertexSBAPointXYZ* v2 = static_cast<const VertexSBAPointXYZ*>(_vertices[0]);Vector2d obs(_measurement);_error = obs-v1->cam_map1(project(v1->estimate().map(v2->estimate())));}

其中map函数如下所示:

     Vector3d map (const Vector3d& xyz) const {return s*(r*xyz) + t;}

可以看到就是把欧式变换替换成了相似变换。其对于virtual void linearizeOplus() 函数,与 EdgeSE3ProjectXYZ 一致。

对于 EdgeInverseSim3ProjectXYZ ,就是 EdgeSE3ProjectXYZ 的逆操作。若 EdgeSE3ProjectXYZ 是把 KP2 对应的地图点投影到 KF1上,与 KP1计算误差,则 EdgeInverseSim3ProjectXYZ 是把 KP1 对应的地图点投影到 KF2 上,与 KP2 计算误差。具体在后面进行分析。
 

三、边缘化Marginalized

上一篇博客提到简单的说G2O 中对路标点设置边缘化(Point->setMarginalized(true))是为了 在计算求解过程中,先消去路标点变量,实现先求解相机位姿,然后再利用求解出来的相机位姿,反过来计算路标点的过程,目的是为了加速求解,并非真的将路标点给边缘化掉。

那么在源码中是如何体现出来的呢?一般在初始化g2o优化器,添加顶点与边之后,都会执行类似 optimizer.optimize(5) 这样一句代码。这里使用的优化器为 g2o::SparseOptimizer optimizer,所以进入到 Thirdparty\g2o\g2o\core\sparse_optimizer.cpp 文件中,可以找到 SparseOptimizer::optimize() 函数。

其可以看到一句代码 ok = _algorithm->init(online),这里的 _algorithm 实际就是创建求解器时设置的 g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr)。另外_algorithm->init(online)代码实现如下:

  bool OptimizationAlgorithmWithHessian::init(bool online){assert(_optimizer && "_optimizer not set");assert(_solver && "Solver not set");_solver->setWriteDebug(_writeDebug->value());bool useSchur=false;for (OptimizableGraph::VertexContainer::const_iterator it=_optimizer->activeVertices().begin(); it!=_optimizer->activeVertices().end(); ++it) {OptimizableGraph::Vertex* v= *it;if (v->marginalized()){useSchur=true;break;}}if (useSchur){if  (_solver->supportsSchur())_solver->setSchur(true);} else {if  (_solver->supportsSchur())_solver->setSchur(false);}bool initState = _solver->init(_optimizer, online);return initState;}

从上面可以看到,其调用了 v->marginalized() 函数,获得该顶点是否需要边缘化,如果进行边缘化,则会对 _solver 进行相关设置。这样对于边缘化的引用,算是有了一个初步的了解。至于后面的 _solver->setSchur(true) 与 _solver->setSchur(false) 有什么区别,有兴趣的朋友可以深入的了解一下代码。
 

四、源码逻辑

代码为 src/Optimizer.cc 中的 Optimizer::OptimizeSim3() 函数。

(01):\color{blue} (01):(01) 初始化g2o优化器,使用L-M迭代;获得当前关键帧pKF1与闭环候选帧pKF2相机内参与位姿。设置待优化的Sim3位姿作为顶点。根据传感器类型决定是否固定尺度,如果为双目或者深度相机则固定尺度。

(02):\color{blue} (02):(02) 对当前帧pKF1的所有关键点进行遍历,如果其未与闭环候选帧pKF2的关键点匹配,则跳出循环。否则获得各自关键点对应的地图点pMP1,pMP2。如果地图点pMP1,pMP2未同时存在,或有任意一个地图点未坏点,则continue。

(03):\color{blue} (03):(03) 为地图点pMP1创建一个VertexSBAPointXYZ类型顶点vPoint1,并且设置vPoint1->setFixed(true),也就是地图点不作优化。同理,为地图点vPoint2也创建一个VertexSBAPointXYZ类型顶点vPoint2,并且设置vPoint2->setFixed(true),地图点不作优化。

(04):\color{blue} (04):(04) ①添加边x1 = S12∗*X2,设置了两个顶点,分别为vPoint2与vSim3,该边计算的误差为→地图点pMP2通过Sim变换正向投影到pKF1与pKF1的匹配点计算误差。②添加边x2 = S21∗*X1,设置了两个顶点,分别为vPoint1与vSim3,该边计算的误差为→地图点pMP1通过Sim变换的逆进行反向投影到pKF2与pKF2的匹配点计算误差。

(04):\color{blue} (04):(04)g2o开始优化,先迭代5次。根据卡方检验删除向或反向投影任意一个超过误差阈值就删掉该边。如果有误差较大的边被剔除那么说明回环质量并不是非常好,还要多迭代几次;反之就少迭代几次,如果经过上面的剔除后剩下的匹配关系已经非常少了,那么就放弃优化。内点数直接设置为0。

(05):\color{blue} (05):(05)再次g2o优化剔除后剩下的边,统计第二次优化之后,这些匹配点中是内点的个数,
用优化后的结果来更新Sim3位姿。
 

五、源码注释

/*** @brief 形成闭环时固定(不优化)地图点进行Sim3位姿优化* 1. Vertex:*     - g2o::VertexSim3Expmap(),两个关键帧的位姿*     - g2o::VertexSBAPointXYZ(),两个关键帧共有的MapPoints* 2. Edge:*     - g2o::EdgeSim3ProjectXYZ(),BaseBinaryEdge*         + Vertex:关键帧的Sim3,MapPoint的Pw*         + measurement:MapPoint在关键帧中的二维位置(u,v)*         + InfoMatrix: invSigma2(与特征点所在的尺度有关)*     - g2o::EdgeInverseSim3ProjectXYZ(),BaseBinaryEdge*         + Vertex:关键帧的Sim3,MapPoint的Pw*         + measurement:MapPoint在关键帧中的二维位置(u,v)*         + InfoMatrix: invSigma2(与特征点所在的尺度有关)* * @param[in] pKF1              当前帧* @param[in] pKF2              闭环候选帧* @param[in] vpMatches1        两个关键帧之间的匹配关系* @param[in] g2oS12            两个关键帧间的Sim3变换,方向是从2到1       * @param[in] th2               卡方检验是否为误差边用到的阈值* @param[in] bFixScale         是否优化尺度,单目进行尺度优化,双目/RGB-D不进行尺度优化* @return int                  优化之后匹配点中内点的个数*/
int Optimizer::OptimizeSim3(KeyFrame *pKF1, KeyFrame *pKF2, vector<MapPoint *> &vpMatches1, g2o::Sim3 &g2oS12, const float th2, const bool bFixScale)
{// Step 1:初始化g2o优化器// 先构造求解器g2o::SparseOptimizer optimizer;// 构造线性方程求解器,Hx = -b的求解器g2o::BlockSolverX::LinearSolverType * linearSolver;// 使用dense的求解器,(常见非dense求解器有cholmod线性求解器和shur补线性求解器)linearSolver = new g2o::LinearSolverDense<g2o::BlockSolverX::PoseMatrixType>();g2o::BlockSolverX * solver_ptr = new g2o::BlockSolverX(linearSolver);// 使用L-M迭代g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);optimizer.setAlgorithm(solver);// Calibration// 内参矩阵const cv::Mat &K1 = pKF1->mK;const cv::Mat &K2 = pKF2->mK;// Camera posesconst cv::Mat R1w = pKF1->GetRotation();const cv::Mat t1w = pKF1->GetTranslation();const cv::Mat R2w = pKF2->GetRotation();const cv::Mat t2w = pKF2->GetTranslation();// Set Sim3 vertex// Step 2: 设置待优化的Sim3位姿作为顶点g2o::VertexSim3Expmap * vSim3 = new g2o::VertexSim3Expmap();    // 根据传感器类型决定是否固定尺度vSim3->_fix_scale=bFixScale;vSim3->setEstimate(g2oS12);vSim3->setId(0);// Sim3 需要优化vSim3->setFixed(false);                             // 因为要优化Sim3顶点,所以设置为falsevSim3->_principle_point1[0] = K1.at<float>(0,2);    // 光心横坐标cxvSim3->_principle_point1[1] = K1.at<float>(1,2);    // 光心纵坐标cyvSim3->_focal_length1[0] = K1.at<float>(0,0);       // 焦距 fxvSim3->_focal_length1[1] = K1.at<float>(1,1);       // 焦距 fyvSim3->_principle_point2[0] = K2.at<float>(0,2);vSim3->_principle_point2[1] = K2.at<float>(1,2);vSim3->_focal_length2[0] = K2.at<float>(0,0);vSim3->_focal_length2[1] = K2.at<float>(1,1);optimizer.addVertex(vSim3);// Set MapPoint vertices// Step 3: 设置匹配的地图点作为顶点const int N = vpMatches1.size();// 获取pKF1的地图点const vector<MapPoint*> vpMapPoints1 = pKF1->GetMapPointMatches();vector<g2o::EdgeSim3ProjectXYZ*> vpEdges12;         //pKF2对应的地图点到pKF1的投影边vector<g2o::EdgeInverseSim3ProjectXYZ*> vpEdges21;  //pKF1对应的地图点到pKF2的投影边vector<size_t> vnIndexEdge;                         //边的索引vnIndexEdge.reserve(2*N);vpEdges12.reserve(2*N);vpEdges21.reserve(2*N);// 核函数的阈值const float deltaHuber = sqrt(th2);int nCorrespondences = 0;// 遍历每对匹配点for(int i=0; i<N; i++){if(!vpMatches1[i])continue;// pMP1和pMP2是匹配的地图点MapPoint* pMP1 = vpMapPoints1[i];MapPoint* pMP2 = vpMatches1[i];// 保证顶点的id能够错开const int id1 = 2*i+1;const int id2 = 2*(i+1);// i2 是 pMP2 在pKF2中对应的索引const int i2 = pMP2->GetIndexInKeyFrame(pKF2);if(pMP1 && pMP2){if(!pMP1->isBad() && !pMP2->isBad() && i2>=0){// 如果这对匹配点都靠谱,并且对应的2D特征点也都存在的话,添加PointXYZ顶点g2o::VertexSBAPointXYZ* vPoint1 = new g2o::VertexSBAPointXYZ();// 地图点转换到各自相机坐标系下的三维点cv::Mat P3D1w = pMP1->GetWorldPos();cv::Mat P3D1c = R1w*P3D1w + t1w;vPoint1->setEstimate(Converter::toVector3d(P3D1c));vPoint1->setId(id1);// 地图点不优化vPoint1->setFixed(true);optimizer.addVertex(vPoint1);g2o::VertexSBAPointXYZ* vPoint2 = new g2o::VertexSBAPointXYZ();cv::Mat P3D2w = pMP2->GetWorldPos();cv::Mat P3D2c = R2w*P3D2w + t2w;vPoint2->setEstimate(Converter::toVector3d(P3D2c));vPoint2->setId(id2);vPoint2->setFixed(true);optimizer.addVertex(vPoint2);}elsecontinue;}elsecontinue;// 对匹配关系进行计数nCorrespondences++;// Step 4: 添加边(地图点投影到特征点)// Set edge x1 = S12*X2// 地图点pMP1对应的观测特征点Eigen::Matrix<double,2,1> obs1;const cv::KeyPoint &kpUn1 = pKF1->mvKeysUn[i];obs1 << kpUn1.pt.x, kpUn1.pt.y;// Step 4.1 闭环候选帧地图点投影到当前关键帧的边 -- 正向投影g2o::EdgeSim3ProjectXYZ* e12 = new g2o::EdgeSim3ProjectXYZ();// vertex(id2)对应的是pKF2 VertexSBAPointXYZ 类型的三维点e12->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id2)));// ? 为什么这里添加的节点的id为0?// 回答:因为vertex(0)对应的是 VertexSim3Expmap 类型的待优化Sim3,其id 为 0e12->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(0)));e12->setMeasurement(obs1);// 信息矩阵和这个特征点的可靠程度(在图像金字塔中的图层)有关const float &invSigmaSquare1 = pKF1->mvInvLevelSigma2[kpUn1.octave];e12->setInformation(Eigen::Matrix2d::Identity()*invSigmaSquare1);// 使用鲁棒核函数g2o::RobustKernelHuber* rk1 = new g2o::RobustKernelHuber;e12->setRobustKernel(rk1);rk1->setDelta(deltaHuber);optimizer.addEdge(e12);// Set edge x2 = S21*X1// Step 4.2 当前关键帧地图点投影到闭环候选帧的边 -- 反向投影// 地图点pMP2对应的观测特征点Eigen::Matrix<double,2,1> obs2;const cv::KeyPoint &kpUn2 = pKF2->mvKeysUn[i2];obs2 << kpUn2.pt.x, kpUn2.pt.y;g2o::EdgeInverseSim3ProjectXYZ* e21 = new g2o::EdgeInverseSim3ProjectXYZ();// vertex(id1)对应的是pKF1 VertexSBAPointXYZ 类型的三维点,内部误差公式也不同e21->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id1)));e21->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(0)));e21->setMeasurement(obs2);float invSigmaSquare2 = pKF2->mvInvLevelSigma2[kpUn2.octave];e21->setInformation(Eigen::Matrix2d::Identity()*invSigmaSquare2);g2o::RobustKernelHuber* rk2 = new g2o::RobustKernelHuber;e21->setRobustKernel(rk2);rk2->setDelta(deltaHuber);optimizer.addEdge(e21);vpEdges12.push_back(e12);vpEdges21.push_back(e21);vnIndexEdge.push_back(i);}// Optimize!// Step 5:g2o开始优化,先迭代5次optimizer.initializeOptimization();optimizer.optimize(5);// Step 6:用卡方检验剔除误差大的边// Check inliersint nBad=0;for(size_t i=0; i<vpEdges12.size();i++){g2o::EdgeSim3ProjectXYZ* e12 = vpEdges12[i];g2o::EdgeInverseSim3ProjectXYZ* e21 = vpEdges21[i];if(!e12 || !e21)continue;if(e12->chi2()>th2 || e21->chi2()>th2){// 正向或反向投影任意一个超过误差阈值就删掉该边size_t idx = vnIndexEdge[i];vpMatches1[idx]=static_cast<MapPoint*>(NULL);optimizer.removeEdge(e12);optimizer.removeEdge(e21);vpEdges12[i]=static_cast<g2o::EdgeSim3ProjectXYZ*>(NULL);vpEdges21[i]=static_cast<g2o::EdgeInverseSim3ProjectXYZ*>(NULL);// 累计删掉的边 数目nBad++;}}// 如果有误差较大的边被剔除那么说明回环质量并不是非常好,还要多迭代几次;反之就少迭代几次int nMoreIterations;if(nBad>0)nMoreIterations=10;elsenMoreIterations=5;// 如果经过上面的剔除后剩下的匹配关系已经非常少了,那么就放弃优化。内点数直接设置为0if(nCorrespondences-nBad<10)return 0;     // Optimize again only with inliers// Step 7:再次g2o优化剔除后剩下的边optimizer.initializeOptimization();optimizer.optimize(nMoreIterations);// 统计第二次优化之后,这些匹配点中是内点的个数int nIn = 0;for(size_t i=0; i<vpEdges12.size();i++){g2o::EdgeSim3ProjectXYZ* e12 = vpEdges12[i];g2o::EdgeInverseSim3ProjectXYZ* e21 = vpEdges21[i];if(!e12 || !e21)continue;if(e12->chi2()>th2 || e21->chi2()>th2){size_t idx = vnIndexEdge[i];vpMatches1[idx]=static_cast<MapPoint*>(NULL);}elsenIn++;}// Recover optimized Sim3// Step 8:用优化后的结果来更新Sim3位姿。g2o::VertexSim3Expmap* vSim3_recov = static_cast<g2o::VertexSim3Expmap*>(optimizer.vertex(0));g2oS12= vSim3_recov->estimate();return nIn;
}

 

五、结语

通过该篇博客,对闭环线程中的 Optimizer::OptimizeSim3→Sim3变换优化 进行详细讲解,下面要讲解的依旧是闭环线程中的优化,其为 Optimizer::OptimizeEssentialGraph()→本质图优化。

 
 
 

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

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

相关文章

白名单里的蓝桥杯“计算思维”竞赛到底考啥,有啥用处?

可能很多人都已经知道了&#xff0c;教育部公布的《2022-2025学年面向中小学生的全国性竞赛活动名单》正式宣告了&#xff0c;蓝桥杯全国软件和信息技术专业人才大赛成为了白名单比赛中的一员了。 而根据蓝桥杯的竞赛说明&#xff0c;2023年4月15~16日&#xff0c;以及4月22~2…

微信对接百度实现文字识别OCR

微信对接百度实现文字识别OCR 前置基础条件&#xff1a; ①注册测试账号 ②开通自己的测试公众号 ③完成与微信互发消息 ④完成自定义菜单栏 详细步骤&#xff1a;https://blog.csdn.net/weixin_45565886/category_12059118.html 1 获取到百度文字识别API ①注册百度智能云账…

Tomcat修改端口、添加域名访问

一、修改改端口 1、修改默认访问端口8080,改为80 # vim /usr/local/tomcat/conf/server.xml (路径是自己的安装目录)<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding=&…

spring 原理解析

spring 要解决的问题 1.spring 是如何创建一个bean对象的 bean对象其实就是一个实例 无非通过一些当时来丰富这个对象的内容 典型的就是aop和依赖注入 spring在通过构造方法创建一个实例对象后 就进入一个aop的阶段&#xff0c;这个阶段来实现属性 参数的依赖注入&#xf…

自然资源部第三地理信息制图院与Bigemap强强联手,共同推动多元化新GIS应用

近日&#xff0c;成都比格图数据处理有限公司(以下简称&#xff1a;Bigemap)与自然资源部第三地理信息制图院正式达成战略合作&#xff0c;未来双方将共同推动"基于矢量、影像、地形、实景三维等GIS数据在行业上的深化应用及国产化替代"合作。 自然资源部第三地理信…

小程序技术可助力智慧医疗企业破茧突围?

智慧医疗作为充分应用计算机技术、信息技术建立的新型医疗方式&#xff0c;正随着新一代信息技术的普及和医疗健康领域需求的变化迎来爆发期。智慧医疗作为充分应用计算机技术、信息技术建立的新型医疗方式&#xff0c;正随着新一代信息技术的普及和医疗健康领域需求的变化迎来…

基于java的果蔬在线销售系统/农产品销售系统

社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要…

8-3 通过Grafana展示Prometheus的Node和Pod数据

文章目录前言Node数据node-exporter指标数据Prometheus采集node-exporter数据Grafana展示node-exporter数据导入模板Pod数据cadvisor指标数据Prometheus采集cadvisor数据Grafana展示cadvisor数据导入模板修改名字前言 在上一章节 8-2 通过Node-Exporter和Cadvisor收集指标数据…

多种点击试剂PEG:DBCO-PEG24-Maleimide, DBCO-PEG12-Mal性质总结

●中文名&#xff1a;二苯并环辛炔-PEG24-马来酰亚胺、二苯并环辛炔-二十四聚乙二醇-马来酰亚胺 ●英文&#xff1a;DBCO-PEG24-Maleimide&#xff0c;DBCO-PEG24-Mal ●外观以及性质&#xff1a;浅黄色或无色油性&#xff0c;西安凯新生物科技有限公司供应的​DBCO系列产品包…

MybatisPlus二级缓存不失效原因(缓存与数据库不一致)

​&#x1f4d2;个人主页&#xff1a;热爱生活的李&#x1f4d2; ​❤️感谢大家阅读本文&#xff0c;同时欢迎访问本人主页查看更多文章​❤️ &#x1f62d;技术太菜了希望下次不要出现了&#x1f62d; &#x1f64f;本人也在学习阶段&#xff0c;如若发现问题&#xff0c;请…

idea中的翻译插件(Translation)出现TKK不可用

idea中的翻译插件&#xff08;Translation&#xff09;不可用 1、问题&#xff1a; 在使用idea中的翻译插件时出现了下面的错误 2、解决方案 2-1、可以选择除谷歌外的 其他三个翻译软件 注意&#xff1a;我 试了一下 有道的 &#xff0c;申请了一个发现&#xff0c;这个并…

透明Png黑白图片上色(重新着色、改变成指定颜色)

场景&#xff1a;有时&#xff0c;我们需要给透明Png黑白图片改变成指定颜色&#xff08;上色&#xff09;。 前提&#xff1a;我们已经做好了带透明通道的PNG图片。 益处&#xff1a;使用此方法&#xff0c;只需要制作一张透明的黑色PNG图片即可&#xff0c;我们需要不同颜色…

UnRaid设备共用其他UnRaid主UPS的详细设置方法

系列文章目录 UnRaid系统添加UPS功能系列文章 第一篇&#xff1a;UnRaid主机连接UPS的设置方法简介 第二篇&#xff1a;UnRaid设备共用其他UnRaid主UPS的详细设置方法&#xff08;本文&#xff09; 第三篇&#xff1a;UnRaid设备共用群晖UPS的详细设置方法 文章目录系列文章目录…

Flutter基础组件:开关、进度组件、图片组件、图标组件

前言 刚开始学习&#xff0c;主要是为了熟悉一下组件的基本用法。参考 Flutter | 老孟 开关 Switch Switch(value: isOpen,onChanged: (value) {setState(() {isOpen value;});})添加图片 Switch(value: isOpen,activeThumbImage: const AssetImage(lib/assets/img/qq.png…

【无人机】基于EKF、UKF、PF、改进PF滤波算法的无人机航迹预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

Linux vmalloc原理与实现

文章目录前言一、vmalloc 原理1.1 vmalloc space1.2 vmalloc实现1.3 __vmalloc_node_range1.3.1 __get_vm_area_node1.3.2 __vmalloc_area_node1.3.3 map_vm_area二、数据结构2.1 struct vm_struct2.2 struct vmap_area三、vmalloc初始化总结参考资料前言 物理上连续的内存映射…

运行mvn打包命令报错

mac中 IntelliJ idea运行mvn clean package -DskipTests. 报错:No compiler is provided in this environment. Perhaps you are running on a JRE. 适合java新手&#xff0c;踩坑的可以试试&#xff0c;大神跳过。 先运行mavn -version&#xff0c;查看runtiime的路径如果是…

GSVA和生存分析

为什么一定要是预后相关基因呢 | 生信菜鸟团 (bio-info-trainee.com) 200块的代码我的学徒免费送给你&#xff0c;GSVA和生存分析 (qq.com) 我们生信技能树B站又免费悄咪咪的上线了一个GSVA生存分析教学视频&#xff0c;然后学徒马上就学习了&#xff0c;居然是主动学习的&a…

C++ Reference: Standard C++ Library reference: C Library: cstdio: vfprintf

C官网参考链接&#xff1a;https://cplusplus.com/reference/cstdio/vfprintf/ 函数 <cstdio> vfprintf int vfprintf ( FILE * stream, const char * format, va_list arg ); 将格式化的数据从可变实参列表写入流 将由format指向的C字符串写入stream&#xff0c;以与pr…

【ML on Kubernetes】第 3 章:探索 Kubernetes

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…