首页 > 其他分享 >ORB-SLAM2 ---- LocalMapping::MapPointCulling()和LocalMapping::CreateNewMapPoints()

ORB-SLAM2 ---- LocalMapping::MapPointCulling()和LocalMapping::CreateNewMapPoints()

时间:2024-11-30 14:01:19浏览次数:8  
标签:关键帧 MapPointCulling const mpCurrentKeyFrame LocalMapping float 地图 ---- cv

文章目录

一、函数意义

这两个函数是局部见图的核心函数之二,作用是删除不好的地图点,为创造新的地图点。学习局部建图线程和跟踪线程一样,先要知道什么是局部见图,局部建图就是优化局部的地图点,包括删除不好的地图点和增加新的地图点,以及后续对地图点位置的优化调整。大家都知道,SLAM是即时定位与实时建图,在ORB-SLAM2中,追踪线程主要负责即时定位,局部建图线程主要负责实时建图。

二、LocalMapping::MapPointCulling()

1. 函数讲解

本函数的作用是,筛选需要筛选的点,将不符合要求的地图点删除。需要筛选地图点的列表在下面那个函数中产生。这里在代码中是先运行MapPointCulling(),后运行CreateNewMapPoints(),但实际上是先创建新的地图点,然后检查新的地图点是否符合要求。这里删除的地图点有三种

  1. 被标记为isBad的点
  2. 跟踪到该点的帧数远远小于预计跟踪到它的帧数(0.25倍)
  3. 该点创建到现在已经经历了两个关键帧,但是观测数值小于阈值(单目2次,双目3次)

2. 函数代码

/**
 * @brief 检查新增地图点,根据地图点的观测情况剔除质量不好的新增的地图点
 * mlpRecentAddedMapPoints:存储新增的地图点,这里是要删除其中不靠谱的
 */ 
void LocalMapping::MapPointCulling()
{
    // Check Recent Added MapPoints
    list<MapPoint*>::iterator lit = mlpRecentAddedMapPoints.begin();
    const unsigned long int nCurrentKFid = mpCurrentKeyFrame->mnId;

    // 设置观测次数的阈值
    int nThObs;
    if(mbMonocular)
        nThObs = 2;
    else
        nThObs = 3;
    const int cnThObs = nThObs;

    // 遍历所有新增的地图点
    while(lit!=mlpRecentAddedMapPoints.end())
    {
        // 获取地图点指针
        MapPoint* pMP = *lit;
        // 将坏点删除
        if(pMP->isBad())
        {
            lit = mlpRecentAddedMapPoints.erase(lit);
        }
        // 跟踪到该地图点的帧数相比预计可观测到该地图点的帧数的比例小于25%,从地图中删除
        else if(pMP->GetFoundRatio()<0.25f )
        {
            pMP->SetBadFlag();
            lit = mlpRecentAddedMapPoints.erase(lit);
        }
        // 从该点建立开始,到现在已经过了不小于2个关键帧
        // 但是观测到该点的相机数却不超过阈值cnThObs,从地图中删除
        else if(((int)nCurrentKFid-(int)pMP->mnFirstKFid)>=2 && pMP->Observations()<=cnThObs)
        {
            pMP->SetBadFlag();
            lit = mlpRecentAddedMapPoints.erase(lit);
        }
        // 从建立该点开始,已经过了3个关键帧而没有被剔除,则认为是质量高的点
        // 因此没有SetBadFlag(),仅从队列中删除
        else if(((int)nCurrentKFid-(int)pMP->mnFirstKFid)>=3)
            lit = mlpRecentAddedMapPoints.erase(lit);
        else
            lit++;
    }
}

三、LocalMapping::CreateNewMapPoints()

1. 函数讲解

本函数的作用是产生新的地图点,产生新地图点的方式是,先找到与当前关键帧相邻关系最好的关键帧(双目10个,单目20,寻找方式如下图所示),然后遍历这些关键帧,将它们与当前关键帧进行特征匹配,找出匹配的点(这里的匹配方式是词袋加速匹配,中见加了一个F12对极约束的判断条件)。找出匹配的点后,删除视角差不符合要求的点,然后将地图点投影到当前帧和相邻帧中,计算投影误差,删除掉投影误差大的点。最后计算两个关键帧相机中心到地图点世界坐标之间的模场,删除模长为0的点(几乎不可能,模长为0表示地图点和相机中心重合),然后计算两个模长的比值,以及带上金字塔尺度的比值,删除两种比值相差太大的点。意思是两帧相邻关系很好,不应该出现匹配的特征点对应的金字塔层级相差太大。接下来就是善后工作,给地图点添加能观测到它的关键帧,个关键帧添加这个地图点的观测。这些新生成的地图点并不是直接就能用,还要经过MapPointCulling()的筛选。这段代码看起来很长其实冗余信息很多,可以将其改到只有原来一半的长度,有兴趣的话可以自己改改看。
请添加图片描述

2. 函数代码

// 生成新的地图带点
void LocalMapping::CreateNewMapPoints()
{
    // Retrieve neighbor keyframes in covisibility graph
    // 设置共视关键帧数量,对于单目相机,
    // 需要更多共视关系较好的关键帧来建立地图,因此如果是单目模式,将 nn 设置为 20,否则为 10。
    int nn = 10;
    if(mbMonocular)
        nn=20;
    
    // 得到与该关键帧连接的前N个最强共视关键帧(已按权值排序),双目10个,单目20
    const vector<KeyFrame*> vpNeighKFs = mpCurrentKeyFrame->GetBestCovisibilityKeyFrames(nn);

    // 初始化特征匹配器
    ORBmatcher matcher(0.6,false);

    // 获取当前关键帧的位姿及内参

    // 获取相机的旋转矩阵以及他的逆
    cv::Mat Rcw1 = mpCurrentKeyFrame->GetRotation();
    cv::Mat Rwc1 = Rcw1.t();

    // 获取相机的平移向量
    cv::Mat tcw1 = mpCurrentKeyFrame->GetTranslation();
    cv::Mat Tcw1(3,4,CV_32F);

    // 将相机的旋转矩阵和平移向量组合成一个齐次变换矩阵Tcw1
    Rcw1.copyTo(Tcw1.colRange(0,3));
    tcw1.copyTo(Tcw1.col(3));

    // 获取相机中心点
    cv::Mat Ow1 = mpCurrentKeyFrame->GetCameraCenter();

    // 得到当前关键帧(左目)光心在世界坐标系中的坐标、内参
    const float &fx1 = mpCurrentKeyFrame->fx;
    const float &fy1 = mpCurrentKeyFrame->fy;
    const float &cx1 = mpCurrentKeyFrame->cx;
    const float &cy1 = mpCurrentKeyFrame->cy;

    // 获取焦距的倒数
    const float &invfx1 = mpCurrentKeyFrame->invfx;
    const float &invfy1 = mpCurrentKeyFrame->invfy;
    const float ratioFactor = 1.5f*mpCurrentKeyFrame->mfScaleFactor;

    // 记录三角化成功的地图点数目
    int nnew=0;

    // Search matches with epipolar restriction and triangulate
    // 遍历与该关键帧连接的前N个最强共视关键帧
    for(size_t i=0; i<vpNeighKFs.size(); i++)
    {
        // 如果关键帧队列中成员数为0,则返回
        if(i>0 && CheckNewKeyFrames())
            return;

        // 获取这个关键字帧的指针
        KeyFrame* pKF2 = vpNeighKFs[i];

        // Check first that baseline is not too short
        // 获取这个关键帧的相机中心
        cv::Mat Ow2 = pKF2->GetCameraCenter();
        // 基线向量,计算两个关键帧间的相机位移
        cv::Mat vBaseline = Ow2-Ow1;
        // 获取相机基线长度
        const float baseline = cv::norm(vBaseline);

        // 双目情况
        if(!mbMonocular)
        {
            // 如果是双目相机,关键帧间距小于本身的基线时不生成3D点
            // 因为太短的基线下能够恢复的地图点不稳定
            if(baseline<pKF2->mb)
            continue;
        }
        else
        {
            // 单目相机情况
            // 获取相邻关键帧的场景深度中值 
            const float medianDepthKF2 = pKF2->ComputeSceneMedianDepth(2);
            const float ratioBaselineDepth = baseline/medianDepthKF2;

            // 如果比例特别小,基线太短恢复3D点不准,那么跳过当前邻接的关键帧,不生成3D点
            if(ratioBaselineDepth<0.01)
                continue;
        }

        // Compute Fundamental Matrix
        // 根据两个关键帧的位姿计算它们之间的基础矩阵F
        // 基础矩阵,通常记作F,描述了两个视图(或相机图像)之间的几何关系。
        // 其主要作用是在两张图像中找到对应点(缩减匹配范围)
        cv::Mat F12 = ComputeF12(mpCurrentKeyFrame,pKF2);

        // Search matches that fullfil epipolar constraint
        // 创建匹配索引的容器
        vector<pair<size_t,size_t> > vMatchedIndices;

        // 利用基础矩阵F12极线约束,用BoW加速匹配两个关键帧的未匹配的特征点,产生新的匹配点对
        // 但是这个函数没用用F12来加速匹配,而是当成了一个判断条件
        matcher.SearchForTriangulation(mpCurrentKeyFrame,pKF2,F12,vMatchedIndices,false);

        // 获取右目的参数(和左目相同) 
        cv::Mat Rcw2 = pKF2->GetRotation();
        cv::Mat Rwc2 = Rcw2.t();
        cv::Mat tcw2 = pKF2->GetTranslation();
        cv::Mat Tcw2(3,4,CV_32F);
        Rcw2.copyTo(Tcw2.colRange(0,3));
        tcw2.copyTo(Tcw2.col(3));

        const float &fx2 = pKF2->fx;
        const float &fy2 = pKF2->fy;
        const float &cx2 = pKF2->cx;
        const float &cy2 = pKF2->cy;
        const float &invfx2 = pKF2->invfx;
        const float &invfy2 = pKF2->invfy;

        // Triangulate each match
        // 对每对匹配通过三角化生成3D点,和 Triangulate函数差不多
        // 遍历所有的匹配点
        const int nmatches = vMatchedIndices.size();
        for(int ikp=0; ikp<nmatches; ikp++)
        {
            // 获取当前关键帧该点的索引,和相邻帧对应该点的索引
            const int &idx1 = vMatchedIndices[ikp].first;
            const int &idx2 = vMatchedIndices[ikp].second;

            // 匹配在当前关键帧中的特征点
            const cv::KeyPoint &kp1 = mpCurrentKeyFrame->mvKeysUn[idx1];
            // mvuRight中存放着双目的深度值,如果不是双目,其值将为-1 
            const float kp1_ur=mpCurrentKeyFrame->mvuRight[idx1];

            // 判断双目的条件,无深度信息则为false,有正的深度信息则为true
            bool bStereo1 = kp1_ur>=0;

            // 当前匹配在邻接关键帧中的特征点 
            const cv::KeyPoint &kp2 = pKF2->mvKeysUn[idx2];
            // mvuRight中存放着双目的深度值,如果不是双目,其值将为-1
            const float kp2_ur = pKF2->mvuRight[idx2];
            bool bStereo2 = kp2_ur>=0;

            // Check parallax between rays
            // 特征点反投影,其实得到的是在各自相机坐标系下的一个非归一化的方向向量,和这个点的反投影射线重合 
            cv::Mat xn1 = (cv::Mat_<float>(3,1) << (kp1.pt.x-cx1)*invfx1, (kp1.pt.y-cy1)*invfy1, 1.0);
            cv::Mat xn2 = (cv::Mat_<float>(3,1) << (kp2.pt.x-cx2)*invfx2, (kp2.pt.y-cy2)*invfy2, 1.0);

            // ray1 和 ray2 代表特征点在世界坐标系中的方向,起始于相机光心,指向该特征点。
            cv::Mat ray1 = Rwc1*xn1;
            cv::Mat ray2 = Rwc2*xn2;

            // 计算视当前帧和邻接关键帧差角的余弦值:
            //  ray1.dot(ray2) 计算 ray1 和 ray2 之间的点积,
            // cv::norm(ray1) 和 cv::norm(ray2) 分别为 ray1 和 ray2 的模长。
            const float cosParallaxRays = ray1.dot(ray2)/(cv::norm(ray1)*cv::norm(ray2));

            // cosParallaxStereo1 和 cosParallaxStereo2表示当前关键帧和相邻关键帧的“立体视差角余弦值”
            // +1是为了初始化一个较大的值
            float cosParallaxStereo = cosParallaxRays+1;
            float cosParallaxStereo1 = cosParallaxStereo;
            float cosParallaxStereo2 = cosParallaxStereo;

            // 传感器是双目相机,并且当前的关键帧的这个点有对应的深度
            // 假设是平行的双目相机,计算出双目相机观察这个点的时候的视差角余弦
            // bStereo1为true,表示当前关键帧中有深度信息(双目或者RGBD)
            if(bStereo1)

                // 这个是以基线长度为对边,地图点在当前帧中的深度为邻边组成角的余弦值
                cosParallaxStereo1 = cos(2*atan2(mpCurrentKeyFrame->mb/2,mpCurrentKeyFrame->mvDepth[idx1]));
            // 传感器是双目相机,并且邻接的关键帧的这个点有对应的深度,和上面一样操作
            // bStereo2为true,表示邻接关键帧中有深度信息(双目或者RGBD)
            else if(bStereo2)
            // 这个是以基线长度为对边,地图点在相邻关键帧中的深度为邻边组成角的余弦值
                cosParallaxStereo2 = cos(2*atan2(pKF2->mb/2,pKF2->mvDepth[idx2]));

            // 得到当前帧和邻接帧中视差最小的一个
            cosParallaxStereo = min(cosParallaxStereo1,cosParallaxStereo2);

            // 三角化恢复3D点
            // 创建三角化的矩阵
            cv::Mat x3D;

            
            // cosParallaxRays>0 && (bStereo1 || bStereo2 || cosParallaxRays<0.9998)表明视差角正常,0.9998 对应1°
            // cosParallaxRays < cosParallaxStereo 表明匹配点对夹角大于双目本身观察三维点夹角
            // 匹配点对夹角大,用三角法恢复3D点(重投影去除误差大的地图点)
            // 将地图点的坐标通过矩阵转换的方式,将其投影到两个相机的坐标系下,
            // 然后计算投影的点与地图点对应特征点的误差大小,误差超过阈值的点去除
            if(cosParallaxRays<cosParallaxStereo && cosParallaxRays>0 && (bStereo1 || bStereo2 || cosParallaxRays<0.9998))
            {
                // Linear Triangulation Method
                // 见Initializer.cc的 Triangulate 函数,实现是一样的,顶多就是把投影矩阵换成了变换矩阵 
                cv::Mat A(4,4,CV_32F);
                A.row(0) = xn1.at<float>(0)*Tcw1.row(2)-Tcw1.row(0);
                A.row(1) = xn1.at<float>(1)*Tcw1.row(2)-Tcw1.row(1);
                A.row(2) = xn2.at<float>(0)*Tcw2.row(2)-Tcw2.row(0);
                A.row(3) = xn2.at<float>(1)*Tcw2.row(2)-Tcw2.row(1);

                cv::Mat w,u,vt;
                cv::SVD::compute(A,w,u,vt,cv::SVD::MODIFY_A| cv::SVD::FULL_UV);

                
                x3D = vt.row(3).t();

                // 归一化之前的检查
                if(x3D.at<float>(3)==0)
                    continue;

                // Euclidean coordinates
                // 归一化成为齐次坐标,然后提取前面三个维度作为欧式坐标
                x3D = x3D.rowRange(0,3)/x3D.at<float>(3);

            }
            // 匹配点对夹角小,用双目恢复3D点
            else if(bStereo1 && cosParallaxStereo1<cosParallaxStereo2)
            {
                // 如果是双目,用视差角更大的那个双目信息来恢复,直接用已知3D点反投影了
                x3D = mpCurrentKeyFrame->UnprojectStereo(idx1);                
            }
            else if(bStereo2 && cosParallaxStereo2<cosParallaxStereo1)
            {
                x3D = pKF2->UnprojectStereo(idx2);
            }
            else
                continue; //No stereo and very low parallax

            // 为方便后续计算,转换成为了行向量
            cv::Mat x3Dt = x3D.t();

            //Check triangulation in front of cameras
            // 检测生成的3D点是否在相机前方,不在的话就放弃这个点
            float z1 = Rcw1.row(2).dot(x3Dt)+tcw1.at<float>(2);
            if(z1<=0)
                continue;

            float z2 = Rcw2.row(2).dot(x3Dt)+tcw2.at<float>(2);
            if(z2<=0)
                continue;

            //Check reprojection error in first keyframe
            // 计算3D点在当前关键帧下的重投影误差,误差过大就放弃这个点

            // 获取地图点在当前关键帧下的金字塔层级的平方
            const float &sigmaSquare1 = mpCurrentKeyFrame->mvLevelSigma2[kp1.octave];

            // 将地图点坐标转换到相机坐标系中
            const float x1 = Rcw1.row(0).dot(x3Dt)+tcw1.at<float>(0);
            const float y1 = Rcw1.row(1).dot(x3Dt)+tcw1.at<float>(1);
            const float invz1 = 1.0/z1;

            // 计算误差
            if(!bStereo1)
            {
                // 将相机坐标系下的坐标投影到平面
                float u1 = fx1*x1*invz1+cx1;
                float v1 = fy1*y1*invz1+cy1;

                // 计算地图点和特征点之间的投影误差
                float errX1 = u1 - kp1.pt.x;
                float errY1 = v1 - kp1.pt.y;

                // 假设测量有一个像素的偏差,2自由度卡方检验阈值是5.991
                // 如果投影误差过大,就放弃这个点
                if((errX1*errX1+errY1*errY1)>5.991*sigmaSquare1)
                    continue;
            }
            // 双目情况同理,多了一个右目的x轴误差
            else
            {
                float u1 = fx1*x1*invz1+cx1;
                float u1_r = u1 - mpCurrentKeyFrame->mbf*invz1;
                float v1 = fy1*y1*invz1+cy1;
                float errX1 = u1 - kp1.pt.x;
                float errY1 = v1 - kp1.pt.y;
                float errX1_r = u1_r - kp1_ur;
                if((errX1*errX1+errY1*errY1+errX1_r*errX1_r)>7.8*sigmaSquare1)
                    continue;
            }

            //Check reprojection error in second keyframe
            // 计算3D点在相邻关键帧下的重投影误差,误差过大就放弃这个点
            const float sigmaSquare2 = pKF2->mvLevelSigma2[kp2.octave];
            const float x2 = Rcw2.row(0).dot(x3Dt)+tcw2.at<float>(0);
            const float y2 = Rcw2.row(1).dot(x3Dt)+tcw2.at<float>(1);
            const float invz2 = 1.0/z2;
            if(!bStereo2)
            {
                float u2 = fx2*x2*invz2+cx2;
                float v2 = fy2*y2*invz2+cy2;
                float errX2 = u2 - kp2.pt.x;
                float errY2 = v2 - kp2.pt.y;
                if((errX2*errX2+errY2*errY2)>5.991*sigmaSquare2)
                    continue;
            }
            else
            {
                float u2 = fx2*x2*invz2+cx2;
                float u2_r = u2 - mpCurrentKeyFrame->mbf*invz2;
                float v2 = fy2*y2*invz2+cy2;
                float errX2 = u2 - kp2.pt.x;
                float errY2 = v2 - kp2.pt.y;
                float errX2_r = u2_r - kp2_ur;
                if((errX2*errX2+errY2*errY2+errX2_r*errX2_r)>7.8*sigmaSquare2)
                    continue;
            }

            // Check scale consistency
            // 检查尺度连续性

            // 世界坐标系下,3D点与当前关键帧相机间的向量,方向由相机指向3D点
            cv::Mat normal1 = x3D-Ow1;
            // 计算模长
            float dist1 = cv::norm(normal1);

            // 世界坐标系下,3D点与相邻关键帧相机间的向量,方向由相机指向3D点
            cv::Mat normal2 = x3D-Ow2;
            // 计算模长
            float dist2 = cv::norm(normal2);

            // 模长不能为0(点不能和相机中心重合)
            if(dist1==0 || dist2==0)
                continue;

            //  ratioDist是不考虑金字塔尺度下的距离比例
            const float ratioDist = dist2/dist1;

            // 金字塔尺度因子的比例
            const float ratioOctave = mpCurrentKeyFrame->mvScaleFactors[kp1.octave]/pKF2->mvScaleFactors[kp2.octave];

            /*if(fabs(ratioDist-ratioOctave)>ratioFactor)
                continue;*/

            // 距离的比例和图像金字塔的比例不应该差太多,否则就跳过
            if(ratioDist*ratioFactor<ratioOctave || ratioDist>ratioOctave*ratioFactor)
                continue;

            // Triangulation is succesfull 
            // 三角化生成3D点成功,构造成MapPoint
            MapPoint* pMP = new MapPoint(x3D,mpCurrentKeyFrame,mpMap);

            // 当前关键帧对该地图点添加观测
            pMP->AddObservation(mpCurrentKeyFrame,idx1);     

            // 相邻关键帧对该地图点添加观测       
            pMP->AddObservation(pKF2,idx2);

            // 将该地图点添加到当前关键帧的地图点列表中
            mpCurrentKeyFrame->AddMapPoint(pMP,idx1);

            // 将该地图点添加到上一关键帧的地图点列表中
            pKF2->AddMapPoint(pMP,idx2);

            // 计算地图点最具代表性的描述子
            pMP->ComputeDistinctiveDescriptors();

            // 这行代码更新地图点的法向量和深度信息
            pMP->UpdateNormalAndDepth();

            // 将新创建的地图点(pMP)添加到全局地图对象(mpMap)中
            mpMap->AddMapPoint(pMP);

            // 将新产生的点放入检测队列
            // 这些MapPoints都会经过MapPointCulling函数的检验
            mlpRecentAddedMapPoints.push_back(pMP);

            // 新创建的地图点计数器nnew加1
            nnew++;
        }
    }
}

四、总结

这两个函数相当于局部见图最开始的部分,利用新产生的关键帧进行局部建图,后续还会对地图点和关键帧进行处理,函数里面最难理解的部分是视角差那部分,但你带着答案去看这一部分就不会觉得很难了。我们的相机拍摄频率是很高的,两个相邻的关键帧之间的相机运动不会太大,所以视角差不会太大,如果视角差过大,说明匹配错了,这个地图点就不应该生成。

标签:关键帧,MapPointCulling,const,mpCurrentKeyFrame,LocalMapping,float,地图,----,cv
From: https://blog.csdn.net/uzi_ccc/article/details/144113139

相关文章

  • 如何让Java的线程彼此同步?
    在Java中,线程同步是一个重要的概念,用于确保多个线程在访问共享资源时能够保持数据的一致性和正确性。Java提供了多种线程同步机制,以下是具体的同步方法:一、使用synchronized关键字synchronized同步方法:即在方法声明中使用synchronized关键字。当一个线程访问某个对象的synchr......
  • 了解AQS(AbstractQueuedSynchronizer)
    AQS(AbstractQueuedSynchronizer)是Java并发包中的一个核心同步器框架,它定义了一套多线程访问共享资源的同步机制。一、AQS的定义AQS,全称AbstractQueuedSynchronizer,即抽象队列同步器,是Java中的一个抽象类。它是构建锁或者其他同步组件的基础框架,通过继承AQS,子类可以实现自己的......
  • Flux【基础篇】:ComfyUI Flux.1工作流的本地部署安装教程
    ComfyUIFlux.1工作流不仅在技术层面上实现了突破,更在艺术创作领域开辟了新的天地。利用提示词创作出独特的AI艺术作品,艺术家可以展现更加个性化和创意的作品。让我们一起探索ComfyUIFlux.1工作流的本地部署安装教程,开启AI艺术创作的新篇章。今天我们来分享一下如何在本......
  • 什么是LoRA模型?如何使用和训练LoRA模型?你想要的都在这!
    大家刚接触StableDiffusion时,会听到很多专业术语,其中LoRA模型必定是会被提及到的,那么什么是LoRA模型?它有什么作用呢?本文来为大家做一个解答~1.什么是LoRaLoRA模型全称是:Low-RankAdaptationofLargeLanguageModels,**可以理解为Stable-Diffusion中的一个插件,仅需要少......
  • AvaloniaUI 制作一个AI聊天界面(未编写登录等操作界面)
    注:本程序后端模型使用的是科大讯飞星火大模型免费版API,可以对接自主训练的模型界面展示窗口布局<WindowIcon="/Assets/avalonia-logo.ico"Title="妃妃小窝"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d"x:Class=&quo......
  • IDEA本地运行Spark程序报错:HADOOP_HOME and hadoop.home.dir are unset. 解决方法
    报错信息java.lang.RuntimeException:java.io.FileNotFoundException:java.io.FileNotFoundException:HADOOP_HOMEandhadoop.home.dirareunset.-seehttps://wiki.apache.org/hadoop/WindowsProblems atorg.apache.hadoop.util.Shell.getWinUtilsPath(Shell.java:737)......
  • SD WebUI必备插件安装,菜鸟轻松成高手!
    一个刚学AI绘画的小菜鸟怎么快速成为StableDiffusionde的高手?答案就是SD插件,只要学会使用SD的各种插件,帮你写正向和负向提示词,修复人脸/身体/手指,高清放大图片,指定人物pose,图片微调等等都可以轻松搞定,善用插件是成为高手必经之路。目录1插件安装方法2基础插件介绍3......
  • 大模型开发工程师:2025年的高薪职业路线图
    在人工智能的浪潮中,大模型(LargeModels)已经成为推动技术进步的重要力量。那么,什么是大模型?大模型开发的前景如何?作为一名大模型开发工程师,需要具备哪些核心能力?就业方向有哪些?又该如何学习大模型?一、什么是大模型?大模型,通常指的是参数量达到亿级甚至千亿级的深度学习模型......
  • node.js毕设健身房预约管理系统程序+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于健身房预约管理系统的研究,现有研究主要以传统的健身房运营管理为主,专门针对健身房预约管理系统的研究较少。在国内外,健身房行业发展迅速,但随着会员......
  • node.js毕设露营活动装备租赁系统 程序+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容选题背景关于露营活动装备租赁系统的研究,现有研究多集中在露营地本身的管理或一般性租赁业务的运营模式上,专门针对露营活动装备租赁系统的研究较少。在国内外,露营产......