上一篇文章讲到了Moving Consistency Check 得到了放置不满足对极约束的特征点矩阵T_M,这一篇文章我们来一起学习一下DS-SLAM的 Segment.cc 语义分割线程和动态点的删除.
1 语义分割线程的开启在System.cc 文件里面:
mpSegment =new Segment(
pascal_prototxt, //模型文件,网络模型
pascal_caffemodel, //训练文件,训练好的权重参数
pascal_png); // 标签文件,上色的图片
mptSegment =new thread(&ORB_SLAM2::Segment::Run,mpSegment);
程序会不停的执行Segment::Run 这个函数,首先当我们创建 Segment对象的时候,程序会执行类构造函数:
Segment::Segment(const string &pascal_prototxt, const string &pascal_caffemodel, const string &pascal_png):mbFinishRequested(false),mSkipIndex(SKIP_NUMBER),mSegmentTime(0),imgIndex(0)
{
model_file = pascal_prototxt; //模型文件,网络模型
trained_file = pascal_caffemodel; //训练文件,训练好的参数
LUT_file = pascal_png; // 调色版
label_colours = cv::imread(LUT_file,1);
cv::cvtColor(label_colours, label_colours, CV_RGB2BGR); // 格式转换
mImgSegmentLatest=cv::Mat(Camera::height,Camera::width,CV_8UC1);
mbNewImgFlag=false;
}
在构造函数里面我们会加载网络模型文件 pascal_prototxt ,权重文件(提前训练好的)pascal_caffemodel,和调色版pascal_png.
关于调色版文件的解释,有如下的回答:
https://blog.csdn.net/yang332233/article/details/107995917
2 Segment线程不停执行Segment::Run() 线程主函数
主要代码如下:
if(mSkipIndex==SKIP_NUMBER)
{
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
// Recognise by Semantin segmentation 分割的代码就只有这一行
mImgSegment=classifier->Predict(mImg, label_colours);
mImgSegment_color = mImgSegment.clone();
// 将单通道的灰度图转化为三通道的 RGB图 ,但是图像虽然还是灰色的,但是已经是三通道的了
cv::cvtColor(mImgSegment,mImgSegment_color, CV_GRAY2BGR);
// 将像素进行映射,使用了Opencv中的LUT函数,运行之后语义分割的图片 有了颜色
LUT(mImgSegment_color, label_colours, mImgSegment_color_final);
// 将原图象和分割后的图像 都resize 成相同的尺寸
cv::resize(mImgSegment, mImgSegment, cv::Size(Camera::width,Camera::height) );
cv::resize(mImgSegment_color_final, mImgSegment_color_final, cv::Size(Camera::width,Camera::height) );
std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();
mSegmentTime+=std::chrono::duration_cast<std::chrono::duration<double> >(t4 - t3).count();
mSkipIndex=0;
imgIndex++;
}
通过caffe框架的Predict函数完成语义分割,得到语义分割的结果mImgSegment,这个图片是灰色的,没有颜色,经过调色版上色之后得到**mImgSegment_color_final。 一会我们要用分割后的图片mImgSegment,去完成运动特征点的删除工作。
SKIP_NUMBER 这个变量的设置,应该是第一幅图片不会进行语义分割,而之后的图片才会进行语义分割。
分割完之后,保存分割的结果,并将对应的标志位设置为1。
// 保存了上一帧 和本帧的 分割图片mImgSegmentLatest ,并且告诉Track 新的分割图片已经就绪
ProduceImgSegment();
3 语义分割线程到此完结,接下来结合语义分割的结果的动态点进行删除
这一步主要是在 CalculEverything这个函数完成的。
// step 5 : 移除 动态的 外点
mCurrentFrame.CalculEverything(
mImRGB, // 彩色图
mImGray, // 灰度图
mImDepth, //深度图
mpSegment->mImgSegmentLatest // 上面图像的语义分割的结果
);
在程序里面实际上没有用到 mImRGB 这个变量。
// 3. 1 遍历语义分割之后的图像,当检查出 “人” 跳出循环
// 1 逐个遍历语义分割之后的图像的像素,当检查出 “人” 跳出循环
for ( int m=0; m<imS.rows; m+=1 )
{
for ( int n=0; n<imS.cols; n+=1 )
{
int labelnum = (int)imS.ptr<uchar>(m)[n];
//The lable of people is 15
if(labelnum == PEOPLE_LABLE)
{
flagprocess=1;
break;
}
}
if(flagprocess == 1)
break;
}
在语义分割图当中,人的灰度值是15 ,当检测到人时,flagprocess 标志位设为1,只有当检测到人的时候,才会执行CheckMovingKeyPoints 这个函数 去看是否去除特征点。
3.2 去除 人身上的动态点
CheckMovingKeyPoints 这个函数如下,通过分割图imgS 和潜在的动态点 T擦除 关键点mvKeysT 中的位于人身上的动态点。
int ORBextractor::CheckMovingKeyPoints(
const cv::Mat &imGray, // 灰度图
const cv::Mat &imS, // 分割图
std::vector<std::vector<cv::KeyPoint>>& mvKeysT, // 关键点
std::vector<cv::Point2f> T) //光流点T矩阵
3.2.1 逐个遍历T 矩阵的特征点以及其周围15✖15的小方格内的 像素灰度值,只要T矩阵的光流点或者周围15✖15的小方格有一个像素值等于15(语义分割的人的灰度值),则说明动态点落在了人身上,人是运动的。这样的条件是不是有点苛刻呢,毕竟只有一个潜在的运动点落在人身上,就认为整个人是运动的
//检查剔除区域(T_M集中每个像素点及周围15像素),因为T_M 矩阵放置的就是离群点。是不是具有动态先验“人类”的标签
// 在T_M 离群点的 15*15 的像素块内发现 人,那么flag_orb_mov 设置为1.
for (int i = 0; i < T.size(); i++)
{
for(int m = -15; m < 15; m++)
{
for(int n = -15; n < 15; n++)
{
// 确定 mx ,my 的范围不能出界
int my = ((int)T[i].y + n) ;
int mx = ((int)T[i].x + m) ;
if( ((int)T[i].y + n) > (Camera::height -1) ) my = (Camera::height - 1) ;
if( ((int)T[i].y + n) < 1 ) my = 0;
if( ((int)T[i].x + m) > (Camera::width -1) ) mx = (Camera::width - 1) ;
if( ((int)T[i].x + m) < 1 ) mx = 0;
// The label of peopel is 15
if((int)imS.ptr<uchar>(my)[mx] == PEOPLE_LABLE)
{
flag_orb_mov=1;
break;
}
}
if(flag_orb_mov==1)
break;
}
if(flag_orb_mov==1)
break;
}
3.2.1 既然人是运动的,那么就将提取的ORB特征点位于 人身上的部分全部剔除。
for (int level = 0; level < nlevels; ++level)
{
vector<cv::KeyPoint>& mkeypoints = mvKeysT[level]; // 提取每一层的金字塔
int nkeypointsLevel = (int)mkeypoints.size();
if(nkeypointsLevel==0)
continue;
if (level != 0)
scale = mvScaleFactor[level];
else
scale =1;
vector<cv::KeyPoint>::iterator keypoint = mkeypoints.begin();
// 标签带有先验动态,则在特征点金字塔内删除该特征点
while(keypoint != mkeypoints.end())
{
cv::Point2f search_coord = keypoint->pt * scale;
// Search in the semantic image
if(search_coord.x >= (Camera::width -1)) search_coord.x=(Camera::width -1);
if(search_coord.y >= (Camera::height -1)) search_coord.y=(Camera::height -1) ;
// 用来访问灰度图像的单个像素。对于灰度图像,每个像素只存储一个值。
int label_coord =(int)imS.ptr<uchar>((int)search_coord.y)[(int)search_coord.x];
//发现这个特征点的坐标 落在 人 身上,则把这个特征点删除
if(label_coord == PEOPLE_LABLE)
{
keypoint=mkeypoints.erase(keypoint); // 将这个特征点删除掉
}
else
{
keypoint++;
}
}
}
程序的大致过程是:遍历所有前面Frame初始化提取的ORB特征点,如果这个特征点落在人身上(程序里是 找出特征点在语义分割图imS的位置(u,v),如果这个位置的像素值是15,也就是这个特征点在人身上,那么就在特征点容器里面把这个点 删除 ),剩下的静态点保留在 mvKeysTemp 中。
至此,动态特征点的删除工作就已经做完了,保留的特征点认为是静态的点,可以参与后续的描述子计算,去畸变和Track部分