1.ORB-SLAM3中如何保存多地图、关键帧、地图点到二进制文件中

1 保存多地图

1.1 为什么保存(视觉)地图

        因为我们要去做导航,导航需要先验地图。因此需要保存地图供导航使用,下面来为大家讲解如何保存多地图。

 1.2 保存多地图的主函数SaveAtlas

/**
 * @brief 保存地图
 * @param type 保存类型
 */
void System::SaveAtlas(int type)
{
    // mStrSaveAtlasToFile 如果配置文件akm_slam.yaml里面没有指定,则不会保存地图
    if(!mStrSaveAtlasToFile.empty())
    {
         1. 预保存想要保存的数据
        mpAtlas->PreSave();

         2. 确定保存的文件名字
        string pathSaveFileName = "./";
        pathSaveFileName = pathSaveFileName.append(mStrSaveAtlasToFile);
        pathSaveFileName = pathSaveFileName.append(".osa");

         3. 保存词典的校验结果及名字
        string strVocabularyChecksum = CalculateCheckSum(mStrVocabularyFilePath,TEXT_FILE);
        std::size_t found = mStrVocabularyFilePath.find_last_of("/\\");
        string strVocabularyName = mStrVocabularyFilePath.substr(found+1);

         4.用txt保存 / 用二进制保存
        if(type == TEXT_FILE)
        {
            cout << "Starting to write the save text file " << endl;
            std::remove(pathSaveFileName.c_str());
            std::ofstream ofs(pathSaveFileName, std::ios::binary);
            boost::archive::text_oarchive oa(ofs);

            oa << strVocabularyName;
            oa << strVocabularyChecksum;
            oa << mpAtlas;
            cout << "End to write the save text file" << endl;
        }
        else if(type == BINARY_FILE) // File binary
        {
            cout << "Starting to write the save binary file" << endl;
            std::remove(pathSaveFileName.c_str());
            std::ofstream ofs(pathSaveFileName, std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << strVocabularyName;
            oa << strVocabularyChecksum;
            oa << mpAtlas;
            cout << "End to write save binary file" << endl;
        }
    }
}

        mStrSaveAtlasToFile是配置文件中传递的参数:

        这里我们赋值成了akm(string mStrSaveAtlasToFile;)。

        即我们如果不传入mStrSaveAtlasToFile这个参数的话,就不会保存地图。

        我们执行预保存的代码 1.2.1 - 1.2.4节有详细的推导。

        我们确定要保存的文件名字:mStrSaveAtlasToFile(akm) + .osa即akm.osa

        保存词典的校验结果及名字

 3. 保存词典的校验结果及名字
// 计算给定文件路径 mStrVocabularyFilePath 对应文件的校验和或者哈希值,并将结果保存在 strVocabularyChecksum 变量中。
string strVocabularyChecksum = CalculateCheckSum(mStrVocabularyFilePath,TEXT_FILE);
// 找到了 mStrVocabularyFilePath 中最后一个目录分隔符(/ 或者 \)的位置,并将其索引保存在 found 变量中。这个操作通常用于从文件路径中提取文件名。
std::size_t found = mStrVocabularyFilePath.find_last_of("/\\");
// 这行代码利用之前找到的目录分隔符的位置 found,从 mStrVocabularyFilePath 中提取文件名部分,并将提取的文件名存储在 strVocabularyName 变量中。
string strVocabularyName = mStrVocabularyFilePath.substr(found+1);

        方便后续的读取。

        我们用二进制保存地图:

         4.用txt保存 / 用二进制保存
        if(type == TEXT_FILE)
        {
            cout << "Starting to write the save text file " << endl;
            std::remove(pathSaveFileName.c_str());
            std::ofstream ofs(pathSaveFileName, std::ios::binary);
            boost::archive::text_oarchive oa(ofs);

            oa << strVocabularyName;
            oa << strVocabularyChecksum;
            oa << mpAtlas;
            cout << "End to write the save text file" << endl;
        }
        else if(type == BINARY_FILE) // File binary
        {
            cout << "Starting to write the save binary file" << endl;
            std::remove(pathSaveFileName.c_str());
            std::ofstream ofs(pathSaveFileName, std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << strVocabularyName;
            oa << strVocabularyChecksum;
            oa << mpAtlas;
            cout << "End to write save binary file" << endl;
        }
    }

        在1.2.1-1.2.4中我们已经准备好了所有的保存变量(含Backup的变量)

        至此地图被保存:

1.2.1 预保存想要保存的数据Atlas::PreSave

/**
 * @brief 预保存,意思是在保存成地图文件之前,要保存到对应变量里面
 */
void Atlas::PreSave()
{
     1. 更新mnLastInitKFidMap
    // mpCurrentMap是当前的地图 Map *Atlas::mpCurrentMap
    if (mpCurrentMap)
    {
        // mnLastInitKFidMap为当前地图创建时第1个关键帧的id,它是在上一个地图最大关键帧id的基础上增加1
        // mspMaps保存了每一个地图
        // 如果地图集不为空且 前地图创建时第1个关键帧的id小于当前地图的最大关键帧
        // 言外之意就是当前地图的关键帧的数量大于1 更新mnLastInitKFidMap为下一个关键帧的索引
        if (!mspMaps.empty() && mnLastInitKFidMap < mpCurrentMap->GetMaxKFid())
            mnLastInitKFidMap = mpCurrentMap->GetMaxKFid() + 1; 
    }

    // 如果 elem1 所指向的 Map 对象的 Id 小于 elem2 所指向的 Map 对象的 Id,则返回 true,
    // 表示 elem1 应该排在 elem2 前面;否则返回 false,表示 elem2 应该排在 elem1 前面或它们相等。
    struct compFunctor
    {
        inline bool operator()(Map *elem1, Map *elem2)
        {
            return elem1->GetId() < elem2->GetId();
        }
    };
    
    // vector<Map *> Atlas::mvpBackupMaps 拷贝地图
    std::copy(mspMaps.begin(), mspMaps.end(), std::back_inserter(mvpBackupMaps));
    
     2. 按照id从小到大排列
    sort(mvpBackupMaps.begin(), mvpBackupMaps.end(), compFunctor());

    std::set<GeometricCamera *> spCams(mvpCameras.begin(), mvpCameras.end());

     3. 遍历所有地图,执行每个地图的预保存
    for (Map *pMi : mvpBackupMaps)
    {
        if (!pMi || pMi->IsBad())
            continue;

        // 如果地图为空,则跳过
        if (pMi->GetAllKeyFrames().size() == 0)
        {
            // Empty map, erase before of save it.
            SetMapBad(pMi);
            continue;
        }
        pMi->PreSave(spCams);
    }

     4. 删除坏地图
    RemoveBadMaps();
}

        我们要先明白几个变量的含义:

        1.mpCurrentMap是当前的地图 Map *Atlas::mpCurrentMap

        2.mnLastInitKFidMap为当前地图创建时第1个关键帧的id,它是在上一个地图最大关键帧id的基础上增加1

        3.mspMaps保存了所有地图:set<Map *> Atlas::mspMaps

        if (!mspMaps.empty() && mnLastInitKFidMap < mpCurrentMap->GetMaxKFid())
            mnLastInitKFidMap = mpCurrentMap->GetMaxKFid() + 1;

        即如果地图集不为空 且 前地图创建时第1个关键帧的id小于当前地图的最大关键帧则更新mnLastInitKFidMap为下一个关键帧的索引。那么也就是说如果多地图系统中有地图且当前地图的关键帧的数量大于1 更新mnLastInitKFidMap为下一个关键帧的索引。

    struct compFunctor
    {
        inline bool operator()(Map *elem1, Map *elem2)
        {
            return elem1->GetId() < elem2->GetId();
        }
    };

        如果 elem1 所指向的 Map 对象的 Id 小于 elem2 所指向的 Map 对象的 Id,则返回 true,表示 elem1 应该排在 elem2 前面;否则返回 false,表示 elem2 应该排在 elem1 前面或它们相等。

    std::copy(mspMaps.begin(), mspMaps.end(), std::back_inserter(mvpBackupMaps));
    sort(mvpBackupMaps.begin(), mvpBackupMaps.end(), compFunctor());

        因此这两行的代码的含义就是将mspMaps(set<Map *> Atlas::mspMaps)存放的所有地图存放到mvpBackupMaps(vector<Map *> Atlas::mvpBackupMaps以便于后续保存,在vector容器中的地图是按照从前到后的顺序排列。

        随后我们遍历每一个地图,执行每张地图的预保存:

     3. 遍历所有地图,执行每个地图的预保存
    for (Map *pMi : mvpBackupMaps)
    {
        if (!pMi || pMi->IsBad())
            continue;

        // 如果地图为空,则跳过
        if (pMi->GetAllKeyFrames().size() == 0)
        {
            // Empty map, erase before of save it.
            SetMapBad(pMi);
            continue;
        }
        pMi->PreSave(spCams);
    }

        如果地图是不好的我们处理下一张地图。

        如果地图关键帧为空的话,我们去将这个地图设置为不好的。       

void Atlas::SetMapBad(Map *pMap)
{
    mspMaps.erase(pMap);
    pMap->SetBad();

    mspBadMaps.insert(pMap);
}

        我们在地图集删去这张地图并且在坏地图中加入这张地图。

        如果这张地图有关键帧存在,那么我们继续执行预保存代码。

1.2.2 预保存地图 Map::PreSave

/** 预保存,也就是把想保存的信息保存到备份的变量中
 * @param spCams 相机
 */
void Map::PreSave(std::set<GeometricCamera *> &spCams)
{
    int nMPWithoutObs = 0;  // 统计用

     1. 剔除一下无效观测
    // 遍历每一个地图点
    for (MapPoint *pMPi : mspMapPoints)
    {
        // 地图点是坏的(优化线程)
        if (!pMPi || pMPi->isBad())
            continue;

        // 如果这个地图点没有被观测到
        if (pMPi->GetObservations().size() == 0)
        {
            nMPWithoutObs++;
        }
        // 能够观测到当前地图点的所有关键帧及该地图点在KF中的索引
        // it->first 观测到地图点的关键帧
        // 观测到地图点的关键帧不存在 或 观测到地图点的地图不是当前地图 或 观测到地图点的关键帧是被优化掉的关键帧
        // 也就是说 这里去除这个地图点在别的地图的观测以及在坏掉的关键帧的观测
        map<KeyFrame *, std::tuple<int, int>> mpObs = pMPi->GetObservations();
        for (map<KeyFrame *, std::tuple<int, int>>::iterator it = mpObs.begin(), end = mpObs.end(); it != end; ++it)
        {
            if (!it->first || it->first->GetMap() != this || it->first->isBad())
            {
                // 删除某个关键帧对当前地图点的观测
                pMPi->EraseObservation(it->first, false);
            }
        }
    }

    // Saves the id of KF origins
     2. 保存最开始的帧的id,貌似一个map的mvpKeyFrameOrigins里面只有一个,可以验证一下
    // vector<unsigned long> Map::mvBackupKeyFrameOriginsId
    mvBackupKeyFrameOriginsId.clear();
    mvBackupKeyFrameOriginsId.reserve(mvpKeyFrameOrigins.size());
    for (int i = 0, numEl = mvpKeyFrameOrigins.size(); i < numEl; ++i)
    {
        mvBackupKeyFrameOriginsId.push_back(mvpKeyFrameOrigins[i]->mnId);
    }

    // Backup of MapPoints
     3. 保存一下对应的mp
    mvpBackupMapPoints.clear();
    for (MapPoint *pMPi : mspMapPoints)
    {
        if (!pMPi || pMPi->isBad())
            continue;

        mvpBackupMapPoints.push_back(pMPi);
        pMPi->PreSave(mspKeyFrames, mspMapPoints);
    }

    // Backup of KeyFrames
     4. 保存一下对应的KF
    mvpBackupKeyFrames.clear();
    for (KeyFrame *pKFi : mspKeyFrames)
    {
        if (!pKFi || pKFi->isBad())
            continue;

        mvpBackupKeyFrames.push_back(pKFi);
        pKFi->PreSave(mspKeyFrames, mspMapPoints, spCams);
    }

     保存一些id
    mnBackupKFinitialID = -1;
    if (mpKFinitial)
    {
        mnBackupKFinitialID = mpKFinitial->mnId;
    }

    mnBackupKFlowerID = -1;
    if (mpKFlowerID)
    {
        mnBackupKFlowerID = mpKFlowerID->mnId;
    }
}

        在这里我们预保存每张地图。

        1.遍历此张地图的所有地图点,先剔除一下无效观测:当观测到地图点的关键帧不存在 或 观测到地图点的地图不是当前地图 或 观测到地图点的关键帧是被优化掉的关键帧时,我们将这个地图点在相应帧的观测删除。也就是说这里去除这个地图点在别的地图的观测以及在坏掉的关键帧的观测

        2.第二步保存这张最开始的帧的id 保存到变量mvBackupKeyFrameOriginsId中。

        3.第三步再次遍历这个地图的所有地图点,将所有地图点保存到mvpBackupMapPoints中。        

        4.第四步预保存地图点。

        5.第五步预保存关键帧。

1.2.3 预保存地图点 MapPoint::PreSave

/**
 * @brief 预保存
 * @param spKF 地图中所有关键帧
 * @param spMP 地图中所有三维点
 */
void MapPoint::PreSave(set<KeyFrame*>& spKF,set<MapPoint*>& spMP)
{
    // 1. 备份替代的MP id
    mBackupReplacedId = -1;
    if(mpReplaced && spMP.find(mpReplaced) != spMP.end())
        mBackupReplacedId = mpReplaced->mnId;

    // 2. 备份观测
    mBackupObservationsId1.clear();
    mBackupObservationsId2.clear();
    // Save the id and position in each KF who view it
    std::vector<KeyFrame*> erase_kfs;
    for(std::map<KeyFrame*,std::tuple<int,int> >::const_iterator it = mObservations.begin(), 
        end = mObservations.end(); it != end; ++it)
    {
        KeyFrame* pKFi = it->first;
        if(spKF.find(pKFi) != spKF.end())
        {
            mBackupObservationsId1[it->first->mnId] = get<0>(it->second);
            mBackupObservationsId2[it->first->mnId] = get<1>(it->second);
        }
        else
        {
            erase_kfs.push_back(pKFi); 
        }
    }
    for (auto pKFi : erase_kfs)
        EraseObservation(pKFi, false);
    // Save the id of the reference KF
    // 3. 备份参考关键帧ID
    if(spKF.find(mpRefKF) != spKF.end())
    {
        mBackupRefKFId = mpRefKF->mnId;
    }
}

        1.备份替换的地图点:如果这个地图点被替换了,我们在地图点集合spMP中寻找这个地图点所对应的ID赋值给mBackupReplacedId

        2.备份预测:mObservations存放所有地图点的预测(被哪一个关键帧观测到 + 在该帧特征点的索引),因此遍历该地图点可以看到的所有关键帧。

        如果这个关键帧存在(没有在优化节点被优化):备份地图点的观测到mBackupObservationsId1、mBackupObservationsId2变量中。(map<unsigned long, int> MapPoint::mBackupObservationsId1)

        如果这个关键帧不存在,删除掉关键帧对地图点的观测。

        3.备份第一次观测到该地图点的关键帧的ID:mBackupRefKFId

1.2.4 预保存关键帧KeyFrame::PreSave

void KeyFrame::PreSave(set<KeyFrame *> &spKF, set<MapPoint *> &spMP, set<GeometricCamera *> &spCam)
{
     1.预保存地图点的ID
    mvBackupMapPointsId.clear();
    mvBackupMapPointsId.reserve(N);
    for (int i = 0; i < N; ++i)
    {

        if (mvpMapPoints[i] && spMP.find(mvpMapPoints[i]) != spMP.end()) // Checks if the element is not null
            mvBackupMapPointsId.push_back(mvpMapPoints[i]->mnId);
        else // If the element is null his value is -1 because all the id are positives
            mvBackupMapPointsId.push_back(-1);
    }
    
     2.预保存本质图
    mBackupConnectedKeyFrameIdWeights.clear();
    for (std::map<KeyFrame *, int>::const_iterator it = mConnectedKeyFrameWeights.begin(), end = mConnectedKeyFrameWeights.end(); it != end; ++it)
    {
        if (spKF.find(it->first) != spKF.end())
            mBackupConnectedKeyFrameIdWeights[it->first->mnId] = it->second;
    }

     3.预保存父母节点的ID
    mBackupParentId = -1;
    if (mpParent && spKF.find(mpParent) != spKF.end())
        mBackupParentId = mpParent->mnId;

     4.预保存孩子节点的ID
    mvBackupChildrensId.clear();
    mvBackupChildrensId.reserve(mspChildrens.size());
    for (KeyFrame *pKFi : mspChildrens)
    {
        if (spKF.find(pKFi) != spKF.end())
            mvBackupChildrensId.push_back(pKFi->mnId);
    }

     5.预保存回环节点的ID
    mvBackupLoopEdgesId.clear();
    mvBackupLoopEdgesId.reserve(mspLoopEdges.size());
    for (KeyFrame *pKFi : mspLoopEdges)
    {
        if (spKF.find(pKFi) != spKF.end())
            mvBackupLoopEdgesId.push_back(pKFi->mnId);
    }

     6.mspMergeEdges
    mvBackupMergeEdgesId.clear();
    mvBackupMergeEdgesId.reserve(mspMergeEdges.size());
    for (KeyFrame *pKFi : mspMergeEdges)
    {
        if (spKF.find(pKFi) != spKF.end())
            mvBackupMergeEdgesId.push_back(pKFi->mnId);
    }

     7.预保存相机信息
    mnBackupIdCamera = -1;
    if (mpCamera && spCam.find(mpCamera) != spCam.end())
        mnBackupIdCamera = mpCamera->GetId();

    mnBackupIdCamera2 = -1;
    if (mpCamera2 && spCam.find(mpCamera2) != spCam.end())
        mnBackupIdCamera2 = mpCamera2->GetId();

     8.预保存IMU信息
    mBackupPrevKFId = -1;
    if (mPrevKF && spKF.find(mPrevKF) != spKF.end())
        mBackupPrevKFId = mPrevKF->mnId;

    mBackupNextKFId = -1;
    if (mNextKF && spKF.find(mNextKF) != spKF.end())
        mBackupNextKFId = mNextKF->mnId;

    if (mpImuPreintegrated)
        mBackupImuPreintegrated.CopyFrom(mpImuPreintegrated);
}

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

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

相关文章

机器学习中的概率与统计知识点汇总

引言 在学习高级知识时&#xff0c;理解基本概念至关重要。为什么&#xff1f;因为基础知识是您构建高级知识的基础。如果你把更多的东西放在薄弱的基础之上&#xff0c;它最终可能会分裂&#xff0c;这意味着你最终无法完全理解你所学的任何知识。因此&#xff0c;让我们尝试…

如何正确选择爬虫采集接口和API?区别在哪里?

在信息时代&#xff0c;数据已经成为了一个国家、一个企业、一个个人最宝贵的资源。而爬虫采集接口则是获取这些数据的重要手段之一。本文将从以下八个方面进行详细讨论&#xff1a; 1.什么是爬虫采集接口&#xff1f; 2.爬虫采集接口的作用和意义是什么&#xff1f; 3.爬虫…

智慧城市政务一网统管解决方案:PPT全文34页,附下载

关键词&#xff1a;智慧政务解决方案&#xff0c;智慧城市解决方案&#xff0c;智慧政务一网统管解决方案&#xff0c;一网统管治理理念&#xff0c;一网统管治理体系&#xff0c;一网统管治理手段&#xff0c;智慧政务综合服务平台建设 一、智慧城市政务一网统管建设背景 一…

CocosCreator 之 Tween缓动系统的使用

版本&#xff1a; 3.4.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac 简介 在CocosCreator 3.x版本后&#xff0c; Tween缓动系统代替了原有的Action动作。官方使用缓动系统的主要目的之一是用于解决离线动画无法满足需求时的动态动画问题。 简单的示例&#xff1a; …

R语言期末考试复习二

上篇文章的后续&#xff01;&#xff01;&#xff01;&#xff01; http://t.csdnimg.cn/sqvYD 1.给向量vec1设置名为"A","B","C","D","E","F","G"。 2.将矩阵mat1的行名设置为"Row1"&#…

8 个适用于电脑的顶级免费分区恢复软件

Windows PC 上的数据管理有时可能会带来压力&#xff0c;尤其是当您有多个分区时。大多数时候&#xff0c;磁盘管理工具使分析磁盘、释放空间甚至创建分区变得非常容易。但有时会发生不可预见的事件&#xff0c;可能导致分区丢失&#xff0c;从而造成潜在的数据灾难。嗯&#x…

ATA-7030高压放大器在等离子体实验中的应用有哪些

高压放大器在等离子体实验中有多种重要应用。等离子体是一种带电粒子与电中性粒子混合的物质&#xff0c;其具有多种独特的物理性质&#xff0c;因此在许多领域具有广泛的应用&#xff0c;例如聚变能源、等离子体医学、材料加工等。下面安泰电子将介绍高压放大器在等离子体实验…

pycharm安装PyQt5及其工具

PyCharm安装PyQt5及其工具&#xff08;Qt Designer、PyUIC、PyRcc&#xff09;详细教程_pycharm pyqt5-CSDN博客 上面是原文链接&#xff0c;根据原文链接&#xff0c;我重新记录一下。IDE&#xff1a;pycharm 2023.2.5 一共需要安装5个。 在PyCharm中如何完整优雅地安装配置…

Spring-SpringFramework特性以及IOC相关知识

SpringFramework五大模块 特性 IOC思想和DI IOC是容器&#xff0c;用于管理资源 IOC&#xff1a;Inversion of Control 反转控制 DI&#xff1a;Dependecy Injection 依赖注入 组件以预先定义好的方式接受来自容器的资源注入 IOC在Spring中的实现 spring提供两种方式&…

2023.11.27如何使用内网穿透工具实现Java远程连接操作本地Elasticsearch搜索引擎

文章目录 前言1. Windows 安装 Cpolar2. 创建Elasticsearch公网连接地址3. 远程连接Elasticsearch4. 设置固定二级子域名 前言 简单几步,结合Cpolar内网穿透工具实现Java远程连接操作本地Elasticsearch。 什么是elasticsearch&#xff1f;一个开源的分布式搜索引擎&#xff0…

Vue 3 面试经验分享

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

powershell获取微软o365 21v日志

0x00 背景 o365 21v为o365的大陆版本&#xff0c;主要给国内用户使用。微软提供了powershell工具和接口获取云上日志。微软o365国内的代理目前是世纪互联。本文介绍如何用powershell和配置证书拉取云上日志。 0x01 实践 第一步&#xff0c;ip权限开通&#xff1a; 由世纪互联…

正则表达式 通配符 awk文本处理工具

目录 什么是正则表达式 概念 正则表达式的结构 正则表达式的组成 元字符 元字符点&#xff08;.&#xff09; 代表字符. 点值表示点需要转义 \ r..t 代表r到t之间任意两个字符 过滤出小写 过滤出非小写 space空格 [[:space:]] 表示次数 位置锚定 例&#xff1a…

小航助学题库蓝桥杯题库stem选拔赛(21年1月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

小航助学题库蓝桥杯题库stem选拔赛(21年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

6.Spring源码解析-loadBeanDefinitions(String location)

这里resourceLoader其实就是ClassPathXmlApplicationContext 1.ClassPathXmlApplicationContext 在上文中图例就能看出来 获取资源组可能存在多个bean.xml 循环单独加载资源组 创建一个编码资源并解析 获取当前正在加载的资源发现是空 创建了一个字节输入流&#xff0c…

HTML5+CSS3+JS小实例:九宫格图片鼠标移入移出方向感知特效

实例:九宫格图片鼠标移入移出方向感知特效 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport&…

单车模型及其线性化

文章目录 1 单车模型2 线性化3 实现效果4 参考资料 1 单车模型 这里讨论的是以后轴为中心的单车运动学模型&#xff0c;由下式表达&#xff1a; S ˙ [ x ˙ y ˙ ψ ˙ ] [ v c o s ( ψ ) v s i n ( ψ ) v t a n ( ψ ) L ] \dot S \begin{bmatrix} \dot x\\ \dot y\\ \d…

【vue_3】关于超链接的问题

1、需求2、修改前的代码3、修改之后&#xff08;1&#xff09;第一次&#xff08;2&#xff09;第二次&#xff08;3&#xff09;第三次&#xff08;4&#xff09;第四次&#xff08;5&#xff09;第五次 1、需求 需求&#xff1a;要给没有超链接的列表添加软超链接 2、修改前…

单片机的串口通信

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、串口是什么&#xff1f;二、单片机结构讲解2.1 串口发送2.2串口接收2.3 还差点什么&#xff1f;2.3.1控制寄存器2.3.1.1 配置方式2.3.1.1 波特率 三、测试通…