ORB-SLAM2 --- Tracking::Track 追踪线程解析

news2024/9/27 9:25:36

1.函数作用

        ORB-SLAM2的三大线程之一---跟踪线程,负责估计运动信息、跟踪局部地图。

        追踪线程的主要工作原理就是我们从数据集中读入一帧帧,刚开始的时候跟踪线程没有进行初始化(没有初始化不知道世界坐标系的原点和相机的位姿),我们初始化跟踪线程,初始成功之后把符合要求的第一帧的相机坐标作为世界坐标系的原点,在之后传进来的每帧,用三种跟踪方式计算相机的坐标,并生成地图点。

        总体来说,ORB-SLAM2跟踪部分主要包括两个阶段,第一个阶段包括三种跟踪方法:用参考关键帧来跟踪恒速模型跟踪重定位跟踪,它们的目的是保证能够“跟的上”,但估计出来的位姿可能没那么准确。第二个阶段是局部地图跟踪,将当前帧的局部关键帧对应的局部地图点投影到该帧,得到更多的特征点匹配关系,对第一阶段的位姿再次优化得到相对准确的位姿。

        流程图如下: 

2.函数流程 

Step 1:地图初始化

Step 2:跟踪进入正常SLAM模式,有地图更新

Step 3:在跟踪得到当前帧初始姿态后,现在对local map进行跟踪得到更多的匹配,并优化当前位姿

Step 4:更新显示线程中的图像、特征点、地图点等信息

Step 5:跟踪成功,更新恒速运动模型

Step 6:清除观测不到的地图点   

Step 7:清除恒速模型跟踪中 UpdateLastFrame中为当前帧临时添加的MapPoints(仅双目和rgbd)

Step 8:检测并插入关键帧,对于双目或RGB-D会产生新的地图点

Step 9 删除那些在bundle adjustment中检测为outlier的地图点

Step 10 如果初始化后不久就跟踪失败,并且relocation也没有搞定,只能重新Reset

Step 11:记录位姿信息,用于最后保存所有的轨迹

3.code 

void Tracking::Track()
{
    // track包含两部分:估计运动、跟踪局部地图
    
    // mState为tracking的状态,包括 SYSTME_NOT_READY, NO_IMAGE_YET, NOT_INITIALIZED, OK, LOST
    // 如果图像复位过、或者第一次运行,则为NO_IMAGE_YET状态
    if(mState==NO_IMAGES_YET)
    {
        mState = NOT_INITIALIZED;
    }

    // mLastProcessedState 存储了Tracking最新的状态,用于FrameDrawer中的绘制
    mLastProcessedState=mState;

    // Get Map Mutex -> Map cannot be changed
    // 地图更新时加锁。保证地图不会发生变化
    // 疑问:这样子会不会影响地图的实时更新?
    // 回答:主要耗时在构造帧中特征点的提取和匹配部分,在那个时候地图是没有被上锁的,有足够的时间更新地图
    unique_lock<mutex> lock(mpMap->mMutexMapUpdate);

    // Step 1:地图初始化
    if(mState==NOT_INITIALIZED)
    {
        if(mSensor==System::STEREO || mSensor==System::RGBD)
            //双目RGBD相机的初始化共用一个函数
            StereoInitialization();
        else
            //单目初始化
            MonocularInitialization();

        //更新帧绘制器中存储的最新状态
        mpFrameDrawer->Update(this);

        //这个状态量在上面的初始化函数中被更新
        if(mState!=OK)
            return;
    }
    else
    {
        // System is initialized. Track Frame.
        // bOK为临时变量,用于表示每个函数是否执行成功
        bool bOK;

        // Initial camera pose estimation using motion model or relocalization (if tracking is lost)
        // mbOnlyTracking等于false表示正常SLAM模式(定位+地图更新),mbOnlyTracking等于true表示仅定位模式
        // tracking 类构造时默认为false。在viewer中有个开关ActivateLocalizationMode,可以控制是否开启mbOnlyTracking
        if(!mbOnlyTracking)
        {
            // Local Mapping is activated. This is the normal behaviour, unless
            // you explicitly activate the "only tracking" mode.

            // Step 2:跟踪进入正常SLAM模式,有地图更新
            // 是否正常跟踪
            if(mState==OK)
            {
                // Local Mapping might have changed some MapPoints tracked in last frame
                // Step 2.1 检查并更新上一帧被替换的MapPoints
                // 局部建图线程则可能会对原有的地图点进行替换.在这里进行检查
                CheckReplacedInLastFrame();

                // Step 2.2 运动模型是空的或刚完成重定位,跟踪参考关键帧;否则恒速模型跟踪
                // 第一个条件,如果运动模型为空,说明是刚初始化开始,或者已经跟丢了
                // 第二个条件,如果当前帧紧紧地跟着在重定位的帧的后面,我们将重定位帧来恢复位姿
                // mnLastRelocFrameId 上一次重定位的那一帧
                if(mVelocity.empty() || mCurrentFrame.mnId<mnLastRelocFrameId+2)
                {
                    // 用最近的关键帧来跟踪当前的普通帧
                    // 通过BoW的方式在参考帧中找当前帧特征点的匹配点
                    // 优化每个特征点都对应3D点重投影误差即可得到位姿
                    bOK = TrackReferenceKeyFrame();
                }
                else
                {
                    // 用最近的普通帧来跟踪当前的普通帧
                    // 根据恒速模型设定当前帧的初始位姿
                    // 通过投影的方式在参考帧中找当前帧特征点的匹配点
                    // 优化每个特征点所对应3D点的投影误差即可得到位姿
                    bOK = TrackWithMotionModel();
                    if(!bOK)
                        //根据恒速模型失败了,只能根据参考关键帧来跟踪
                        bOK = TrackReferenceKeyFrame();
                }
            }
            else
            {
                // 如果跟踪状态不成功,那么就只能重定位了
                // BOW搜索,EPnP求解位姿
                bOK = Relocalization();
            }
        }
        else        
        {
            // Localization Mode: Local Mapping is deactivated
            // Step 2:只进行跟踪tracking,局部地图不工作
            if(mState==LOST)
            {
                // Step 2.1 如果跟丢了,只能重定位
                bOK = Relocalization();
            }
            else    
            {
                // mbVO是mbOnlyTracking为true时的才有的一个变量
                // mbVO为false表示此帧匹配了很多的MapPoints,跟踪很正常 (注意有点反直觉)
                // mbVO为true表明此帧匹配了很少的MapPoints,少于10个,要跪的节奏
                if(!mbVO)
                {
                    // Step 2.2 如果跟踪正常,使用恒速模型 或 参考关键帧跟踪
                    // In last frame we tracked enough MapPoints in the map
                    if(!mVelocity.empty())
                    {
                        bOK = TrackWithMotionModel();
                        // ? 为了和前面模式统一,这个地方是不是应该加上
                        // if(!bOK)
                        //    bOK = TrackReferenceKeyFrame();
                    }
                    else
                    {
                        // 如果恒速模型不被满足,那么就只能够通过参考关键帧来定位
                        bOK = TrackReferenceKeyFrame();
                    }
                }
                else
                {
                    // In last frame we tracked mainly "visual odometry" points.
                    // We compute two camera poses, one from motion model and one doing relocalization.
                    // If relocalization is sucessfull we choose that solution, otherwise we retain
                    // the "visual odometry" solution.

                    // mbVO为true,表明此帧匹配了很少(小于10)的地图点,要跪的节奏,既做跟踪又做重定位

                    //MM=Motion Model,通过运动模型进行跟踪的结果
                    bool bOKMM = false;
                    //通过重定位方法来跟踪的结果
                    bool bOKReloc = false;
                    
                    //运动模型中构造的地图点
                    vector<MapPoint*> vpMPsMM;
                    //在追踪运动模型后发现的外点
                    vector<bool> vbOutMM;
                    //运动模型得到的位姿
                    cv::Mat TcwMM;

                    // Step 2.3 当运动模型有效的时候,根据运动模型计算位姿
                    if(!mVelocity.empty())
                    {
                        bOKMM = TrackWithMotionModel();

                        // 将恒速模型跟踪结果暂存到这几个变量中,因为后面重定位会改变这些变量
                        vpMPsMM = mCurrentFrame.mvpMapPoints;
                        vbOutMM = mCurrentFrame.mvbOutlier;
                        TcwMM = mCurrentFrame.mTcw.clone();
                    }

                    // Step 2.4 使用重定位的方法来得到当前帧的位姿
                    bOKReloc = Relocalization();

                    // Step 2.5 根据前面的恒速模型、重定位结果来更新状态
                    if(bOKMM && !bOKReloc)
                    {
                        // 恒速模型成功、重定位失败,重新使用之前暂存的恒速模型结果
                        mCurrentFrame.SetPose(TcwMM);
                        mCurrentFrame.mvpMapPoints = vpMPsMM;
                        mCurrentFrame.mvbOutlier = vbOutMM;

                        //? 疑似bug!这段代码是不是重复增加了观测次数?后面 TrackLocalMap 函数中会有这些操作
                        // 如果当前帧匹配的3D点很少,增加当前可视地图点的被观测次数
                        if(mbVO)
                        {
                            // 更新当前帧的地图点被观测次数
                            for(int i =0; i<mCurrentFrame.N; i++)
                            {
                                //如果这个特征点形成了地图点,并且也不是外点的时候
                                if(mCurrentFrame.mvpMapPoints[i] && !mCurrentFrame.mvbOutlier[i])
                                {
                                    //增加能观测到该地图点的帧数
                                    mCurrentFrame.mvpMapPoints[i]->IncreaseFound();
                                }
                            }
                        }
                    }
                    else if(bOKReloc)
                    {
                        // 只要重定位成功整个跟踪过程正常进行(重定位与跟踪,更相信重定位)
                        mbVO = false;
                    }
                    //有一个成功我们就认为执行成功了
                    bOK = bOKReloc || bOKMM;
                }
            }
        }

        // 将最新的关键帧作为当前帧的参考关键帧
        mCurrentFrame.mpReferenceKF = mpReferenceKF;

        // If we have an initial estimation of the camera pose and matching. Track the local map.
        // Step 3:在跟踪得到当前帧初始姿态后,现在对local map进行跟踪得到更多的匹配,并优化当前位姿
        // 前面只是跟踪一帧得到初始位姿,这里搜索局部关键帧、局部地图点,和当前帧进行投影匹配,得到更多匹配的MapPoints后进行Pose优化
        if(!mbOnlyTracking)
        {
            if(bOK)
                bOK = TrackLocalMap();
        }
        else
        {
            // mbVO true means that there are few matches to MapPoints in the map. We cannot retrieve
            // a local map and therefore we do not perform TrackLocalMap(). Once the system relocalizes
            // the camera we will use the local map again.

            // 重定位成功
            if(bOK && !mbVO)
                bOK = TrackLocalMap();
        }

        //根据上面的操作来判断是否追踪成功
        if(bOK)
            mState = OK;
        else
            mState=LOST;

        // Step 4:更新显示线程中的图像、特征点、地图点等信息
        mpFrameDrawer->Update(this);

        // If tracking were good, check if we insert a keyframe
        //只有在成功追踪时才考虑生成关键帧的问题
        if(bOK)
        {
            // Update motion model
            // Step 5:跟踪成功,更新恒速运动模型
            if(!mLastFrame.mTcw.empty())
            {
                // 更新恒速运动模型 TrackWithMotionModel 中的mVelocity
                cv::Mat LastTwc = cv::Mat::eye(4,4,CV_32F);
                mLastFrame.GetRotationInverse().copyTo(LastTwc.rowRange(0,3).colRange(0,3));
                mLastFrame.GetCameraCenter().copyTo(LastTwc.rowRange(0,3).col(3));
                // mVelocity = Tcl = Tcw * Twl,表示上一帧到当前帧的变换, 其中 Twl = LastTwc
                mVelocity = mCurrentFrame.mTcw*LastTwc; 
            }
            else
                //否则速度为空
                mVelocity = cv::Mat();

            //更新显示中的位姿
            mpMapDrawer->SetCurrentCameraPose(mCurrentFrame.mTcw);

            // Clean VO matches
            // Step 6:清除观测不到的地图点   
            for(int i=0; i<mCurrentFrame.N; i++)
            {
                MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];
                if(pMP)
                    if(pMP->Observations()<1)
                    {
                        mCurrentFrame.mvbOutlier[i] = false;
                        mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);
                    }
            }

            // Delete temporal MapPoints
            // Step 7:清除恒速模型跟踪中 UpdateLastFrame中为当前帧临时添加的MapPoints(仅双目和rgbd)
            // 步骤6中只是在当前帧中将这些MapPoints剔除,这里从MapPoints数据库中删除
            // 临时地图点仅仅是为了提高双目或rgbd摄像头的帧间跟踪效果,用完以后就扔了,没有添加到地图中
            for(list<MapPoint*>::iterator lit = mlpTemporalPoints.begin(), lend =  mlpTemporalPoints.end(); lit!=lend; lit++)
            {
                MapPoint* pMP = *lit;
                delete pMP;
            }

            // 这里不仅仅是清除mlpTemporalPoints,通过delete pMP还删除了指针指向的MapPoint
            // 不能够直接执行这个是因为其中存储的都是指针,之前的操作都是为了避免内存泄露
            mlpTemporalPoints.clear();

            // Check if we need to insert a new keyframe
            // Step 8:检测并插入关键帧,对于双目或RGB-D会产生新的地图点
            if(NeedNewKeyFrame())
                CreateNewKeyFrame();

            // We allow points with high innovation (considererd outliers by the Huber Function)
            // pass to the new keyframe, so that bundle adjustment will finally decide
            // if they are outliers or not. We don't want next frame to estimate its position
            // with those points so we discard them in the frame.
            // 作者这里说允许在BA中被Huber核函数判断为外点的传入新的关键帧中,让后续的BA来审判他们是不是真正的外点
            // 但是估计下一帧位姿的时候我们不想用这些外点,所以删掉

            //  Step 9 删除那些在bundle adjustment中检测为outlier的地图点
            for(int i=0; i<mCurrentFrame.N;i++)
            {
                // 这里第一个条件还要执行判断是因为, 前面的操作中可能删除了其中的地图点
                if(mCurrentFrame.mvpMapPoints[i] && mCurrentFrame.mvbOutlier[i])
                    mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);
            }
        }

        // Reset if the camera get lost soon after initialization
        // Step 10 如果初始化后不久就跟踪失败,并且relocation也没有搞定,只能重新Reset
        if(mState==LOST)
        {
            //如果地图中的关键帧信息过少的话,直接重新进行初始化了
            if(mpMap->KeyFramesInMap()<=5)
            {
                cout << "Track lost soon after initialisation, reseting..." << endl;
                mpSystem->Reset();
                return;
            }
        }

        //确保已经设置了参考关键帧
        if(!mCurrentFrame.mpReferenceKF)
            mCurrentFrame.mpReferenceKF = mpReferenceKF;

        // 保存上一帧的数据,当前帧变上一帧
        mLastFrame = Frame(mCurrentFrame);
    }

    // Store frame pose information to retrieve the complete camera trajectory afterwards.
    // Step 11:记录位姿信息,用于最后保存所有的轨迹
    if(!mCurrentFrame.mTcw.empty())
    {
        // 计算相对姿态Tcr = Tcw * Twr, Twr = Trw^-1
        cv::Mat Tcr = mCurrentFrame.mTcw*mCurrentFrame.mpReferenceKF->GetPoseInverse();
        //保存各种状态
        mlRelativeFramePoses.push_back(Tcr);
        mlpReferences.push_back(mpReferenceKF);
        mlFrameTimes.push_back(mCurrentFrame.mTimeStamp);
        mlbLost.push_back(mState==LOST);
    }
    else
    {
        // This can happen if tracking is lost
        // 如果跟踪失败,则相对位姿使用上一次值
        mlRelativeFramePoses.push_back(mlRelativeFramePoses.back());
        mlpReferences.push_back(mlpReferences.back());
        mlFrameTimes.push_back(mlFrameTimes.back());
        mlbLost.push_back(mState==LOST);
    }

}// Tracking 

4.函数解析 

        当一帧传入跟踪线程时,我们先判断跟踪线程的状态:

        跟踪状态有如下几种:

    ///跟踪状态类型
    enum eTrackingState{
        SYSTEM_NOT_READY=-1,        //系统没有准备好的状态,一般就是在启动后加载配置文件和词典文件时候的状态
        NO_IMAGES_YET=0,            //当前无图像
        NOT_INITIALIZED=1,          //有图像但是没有完成初始化
        OK=2,                       //正常时候的工作状态
        LOST=3                      //系统已经跟丢了的状态
    };

    ///跟踪状态
    eTrackingState mState;
    ///上一帧的跟踪状态.这个变量在绘制当前帧的时候会被使用到
    eTrackingState mLastProcessedState;

        如果图像复位过、或者第一次运行,则为NO_IMAGE_YET状态。

        如果我们的SLAM系统没有初始化,则进入SLAM系统初始化函数Tracking::MonocularInitialization并更新帧绘制器中存储的最新状态。

ORB-SLAM2 ---- Tracking::MonocularInitialization函数解析https://mp.csdn.net/mp_blog/creation/editor/127816993

        如果地图初始化成功的话,我们先检查上一帧中的地图点是否需要被替换。

void Tracking::CheckReplacedInLastFrame()
{
    for(int i =0; i<mLastFrame.N; i++)
    {
        MapPoint* pMP = mLastFrame.mvpMapPoints[i];
        //如果这个地图点存在
        if(pMP)
        {
            // 获取其是否被替换,以及替换后的点
            // 这也是程序不直接删除这个地图点删除的原因
            MapPoint* pRep = pMP->GetReplaced();
            if(pRep)
            {   
                //然后替换一下
                mLastFrame.mvpMapPoints[i] = pRep;
            }
        }
    }
}

        运动模型是空的或刚完成重定位,跟踪参考关键帧;

        如果运动模型为空,说明是刚初始化开始,或者已经跟丢了;如果当前帧紧紧地跟着在重定位的帧的后面,我们将重定位帧来恢复位姿。

ORB-SLAM2 ---- Tracking::TrackReferenceKeyFrame函数解析https://blog.csdn.net/qq_41694024/article/details/126467663        否则用恒速模型追踪,用最近的普通帧来跟踪当前的普通帧。

ORB-SLAM2 ---- Tracking::TrackWithMotionModel函数解析https://blog.csdn.net/qq_41694024/article/details/128192481        根据恒速模型失败了,只能根据参考关键帧来跟踪。

ORB-SLAM2 ---- Tracking::TrackReferenceKeyFrame函数解析https://blog.csdn.net/qq_41694024/article/details/126467663        如果跟踪状态不成功,那么就只能重定位了:

ORB-SLAM2 ---- Tracking::Relocalization函数解析https://blog.csdn.net/qq_41694024/article/details/128069470

        若得到了当前帧的位姿,将最新的关键帧作为当前帧的参考关键帧。

        在跟踪得到当前帧初始姿态后,现在对local map进行跟踪得到更多的匹配,并优化当前位姿。

ORB-SLAM2 --- Tracking::TrackLocalMap函数https://blog.csdn.net/qq_41694024/article/details/128307565        如果跟踪局部成功,则更新SLAM系统的状态mState为OK,即SLAM系统正常运行;若跟踪局部地图失败的话,则置SLAM系统的状态为LOST。下次进来一帧后进行重新初始化。

        更新显示线程中的图像、特征点、地图点等信息。

        成功追踪时才考虑生成关键帧的问题:

        跟踪成功,更新恒速运动模型。

            if(!mLastFrame.mTcw.empty())
            {
                // 更新恒速运动模型 TrackWithMotionModel 中的mVelocity
                cv::Mat LastTwc = cv::Mat::eye(4,4,CV_32F);
                mLastFrame.GetRotationInverse().copyTo(LastTwc.rowRange(0,3).colRange(0,3));
                mLastFrame.GetCameraCenter().copyTo(LastTwc.rowRange(0,3).col(3));
                // mVelocity = Tcl = Tcw * Twl,表示上一帧到当前帧的变换, 其中 Twl = LastTwc
                mVelocity = mCurrentFrame.mTcw*LastTwc; 
            }
            else
                //否则速度为空
                mVelocity = cv::Mat();
cv::Mat mTcw; ///< 相机姿态 世界坐标系到相机坐标坐标系的变换矩阵,是我们常规理解中的相机位姿
    /**
     * @brief Get the Rotation Inverse object
     * mRwc存储的是从当前相机坐标系到世界坐标系所进行的旋转,而我们一般用的旋转则说的是从世界坐标系到当前相机坐标系的旋转
     * @return 返回从当前帧坐标系到世界坐标系的旋转
     */
    inline cv::Mat GetRotationInverse()
	{
        return mRwc.clone();
    }

    // Returns the camera center.
    /**
     * @brief 返回位于当前帧位姿时,相机的中心
     * 
     * @return cv::Mat 相机中心在世界坐标系下的3D点坐标
     */
    inline cv::Mat GetCameraCenter()
	{
        return mOw.clone();
    }

        如果上一帧的相机位姿不为空,更新速度mVelocity,意义是表示上一帧到当前帧的变换。

        清除观测不到的地图点。

            for(int i=0; i<mCurrentFrame.N; i++)
            {
                MapPoint* pMP = mCurrentFrame.mvpMapPoints[i];
                if(pMP)
                    if(pMP->Observations()<1)
                    {
                        mCurrentFrame.mvbOutlier[i] = false;
                        mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);
                    }
            }

        清除恒速模型跟踪中 UpdateLastFrame中为当前帧临时添加的MapPoints(仅双目和rgbd),上一步是在当前帧中将这些MapPoints剔除,这里从MapPoints数据库中删除。临时地图点仅仅是为了提高双目或rgbd摄像头的帧间跟踪效果,用完以后就扔了,没有添加到地图中。

            for(list<MapPoint*>::iterator lit = mlpTemporalPoints.begin(), lend =  mlpTemporalPoints.end(); lit!=lend; lit++)
            {
                MapPoint* pMP = *lit;
                delete pMP;
            }

        检测并插入关键帧,对于双目或RGB-D会产生新的地图点。

ORB-SLAM2 --- Tracking::NeedNewKeyFrame函数icon-default.png?t=MBR7https://mp.csdn.net/mp_blog/creation/editor/128473416ORB-SLAM2 --- Tracking::NeedNewKeyFrame函数icon-default.png?t=MBR7https://mp.csdn.net/mp_blog/creation/editor/128473416

        删除那些在bundle adjustment中检测为outlier的地图点。

for(int i=0; i<mCurrentFrame.N;i++)
{
    // 这里第一个条件还要执行判断是因为, 前面的操作中可能删除了其中的地图点
    if(mCurrentFrame.mvpMapPoints[i] && mCurrentFrame.mvbOutlier[i])
        mCurrentFrame.mvpMapPoints[i]=static_cast<MapPoint*>(NULL);
}

        如果初始化后不久就跟踪失败,并且relocation也没有搞定,只能重新Reset。如果地图中的关键帧信息过少的话,直接重新进行初始化了。

if(mState==LOST)
{
    //如果地图中的关键帧信息过少的话,直接重新进行初始化了
    if(mpMap->KeyFramesInMap()<=5)
    {
        cout << "Track lost soon after initialisation, reseting..." << endl;
        mpSystem->Reset();
        return;
    }
}

        确保已经设置了参考关键帧,保存上一帧的数据,当前帧变上一帧。

if(!mCurrentFrame.mpReferenceKF)
    mCurrentFrame.mpReferenceKF = mpReferenceKF;

// 保存上一帧的数据,当前帧变上一帧
mLastFrame = Frame(mCurrentFrame);

        最后记录位姿信息,用于最后保存所有的轨迹。

    if(!mCurrentFrame.mTcw.empty())
    {
        // 计算相对姿态Tcr = Tcw * Twr, Twr = Trw^-1
        cv::Mat Tcr = mCurrentFrame.mTcw*mCurrentFrame.mpReferenceKF->GetPoseInverse();
        //保存各种状态
        mlRelativeFramePoses.push_back(Tcr);
        mlpReferences.push_back(mpReferenceKF);
        mlFrameTimes.push_back(mCurrentFrame.mTimeStamp);
        mlbLost.push_back(mState==LOST);
    }
    else
    {
        // This can happen if tracking is lost
        // 如果跟踪失败,则相对位姿使用上一次值
        mlRelativeFramePoses.push_back(mlRelativeFramePoses.back());
        mlpReferences.push_back(mlpReferences.back());
        mlFrameTimes.push_back(mlFrameTimes.back());
        mlbLost.push_back(mState==LOST);
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

做开发的朋友说软件测试是个人都能学,我当面怒怼

那就分析一下测试工程师需要会哪些技能&#xff1a; 01 软件测试理论 作为一个软件测试人员&#xff0c;总不能不知道软件测试本身的东西吧。 你总要知道软件测试是什么&#xff1f;什么是测试用例&#xff1f;什么是缺陷&#xff1f;软件测试有些什么特点&#xff1f;这些…

OpenHarmony#深入浅出学习eTs#(八)“猜大小”小游戏

本项目Gitee仓地址&#xff1a;[深入浅出eTs学习: 带大家深入浅出学习eTs (gitee.com)]( 上一章节提到的模拟器存在的BUG问题&#xff0c;目前没有办法直接改善&#xff0c;本来打算直接使用鸿蒙远程设备来实现&#xff0c;但是发现支持API8的设备都被抢光了&#xff08;包括模…

如何使用谷歌地图和LightningChart创建地理空间地图图表?

LightningChart JS 是一款高性能的 JavaScript 图表工具&#xff0c;专注于性能密集型、实时可视化图表解决方案。 LightningChart .JS | 下载试用&#xff08;qun&#xff1a;740060302&#xff09;https://www.evget.com/product/4189/download Google Static Maps API 是一…

前端基础(二)_JavaScript变量、JavaScript标识符、JavaScript获取元素、JavaScript的鼠标事件

一、JavaScript变量 变量是存储数据的容器&#xff0c;例x10&#xff0c;则x中存储的值为10。 语法&#xff1a;var 变量名 值。 1.1、变量的声明 先声明后赋值 var x; // 声明变量x alert(x); // undefined 所有声明了但是没有赋值的变量&#xff0c;结果都为undefined X…

单商户商城系统功能拆解55—后台设置

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

水文监测系统 水文遥测终端机 遥测终端机-助力母亲河平稳度汛

平升电子水文监测系统/水文遥测终端机/遥测终端机实现对江河流域水位、降水量、流量、流速、水质、闸门开启度、墒情等数据的实时采集、报送和处理。为防汛抗旱减灾提供科学依据和有效信息共享&#xff0c;保障人民群众生命财产安全&#xff0c;满足水利和经济社会发展对水文服…

虚拟化平台安装并升级显卡驱动

前言 在虚拟化平台上&#xff0c;虚拟化要使用vGPU&#xff0c;需要同时在主机和虚拟机上安装显卡驱动&#xff0c;主机和虚拟机的显卡驱动需要保守一致。安装驱动时先安装主机驱动&#xff0c;再安装虚拟机驱动。 驱动下载 可以从显卡官网许可中心下载对应的虚拟化驱动&…

一篇文章教小白学会搭建 vite + ts + vue3 项目,手把手教程,不会算我输

目录 一、基础环境和工具 1. 使用 pnpm 代替 npm 2. 使用 pinia 代替 vuex 3. 使用 windicss 4. 使用 vscode 5. 使用 sourcetree 6. 了解 vite ts vue 二、项目搭建 1. 流程图 2. 初始化项目 2.1 使用命令初始化项目 2.2 项目结构 3. 使用 git 3.1 创建本地的…

【JavaSE成神之路】数组思考题讲解

哈喽&#xff0c;我是兔哥呀&#xff0c;今天就让我们继续这个JavaSE成神之路&#xff01; 这一节啊&#xff0c;咱们要学习的内容是数组的思考题讲解&#xff0c;也就是上一节我留下的两个小作业。 数组是非常重要的知识点&#xff0c;也是我们后续学习各种数据结构的基础&am…

【MMC子系统】 一、MMC/SD/SDIO介绍

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、前言2、MMC/SD/SDIO介绍3、总线接口4、参考文章1、前言 该节学习Linux Kernel的MMC子系统&#xff…

JVM学习笔记(3)—— 运行时数据区—— 程序计数器、虚拟机栈、本地方法栈

程序计数器、虚拟机栈、本地方法栈都是线程私有的&#xff0c;jvm中每个线程都有一份 一、程序计数器 JVM中的程序计数器是一个与PC寄存器功能类似的逻辑结构&#xff0c;用于记录当前线程要执行的下一条jvm指令的地址&#xff0c;解释器读取到对应的jvm指令后将其翻译成机器指…

解决ubuntu(Linux)桌面/应用不插电掉帧问题

解决ubuntu(Linux)桌面/应用不插电掉帧问题 笔记本安装的ubuntu的gnome桌面&#xff0c;插电状态下触摸板动画丝滑&#xff0c;翻看浏览器文章的时候也不会有延迟掉帧的情况&#xff0c;但是离开了充电器&#xff0c;电池供电就会掉帧。 思路&#xff1a;在插电模式下&#x…

Spring Boot三种跨域解决方案与Spring Security跨域解决方案

跨域解决方案1、什么是跨域2、Spring Boot跨域解决方案1、服务端代码2、前端页面3、运行服务3、Spring Security跨域解决方案1、什么是跨域 什么是跨域&#xff0c;首先可以参考我之前写的这篇文章&#xff1a;JavaWeb跨域问题及解决方案 &#xff0c;另外我下面会做补充。 很…

unreal中actor的property replicate简单小节

首先参考的网址是官网中的&#xff1a; https://docs.unrealengine.com/5.1/en-US/multiplayer-programming-quick-start-for-unreal-engine/ unreal引擎的版本是5.1 还原的过程相对比较简单&#xff0c;主要的精力花在了编译报错和调试的过程。 属性复制的流程如下&#xff…

尚医通-MongDB简介-安装-概念-操作(十六)

目录 &#xff08;1&#xff09;MongDB-简介和安装 &#xff08;2&#xff09;MongDB-概念和操作 &#xff08;1&#xff09;MongDB-简介和安装 1、NoSQL简介 MongDB为什么要用呢&#xff1f;之前我们用MySQL数据库&#xff0c;如果数据达到一定的量级&#xff0c;或者有高…

QT 之SQLite数据库

文章目录一、windows下使用命令行方法操作db文件1、 安装sqlite2、 使用sqlite二、QT操作sqlite1、 建立数据库2、 打开数据库3 、关闭数据库4、 创建数据表5 、插入数据6、 删除表数据7、 修改表数据8、 查询数据——遍历查询、条件查询一、windows下使用命令行方法操作db文件…

计算机网络期中考试部分题目

1. &#xff08;1&#xff09;以太网帧的数据部分是IP数据报&#xff0c;只要数出相应字段所在的字节即可。由图可知以太网帧头部有6&#xff0b;6214字节&#xff0c;IP数据报首部的目的P地址字段前有4x4 16字节&#xff0c;从帧的第1字节开始数141630字节&#xff0c;得目的P…

java怎么完成输出语句

我们在前面的文章和案例中&#xff0c;其实早就知道Java是如何实现输出功能的了。没错&#xff0c;就是利用System.out.println()语句来实现的&#xff01;接下来壹哥就给大家详细解读一下这个语句的作用及其含义。 1. System 我们先来看看System是个什么东东。 System是Jav…

不推荐别的了,IDEA 自带的数据库工具就很牛逼!

MySQL 等数据库客户端软件市面上非常多了&#xff0c;别的栈长就不介绍了&#xff0c; 其实 IntelliJ IDEA 自带的数据库工具就很牛逼&#xff0c;不信你继续往下看。 本文以 IntelliJ IDEA/ Mac 版本作为演示&#xff0c;其他版本的应该也差距不大&#xff01; 1、打开数据库…

JPE驱动器维修印刷机驱动器维修JV4-380-1410

印刷机水辘/墨辘/墨斗辊电机马达驱动器维修、墨控电机驱动器维修、JPE驱动器维修。 三菱印刷机电路板维修范围&#xff1a;东洋变频器维修&#xff0c;油墨电机维修&#xff0c;水辊变频器维修&#xff0c;电眼控制板维修&#xff0c;接口板维修&#xff0c;电源维修等。 小森…