2.ORB-SLAM3中如何从二进制文件中加载多地图、关键帧、地图点等数据结构

目录

1 为什么保存&加载(视觉)地图

1.1 加载多地图的主函数

1.2 加载各个地图 Atlas::PostLoad

1.3 加载关键帧及地图点Map::PostLoad

1.4 恢复地图点信息 MapPoint::PostLoad

1.5 恢复关键帧信息KeyFrame::PostLoad


1 为什么保存&加载(视觉)地图

        因为我们要去做导航,导航需要先验地图。因此需要保存地图供导航使用。

        上篇博客我们讲解了如何保存地图数据,这是我们已经保存下来的文件。

        下面来为大家讲解如何加载地图数据。

1.1 加载多地图的主函数

        我们看System.cc文件中的代码:

    // 查看配置文件版本,不同版本有不同处理方法
    cv::FileNode node = fsSettings["File.version"];
    if(!node.empty() && node.isString() && node.string() == "1.0")
    {
        settings_ = new Settings(strSettingsFile,mSensor);

        // 保存及加载地图的名字
        mStrLoadAtlasFromFile = settings_->atlasLoadFile();
        mStrSaveAtlasToFile = settings_->atlasSaveFile();

        cout << (*settings_) << endl;
    }
    // 我们的配置文件版本为1.0版本  LoadAtlasFromFile = ‘akm’  SaveAtlasToFile = ‘akm’
    // mStrLoadAtlasFromFile = ‘akm’
    // mStrSaveAtlasToFile = ‘akm’
    else
    {
        settings_ = nullptr;
        cv::FileNode node = fsSettings["System.LoadAtlasFromFile"];
        if(!node.empty() && node.isString())
        {
            mStrLoadAtlasFromFile = (string)node;
        }

        node = fsSettings["System.SaveAtlasToFile"];
        if(!node.empty() && node.isString())
        {
            mStrSaveAtlasToFile = (string)node;
        }
    }

        我们的配置文件版本时1.0版本。

mStrSaveAtlasToFile

        在setting构造函数中我们读取了下面的参数:

void Settings::readLoadAndSave(cv::FileStorage &fSettings)
{
    bool found;

    sLoadFrom_ = readParameter<string>(fSettings, "System.LoadAtlasFromFile", found, false);
    sSaveto_ = readParameter<string>(fSettings, "System.SaveAtlasToFile", found, false);
}

        因此,mStrLoadAtlasFromFilemStrSaveAtlasToFile变量都是字符串'akm'。

        接着看这段:

    // 如果没有先验地图的话 mStrLoadAtlasFromFile = ‘akm’
    if(mStrLoadAtlasFromFile.empty())
    {
        //Load ORB Vocabulary
        cout << endl << "Loading ORB Vocabulary. This could take a while..." << endl;

        // 建立一个新的ORB字典
        mpVocabulary = new ORBVocabulary();
        // 读取预训练好的ORB字典并返回成功/失败标志
        bool bVocLoad = mpVocabulary->loadFromBinaryFile(strVocFile);
        // 如果加载失败,就输出错误信息
        if(!bVocLoad)
        {
            cerr << "Wrong path to vocabulary. " << endl;
            cerr << "Falied to open at: " << strVocFile << endl;
            exit(-1);
        }
        cout << "Vocabulary loaded!" << endl << endl;

        //Create KeyFrame Database
        // Step 4 创建关键帧数据库
        mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);

        //Create the Atlas
        // Step 5 创建多地图,参数0表示初始化关键帧id为0
        cout << "Initialization of Atlas from scratch " << endl;
        mpAtlas = new Atlas(0);
    }

    // 如果有先验地图的话
    else
    {
        //Load ORB Vocabulary
        cout << endl << "Loading ORB Vocabulary. This could take a while..." << endl;

        mpVocabulary = new ORBVocabulary();
        bool bVocLoad = mpVocabulary->loadFromBinaryFile(strVocFile);
        if(!bVocLoad)
        {
            cerr << "Wrong path to vocabulary. " << endl;
            cerr << "Falied to open at: " << strVocFile << endl;
            exit(-1);
        }
        cout << "Vocabulary loaded!" << endl << endl;

        //Create KeyFrame Database
        mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);

        cout << "Load File" << endl;

        // Load the file with an earlier session
        //clock_t start = clock();
        cout << "Initialization of Atlas from file: " << mStrLoadAtlasFromFile << endl;
         加载地图
        bool isRead = LoadAtlas(FileType::BINARY_FILE);

        if(!isRead)
        {
            cout << "Error to load the file, please try with other session file or vocabulary file" << endl;
            exit(-1);
        }
        //mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);


        //cout << "KF in DB: " << mpKeyFrameDatabase->mnNumKFs << "; words: " << mpKeyFrameDatabase->mnNumWords << endl;

        loadedAtlas = true;
        //如果选择杶定位模式 先用我们写的方式加载地图
        // mode2 pure location
        if(mode2)
        {
            mpAtlas->LoadMap();
        }

        else
            mpAtlas->CreateNewMap();

        //clock_t timeElapsed = clock() - start;
        //unsigned msElapsed = timeElapsed / (CLOCKS_PER_SEC / 1000);
        //cout << "Binary file read in " << msElapsed << " ms" << endl;

        //usleep(10*1000*1000);
    }

        我们有先验地图(mStrLoadAtlasFromFile不为空),因此走else语句。

        1.我们首先读取ORB词典        

        2.利用ORB词典创建关键帧数据库

        3.加载地图

        这是我们这次的重头戏,看看如何加载地图:

/**
 * @brief 加载地图
 * @param type 保存类型
 */
bool System::LoadAtlas(int type)
{
     1. 加载地图文件 这里的pathLoadFileName就是 ./ + akm + .osa
    // /home/liuhongwei/Desktop/final/catkin_nav/src/akm_nav/thirdparty/ORB_SLAM3_relocation_nav/akm.osa
    string strFileVoc, strVocChecksum;
    bool isRead = false;

    string pathLoadFileName = "./";
    pathLoadFileName = pathLoadFileName.append(mStrLoadAtlasFromFile);
    pathLoadFileName = pathLoadFileName.append(".osa");

    if(type == TEXT_FILE) // File text
    {
        cout << "Starting to read the save text file " << endl;
        std::ifstream ifs(pathLoadFileName, std::ios::binary);
        if(!ifs.good())
        {
            cout << "Load file not found" << endl;
            return false;
        }
        // strVocabularyName
        boost::archive::text_iarchive ia(ifs);
        ia >> strFileVoc;
        ia >> strVocChecksum;
        ia >> mpAtlas;
        cout << "End to load the save text file " << endl;
        isRead = true;
    }
    // 这里我们是二值地图
    else if(type == BINARY_FILE) // File binary
    {
        cout << "Starting to read the save binary file"  << endl;
        // 这段代码是用 C++ 语言中的 ifstream 类来创建一个文件输入流对象。
        // 它打开一个文件以供读取,并使用 `std::ios::binary` 标志表示以二进制模式打开文件,而不是文本模式。
        // 在这个例子中,`pathLoadFileName` 是文件的路径和名称。
        std::ifstream ifs(pathLoadFileName, std::ios::binary);

        //这段代码首先检查文件输入流 ifs 是否成功打开并准备好进行读取。
        // 如果文件未能成功打开(即 !ifs.good()),它会输出错误信息 "Load file not found",然后返回 false,表示加载文件失败。
        if(!ifs.good())
        {
            cout << "Load file not found" << endl;
            return false;
        }
        // 如果文件成功打开,它使用 Boost 库中的 boost::archive::binary_iarchive 对象 ia 对打开的文件进行反序列化操作。
        boost::archive::binary_iarchive ia(ifs);
        // 通过 ia 从文件中读取了 strFileVoc、strVocChecksum 和 mpAtlas。
        ia >> strFileVoc;
        ia >> strVocChecksum;
        ia >> mpAtlas;
        cout << "End to load the save binary file" << endl;
        isRead = true;
    }

     2. 如果加载成功
    if(isRead)
    {
        //Check if the vocabulary is the same
        // 校验词典是否一样
        string strInputVocabularyChecksum = CalculateCheckSum(mStrVocabularyFilePath,TEXT_FILE);

        if(strInputVocabularyChecksum.compare(strVocChecksum) != 0)
        {
            cout << "The vocabulary load isn't the same which the load session was created " << endl;
            cout << "-Vocabulary name: " << strFileVoc << endl;
            return false; // Both are differents
        }

        // 加载对应数据
        mpAtlas->SetKeyFrameDababase(mpKeyFrameDatabase);
        mpAtlas->SetORBVocabulary(mpVocabulary);
        mpAtlas->PostLoad();

        return true;
    }
    return false;
}

        1.首先我们读取我们保存的二进制文件:

        pathLoadFileName = ./akm.osa

        2.从二进制文件中读取地图:

        我们回忆一下存储地图的过程:

        我们存储了ORB词典的位置、checksum校验和以及多地图数据库。

        因此我们在读取时候也是按这三个顺序读取。

        这个数据结构包含以下内容:

        它就是一个指针保存了所有地图和关键帧(Map structure that stores the pointers to all KeyFrames and MapPoints.)

        3.如果读取成功,我们设置关键帧的数据库为System.cc中初始化的关键帧数据库,ORB词典为System.cc初始化的ORB词典。

        执行加载数据函数mpAtlas->PostLoad(); 1.2-1.5节说明

        如果执行成功,那么我们已经恢复了所有的地图了,开始下一步的工作

1.2 加载各个地图 Atlas::PostLoad

/**
 * @brief 后加载,意思是读取地图文件后加载各个信息
 */
void Atlas::PostLoad()
{
     1. 读取当前所有相机
    map<unsigned int, GeometricCamera *> mpCams;
    for (GeometricCamera *pCam : mvpCameras)
    {
        mpCams[pCam->GetId()] = pCam;
    }

    mspMaps.clear();
    unsigned long int numKF = 0, numMP = 0;
     2. 加载各个地图
    for (Map *pMi : mvpBackupMaps)
    {
        mspMaps.insert(pMi);
        pMi->PostLoad(mpKeyFrameDB, mpORBVocabulary, mpCams);
        numKF += pMi->GetAllKeyFrames().size();
        numMP += pMi->GetAllMapPoints().size();
    }
    //mvpBackupMaps.clear();
}

        我们先来看读取到Atlas类中的变量:

protected:

    // 地图集合
    std::set<Map*> mspMaps;
    // 坏的地图集合
    std::set<Map*> mspBadMaps;
    // Its necessary change the container from set to vector because libboost 1.58 and Ubuntu 16.04 have an error with this cointainer
    // 备份地图集合
    std::vector<Map*> mvpBackupMaps;

    // 当前地图
    Map* mpCurrentMap;

    // 相机模型
    std::vector<GeometricCamera*> mvpCameras;

    unsigned long int mnLastInitKFidMap;

    Viewer* mpViewer;
    bool mHasViewer;

    // Class references for the map reconstruction from the save file
    KeyFrameDatabase* mpKeyFrameDB;
    ORBVocabulary* mpORBVocabulary;

    // Mutex
    std::mutex mMutexAtlas;

        1.首先把所有的相机模型读取到变量map<unsigned int, GeometricCamera *> mpCams里面。(第几个相机,对应的相机抽象模型)

        2.从备份地图mvpBackupMaps中加载地图:

        将地图存储进地图数据结构mspMaps(保存所有地图的数据结构),用numKF和numMP获取每个地图的关键帧和地图点数量。(dubug信息貌似,这两个临时变量没什么用呀)。

1.3 加载关键帧及地图点Map::PostLoad

        我们这个函数是恢复每个地图都被调用一次,因此是恢复一张地图的关键帧及地图点。

    for (Map *pMi : mvpBackupMaps)
    {
        mspMaps.insert(pMi);
        pMi->PostLoad(mpKeyFrameDB, mpORBVocabulary, mpCams);
        numKF += pMi->GetAllKeyFrames().size();
        numMP += pMi->GetAllMapPoints().size();
    }

        我们来看代码:

/** 后加载,也就是把备份的变量恢复到正常变量中
 * @param spCams 相机
 */
void Map::PostLoad(KeyFrameDatabase *pKFDB, ORBVocabulary *pORBVoc /*, map<long unsigned int, KeyFrame*>& mpKeyFrameId*/, map<unsigned int, GeometricCamera *> &mpCams)
{
    std::copy(mvpBackupMapPoints.begin(), mvpBackupMapPoints.end(), std::inserter(mspMapPoints, mspMapPoints.begin()));
    std::copy(mvpBackupKeyFrames.begin(), mvpBackupKeyFrames.end(), std::inserter(mspKeyFrames, mspKeyFrames.begin()));

     1. 恢复map中的地图点,注意此时mp中只恢复了保存的量
    map<long unsigned int, MapPoint *> mpMapPointId;
    for (MapPoint *pMPi : mspMapPoints)
    {
        if (!pMPi || pMPi->isBad())
            continue;

        pMPi->UpdateMap(this);
        mpMapPointId[pMPi->mnId] = pMPi;
    }

     2. 恢复map中的kf,注意此时kf中只恢复了保存的量
    map<long unsigned int, KeyFrame *> mpKeyFrameId;
    for (KeyFrame *pKFi : mspKeyFrames)
    {
        if (!pKFi || pKFi->isBad())
            continue;

        pKFi->UpdateMap(this);
        pKFi->SetORBVocabulary(pORBVoc);
        pKFi->SetKeyFrameDatabase(pKFDB);
        mpKeyFrameId[pKFi->mnId] = pKFi;
    }

    // References reconstruction between different instances
     3. 使用mp中的备份变量恢复正常变量
    for (MapPoint *pMPi : mspMapPoints)
    {
        if (!pMPi || pMPi->isBad())
            continue;

        pMPi->PostLoad(mpKeyFrameId, mpMapPointId);
    }

     4. 使用kf中的备份变量恢复正常变量
    for (KeyFrame *pKFi : mspKeyFrames)
    {
        if (!pKFi || pKFi->isBad())
            continue;

        pKFi->PostLoad(mpKeyFrameId, mpMapPointId, mpCams);
        pKFDB->add(pKFi);
    }

     5. 恢复ID
    if (mnBackupKFinitialID != -1)
    {
        mpKFinitial = mpKeyFrameId[mnBackupKFinitialID];
    }

    if (mnBackupKFlowerID != -1)
    {
        mpKFlowerID = mpKeyFrameId[mnBackupKFlowerID];
    }

    mvpKeyFrameOrigins.clear();
    mvpKeyFrameOrigins.reserve(mvBackupKeyFrameOriginsId.size());
    for (int i = 0; i < mvBackupKeyFrameOriginsId.size(); ++i)
    {
        mvpKeyFrameOrigins.push_back(mpKeyFrameId[mvBackupKeyFrameOriginsId[i]]);
    }

    mvpBackupMapPoints.clear();
}

        1.首先将备份的所有关键帧mvpBackupKeyFrames

信息和所有的地图点mvpBackupMapPoints提取出来,mspMapPointsmspKeyFrames存储了运行状态的所有地图点和关键帧

        2.缓存所有地图点mspMapPoints:用mpMapPointId数据结构保存(地图点ID、地图点的指针)并更新这个地图点对本地图可视。

     1. 恢复map中的地图点,注意此时mp中只恢复了保存的量
    map<long unsigned int, MapPoint *> mpMapPointId;
    for (MapPoint *pMPi : mspMapPoints)
    {
        if (!pMPi || pMPi->isBad())
            continue;

        pMPi->UpdateMap(this);
        mpMapPointId[pMPi->mnId] = pMPi;
    }
void MapPoint::UpdateMap(Map* pMap)
{
    unique_lock<mutex> lock(mMutexMap);
    mpMap = pMap;
}

        3.缓存所有关键帧:用mpKeyFrameId数据结构保存(帧ID、帧指针)。

     2. 恢复map中的kf,注意此时kf中只恢复了保存的量
    map<long unsigned int, KeyFrame *> mpKeyFrameId;
    for (KeyFrame *pKFi : mspKeyFrames)
    {
        if (!pKFi || pKFi->isBad())
            continue;

        pKFi->UpdateMap(this);
        pKFi->SetORBVocabulary(pORBVoc);
        pKFi->SetKeyFrameDatabase(pKFDB);
        mpKeyFrameId[pKFi->mnId] = pKFi;
    }

        恢复地图对该关键帧的观测:

void KeyFrame::UpdateMap(Map *pMap)
{
    unique_lock<mutex> lock(mMutexMap);
    mpMap = pMap;
}

        设置该帧的ORB词典(必须设置的,每一帧的构造函数中有)

void KeyFrame::SetORBVocabulary(ORBVocabulary *pORBVoc)
{
    mpORBvocabulary = pORBVoc;
}

        设置该帧的关键帧数据库:

void KeyFrame::SetKeyFrameDatabase(KeyFrameDatabase *pKFDB)
{
    mpKeyFrameDB = pKFDB;
}

        4.恢复本地图地图点信息:1.4节有详细介绍

     3. 使用mp中的备份变量恢复正常变量
    for (MapPoint *pMPi : mspMapPoints)
    {
        if (!pMPi || pMPi->isBad())
            continue;

        pMPi->PostLoad(mpKeyFrameId, mpMapPointId);
    }

        5.恢复本地图关键帧信息:1.5节有详细介绍

     4. 使用kf中的备份变量恢复正常变量
    for (KeyFrame *pKFi : mspKeyFrames)
    {
        if (!pKFi || pKFi->isBad())
            continue;

        pKFi->PostLoad(mpKeyFrameId, mpMapPointId, mpCams);
        pKFDB->add(pKFi);
    }

1.4 恢复地图点信息 MapPoint::PostLoad

/**
 * @brief 后加载
 */
void MapPoint::PostLoad(map<long unsigned int, KeyFrame*>& mpKFid, map<long unsigned int, MapPoint*>& mpMPid)
{
    // 1. 根据保存的ID加载参考关键帧与替代点
    mpRefKF = mpKFid[mBackupRefKFId];
    if(!mpRefKF)
    {
        cout << "ERROR: MP without KF reference " << mBackupRefKFId << "; Num obs: " << nObs << endl;
    }
    mpReplaced = static_cast<MapPoint*>(NULL);
    if(mBackupReplacedId>=0)
    {
        map<long unsigned int, MapPoint*>::iterator it = mpMPid.find(mBackupReplacedId);
        if (it != mpMPid.end())
            mpReplaced = it->second;
    }

    // 2. 加载观测
    mObservations.clear();

    for(map<long unsigned int, int>::const_iterator it = mBackupObservationsId1.begin(), end = mBackupObservationsId1.end(); it != end; ++it)
    {
        KeyFrame* pKFi = mpKFid[it->first];
        map<long unsigned int, int>::const_iterator it2 = mBackupObservationsId2.find(it->first);
        std::tuple<int, int> indexes = tuple<int,int>(it->second,it2->second);
        if(pKFi)
        {
           mObservations[pKFi] = indexes;
        }
    }

    mBackupObservationsId1.clear();
    mBackupObservationsId2.clear();
}

          1.首先恢复地图点的参考关键帧与替代点:回忆ORB-SLAM2/3算法,一个地图的参考关键帧是建立它的关键帧。

        2.加载观测:mObservations存储地图点可以被哪一帧观测,在该帧的序号是多少。

1.5 恢复关键帧信息KeyFrame::PostLoad

        主要是恢复关键帧位姿、地图点、图连接,这里在上篇博客说明了,不再阐述:

void KeyFrame::PostLoad(map<long unsigned int, KeyFrame *> &mpKFid, map<long unsigned int, MapPoint *> &mpMPid, map<unsigned int, GeometricCamera *> &mpCamId)
{
    // Rebuild the empty variables

     1.恢复关键帧位姿
    SetPose(mTcw);

    mTrl = mTlr.inverse();

    // Reference reconstruction
     2.恢复关键帧地图点
    mvpMapPoints.clear();
    mvpMapPoints.resize(N);
    for (int i = 0; i < N; ++i)
    {
        if (mvBackupMapPointsId[i] != -1)
            mvpMapPoints[i] = mpMPid[mvBackupMapPointsId[i]];
        else
            mvpMapPoints[i] = static_cast<MapPoint *>(NULL);
    }

    // Conected KeyFrames with him weight
    mConnectedKeyFrameWeights.clear();
    for (map<long unsigned int, int>::const_iterator it = mBackupConnectedKeyFrameIdWeights.begin(), end = mBackupConnectedKeyFrameIdWeights.end();
            it != end; ++it)
    {
        KeyFrame *pKFi = mpKFid[it->first];
        mConnectedKeyFrameWeights[pKFi] = it->second;
    }

    // Restore parent KeyFrame
    if (mBackupParentId >= 0)
        mpParent = mpKFid[mBackupParentId];

    // KeyFrame childrens
    mspChildrens.clear();
    for (vector<long unsigned int>::const_iterator it = mvBackupChildrensId.begin(), end = mvBackupChildrensId.end(); it != end; ++it)
    {
        mspChildrens.insert(mpKFid[*it]);
    }

    // Loop edge KeyFrame
    mspLoopEdges.clear();
    for (vector<long unsigned int>::const_iterator it = mvBackupLoopEdgesId.begin(), end = mvBackupLoopEdgesId.end(); it != end; ++it)
    {
        mspLoopEdges.insert(mpKFid[*it]);
    }

    // Merge edge KeyFrame
    mspMergeEdges.clear();
    for (vector<long unsigned int>::const_iterator it = mvBackupMergeEdgesId.begin(), end = mvBackupMergeEdgesId.end(); it != end; ++it)
    {
        mspMergeEdges.insert(mpKFid[*it]);
    }

    // Camera data
    if (mnBackupIdCamera >= 0)
    {
        mpCamera = mpCamId[mnBackupIdCamera];
    }
    else
    {
        cout << "ERROR: There is not a main camera in KF " << mnId << endl;
    }
    if (mnBackupIdCamera2 >= 0)
    {
        mpCamera2 = mpCamId[mnBackupIdCamera2];
    }

    // Inertial data
    if (mBackupPrevKFId != -1)
    {
        mPrevKF = mpKFid[mBackupPrevKFId];
    }
    if (mBackupNextKFId != -1)
    {
        mNextKF = mpKFid[mBackupNextKFId];
    }
    mpImuPreintegrated = &mBackupImuPreintegrated;

    // Remove all backup container
    mvBackupMapPointsId.clear();
    mBackupConnectedKeyFrameIdWeights.clear();
    mvBackupChildrensId.clear();
    mvBackupLoopEdgesId.clear();

    UpdateBestCovisibles();
}

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

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

相关文章

如何写好产品软文?软文撰写指南!

针对某种产品写一篇软文&#xff0c;我们应该怎么构思&#xff0c;怎么提笔去写&#xff0c;怎么写得让用户认可我们的产品&#xff0c;并产生消费的冲动&#xff0c;这是需要讲究技巧的。 今天伯乐网络传媒来给大家分享三个步骤&#xff0c;教你轻轻松松撰写一篇爆文&#xf…

记一次域控迁移并升级

域环境&#xff1a; 域控级别&#xff1a;windows server2008R2 主域控&#xff1a;win server 2008R2 辅域控&#xff1a;win server 2016 需求&#xff1a;新购一台win server 2022&#xff0c;需要将主域控迁移到新服务器中&#xff0c;并升级域控级别为最新 检查域控 …

什么软件能去水印?分享三款实用去水印工具

什么软件能去水印&#xff1f;去水印你还在担心会损伤画质或处理不干净&#xff1f;今天分享三款好用的图片去水印工具&#xff0c;手机和电脑软件都有&#xff0c;操作简单&#xff0c;去水印速度快&#xff0c;而且去水印后几乎看不水印痕迹&#xff01; 1、水印云 一款图片编…

贪心算法策略实现

贪心算法 贪心算法&#xff1a;基于某种情况进行一个排序。 贪心算法得到的是优良解&#xff0c;而非全局最优解。需要证明局部最优解 全局最优解 经典贪心算法 —— 会议问题 对于这个问题 &#xff0c;我们提出贪心策略&#xff1a; 策略1&#xff1a;按照会议的持续时间长…

函数声明与函数表达式

函数声明 一个标准的函数声明&#xff0c;由关键字function 、函数名、形参和代码块组成。 有名字的函数又叫具名函数。 举个例子&#xff1a; function quack(num) { for (var i 0; i < num; i) {console.log("Quack!")} } quack(3)函数表达式 函数没有名称…

前端代码提交gitlab出现语法错误无法提交

错误 找到项目里面的.git文件夹 下面有一个hooks 删除pre-commit文件&#xff08;git语法校验代码&#xff09;

人工智能即将彻底改变你使用计算机的方式

文章目录 每个人的私人助理“Clippy 是一个机器人&#xff0c;而不是特工。”卫生保健“一半需要心理健康护理的美国退伍军人没有得到治疗。”教育生产率娱乐和购物科技行业的冲击波技术挑战隐私和其他重大问题 今天我仍然像保罗艾伦和我创办微软时一样热爱软件。但是&#xff…

Nodejs+Vue校园餐厅外卖订餐点餐系统 PHP高校食堂 微信小程序_0u4hl 多商家

对于校园订餐小程序将是又一个传统管理到智能化信息管理的改革&#xff0c;对于传统的校园订餐管理&#xff0c;所包括的信息内容比较多&#xff0c;对于用户想要对这些数据进行管理维护需要花费很大的时间信息&#xff0c;而且对于数据的存储比较麻烦&#xff0c;想要查找某一…

Elasticsearch:向量搜索 (kNN) 实施指南 - API 版

作者&#xff1a;Jeff Vestal 本指南重点介绍通过 HTTP 或 Python 使用 Elasticsearch API 设置 Elasticsearch 以进行近似 k 最近邻 (kNN) 搜索。 对于主要使用 Kibana 或希望通过 UI 进行测试的用户&#xff0c;请访问使用 Elastic 爬虫的语义搜索入门指南。你也可以参考文章…

【嵌入式】开源shell命令行的移植和使用(2)——letter-shell

目录 一 背景说明 二 移植准备 三 移植过程 四 自定义命令 五 实际使用 一 背景说明 之前使用过一款开源shell工具 nr_micro_shell &#xff08;【嵌入式】开源shell命令行的移植和使用&#xff08;1&#xff09;——nr_micro_shell-CSDN博客&#xff09;&#xff0c;感觉…

【Linux】了解进程的基础知识

进程 1. 进程的概念1.1 进程的理解1.2 Linux下的进程1.3 查看进程属性1.4 getpid和getppid 2. 创建进程3. 进程状态4. 进程优先级5. 进程切换6. 环境变量7. 本地变量与内建命令 1. 进程的概念 一个已经加载到内存中的程序&#xff0c;叫做进程&#xff08;也叫任务&#xff09…

echarts点击事件

有这么个需求要点击叶片的时候跳转页面 代码&#xff1a;点击之后 报错了 解决办法 1、使用箭头函数&#xff08;箭头函数没有自己的 this&#xff0c;所以在箭头函数中使用 this 时&#xff0c;其指向与外层作用域相同。&#xff09;或者使用闭包来解决上下文的问题。 2、使…

QT基础实践之QQ登录界面

文章目录 QQ登录界面源码分享演示图代码分析 QQ登录界面 源码分享 链接&#xff1a;https://pan.baidu.com/s/1v_J4WQjZoSAoMrIpx88PbA 提取码&#xff1a;qwer 记得把图片放入Debug文件 演示图 代码分析 已注释 较为详细 widget.h #ifndef WIDGET_H #define WIDGET_H#inc…

Leetcode98 验证二叉搜索树

题意理解&#xff1a; 首先明确二叉树的定义&#xff0c;对于所有节点&#xff0c;根节点的值大于左子树所有节点的值&#xff0c;小于右子树所有节点的值。 注意一个误区&#xff1a; 根节点简单和左孩子&#xff0c;右孩子比大小是不够的&#xff0c;要和子树比&#xff0c;…

Django项目部署本地windows IIS(详细版)和static文件设置(页面样式正常显示)

目录 必要条件&#xff1a; 一、下载并启用wfastcgi 二、window安装 IIS功能 三、IIS管理器中添加网站 1、复制项目 2、复制wfastcgi.py文件 3、创建文件web.config 4、添加网站&#xff0c;填写信息 5、启动fastcgi程序 6、修改进程标识 四、static文件设置和正确显…

凝聚数字经济发展新力量,四象科技受邀出席2023全球数商大会

11月25日&#xff0c;2023全球数商大会在上海开幕。本届大会以“数联全球、商通未来”为主题&#xff0c;上海市委副书记、市长龚正出席大会并宣布大会开幕&#xff0c;国家发展改革委党组成员&#xff0c;国家数据局党组书记、局长刘烈宏&#xff0c;上海市副市长陈杰致辞。四…

智能学习台灯_AI摄像头学习机基于MTk8175方案

智能学习台灯是一款专为中小学生设计的学习辅助工具&#xff0c;具有多项突出的参数和功能。首先&#xff0c;它采用了基于联发科MTK平台的解决方案&#xff0c;内置了12纳米四核Cortex-A53处理器&#xff0c;提供了稳定而高效的性能。操作系统方面&#xff0c;智能学习台灯运行…

山西临县“5·7”火灾事故调查报告公布,揭秘富维烟火报警系统

近日&#xff0c;山西临县“57”火灾事故调查报告震惊全国&#xff0c;提醒我们火灾防控的重要性。在这起悲剧中&#xff0c;我们深刻认识到&#xff0c;及时发现火灾并迅速应对至关重要。这不仅是对生命安全的保护&#xff0c;也是对财产损失的有效减少。而在这方面&#xff0…

SQL注入-报错注入

目录 一&#xff0c;sql报错注入概述&#xff1a; 二&#xff0c;报错注入函数&#xff1a; extractvalue() updatexml() floor()、rand()、count()、group by联用 其它函数 三&#xff0c;SQL报错注入实例&#xff1a; extractvalue() floor()、rand()、count()、grou…

统计学中两组数据如何进行差异性(相关性)分析?

变量说明&#xff1a; 在确定分析方法前&#xff0c;我们需要了解手中的数据类型&#xff0c;这是最基础也是有必要的&#xff0c;在所有的数据类型中&#xff0c;我们将数据类型分为分类变量也为定类变量和连续变量也称为定量变量&#xff0c;那么什么是定类变量&#xff1f;…