1、主要功能
Tracking线程的输入为图片帧,然后根据输入的Frame来进行四个步骤:
1)ORB特征提取;
2)从上一帧进行初始化位姿估计或者进行重定位;
3)跟踪局部地图;
4)新的关键帧生成。
Tracking线程的输出为新生成的关键帧,并将新生成的关键帧传给 LocalMapping线程 继续后边的工作。
2、代码
具体步骤
Step 1 从配置文件中加载相机参数
Step 2 加载ORB特征点有关的参数,并新建特征点提取器
/**
* @brief 构造函数
*
* @param[in] pSys 系统实例
* @param[in] pVoc 字典指针
* @param[in] pFrameDrawer 帧绘制器
* @param[in] pMapDrawer 地图绘制器
* @param[in] pMap 地图句柄
* @param[in] pKFDB 关键帧数据库句柄
* @param[in] strSettingPath 配置文件路径
* @param[in] sensor 传感器类型
*/
Tracking(System* pSys, ORBVocabulary* pVoc, FrameDrawer* pFrameDrawer, MapDrawer* pMapDrawer, Map* pMap,KeyFrameDatabase* pKFDB, const string &strSettingPath, const int sensor)
{
// Step 1 从配置文件中加载相机参数
cv::FileStorage fSettings(strSettingPath, cv::FileStorage::READ);
float fx = fSettings["Camera.fx"];
float fy = fSettings["Camera.fy"];
float cx = fSettings["Camera.cx"];
float cy = fSettings["Camera.cy"];
// |fx 0 cx|
// K = |0 fy cy|
// |0 0 1 |
//构造相机内参矩阵
cv::Mat K = cv::Mat::eye(3,3,CV_32F);
K.at<float>(0,0) = fx;
K.at<float>(1,1) = fy;
K.at<float>(0,2) = cx;
K.at<float>(1,2) = cy;
K.copyTo(mK); // mK 是Initializer.h 里面的全局变量
// 图像矫正系数
// [k1 k2 p1 p2 k3]
cv::Mat DistCoef(4,1,CV_32F);
DistCoef.at<float>(0) = fSettings["Camera.k1"];
DistCoef.at<float>(1) = fSettings["Camera.k2"];
DistCoef.at<float>(2) = fSettings["Camera.p1"];
DistCoef.at<float>(3) = fSettings["Camera.p2"];
const float k3 = fSettings["Camera.k3"];
//有些相机的畸变系数中会没有k3项
if(k3!=0)
{
DistCoef.resize(5);
DistCoef.at<float>(4) = k3;
}
DistCoef.copyTo(mDistCoef); // mDistCoef 是Initializer.h 里面的全局变量
// 双目摄像头baseline * fx 50
mbf = fSettings["Camera.bf"];
float fps = fSettings["Camera.fps"];
if(fps==0)
fps=30;
// Max/Min Frames to insert keyframes and to check relocalisation
mMinFrames = 0;
mMaxFrames = fps;
//输出
cout << endl << "Camera Parameters: " << endl;
cout << "- fx: " << fx << endl;
cout << "- fy: " << fy << endl;
cout << "- cx: " << cx << endl;
cout << "- cy: " << cy << endl;
cout << "- k1: " << DistCoef.at<float>(0) << endl;
cout << "- k2: " << DistCoef.at<float>(1) << endl;
if(DistCoef.rows==5)
cout << "- k3: " << DistCoef.at<float>(4) << endl;
cout << "- p1: " << DistCoef.at<float>(2) << endl;
cout << "- p2: " << DistCoef.at<float>(3) << endl;
cout << "- fps: " << fps << endl;
// 1:RGB 0:BGR
int nRGB = fSettings["Camera.RGB"];
mbRGB = nRGB; //mbRGB 是Tracking.h 里面的全局变量
if(mbRGB)
cout << "- color order: RGB (ignored if grayscale)" << endl;
else
cout << "- color order: BGR (ignored if grayscale)" << endl;
// Step 2 加载ORB特征点有关的参数,并新建特征点提取器
// 每一帧提取的特征点数 1000
int nFeatures = fSettings["ORBextractor.nFeatures"];
// 图像建立金字塔时的变化尺度 1.2
float fScaleFactor = fSettings["ORBextractor.scaleFactor"];
// 尺度金字塔的层数 8
int nLevels = fSettings["ORBextractor.nLevels"];
// 提取fast特征点的默认阈值 20
int fIniThFAST = fSettings["ORBextractor.iniThFAST"];
// 如果默认阈值提取不出足够fast特征点,则使用最小阈值 8
int fMinThFAST = fSettings["ORBextractor.minThFAST"];
// tracking过程都会用到mpORBextractorLeft作为特征点提取器
mpORBextractorLeft = new ORBextractor(
nFeatures, //参数的含义还是看上面的注释吧
fScaleFactor,
nLevels,
fIniThFAST,
fMinThFAST);
// 如果是双目,tracking过程中还会用用到mpORBextractorRight作为右目特征点提取器
if(sensor==System::STEREO)
mpORBextractorRight = new ORBextractor(nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);
// 在单目初始化的时候,会用 mpIniORBextractor 来作为特征点提取器
if(sensor==System::MONOCULAR)
mpIniORBextractor = new ORBextractor(2*nFeatures,fScaleFactor,nLevels,fIniThFAST,fMinThFAST);
cout << endl << "ORB Extractor Parameters: " << endl;
cout << "- Number of Features: " << nFeatures << endl;
cout << "- Scale Levels: " << nLevels << endl;
cout << "- Scale Factor: " << fScaleFactor << endl;
cout << "- Initial Fast Threshold: " << fIniThFAST << endl;
cout << "- Minimum Fast Threshold: " << fMinThFAST << endl;
if(sensor==System::STEREO || sensor==System::RGBD)
{
// 判断一个3D点远/近的阈值 mbf * 35 / fx
//ThDepth其实就是表示基线长度的多少倍
mThDepth = mbf*(float)fSettings["ThDepth"]/fx;
cout << endl << "Depth Threshold (Close/Far Points): " << mThDepth << endl;
}
if(sensor==System::RGBD)
{
// 深度相机disparity转化为depth时的因子
mDepthMapFactor = fSettings["DepthMapFactor"];
if(fabs(mDepthMapFactor)<1e-5)
mDepthMapFactor=1;
else
mDepthMapFactor = 1.0f/mDepthMapFactor;
}
}
2.1 ORBextractor ()
程序
//特征点提取器的构造函数, 后面在仔细介绍程序和理论部分
int _nfeatures, //指定要提取的特征点数目
float _scaleFactor, //指定图像金字塔的缩放系数
int _nlevels, //指定图像金字塔的层数
int _iniThFAST, //指定初始的FAST特征点提取参数,可以提取出最明显的角点
int _minThFAST): //如果因为图像纹理不丰富提取出的特征点不多,为了达到想要的特征点数目,
//就使用这个参数提取出不是那么明显的角点
nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),
iniThFAST(_iniThFAST), minThFAST(_minThFAST)// 全局变量
ORBextractor::ORBextractor(int _nfeatures, float _scaleFactor, int _nlevels,
int _iniThFAST, int _minThFAST):
nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),
iniThFAST(_iniThFAST), minThFAST(_minThFAST)
{
//存储每层图像缩放系数的vector调整为符合图层数目的大小
mvScaleFactor.resize(nlevels);
//存储这个sigma^2,其实就是每层图像相对初始图像缩放因子的平方
mvLevelSigma2.resize(nlevels);
//对于初始图像,这两个参数都是1
mvScaleFactor[0]=1.0f;
mvLevelSigma2[0]=1.0f;
//然后逐层计算图像金字塔中图像相当于初始图像的缩放系数
for(int i=1; i<nlevels; i++)
{
//其实就是这样累乘计算得出来的
mvScaleFactor[i]=mvScaleFactor[i-1]*scaleFactor;
//原来这里的sigma^2就是每层图像相对于初始图像缩放因子的平方
mvLevelSigma2[i]=mvScaleFactor[i]*mvScaleFactor[i];
}
//接下来的两个向量保存上面的参数的倒数,操作都是一样的就不再赘述了
mvInvScaleFactor.resize(nlevels);
mvInvLevelSigma2.resize(nlevels);
for(int i=0; i<nlevels; i++)
{
mvInvScaleFactor[i]=1.0f/mvScaleFactor[i];
mvInvLevelSigma2[i]=1.0f/mvLevelSigma2[i];
}
//调整图像金字塔vector以使得其符合设定的图像层数
mvImagePyramid.resize(nlevels);
//每层需要提取出来的特征点个数,这个向量也要根据图像金字塔设定的层数进行调整
mnFeaturesPerLevel.resize(nlevels);
//图片降采样缩放系数的倒数
float factor = 1.0f / scaleFactor;
//每个单位缩放系数所希望的特征点个数
float nDesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)pow((double)factor, (double)nlevels));
//用于在特征点个数分配的,特征点的累计计数清空
int sumFeatures = 0;
//开始逐层计算要分配的特征点个数,顶层图像除外(看循环后面)
for( int level = 0; level < nlevels-1; level++ )
{
//分配 cvRound : 返回个参数最接近的整数值
mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);
//累计
sumFeatures += mnFeaturesPerLevel[level];
//乘系数
nDesiredFeaturesPerScale *= factor;
}
//由于前面的特征点个数取整操作,可能会导致剩余一些特征点个数没有被分配
//所以这里就将这个余出来的特征点分配到最高的图层中
mnFeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);
//成员变量pattern的长度,也就是点的个数,这里的512表示512个点(上面的数组中是存储的坐标所以是256*2*2)
const int npoints = 512;
//获取用于计算BRIEF描述子的随机采样点点集头指针
//注意到pattern0数据类型为Points*,bit_pattern_31_是int[]型,所以这里需要进行强制类型转换
const Point* pattern0 = (const Point*)bit_pattern_31_;
//使用std::back_inserter的目的是可以快覆盖掉这个容器pattern之前的数据
//其实这里的操作就是,将在全局变量区域的、int格式的随机采样点以cv::point格式复制到当前类对象中的成员变量中
std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));
//下面的内容是和特征点的旋转计算有关的 ,pre-compute the end of a row in a circular patch
//预先计算圆形patch中行的结束位置,+1中的1表示那个圆的中间行
umax.resize(HALF_PATCH_SIZE + 1);
//cvFloor返回不大于参数的最大整数值,cvCeil返回不小于参数的最小整数值,cvRound则是四舍五入
int v, //循环辅助变量
v0, //辅助变量
//计算圆的最大行号,+1应该是把中间行也给考虑进去了
vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1);
//NOTICE 注意这里的最大行号指的是计算的时候的最大行号,
//此行的和圆的角点在45°圆心角的一边上,之所以这样选择,是因为圆周上的对称特性
//这里的二分之根2就是对应那个45°圆心角
int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);
//半径的平方
const double hp2 = HALF_PATCH_SIZE*HALF_PATCH_SIZE;
//利用圆的方程计算每行像素的u坐标边界(max)
for (v = 0; v <= vmax; ++v)
umax[v] = cvRound(sqrt(hp2 - v * v));
//结果都是大于0的结果,表示x坐标在这一行的边界
// Make sure we are symmetric
//这里其实是使用了对称的方式计算上四分之一的圆周上的umax,目的也是为了保持严格的对称(如果按照常规的想法做,由于cvRound就会很容易出现不对称的情况,
//同时这些随机采样的特征点集也不能够满足旋转之后的采样不变性了)
for (v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v)
{
while (umax[v0] == umax[v0 + 1])
++v0;
umax[v] = v0;
++v0;
}
}
理论
1、特征点分布有关的理论
未完代序。。。
2、umax的计算
为什么要计算umax?
我们知道,ORB特征点是由FAST关键点和BRIEF描述子组成的。
ORB特征点的关键点告诉我们ORB特征点的位置信息。在后面我们还需要计算ORB特征点的方向。
而ORB特征点的方向是根据像素坐标点为中心的直径为PATCH_SIZE,半径为HALF_PATCH_SIZE的patch圆内计算出来的(具体计算方法在)。
在源文件ORBextractor.cc,我们可以看到这个patch圆的相关信息
const int PATCH_SIZE = 31;
const int HALF_PATCH_SIZE = 15;
const int EDGE_THRESHOLD = 19;
于是我们需要先解决如何这个描述这个patch圆。
思想:
当0<v<vmax时,我们通过三角形AGH,利用勾股定理计算umax[v]。
当v>vmax,我们就利用圆的对称性,计算余下的umax[v]。
2.2 Tracking::GrabImageMonocular()
位置:这段代码在主函数的 SLAM.TrackMonocular(im,tframe) 里面!
功能:
输入左目RGB或RGBA图像,输出世界坐标系到该帧相机坐标系的变换矩阵
im 单目图像
timestamp 时间戳
return cv::Mat
* Step 1 :将彩色图像转为灰度图像
* Step 2 :构造Frame
* Step 3 :跟踪
代码:
cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im,const double ×tamp)
{
mImGray = im;
// Step 1 :将彩色图像转为灰度图像;若图片是3、4通道的,还需要转化成灰度图
if(mImGray.channels()==3)
{
if(mbRGB)
cvtColor(mImGray,mImGray,CV_RGB2GRAY);
else
cvtColor(mImGray,mImGray,CV_BGR2GRAY);
}
else if(mImGray.channels()==4)
{
if(mbRGB)
cvtColor(mImGray,mImGray,CV_RGBA2GRAY);
else
cvtColor(mImGray,mImGray,CV_BGRA2GRAY);
}
// Step 2 :构造Frame;判断该帧是不是初始化
if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET) //没有成功初始化的前一个状态就是NO_IMAGES_YET
mCurrentFrame = Frame(
mImGray,
timestamp,
mpIniORBextractor, //初始化ORB特征点提取器会提取2倍的指定特征点数目
mpORBVocabulary,
mK,
mDistCoef,
mbf,
mThDepth);
else
mCurrentFrame = Frame(
mImGray,
timestamp,
mpORBextractorLeft, //正常运行的时的ORB特征点提取器,提取指定数目特征点
mpORBVocabulary,
mK,
mDistCoef,
mbf,
mThDepth);
// Step 3 :跟踪
Track();
//返回当前帧的位姿
return mCurrentFrame.mTcw.clone();
}
2.2.1 Frame.cc中的单目Frame构造函数代码解析
* 单目帧构造函数
* param[in] imGray //灰度图
* param[in] timeStamp //时间戳
* param[in & out] extractor //ORB特征点提取器的句柄
* param[in] voc //ORB字典的句柄
* param[in] K //相机的内参数矩阵
* param[in] distCoef //相机的去畸变参数
* param[in] bf //baseline*f
* param[in] thDepth //区分远近点的深度阈值
Frame::Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth)
:mpORBvocabulary(voc),mpORBextractorLeft(extractor),mpORBextractorRight(static_cast<ORBextractor*>(NULL)), mTimeStamp(timeStamp), mK(K.clone()), mDistCoef(distCoef.clone()), mbf(bf), mThDepth(thDepth)
{
// Frame ID
// Step 1 帧的ID 自增
mnId=nNextId++;
// Step 2 计算图像金字塔的参数
// Scale Level Info
//获取图像金字塔的层数
mnScaleLevels = mpORBextractorLeft->GetLevels();
//获取每层的缩放因子
mfScaleFactor = mpORBextractorLeft->GetScaleFactor();
//计算每层缩放因子的自然对数
mfLogScaleFactor = log(mfScaleFactor);
//获取各层图像的缩放因子
mvScaleFactors = mpORBextractorLeft->GetScaleFactors();
//获取各层图像的缩放因子的倒数
mvInvScaleFactors = mpORBextractorLeft->GetInverseScaleFactors();
//获取sigma^2
mvLevelSigma2 = mpORBextractorLeft->GetScaleSigmaSquares();
//获取sigma^2的倒数
mvInvLevelSigma2 = mpORBextractorLeft->GetInverseScaleSigmaSquares();
// ORB extraction
// Step 3 对这个单目图像进行提取特征点, 第一个参数0-左图, 1-右图
ExtractORB(0,imGray);
//求出特征点的个数
N = mvKeys.size();
//如果没有能够成功提取出特征点,那么就直接返回了
if(mvKeys.empty())
return;
// Step 4 用OpenCV的矫正函数、内参对提取到的特征点进行矫正
UndistortKeyPoints();
// Set no stereo information
// 由于单目相机无法直接获得立体信息,所以这里要给右图像对应点和深度赋值-1表示没有相关信息
mvuRight = vector<float>(N,-1);
mvDepth = vector<float>(N,-1);
// 初始化本帧的地图点
mvpMapPoints = vector<MapPoint*>(N,static_cast<MapPoint*>(NULL));
// 记录地图点是否为外点,初始化均为外点false
mvbOutlier = vector<bool>(N,false);
// This is done only for the first Frame (or after a change in the calibration)
// Step 5 计算去畸变后图像边界,将特征点分配到网格中。这个过程一般是在第一帧或者是相机标定参数发生变化之后进行
if(mbInitialComputations)
{
// 计算去畸变后图像的边界
ComputeImageBounds(imGray);
// 表示一个图像像素相当于多少个图像网格列(宽)
mfGridElementWidthInv=static_cast<float>(FRAME_GRID_COLS)/static_cast<float>(mnMaxX-mnMinX);
// 表示一个图像像素相当于多少个图像网格行(高)
mfGridElementHeightInv=static_cast<float>(FRAME_GRID_ROWS)/static_cast<float>(mnMaxY-mnMinY);
//给类的静态成员变量复制
fx = K.at<float>(0,0);
fy = K.at<float>(1,1);
cx = K.at<float>(0,2);
cy = K.at<float>(1,2);
// 猜测是因为这种除法计算需要的时间略长,所以这里直接存储了这个中间计算结果
invfx = 1.0f/fx;
invfy = 1.0f/fy;
//特殊的初始化过程完成,标志复位
mbInitialComputations=false;
}
//计算 basline
mb = mbf/fx;
// 把每一帧分割成48x64个网格
// 根据关键点的畸变矫正后的位置分在不同的网格里面.
//根据关键点的位置将其分布在不同网格中
AssignFeaturesToGrid();
}
2.3 流程图
参考:https://blog.csdn.net/liu502617169/article/details/89423494
https://blog.csdn.net/moyu123456789/article/details/90241784