当前位置: 首页 > news >正文

ORBSLAM3 --- 双目惯导执行ORBSLAM3(一):Stereo_intertail_euroc.cc文件解析

1.执行双目例程的参数

        在Clion中,我们输入以下参数:

/home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Vocabulary/ORBvoc.txt
/home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Examples_old/Stereo-Inertial/EuRoC.yaml
/home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03
/home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Examples_old/Stereo-Inertial/EuRoC_TimeStamps/MH03.txt

        分别对应着ORB词典的位置、配置文件的地址、图像序列的地址、时间戳的地址。

2. Stereo_intertail_euroc.cc文件解析

2.1 标注代码

int main(int argc, char **argv)
{// ORBSLAM3支持多序列建图(多地图建图)//if(argc < 5){cerr << endl << "Usage: ./stereo_inertial_euroc path_to_vocabulary path_to_settings path_to_sequence_folder_1 path_to_times_file_1 (path_to_image_folder_2 path_to_times_file_2 ... path_to_image_folder_N path_to_times_file_N) " << endl;return 1;}const int num_seq = (argc-3)/2;cout << "num_seq = " << num_seq << endl;bool bFileName= (((argc-3) % 2) == 1);string file_name;if (bFileName){file_name = string(argv[argc-1]);cout << "file name: " << file_name << endl;}// Load all sequences:int seq;vector< vector<string> > vstrImageLeft;vector< vector<string> > vstrImageRight;vector< vector<double> > vTimestampsCam;vector< vector<cv::Point3f> > vAcc, vGyro;vector< vector<double> > vTimestampsImu;vector<int> nImages;vector<int> nImu;vector<int> first_imu(num_seq,0);vstrImageLeft.resize(num_seq);vstrImageRight.resize(num_seq);vTimestampsCam.resize(num_seq);vAcc.resize(num_seq);vGyro.resize(num_seq);vTimestampsImu.resize(num_seq);nImages.resize(num_seq);nImu.resize(num_seq);int tot_images = 0;for (seq = 0; seq<num_seq; seq++){cout << "Loading images for sequence " << seq << "...";string pathSeq(argv[(2*seq) + 3]);string pathTimeStamps(argv[(2*seq) + 4]);string pathCam0 = pathSeq + "/mav0/cam0/data";string pathCam1 = pathSeq + "/mav0/cam1/data";string pathImu = pathSeq + "/mav0/imu0/data.csv";LoadImages(pathCam0, pathCam1, pathTimeStamps, vstrImageLeft[seq], vstrImageRight[seq], vTimestampsCam[seq]);cout << "LOADED!" << endl;cout << "Loading IMU for sequence " << seq << "...";LoadIMU(pathImu, vTimestampsImu[seq], vAcc[seq], vGyro[seq]);cout << "LOADED!" << endl;nImages[seq] = vstrImageLeft[seq].size();tot_images += nImages[seq];nImu[seq] = vTimestampsImu[seq].size();if((nImages[seq]<=0)||(nImu[seq]<=0)){cerr << "ERROR: Failed to load images or IMU for sequence" << seq << endl;return 1;}// Find first imu to be considered, supposing imu measurements start firstwhile(vTimestampsImu[seq][first_imu[seq]]<=vTimestampsCam[seq][0])first_imu[seq]++;first_imu[seq]--; // first imu measurement to be considered}// Read rectification parameterscv::FileStorage fsSettings(argv[2], cv::FileStorage::READ);if(!fsSettings.isOpened()){cerr << "ERROR: Wrong path to settings" << endl;return -1;}cv::Mat K_l, K_r, P_l, P_r, R_l, R_r, D_l, D_r;fsSettings["LEFT.K"] >> K_l;fsSettings["RIGHT.K"] >> K_r;fsSettings["LEFT.P"] >> P_l;fsSettings["RIGHT.P"] >> P_r;fsSettings["LEFT.R"] >> R_l;fsSettings["RIGHT.R"] >> R_r;fsSettings["LEFT.D"] >> D_l;fsSettings["RIGHT.D"] >> D_r;int rows_l = fsSettings["LEFT.height"];int cols_l = fsSettings["LEFT.width"];int rows_r = fsSettings["RIGHT.height"];int cols_r = fsSettings["RIGHT.width"];if(K_l.empty() || K_r.empty() || P_l.empty() || P_r.empty() || R_l.empty() || R_r.empty() || D_l.empty() || D_r.empty() ||rows_l==0 || rows_r==0 || cols_l==0 || cols_r==0){cerr << "ERROR: Calibration parameters to rectify stereo are missing!" << endl;return -1;}cv::Mat M1l,M2l,M1r,M2r;cv::initUndistortRectifyMap(K_l,D_l,R_l,P_l.rowRange(0,3).colRange(0,3),cv::Size(cols_l,rows_l),CV_32F,M1l,M2l);cv::initUndistortRectifyMap(K_r,D_r,R_r,P_r.rowRange(0,3).colRange(0,3),cv::Size(cols_r,rows_r),CV_32F,M1r,M2r);// Vector for tracking time statisticsvector<float> vTimesTrack;vTimesTrack.resize(tot_images);cout << endl << "-------" << endl;cout.precision(17);// Create SLAM system. It initializes all system threads and gets ready to process frames.ORB_SLAM3::System SLAM(argv[1],argv[2],ORB_SLAM3::System::IMU_STEREO, true);float imageScale = SLAM.GetImageScale();cv::Mat imLeft, imRight, imLeftRect, imRightRect;for (seq = 0; seq<num_seq; seq++){// Seq loopvector<ORB_SLAM3::IMU::Point> vImuMeas;double t_rect = 0.f;double t_resize = 0.f;double t_track = 0.f;int num_rect = 0;int proccIm = 0;for(int ni=0; ni<nImages[seq]; ni++, proccIm++){// Read left and right images from fileimLeft = cv::imread(vstrImageLeft[seq][ni],cv::IMREAD_UNCHANGED);imRight = cv::imread(vstrImageRight[seq][ni],cv::IMREAD_UNCHANGED);if(imLeft.empty()){cerr << endl << "Failed to load image at: "<< string(vstrImageLeft[seq][ni]) << endl;return 1;}if(imRight.empty()){cerr << endl << "Failed to load image at: "<< string(vstrImageRight[seq][ni]) << endl;return 1;}#ifdef REGISTER_TIMES#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t_Start_Rect = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t_Start_Rect = std::chrono::monotonic_clock::now();#endif
#endifcv::remap(imLeft,imLeftRect,M1l,M2l,cv::INTER_LINEAR);cv::remap(imRight,imRightRect,M1r,M2r,cv::INTER_LINEAR);#ifdef REGISTER_TIMES#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t_End_Rect = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t_End_Rect = std::chrono::monotonic_clock::now();#endift_rect = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(t_End_Rect - t_Start_Rect).count();SLAM.InsertRectTime(t_rect);t_rect = std::chrono::duration_cast<std::chrono::duration<double> >(t_End_Rect - t_Start_Rect).count();
#endifif(imageScale != 1.f){
#ifdef REGISTER_TIMES#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t_Start_Resize = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t_Start_Resize = std::chrono::monotonic_clock::now();#endif
#endifint width = imLeftRect.cols * imageScale;int height = imLeftRect.rows * imageScale;cv::resize(imLeftRect, imLeftRect, cv::Size(width, height));cv::resize(imRightRect, imRightRect, cv::Size(width, height));
#ifdef REGISTER_TIMES#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t_End_Resize = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t_End_Resize = std::chrono::monotonic_clock::now();#endift_resize = std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(t_End_Resize - t_Start_Resize).count();SLAM.InsertResizeTime(t_resize);
#endif}double tframe = vTimestampsCam[seq][ni];// Load imu measurements from previous framevImuMeas.clear();if(ni>0)while(vTimestampsImu[seq][first_imu[seq]]<=vTimestampsCam[seq][ni]) // while(vTimestampsImu[first_imu]<=vTimestampsCam[ni]){vImuMeas.push_back(ORB_SLAM3::IMU::Point(vAcc[seq][first_imu[seq]].x,vAcc[seq][first_imu[seq]].y,vAcc[seq][first_imu[seq]].z,vGyro[seq][first_imu[seq]].x,vGyro[seq][first_imu[seq]].y,vGyro[seq][first_imu[seq]].z,vTimestampsImu[seq][first_imu[seq]]));first_imu[seq]++;}#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t1 = std::chrono::monotonic_clock::now();#endif// Pass the images to the SLAM systemSLAM.TrackStereo(imLeftRect,imRightRect,tframe,vImuMeas);#ifdef COMPILEDWITHC11std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();#elsestd::chrono::monotonic_clock::time_point t2 = std::chrono::monotonic_clock::now();#endif#ifdef REGISTER_TIMESt_track = t_rect + t_resize + std::chrono::duration_cast<std::chrono::duration<double,std::milli> >(t2 - t1).count();SLAM.InsertTrackTime(t_track);
#endifdouble ttrack= std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();vTimesTrack[ni]=ttrack;// Wait to load the next framedouble T=0;if(ni<nImages[seq]-1)T = vTimestampsCam[seq][ni+1]-tframe;else if(ni>0)T = tframe-vTimestampsCam[seq][ni-1];if(ttrack<T)usleep((T-ttrack)*1e6); // 1e6}if(seq < num_seq - 1){cout << "Changing the dataset" << endl;SLAM.ChangeDataset();}}// Stop all threadsSLAM.Shutdown();// Save camera trajectoryif (bFileName){const string kf_file =  "kf_" + string(argv[argc-1]) + ".txt";const string f_file =  "f_" + string(argv[argc-1]) + ".txt";SLAM.SaveTrajectoryEuRoC(f_file);SLAM.SaveKeyFrameTrajectoryEuRoC(kf_file);SLAM.SaveMap("patcheuroc.txt",imLeftRect.size);}else{SLAM.SaveTrajectoryEuRoC("CameraTrajectory.txt");SLAM.SaveKeyFrameTrajectoryEuRoC("KeyFrameTrajectory.txt");SLAM.SaveMap("patcheuroc.txt",imLeftRect.size);}return 0;
}

2.2 代码解析----读取图片、IMU信息

        由于ORBSLAM3支持多序列建图,因此在main函数中我们可以输入多个图像序列和时间戳:

    if(argc < 5){cerr << endl << "Usage: ./stereo_inertial_euroc path_to_vocabulary path_to_settings path_to_sequence_folder_1 path_to_times_file_1 (path_to_image_folder_2 path_to_times_file_2 ... path_to_image_folder_N path_to_times_file_N) " << endl;return 1;}

        并且如果输入有错误的话输出一行字并退出SLAM系统。

        首先我们读取图像:我们看这几个路径

        // argv[3] = /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03string pathSeq(argv[(2*seq) + 3]);// argv[4] = /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Examples_old/Stereo-Inertial/EuRoC_TimeStamps/MH03.txtstring pathTimeStamps(argv[(2*seq) + 4]);// /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03/mav0/cam0/datastring pathCam0 = pathSeq + "/mav0/cam0/data";// /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03/mav0/cam1/datastring pathCam1 = pathSeq + "/mav0/cam1/data";// /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03/mav0/imu0/data.csvstring pathImu = pathSeq + "/mav0/imu0/data.csv";

        我们看读取图片的函数LoadImage:

// /mav0/cam0/data  /mav0/cam1/data
// /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Examples_old/Stereo-Inertial/EuRoC_TimeStamps/MH03.txt
// vector< vector<string> > vstrImageLeft
// vector< vector<string> > vstrImageRight
// vector< vector<double> > vTimestampsCam
void LoadImages(const string &strPathLeft, const string &strPathRight, const string &strPathTimes,vector<string> &vstrImageLeft, vector<string> &vstrImageRight, vector<double> &vTimeStamps)
{ifstream fTimes;fTimes.open(strPathTimes.c_str());vTimeStamps.reserve(5000);vstrImageLeft.reserve(5000);vstrImageRight.reserve(5000);while(!fTimes.eof()){string s;getline(fTimes,s);if(!s.empty()){stringstream ss;ss << s;vstrImageLeft.push_back(strPathLeft + "/" + ss.str() + ".png");vstrImageRight.push_back(strPathRight + "/" + ss.str() + ".png");double t;ss >> t;vTimeStamps.push_back(t/1e9);}}
}

        我们先看看strPathTimes里面的内容:里面存放所有图像的时间戳

        vstrImageLeft里面存放着/mav0/cam0/data/1403637xxxxx104.png,很显然,这里面存放着时间戳对应的图像,vstrImageRight同理。vTimeStamps存放着这个时间戳。

        即vstrImageLeft存放图像的路径,vTimeStamps存放图像对应的时间戳。

        我们看读取IMU的函数:

// pathImu : /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/data/03/mav0/imu0/data.csv
// vTimestampsImu vector< vector<double> > vTimestampsImu
// vAcc  vector< vector<cv::Point3f> > vAcc, vGyro
// vGyro  vector< vector<cv::Point3f> > vAcc, vGyro
void LoadIMU(const string &strImuPath, vector<double> &vTimeStamps, vector<cv::Point3f> &vAcc, vector<cv::Point3f> &vGyro)
{ifstream fImu;fImu.open(strImuPath.c_str());vTimeStamps.reserve(5000);vAcc.reserve(5000);vGyro.reserve(5000);while(!fImu.eof()){string s;getline(fImu,s);if (s[0] == '#')continue;if(!s.empty()){string item;size_t pos = 0;double data[7];int count = 0;while ((pos = s.find(',')) != string::npos) {item = s.substr(0, pos);data[count++] = stod(item);s.erase(0, pos + 1);}item = s.substr(0, pos);data[6] = stod(item);vTimeStamps.push_back(data[0]/1e9);vAcc.push_back(cv::Point3f(data[4],data[5],data[6]));vGyro.push_back(cv::Point3f(data[1],data[2],data[3]));}}
}

        vAcc存放加速度计的信息。

        vGyro存放角速度的信息。

        vTimestampsImu存放IMU的时间戳。

        对于多序列数据集来说,nImages存放着每一个序列的图片数量,tot_images存放所有序列的图像数量,nImu存放每一个序列的IMU数据的数量。

        为了对齐两者的数据(我的理解是可能IMU可能初始化需要一些时间.....):

        while(vTimestampsImu[seq][first_imu[seq]]<=vTimestampsCam[seq][0])first_imu[seq]++;first_imu[seq]--; // first imu measurement to be considered

        如果vTimestampsImu[0][first_imu[0]] = vTimestampsImu[0][0] <= vTimestampsCam[0][0]的话我们把IMU的时间戳向上增加。即对齐时间戳。

        接下来我们看看读取配置文件的部分:

LEFT.D: !!opencv-matrixrows: 1cols: 5dt: ddata:[-0.28340811, 0.07395907, 0.00019359, 1.76187114e-05, 0.0]
LEFT.K: !!opencv-matrixrows: 3cols: 3dt: ddata: [458.654, 0.0, 367.215, 0.0, 457.296, 248.375, 0.0, 0.0, 1.0]
LEFT.R:  !!opencv-matrixrows: 3cols: 3dt: ddata: [0.999966347530033, -0.001422739138722922, 0.008079580483432283, 0.001365741834644127, 0.9999741760894847, 0.007055629199258132, -0.008089410156878961, -0.007044357138835809, 0.9999424675829176]
LEFT.Rf:  !!opencv-matrixrows: 3cols: 3dt: fdata: [0.999966347530033, -0.001422739138722922, 0.008079580483432283, 0.001365741834644127, 0.9999741760894847, 0.007055629199258132, -0.008089410156878961, -0.007044357138835809, 0.9999424675829176]
LEFT.P:  !!opencv-matrixrows: 3cols: 4dt: ddata: [435.2046959714599, 0, 367.4517211914062, 0,  0, 435.2046959714599, 252.2008514404297, 0,  0, 0, 1, 0]

        主要读取的有几部分:

        D:畸变系数

        P:这个矩阵是一个 3*4 的相机投影矩阵(P),通常用于将三维世界坐标系中的点投影到相机图像平面上,得到其在二维图像上的坐标

P = K[R|t]

         其中,fx 和 fy 是相机的内参矩阵,分别表示相机在 x 和 y 方向上的焦距,cx 和 cy 是相机的光心在图像平面上的坐标。在这个矩阵中,fx = fy = 435.2046959714599,cx = 367.4517211914062,cy = 252.2008514404297,表示了这个相机的内参信息。

这个矩阵中最后一列的值都是0,通常用于齐次坐标的变换。在这个矩阵中,最后一列的值表示图像点在相机坐标系中的 z 轴坐标,因为这是一个投影矩阵,所以z轴坐标始终为0。

总的来说,这个矩阵描述了一个内参已知的相机在三维空间中的位置和方向,可以用于将三维点投影到相机坐标系中,并进一步投影到相机图像平面上。

        K:这个矩阵是一个 3*3 的相机内参矩阵(K),也称为相机矩阵。它描述了相机的内部参数,包括焦距和光心在像素坐标系中的位置。

其中,fx 和 fy 分别表示相机在 x 和 y 方向上的焦距,cx 和 cy 表示相机的光心在像素坐标系中的坐标。在这个矩阵中,fx = 458.654,fy = 457.296,cx = 367.215,cy = 248.375,表示了这个相机的内参信息。

        这个矩阵常用于相机标定和相机几何变换中。相机标定是指通过多次拍摄已知的空间点并测量它们在图像中的位置来确定相机的内部参数,而相机几何变换是指将图像中的点从像素坐标系转换为相机坐标系或世界坐标系。         

        LEFT.P 和 LEFT.K 表示的是相机内参矩阵和相机投影矩阵,它们不同的地方在于是否包含了相机的外参信息。

具体来说,相机内参矩阵 LEFT.K 只包含了相机的内部参数,即相机在水平和垂直方向上的焦距和光心在像素坐标系中的位置,而不包含相机在世界坐标系中的位置和方向。

相机投影矩阵 LEFT.P 则包含了相机的内部参数和外部参数,即相机在世界坐标系中的位置和方向。它可以将三维世界坐标系中的点投影到相机坐标系中,再进一步投影到相机图像平面上,得到其在二维图像上的坐标。

        因此,LEFT.P 和 LEFT.K 的具体含义和使用场景不同。在相机标定和相机几何变换中,常常需要用到相机内参矩阵,而在三维重建和机器人视觉等应用中,则需要用到相机投影矩阵。

        再详细解释一下:

        假设你拍摄了一个相机移动的视频,现在你需要使用这个相机的参数来进行三维重建。在这种情况下,你需要使用相机的内参矩阵 LEFT.K 和外参矩阵 LEFT.R 来计算相机的旋转和平移,以及畸变参数 LEFT.D 来校正图像畸变。

接着,你需要将相机的内参矩阵 LEFT.K 和外参矩阵 LEFT.P 结合起来,得到一个新的投影矩阵 P'。这个新的投影矩阵 P' 将会被用来将图像坐标转换为相机坐标,然后再进行三维重建。

        具体地,你可以使用以下公式计算新的投影矩阵 P':

P^{'} = K*P

        其中 * 表示矩阵乘法,K 是相机的内参矩阵 LEFT.K,P 是相机的外参矩阵 LEFT.P。这个公式将外参矩阵 LEFT.P 中的平移向量和旋转矩阵都结合在了一起,得到了一个新的投影矩阵 P'。

        有了新的投影矩阵 P',你就可以使用它来将图像坐标转换为相机坐标,然后再进行三维重建。

2.3 代码解析---图像去畸变部分

cv::initUndistortRectifyMap(K_l,D_l,R_l,P_l.rowRange(0,3).colRange(0,3),cv::Size(cols_l,rows_l),CV_32F,M1l,M2l);
cv::initUndistortRectifyMap(K_r,D_r,R_r,P_r.rowRange(0,3).colRange(0,3),cv::Size(cols_r,rows_r),CV_32F,M1r,M2r);

      这是 OpenCV 中用于图像去畸变和校正的函数 cv::initUndistortRectifyMap()。以下是每个参数的含义:

        K_l:左相机内参矩阵,为 3x3 浮点型矩阵。
        D_l:左相机畸变参数,为 1xN 或 Nx1 浮点型向量,其中 N 是畸变系数的数量(通常为 4 或 5)。
        R_l:左相机旋转矩阵,为 3x3 浮点型矩阵。
        P_l.rowRange(0,3).colRange(0,3):左相机投影矩阵(3x3),它包含左相机的内参矩阵和旋转矩阵,用于计算校正后的图像。
        cv::Size(cols_l,rows_l):输出映射的图像大小(宽度 x 高度)。
        CV_32F:输出映射数据的类型,这里使用单精度浮点型。
        M1l 和 M2l:可选参数,输出的映射数据,是两个矩阵,每个矩阵的大小是 cv::Size(cols_l, rows_l)。

        R_l 和 P_l 是摄像机标定时的两个重要参数,它们分别表示左相机的旋转矩阵和投影矩阵,用于计算校正后的图像。

        旋转矩阵 R_l:表示将左相机的坐标系旋转到与右相机坐标系相同的旋转矩阵。在立体视觉中,我们需要保持左右相机的坐标系一致,才能进行深度的计算和匹配。通过标定得到的 R_l,我们可以将左相机的图像校正到与右相机相同的视角下。
        投影矩阵 P_l:是左相机的投影矩阵,它包含左相机的内参矩阵和旋转矩阵,用于将校正后的图像投影到三维坐标系。通过标定得到的 P_l,我们可以将校正后的图像转换为三维点云,然后与右相机的点云进行匹配,计算两个相机之间的距离和深度信息。
        总之,R_l 和 P_l 是计算立体视觉中校正后的图像和深度信息所必需的参数。

        该函数计算左相机图像去畸变和校正后的映射,以便校正后的图像具有更好的几何性质。这个函数将计算从畸变图像坐标到校正后的图像坐标的映射,以便在校正图像中重新投影畸变图像的像素。使用返回的映射和 cv::remap() 函数,可以将畸变的左相机图像转换为校正后的图像。

cv::remap(imLeft,imLeftRect,M1l,M2l,cv::INTER_LINEAR);
cv::remap(imRight,imRightRect,M1r,M2r,cv::INTER_LINEAR);

        这两行代码使用了 OpenCV 库中的 cv::remap() 函数,对输入的图像进行重映射操作。

具体来说,cv::remap() 函数通过输入的像素映射数据 M1l、M2l 和 M1r、M2r,将 imLeft 和 imRight 两个原始图像进行畸变校正和图像矫正。其中,imLeft 和 imRight 分别是左右相机采集的原始图像,imLeftRect 和 imRightRect 分别是经过校正和矫正后的图像。

        cv::remap() 函数的第一个参数是输入图像,第二个参数是输出图像,第三个参数和第四个参数分别是横向和纵向的像素映射数据。第五个参数是插值方法,可以选择不同的插值方法,比如 cv::INTER_LINEAR 表示双线性插值。

        这两行代码的作用是根据畸变校正和图像矫正的映射数据,将左右相机采集的原始图像进行处理,得到经过畸变校正和图像矫正后的左右相机图像。这样做的目的是为了减小图像畸变和视差对立体匹配和三维重建的影响。

        将去畸变后的图像输入到追踪线程。

相关文章:

ORBSLAM3 --- 双目惯导执行ORBSLAM3(一):Stereo_intertail_euroc.cc文件解析

1.执行双目例程的参数 在Clion中&#xff0c;我们输入以下参数&#xff1a; /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Vocabulary/ORBvoc.txt /home/liuhongwei/Desktop/slam/ORB_SLAM3_detailed_comments-master/Examples_old/Stereo-Inertial/EuRo…...

五 MySQL 存储过程

五、企业级开发技术 5.1 存储过程 关于存储过程我只能说请看下图&#xff0c;这是阿里巴巴发布的《阿里巴巴Java开发手册&#xff08;终极版&#xff09;v1.3版本》在 MySQL 第七条中强制指出禁止使用存储过程 所以对于存储过程不必深究&#xff0c;做到会写能看懂即可 [外链…...

【指针函数和函数指针】

指针函数和函数指针1. 概述2. 案例分析指针函数函数指针1. 概述 函数指针和指针函数是两个不同的概念。 函数指针是指一个指针变量&#xff0c;该指针变量存储了一个函数的地址。通过函数指针可以实现动态调用函数&#xff0c;根据需要在程序运行时指定要调用的函数。函数指针的…...

实现卡片高度增加时的缓动动画效果

在开发中&#xff0c;我们可能会遇到需要让卡片高度由内容撑起&#xff08;即不能手动设置height&#xff09;&#xff0c;并且在高度增加时增加缓动动画的需求。本文将介绍几种实现方式。 文章目录方法1&#xff1a;使用CSS的max-height属性和:hover伪类特定例子&#xff1a;鼠…...

什么是HRMS?哪些工作需要使用HRMS?

当今企业的发展离不开技术支持&#xff0c;同样&#xff0c;在管理方面也需要与时俱进&#xff0c;进行数字化转型。人力资源技术的运用是企业管理数字化转型的重要表现之一。在企业选择一款HR软件之前&#xff0c;应该先认识到&#xff0c;什么是人力资源管理软件——即HRMS。…...

【C语言蓝桥杯每日一题】—— 饮料换购

【C语言蓝桥杯每日一题】—— 饮料换购&#x1f60e;前言&#x1f64c;饮料换购&#x1f64c;喝汽水问题&#x1f64c;饮料换购解题源码分享 &#x1f60a;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神贯注的…...

PMP适合哪些人考?

其实很多小白在最开始了解PMP考试的时候都会有同一个问题&#xff0c;那就是&#xff1a; “我适不适合考PMP&#xff1f;” 如果想做管理&#xff0c;那么一定要考PMP证书。PMP证书是国际认证&#xff0c;在国内的认可度也很高&#xff0c;可以说是管理岗位的入门认证。注意…...

中华好诗词大学季第二季(二)

第四期 1,宋代林升的《题临安邸》是一首著名的墙头诗&#xff0c;请问这里的”邸“指的是什么&#xff1f;旅店 2,宋代林升的《题临安邸》的“临安”是指那个城市&#xff1f;杭州 3,“申黜褒女进&#xff0c;班去赵姬升”具体写到了历史上那四个女人 申皇后&#xff0c;褒…...

【Linux】时间日期指令、查找指令、压缩和解压指令

目录1 时间日期类1.1 date指令-显示当前日期1.2 date指令-设置日期1.3 cal指令2 搜索查找类2.1 find指令2.2 locate指令2.3 grep指令和管道符号 |3 压缩和解压类3.1 gzip/gunzip 指令3.2 zip/unzip 指令1 时间日期类 1.1 date指令-显示当前日期 基本语法 date (功能描述:显示…...

python社区志愿者服务管理系统-vue

本系统主要实现一个基于web的校园志愿者活动系统。此网站是为了给予在校生通过网上报名来参加志愿者活动&#xff0c;省去了各种班群申报的中间环节。利用数据库和python进行web开发。 能实现的基本功能如下&#xff1a; (1)登陆、注册的功能&#xff1a;用户填写用户名和密码进…...

计算机网络 常见网卡信息

文章目录1. PCI 网卡2. PCI Express 网卡3. USB网卡4. 无线网卡万兆网卡光纤网卡1. PCI 网卡 接口类型&#xff1a;PCI 传输速率&#xff1a;10/100Mbps或1000Mbps 支持协议&#xff1a;TCP/IP、UDP、IPX/SPX等 缓存大小&#xff1a;通常为64KB或128KB 2. PCI Express 网卡 …...

Python 自动化指南(繁琐工作自动化)第二版:附录 B:运行程序

原文&#xff1a;https://automateboringuff.com/2e/appendixb/ 如果您在 Mu 中打开了一个程序&#xff0c;运行它只需按 F5 或单击窗口顶部的运行按钮。这是一种在编写程序的同时运行程序的简单方法&#xff0c;但是打开 Mu 来运行你完成的程序可能是一种负担。根据您使用的操…...

自然语言处理实战项目2-文本关键词抽取和关键词分值评估

大家好&#xff0c;我是微学AI&#xff0c;今天给大家带来自然语言处理实战项目2-文本关键词抽取和关键词分值评估。关键词抽取是自然语言处理中的重要任务&#xff0c;也是基础任务。 一、关键词抽取传统方法 1.基于统计的方法&#xff1a; 基于统计的方法是通过对一组文本…...

软件测试面试,项目经验板块如何答?初中高级测试工程师都问什么?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 项目经验相关的问题…...

软件测试之测试用例的设计

对于测试工作而言&#xff0c;最重要的无疑就是测试用例的设计。好的测试用例可以帮助测试人员更好更快地发现软件中的错误&#xff0c;对于提高产品质量意义重大。本文就是针对测试用例的设计方法。 文章目录测试用例的基本要素测试用例的设计设计测试用例的具体方法等价类划分…...

MySQL安装与配置(保姆级教程)

MySQL安装 我们进入Mysql的官网进行下载MySQL Community Edition(GPL),这里我们以8.0.32.0版本为例&#xff0c;点击下面进行下载&#xff1a; MySQL Community Edition(GPL) 此时我们选择下面一个32位(64位的系统也选他)&#xff0c;上面那个是在线安装等待时间比较长 当然我们…...

MATLAB算法实战应用案例精讲-【元启发式算法】随机蛙跳跃算法(SFLA)(附matlab代码实现)

目录 前言 知识储备 多目标优化问题 多目标元启发式优化方法 算法原理 数...

内网穿透:远程访问内网IP中的电脑

需求&#xff1a;家里电脑在路由器内网中&#xff0c;能连外网。想在外地时能ssh&#xff08;也即vscode&#xff09;访问家里的电脑。 家里电脑系统&#xff1a;win11&#xff08;Ubuntu流程也一模一样&#xff09; 具体流程 家里电脑下载【花生壳】内网穿透软件并登录&#…...

day4 selenium爬取数据总结

day4 selenium爬取数据 一、selenium基本操作 导入相关模块&#xff1a; from selenium.webdriver import Chrome(一)、创建浏览器对象 b Chrome()(二)、打开网页&#xff08;需要爬取哪个页面的数据就打开该页面对应的网页地址&#xff09; 案例&#xff1a;爬取豆瓣电影…...

信息收集之WAF绕过

信息收集之WAF绕过前言一、工具进行目录扫描1. 工具的下载2. 工具的使用二、Python代码进行目录扫描前言 对于web安全无WAF的信息收集&#xff0c;大家可以查看如下链接的文章&#xff1a; web安全之信息收集 对于有WAF信息收集&#xff0c;看如下所示&#xff1a;&#xff08;…...

从数据到智慧,TOOM舆情监测系统让你的决策更加精准!

当今社会信息化程度日益提高&#xff0c;网络平台已成为人们获取最新信息的主要途径&#xff0c;无论是个体还是组织、政府还是企业&#xff0c;都需要通过各种手段及时了解社会舆情&#xff0c;把握市场动态&#xff0c;调整经营策略。而舆情监测系统无疑是这些手段中最为有效…...

ChatGPT中文版网页插件-如何体验chatGPT

ChatGPT中文版网页插件 目前&#xff0c;OpenAI的ChatGPT并没有官方提供中文版的网页插件&#xff0c;但是&#xff0c;一些第三方网站和开发者已经开始提供一些针对中文的自然语言处理和对话机器人服务。以下是其中一些例子&#xff1a; 问答机器人&#xff1a; 像小蛮腰和小…...

Docker的网络模式

Docker常见的几种网络模式 docker network ls 查看使用了哪些网络 [rootcentos8-nat-168-182-152 ~]# docker network ls NETWORK ID NAME DRIVER SCOPE c0184302f6a8 bridge bridge local 420492e04276 host host local …...

基于vue3.2、three实现地图在地图加载

基于vue3.2、three实现地图在地图加载code效果预览地址code 在这里插入代码片 import { ref, onMounted } from "vue"import * as THREE from "three"; import Earth from "./textures/Earth.png" import EarthSpec from "./textures/Eart…...

【C++】---优先级队列 仿函数

文章目录优先级队列介绍优先级队列使用仿函数优先级队列模拟实现优先级队列介绍 优先队列是一种容器适配器 &#xff0c;它的底层实现是堆&#xff0c;虽然它的名字里面有队列&#xff0c;但它并没有队列先进先出的特性 优先级队列定义在头文件中&#xff0c;其模板参数有三个…...

图的遍历算法

图的遍历1.连通图的深度优先搜索1.1. 递归1.2.非递归2.连通图的广度优先遍历3. 非连通图的深度&#xff08;广度&#xff09;优先遍历1.连通图的深度优先搜索 算法思想&#xff1a;从图中某个顶点vi出发&#xff0c;访问此顶点&#xff0c;然后依次从v1的各个未被访问的邻接点…...

【蓝桥杯集训·每日一题】 AcWing 3996. 涂色

文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴区间DPUnique函数一、题目 1、原题链接 3996. 涂色 2、题目描述 有 n 个砖块排成一排&#xff0c;从左到右编号为 1∼n。 其中&#xff0c;第 i 个砖块的初始颜色为 ci。 …...

人工智能中的Web端编程

Java是当前的主流编程语言之一&#xff0c;常年稳居TIOBE编程语言排行榜前五。Java的使用领域非常广泛&#xff0c;包括了桌面端编程、Web端编程、移动端编程等几乎所有的编程领域。Java是Web端编程使用最广泛的编程语言之一。要学习Web端编程&#xff0c;需要了解Java语言的知…...

jsp+mysql+J2EE校园自行车租赁系统cdA1A2程序

本系统的具体功能有以下六项&#xff1a; 1、用户信息管理模块&#xff1a;用户需要注册成为本网站的用户&#xff0c;同时修改自己的用户资料&#xff0c;在必要时修改自己的登陆密码。 2、车辆查询模块:用户可以根据自己的要求&#xff0c;按照不同的查询方式来查询自己想要的…...

当营养遇上肠道菌群:探究其对儿童健康的影响

谷禾健康 越来越多的证据表明&#xff0c;肠道菌群定植紊乱和微生物多样性减少与全球非传染性疾病 (NCD) 的增加有关。影响儿童和青少年的非传染性疾病包括肥胖及其相关合并症、自身免疫性疾病、过敏性疾病和哮喘。饮食变化也与非传染性疾病的发病机制有关&#xff0c;并且由于…...