ORB特征提取算法是一种通过检测提取待测图片与模板图片中的灰度特征,实现模板图片与待测图片匹配的一种特征提取算法。相比于模板匹配matchTemple,ORB更集中于图像的灰度细节,速度也更快。
ORB的全称是ORiented Brief,也被称为rBrief。该算法在ICCV2011上由《ORB: an efficient alternative to SIFT or SURF》一文提出,并很快被收录于OpenCV2版本中。在OpenCV3.0中曾被放到OpenCV-contrib第三方扩展包中(当时可能因为版权),后来又被集成回OpenCV3本体中。ORB在OpenCV中分为CPU、GPU(CUDA)两种实现途径,ORB本身是为无法用GPU加速的CPU运算处理器设计的,网上能查到的CPU版ORB算法程序也占大多数,此文也暂时先介绍CPU版的ORB程序。
相比于先前的surf、sift特征提取算法,ORB的运行效率都更快(超过10倍),且具有surf和sift都不具备的旋转不变性。ORB本身不具备尺度不变性,但OpenCV里通过图像金字塔使ORB算法仍能检测出发生了一定程度尺度变换的目标,并通过透视变换等方法校正目标。(详情可参考毛星云的《OpenCV3编程入门》)
博主此文中的ORB代码在OpenCV3.1与3.3上都进行了实现,运行环境为Ubuntu14.04与16.04,代码复制后修改待测图像、模板图像的文件名,在编译中加入OpenCV的动态库,可直接使用。
博主用的模板是
分辨率为170*209,命名为model.jpg;
用的目标检测图片是
分辨率为1200*668,命名为ORB_test.jpg;
在IDE中创建C++项目,用C++11的规则运行如下程序:
[cpp] view plain copy
- #include <iostream>
- #include <signal.h>
- #include <vector>
-
- #include <opencv2/opencv.hpp>
-
- using namespace cv;
- using namespace std;
-
-
- int main()
- {
- Mat img_1 = imread("model.jpg");
- Mat img_2 = imread("ORB_test.jpg");
-
- if (!img_1.data || !img_2.data)
- {
- cout << "error reading images " << endl;
- return -1;
- }
-
- vector<Point2f> recognized;
- vector<Point2f> scene;
-
- recognized.resize(500);
- scene.resize(500);
-
- Mat d_srcL, d_srcR;
-
- Mat img_matches, des_L, des_R;
- //ORB算法的目标必须是灰度图像
- cvtColor(img_1, d_srcL, COLOR_BGR2GRAY);//CPU版的ORB算法源码中自带对输入图像灰度化,此步可省略
- cvtColor(img_2, d_srcR, COLOR_BGR2GRAY);
-
- Ptr<ORB> d_orb = ORB::create();
-
- Mat d_descriptorsL, d_descriptorsR, d_descriptorsL_32F, d_descriptorsR_32F;
-
- vector<KeyPoint> keyPoints_1, keyPoints_2;
-
- //设置关键点间的匹配方式为NORM_L2,更建议使用 FLANNBASED = 1, BRUTEFORCE = 2, BRUTEFORCE_L1 = 3, BRUTEFORCE_HAMMING = 4, BRUTEFORCE_HAMMINGLUT = 5, BRUTEFORCE_SL2 = 6
- Ptr<DescriptorMatcher> d_matcher = DescriptorMatcher::create(NORM_L2);
-
- std::vector<DMatch> matches;//普通匹配
- std::vector<DMatch> good_matches;//通过keyPoint之间距离筛选匹配度高的匹配结果
-
- d_orb -> detectAndCompute(d_srcL, Mat(), keyPoints_1, d_descriptorsL);
-
- d_orb -> detectAndCompute(d_srcR, Mat(), keyPoints_2, d_descriptorsR);
-
- d_matcher -> match(d_descriptorsL, d_descriptorsR, matches);
-
- int sz = matches.size();
- double max_dist = 0; double min_dist = 100;
-
- for (int i = 0; i < sz; i++)
- {
- double dist = matches[i].distance;
- if (dist < min_dist) min_dist = dist;
- if (dist > max_dist) max_dist = dist;
- }
-
- cout << "-- Max dist : " << max_dist << endl;
- cout << "-- Min dist : " << min_dist << endl;
-
- for (int i = 0; i < sz; i++)
- {
- if (matches[i].distance < 0.6*max_dist)
- {
- good_matches.push_back(matches[i]);
- }
- }
- //提取良好匹配结果中在待测图片上的点集,确定匹配的大概位置
- for (size_t i = 0; i < good_matches.size(); ++i)
- {
- scene.push_back(keyPoints_2[ good_matches[i].trainIdx ].pt);
- }
-
- for(unsigned int j = 0; j < scene.size(); j++)
- cv::circle(img_2, scene[j], 2, cv::Scalar(0, 255, 0), 2);
- //画出普通匹配结果
- Mat ShowMatches;
- drawMatches(img_1,keyPoints_1,img_2,keyPoints_2,matches,ShowMatches);
- imshow("matches", ShowMatches);
- imwrite("matches.png", ShowMatches);
- //画出良好匹配结果
- Mat ShowGoodMatches;
- drawMatches(img_1,keyPoints_1,img_2,keyPoints_2,good_matches,ShowGoodMatches);
- imshow("good_matches", ShowGoodMatches);
- imwrite("good_matches.png", ShowGoodMatches);
- //画出良好匹配结果中在待测图片上的点集
- imshow("MatchPoints_in_img_2", img_2);
- imwrite("MatchPoints_in_img_2.png", img_2);
- waitKey(0);
-
- return 0;
- }
程序运行结果如下:
1、显示模板、待测图间keyPoint之间的最大、最小距离,用于筛选良好的匹配结果:
2、显示良好的匹配结果,程序中设置模板与待测图间keyPoint的距离在最大keyPoint距离的0.6倍以内。从显示的图像上可见,drawMatches将模板与待测图连接并放在一张图上,keyPoint一般是选择图形的边缘、灰度变化较大的地方,这也意味着图像中若图形边缘较多,灰度变化的地方较多,则关键点也较多,匹配的效果也更好:
博主也试过将待测图片换成摄像头输入(将OpenCV读入的摄像头图像>>img_2,核心程序用for循环即可),在drawMatches的显示中效果极佳,模板与摄像头图像中的目标匹配良好,说明ORB算法完全可用于摄像头的目标检测。但根据博主的测试经验,将ORB用于摄像头目标检测需注意两点:
(1)模板的灰度变换一定要较多且明显(否则在复杂环境下匹配集中度不高),建议使用相同摄像头在相同分辨率下获取模板(OpenCV获取模板的程序博主下几篇文章中将给出);
(2)一旦摄像头全黑,程序无法匹配到任何图像时ORB程序将崩溃,可能需要用try或守护进程等解决方法(一定时间间隔检查程序是否在运行,不在运行则启动程序)。
3、显示普通匹配结果,以示良好匹配与普通匹配间的差别。该匹配结果和match选择方法也有很大关系,在合适的match方法作用下,良好匹配结果的匹配线应尽可能相互平行,或呈扇形(和旋转目标进行匹配):
4、显示良好匹配结果中在待测图片上的点集,可见大多数在灰度变化较大、较密集的地方,匹配程度较高,但附近也存在误差点。既然已将这些点集提取出,之后就可用概率统计分布的方式排除误差,框出目标的大概位置。
得到目标的大概位置后,一般要用OpenCV中的透视变换perspectiveTransform或warpPerspective来确定、框出物体的实际位置,确认其中心坐标。博主也在研究这一方面的内容,可能需要和matchTemple模板匹配结合,欢迎讨论!