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),通常用于将三维世界坐标系中的点投影到相机图像平面上,得到其在二维图像上的坐标。
其中,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':
其中 * 表示矩阵乘法,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中,我们输入以下参数: /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 存储过程 关于存储过程我只能说请看下图,这是阿里巴巴发布的《阿里巴巴Java开发手册(终极版)v1.3版本》在 MySQL 第七条中强制指出禁止使用存储过程 所以对于存储过程不必深究,做到会写能看懂即可 [外链…...
【指针函数和函数指针】
指针函数和函数指针1. 概述2. 案例分析指针函数函数指针1. 概述 函数指针和指针函数是两个不同的概念。 函数指针是指一个指针变量,该指针变量存储了一个函数的地址。通过函数指针可以实现动态调用函数,根据需要在程序运行时指定要调用的函数。函数指针的…...
实现卡片高度增加时的缓动动画效果
在开发中,我们可能会遇到需要让卡片高度由内容撑起(即不能手动设置height),并且在高度增加时增加缓动动画的需求。本文将介绍几种实现方式。 文章目录方法1:使用CSS的max-height属性和:hover伪类特定例子:鼠…...
什么是HRMS?哪些工作需要使用HRMS?
当今企业的发展离不开技术支持,同样,在管理方面也需要与时俱进,进行数字化转型。人力资源技术的运用是企业管理数字化转型的重要表现之一。在企业选择一款HR软件之前,应该先认识到,什么是人力资源管理软件——即HRMS。…...
【C语言蓝桥杯每日一题】—— 饮料换购
【C语言蓝桥杯每日一题】—— 饮料换购😎前言🙌饮料换购🙌喝汽水问题🙌饮料换购解题源码分享 😊总结撒花💞😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的…...
PMP适合哪些人考?
其实很多小白在最开始了解PMP考试的时候都会有同一个问题,那就是: “我适不适合考PMP?” 如果想做管理,那么一定要考PMP证书。PMP证书是国际认证,在国内的认可度也很高,可以说是管理岗位的入门认证。注意…...
中华好诗词大学季第二季(二)
第四期 1,宋代林升的《题临安邸》是一首著名的墙头诗,请问这里的”邸“指的是什么?旅店 2,宋代林升的《题临安邸》的“临安”是指那个城市?杭州 3,“申黜褒女进,班去赵姬升”具体写到了历史上那四个女人 申皇后,褒…...
【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的校园志愿者活动系统。此网站是为了给予在校生通过网上报名来参加志愿者活动,省去了各种班群申报的中间环节。利用数据库和python进行web开发。 能实现的基本功能如下: (1)登陆、注册的功能:用户填写用户名和密码进…...
计算机网络 常见网卡信息
文章目录1. PCI 网卡2. PCI Express 网卡3. USB网卡4. 无线网卡万兆网卡光纤网卡1. PCI 网卡 接口类型:PCI 传输速率:10/100Mbps或1000Mbps 支持协议:TCP/IP、UDP、IPX/SPX等 缓存大小:通常为64KB或128KB 2. PCI Express 网卡 …...
Python 自动化指南(繁琐工作自动化)第二版:附录 B:运行程序
原文:https://automateboringuff.com/2e/appendixb/ 如果您在 Mu 中打开了一个程序,运行它只需按 F5 或单击窗口顶部的运行按钮。这是一种在编写程序的同时运行程序的简单方法,但是打开 Mu 来运行你完成的程序可能是一种负担。根据您使用的操…...
自然语言处理实战项目2-文本关键词抽取和关键词分值评估
大家好,我是微学AI,今天给大家带来自然语言处理实战项目2-文本关键词抽取和关键词分值评估。关键词抽取是自然语言处理中的重要任务,也是基础任务。 一、关键词抽取传统方法 1.基于统计的方法: 基于统计的方法是通过对一组文本…...
软件测试面试,项目经验板块如何答?初中高级测试工程师都问什么?
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 项目经验相关的问题…...
软件测试之测试用例的设计
对于测试工作而言,最重要的无疑就是测试用例的设计。好的测试用例可以帮助测试人员更好更快地发现软件中的错误,对于提高产品质量意义重大。本文就是针对测试用例的设计方法。 文章目录测试用例的基本要素测试用例的设计设计测试用例的具体方法等价类划分…...
MySQL安装与配置(保姆级教程)
MySQL安装 我们进入Mysql的官网进行下载MySQL Community Edition(GPL),这里我们以8.0.32.0版本为例,点击下面进行下载: MySQL Community Edition(GPL) 此时我们选择下面一个32位(64位的系统也选他),上面那个是在线安装等待时间比较长 当然我们…...
MATLAB算法实战应用案例精讲-【元启发式算法】随机蛙跳跃算法(SFLA)(附matlab代码实现)
目录 前言 知识储备 多目标优化问题 多目标元启发式优化方法 算法原理 数...
内网穿透:远程访问内网IP中的电脑
需求:家里电脑在路由器内网中,能连外网。想在外地时能ssh(也即vscode)访问家里的电脑。 家里电脑系统:win11(Ubuntu流程也一模一样) 具体流程 家里电脑下载【花生壳】内网穿透软件并登录&#…...
day4 selenium爬取数据总结
day4 selenium爬取数据 一、selenium基本操作 导入相关模块: from selenium.webdriver import Chrome(一)、创建浏览器对象 b Chrome()(二)、打开网页(需要爬取哪个页面的数据就打开该页面对应的网页地址) 案例:爬取豆瓣电影…...
信息收集之WAF绕过
信息收集之WAF绕过前言一、工具进行目录扫描1. 工具的下载2. 工具的使用二、Python代码进行目录扫描前言 对于web安全无WAF的信息收集,大家可以查看如下链接的文章: web安全之信息收集 对于有WAF信息收集,看如下所示:(…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...






