首先,我们需要知道这部分代码的目的和步骤:
理清逻辑之后,咱们来开始看代码
argc存储了传入程序的参数个数,由于第一个参数默认是程序本身,又传入了两张图片,因此参数个数为3个
int main(int argc, char **argv) {
if (argc != 3) {
cout << "usage: feature_extraction img1 img2" << endl;
return 1;
}
创建了两个Mat类型的变量img_1和img_2,分别使用imread函数将argv[1]和argv[2]所指向的两个参数按照彩色图像的方式读入,并存储到img_1和img_2中。
//-- 读取图像
Mat img_1 = imread(argv[1], CV_LOAD_IMAGE_COLOR);
Mat img_2 = imread(argv[2], CV_LOAD_IMAGE_COLOR);
assert(img_1.data != nullptr && img_2.data != nullptr); //如果表达式不为0,则继续执行后面的语句。
//-- 初始化
std::vector keypoints_1, keypoints_2; //定义了两个vector容器keypoints_1和 keypoints_2,存放的对象则是KeyPoint类型
Mat descriptors_1, descriptors_2;
// “Ptr detector = ”等价于 “FeatureDetector * detector =”
//Ptr是OpenCV中使用的智能指针模板类
Ptr detector = ORB::create();//特征检测器FeatureDetetor,通过定义FeatureDetector的对象可以使用多种特征检测及匹配方法,通过create()函数调用。
Ptr descriptor = ORB::create();//描述子提取器DescriptorExtractor是提取关键点的描述向量类抽象基类。
Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");//描述子匹配器DescriptorMatcher用于特征匹配,"Brute-force-Hamming"表示使用汉明距离进行匹配。
通过刚定义的detector中的detect函数,将img中的像素进行分析处理,并将提取出的特征点存于keypoints容器中;
//第一步,检测Oriented Fast角点位置
chrono::steady_clock::time_point t1 = chrono::steady_clock::now(); 计时 t1时刻
detector->detect(img_1, keypoints_1); //对参数1图像进行特征的提取,并存放入参数2的数组中
detector->detect(img_2, keypoints_2);
使用刚定义的descriptor中的compute函数,对每张img中keypoints所对应的每个像素点进行描述子的计算,并存于Mat类变量descriptor中
//第二步,根据角点计算BREIF描述子
descriptor->compute(img_1, keypoints_1, descriptors_1);//computer()计算关键点的描述子向量
descriptor->compute(img_2, keypoints_2, descriptors_2);
chrono::steady_clock::time_point t2 = chrono::steady_clock::now(); //计时 t2时刻
chrono::duration time_used = chrono::duration_cast>(t2 - t1);//(t2-t1)的时间,其实就是在计算 提取ORB及计算BRIEF的时间
cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;
Mat outimg1;
drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);//将ORB圈出来
imshow("ORB features", outimg1);
定义一个容器,存储的对象类型为cv::DMatch,使用matcher中的match函数,将存有描述子信息的Mat类变量descriptors_1与descriptors_2进行相似度匹配,并存于matches容器中。
vector matches; //DMatch是匹配关键点描述子 类, matches用于存放匹配项
t1 = chrono::steady_clock::now();
matcher->match(descriptors_1, descriptors_2, matches); //对参数1 2的描述子进行匹配,并将匹配项存放于matches中
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast>(t2 - t1);
cout << "match the ORB cost: " << time_used.count() << "seconds. " << endl;
//第四步,匹配点对筛选
//计算最小距离和最大距离
auto min_max = minmax_element(matches.begin(), matches.end(),
[](const DMatch &m1, const DMatch &m2){ return m1.distance < m2.distance; });
// auto 可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型
// minmax_element()返回指向范围内最小和最大元素的一对迭代器。参数1 2为起止迭代器范围
double min_dist = min_max.first->distance; // min_max存储了一堆迭代器,first指向最小元素
double max_dist = min_max.second->distance; // second指向最大元素
//当描述子之间的距离大于两倍最小距离时,就认为匹配有误。但有时最小距离会非常小,所以要设置一个经验值30作为下限。
vector good_matches; //存放良好的匹配项
//当描述自之间的距离大于两倍的min_dist,即认为匹配有误,舍弃掉。
//但是有时最小距离非常小,比如趋近于0了,所以这样就会导致min_dist到2*min_dist之间没有几个匹配。
// 所以,在2*min_dist小于30的时候,就取30当上限值,小于30即可,不用2*min_dist这个值了
for(int i = 0; i < descriptors_1.rows; ++i){
if(matches[i].distance <= max(2 * min_dist, 30.0)){
good_matches.push_back(matches[i]);
}
}
调用drawMatches函数对两张图像img_1、img_2以及其之间的特征点配对进行连线与拼接,将左右两张图拼接成一张图并存入Mat类型对象img_match中。
Mat img_match;//所有匹配点图
Mat img_goodmatch;//筛选后的匹配点图
drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);
drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);
imshow("all matches", img_match);
imshow("good matches", img_goodmatch);
waitKey(0);
return 0;
最后效果: