理解这些变量的含义非常重要,有一些非常容易搞混,让人费解。。。
直接看吧!
cv::Mat mask;//图像掩膜
cv::Mat fisheye_mask;//鱼眼相机的掩膜
// prev_img是上一次发布的帧的图像数据
// cur_img是光流跟踪的前一帧的图像数据
// forw_img是光流跟踪的后一帧的图像数据
cv::Mat prev_img, cur_img, forw_img;
vector n_pts;//每一帧中新提取的特征点
vector prev_pts, cur_pts, forw_pts;//对应的图像特征点
vector prev_un_pts, cur_un_pts;//归一化相机坐标系下的坐标
vector pts_velocity;//当前帧相对前一帧特征点沿x,y方向的像素移动速度
vector ids;//能够被跟踪到的特征点的id
vector track_cnt;//当前帧forw_img中每个特征点被追踪的次数
map cur_un_pts_map;//把特征点的id和点封装的数据类型
map prev_un_pts_map;
camodocal::CameraPtr m_camera;//相机模型
double cur_time;
double prev_time;
static int n_id;//用来作为特征点id,每检测到一个新的特征点,就将n_id作为该特征点的id,然后n_id加1
有一个非常好用,真的帮助理解的参考,必须要奉上: https://blog.csdn.net/liuzheng1/article/details/89406276
它是我看到的对于帮助理解这个函数最容易的参考资料了。。。
void FeatureTracker::readImage(const cv::Mat &_img, double _cur_time)
{
cv::Mat img;
TicToc t_r;
cur_time = _cur_time;
//【1】判断图像是否需要均衡化
if (EQUALIZE)//均衡化增强对比度 默认值为1,表示图像太亮或者太暗
{
cv::Ptr clahe = cv::createCLAHE(3.0, cv::Size(8, 8));//opencv里头的函数
TicToc t_c;
clahe->apply(_img, img);
ROS_DEBUG("CLAHE costs: %fms", t_c.toc());
}
else
img = _img;
//[2]如果当前帧的图像数据forw_img为空,说明当前是第一次读入图像数据,将读入的图像赋给当前帧forw_img,同时,还将读入的图像赋给prev_img、cur_img
if (forw_img.empty())
{
prev_img = cur_img = forw_img = img;
}
else
{
forw_img = img;
}
forw_pts.clear();//此时forw_pts还保存的是上一帧图像中的特征点,所以把它清除
//[3]判断光流跟踪前一帧中特征点规模是否不为0,不为0表示有图像数据点,对其进行光流跟踪
if (cur_pts.size() > 0)
{
TicToc t_o;
vector status;//status表示 cur_pts和forw_pts中对应点对是否跟踪成功,无法被追踪到的点标记为0
vector err;
cv::calcOpticalFlowPyrLK(cur_img, forw_img, cur_pts, forw_pts, status, err, cv::Size(21, 21), 3);//calcOpticalFlowPyrLK() 从cur_pts到forw_pts做LK金字塔光流法
for (int i = 0; i < int(forw_pts.size()); i++)//光流跟踪结束后,判断跟踪成功的角点是否都在图像内,不在图像内的status置为0
if (status[i] && !inBorder(forw_pts[i]))
status[i] = 0;
//根据status,把跟踪失败的点剔除
//不仅要从当前帧数据forw_pts中剔除,而且还要从cur_un_pts、prev_pts和cur_pts中剔除
//prev_pts和cur_pts中的特征点是一一对应的
//记录特征点id的ids,和记录特征点被跟踪次数的track_cnt也要剔除
reduceVector(prev_pts, status);
reduceVector(cur_pts, status);
reduceVector(forw_pts, status);
reduceVector(ids, status);
reduceVector(cur_un_pts, status);
reduceVector(track_cnt, status);
ROS_DEBUG("temporal optical flow costs: %fms", t_o.toc());
}
//[4]光流追踪成功,特征点被成功跟踪的次数track_cnt就加1
for (auto &n : track_cnt)//track_cnt为每个角点的跟踪次数
n++;
//[5]发布这一帧数据
if (PUB_THIS_FRAME)
{
rejectWithF();//通过基本矩阵F剔除outliers
ROS_DEBUG("set mask begins");
TicToc t_m;
setMask();//保证相邻的特征点之间要相隔30个像素,设置mask
ROS_DEBUG("set mask costs %fms", t_m.toc());
ROS_DEBUG("detect feature begins");
TicToc t_t;
//[6] //计算是否需要提取新的特征点
int n_max_cnt = MAX_CNT - static_cast(forw_pts.size());
if (n_max_cnt > 0)
{
if(mask.empty())
cout << "mask is empty " << endl;
if (mask.type() != CV_8UC1)
cout << "mask type wrong " << endl;
if (mask.size() != forw_img.size())
cout << "wrong size " << endl;
/**
* cv::goodFeaturesToTrack()
* @brief 在mask中不为0的区域检测新的特征点
* @optional ref:https://docs.opencv.org/3.1.0/dd/d1a/group__imgproc__feature.html#ga1d6bb77486c8f92d79c8793ad995d541
* @param[in] InputArray _image=forw_img 输入图像
* @param[out] _corners=n_pts 存放检测到的角点的vector
* @param[in] maxCorners=MAX_CNT - forw_pts.size() 返回的角点的数量的最大值
* @param[in] qualityLevel=0.01 角点质量水平的最低阈值(范围为0到1,质量最高角点的水平为1),小于该阈值的角点被拒绝
* @param[in] minDistance=MIN_DIST 返回角点之间欧式距离的最小值
* @param[in] _mask=mask 和输入图像具有相同大小,类型必须为CV_8UC1,用来描述图像中感兴趣的区域,只在感兴趣区域中检测角点
* @param[in] blockSize:计算协方差矩阵时的窗口大小
* @param[in] useHarrisDetector:指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
* @param[in] harrisK:Harris角点检测需要的k值
* @return void
*/
cv::goodFeaturesToTrack(forw_img, n_pts, MAX_CNT - forw_pts.size(), 0.01, MIN_DIST, mask);//MAX_CNT=150
}
else
n_pts.clear();//n_pts是用来存储提取的新的特征点的,在这个条件下表示不需要提取新的特征点,将上一帧中提取的点clear掉
ROS_DEBUG("detect feature costs: %fms", t_t.toc());
//[7]将n_pts中新提取到的角点放到forw_pts中,id初始化-1,track_cnt初始化为1.
ROS_DEBUG("add feature begins");
TicToc t_a;
addPoints();
ROS_DEBUG("selectFeature costs: %fms", t_a.toc());
}
//[8]将当前帧数据传递给上一帧
prev_img = cur_img;//在第一帧处理中还是等于当前帧forw_img
prev_pts = cur_pts;//在第一帧中不做处理
prev_un_pts = cur_un_pts;//在第一帧中不做处理
cur_img = forw_img;//将当前帧赋值给上一帧
cur_pts = forw_pts;
//[9]//从第二张图像输入后每进行一次循环,最后还需要对匹配的特征点对进行畸变矫正和深度归一化,计算速度
undistortedPoints();
prev_time = cur_time;
}
基本上每一个步骤都给了非常详细的解释,自行看代码吧,没什么话说了。。。在readImage()函数中又调用了6个作者自己写的函数,分别是:
bool inBorder(const cv::Point2f &pt)//判断点是否在图像内
void reduceVector(vector &v, vector status)//根据状态status进行重组,将staus中为1的对应点保存下来,为0的对应点对去除掉
void FeatureTracker::setMask()//对跟踪点进行排序并去除密集点
void FeatureTracker::addPoints()//把新提取的特征点放到forw_pts中,id初始化-1,track_cnt初始化为1.
void FeatureTracker::rejectWithF()//通过基本矩阵(F)去除外点outliers
void FeatureTracker::undistortedPoints()//进行畸变矫正
其中,setMask()注释为:
void FeatureTracker::setMask()
{
//[1]判断是否是鱼眼相机,采取相应的掩膜方式
if(FISHEYE)
mask = fisheye_mask.clone();
else
mask = cv::Mat(ROW, COL, CV_8UC1, cv::Scalar(255));
//[2]定义复合数据类型,三个数据类型分别表示特征点被track次数、特征点、特征点的id
vector>> cnt_pts_id;
//[3]将当前帧中的特征点,及特征点id和被track的次数打包push进cn_pts_id中
for (unsigned int i = 0; i < forw_pts.size(); i++)
cnt_pts_id.push_back(make_pair(track_cnt[i], make_pair(forw_pts[i], ids[i])));
//[4]按特征点的跟踪次数由大到小排序
sort(cnt_pts_id.begin(), cnt_pts_id.end(), [](const pair> &a, const pair> &b)
{
return a.first > b.first;
});
//[5]清空track_cnt,forw_pts,id并重新存入
forw_pts.clear();
ids.clear();
track_cnt.clear();
//[6]遍历cnt_pts_id,操作目的为使图像提取的特征点更均匀
for (auto &it : cnt_pts_id)
{
if (mask.at(it.second.first) == 255) //当前特征点位置对应的mask值为255,则保留当前特征点,将对应的特征点位置pts,id,被追踪次数cnt分别存入
{
forw_pts.push_back(it.second.first);
ids.push_back(it.second.second);
track_cnt.push_back(it.first);
//图片,点,半径,颜色为0表示在角点检测在该点不起作用,粗细(-1)表示填充
//在mask中将当前特征点周围半径为MIN_DIST的区域设置为0,后面不再选取该区域内的点(使跟踪点不集中在一个区域上)
cv::circle(mask, it.second.first, MIN_DIST, 0, -1);
}
}
}
rejectWithF()注释如下:
void FeatureTracker::rejectWithF()
{
if (forw_pts.size() >= 8)
{
ROS_DEBUG("FM ransac begins");
TicToc t_f;
//[1]构建cv::Point2f类型的点为cv::findFundamentalMat()做准备
vector un_cur_pts(cur_pts.size()), un_forw_pts(forw_pts.size());
for (unsigned int i = 0; i < cur_pts.size(); i++)
{
//根据不同的相机模型将二维坐标转换到三维坐标
//对于PINHOLE(针孔相机)可将像素坐标转换到归一化平面并去畸变
//对于CATA(卡特鱼眼相机)将像素坐标投影到单位圆内
Eigen::Vector3d tmp_p;
m_camera->liftProjective(Eigen::Vector2d(cur_pts[i].x, cur_pts[i].y), tmp_p);
tmp_p.x() = FOCAL_LENGTH * tmp_p.x() / tmp_p.z() + COL / 2.0;// 转换为像素坐标
tmp_p.y() = FOCAL_LENGTH * tmp_p.y() / tmp_p.z() + ROW / 2.0;
un_cur_pts[i] = cv::Point2f(tmp_p.x(), tmp_p.y());
m_camera->liftProjective(Eigen::Vector2d(forw_pts[i].x, forw_pts[i].y), tmp_p);
tmp_p.x() = FOCAL_LENGTH * tmp_p.x() / tmp_p.z() + COL / 2.0;
tmp_p.y() = FOCAL_LENGTH * tmp_p.y() / tmp_p.z() + ROW / 2.0;
un_forw_pts[i] = cv::Point2f(tmp_p.x(), tmp_p.y());
}
vector status;
//[2]调用cv::findFundamentalMat对un_cur_pts和un_forw_pts计算F矩阵,RANSAC去除outliers
cv::findFundamentalMat(un_cur_pts, un_forw_pts, cv::FM_RANSAC, F_THRESHOLD, 0.99, status);
int size_a = cur_pts.size();
reduceVector(prev_pts, status);//根据status进行重组
reduceVector(cur_pts, status);
reduceVector(forw_pts, status);
reduceVector(cur_un_pts, status);
reduceVector(ids, status);
reduceVector(track_cnt, status);
ROS_DEBUG("FM ransac: %d -> %lu: %f", size_a, forw_pts.size(), 1.0 * forw_pts.size() / size_a);
ROS_DEBUG("FM ransac costs: %fms", t_f.toc());
}
}
undistortedPoints()注释如下:
void FeatureTracker::undistortedPoints()//计算点的在x,y方向上的跟踪速度
{
//【1】清除cur_un_pts、cur_un_pts_map的状态
cur_un_pts.clear();
cur_un_pts_map.clear();
//cv::undistortPoints(cur_pts, un_pts, K, cv::Mat());
//[2]给cur_un_pts、cur_un_pts_map装进新的值
for (unsigned int i = 0; i < cur_pts.size(); i++)
{
Eigen::Vector2d a(cur_pts[i].x, cur_pts[i].y);
Eigen::Vector3d b;
m_camera->liftProjective(a, b);//转像素坐标
cur_un_pts.push_back(cv::Point2f(b.x() / b.z(), b.y() / b.z()));
cur_un_pts_map.insert(make_pair(ids[i], cv::Point2f(b.x() / b.z(), b.y() / b.z())));
//printf("cur pts id %d %f %f", ids[i], cur_un_pts[i].x, cur_un_pts[i].y);
}
// 【3】判断上一帧中特征点点是否为0,不为0则计算点跟踪的速度
if (!prev_un_pts_map.empty())
{
double dt = cur_time - prev_time;
pts_velocity.clear(); // pts_velocity表示当前帧相对前一帧特征点沿x,y方向的像素移动速度
for (unsigned int i = 0; i < cur_un_pts.size(); i++)
{
if (ids[i] != -1)//如果点不是第一次被track
{
std::map::iterator it;
it = prev_un_pts_map.find(ids[i]);
if (it != prev_un_pts_map.end())
{
double v_x = (cur_un_pts[i].x - it->second.x) / dt;//计算x方向上的速度
double v_y = (cur_un_pts[i].y - it->second.y) / dt;//计算y方向上的速度
pts_velocity.push_back(cv::Point2f(v_x, v_y));
}
else
pts_velocity.push_back(cv::Point2f(0, 0));
}
else//点第一次被track
{
pts_velocity.push_back(cv::Point2f(0, 0));
}
}
}
else//为0则push一个值
{
for (unsigned int i = 0; i < cur_pts.size(); i++)
{
pts_velocity.push_back(cv::Point2f(0, 0));
}
}
//[4]当前帧中的点和点的id传递到上一帧
prev_un_pts_map = cur_un_pts_map;
}
其它几个函数就不注释啦!