之前的文章SURF和SIFT算子实现特征点检测和SURF算子实现特征点提取与匹配简单地讲了利用SIFT和SURF算子检测特征点,并且对特征点进行特征提取得到特征描述符(descriptors),在此基础上还可以进一步利用透视变换和空间映射找出已知物体(目标检测)。这里具体的实现是首先采用SURF/SIFT特征点检测与特征提取,然后采用FLANN匹配法保留好的匹配点,再利用findHomography找出相应的透视变换,最后采用perspectiveTransform函数映射点群,在场景中获取目标的位置。
实验所用环境是opencv2.4.0+vs2008+win7,需要注意opencv2.4.X版本中SurfFeatureDetector/SiftFeatureDetector是包含在opencv2/nonfree/features2d.hpp中,FlannBasedMatcher是包含在opencv2/features2d/features2d.hpp中。
/** * @概述: 采用SURF算子在场景中进行已知目标检测 * @类和函数: SurfFeatureDetector + SurfDescriptorExtractor + FlannBasedMatcher + findHomography + perspectiveTransform * @实现步骤: * Step 1: 在图像中使用SURF算法SurfFeatureDetector检测关键点 * Step 2: 对检测到的每一个关键点使用SurfDescriptorExtractor计算其特征向量(也称描述子) * Step 3: 使用FlannBasedMatcher通过特征向量对关键点进行匹配,使用阈值剔除不好的匹配 * Step 4: 利用findHomography基于匹配的关键点找出相应的透视变换 * Step 5: 利用perspectiveTransform函数映射点群,在场景中获取目标的位置 * @author: holybin */ #include <ctime> #include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/nonfree/features2d.hpp" //SurfFeatureDetector实际在该头文件中 #include "opencv2/features2d/features2d.hpp" //FlannBasedMatcher实际在该头文件中 #include "opencv2/calib3d/calib3d.hpp" //findHomography所需头文件 using namespace cv; using namespace std; int main( int argc, char** argv ) { Mat imgObject = imread( "D:\\opencv_pic\\cat3d120.jpg", CV_LOAD_IMAGE_GRAYSCALE ); Mat imgScene = imread( "D:\\opencv_pic\\cat0.jpg", CV_LOAD_IMAGE_GRAYSCALE ); if( !imgObject.data || !imgScene.data ) { cout<< " --(!) Error reading images "<<endl; return -1; } double begin = clock(); ///-- Step 1: 使用SURF算子检测特征点 int minHessian = 400; SurfFeatureDetector detector( minHessian ); vector<KeyPoint> keypointsObject, keypointsScene; detector.detect( imgObject, keypointsObject ); detector.detect( imgScene, keypointsScene ); cout<<"object--number of keypoints: "<<keypointsObject.size()<<endl; cout<<"scene--number of keypoints: "<<keypointsScene.size()<<endl; ///-- Step 2: 使用SURF算子提取特征(计算特征向量) SurfDescriptorExtractor extractor; Mat descriptorsObject, descriptorsScene; extractor.compute( imgObject, keypointsObject, descriptorsObject ); extractor.compute( imgScene, keypointsScene, descriptorsScene ); ///-- Step 3: 使用FLANN法进行匹配 FlannBasedMatcher matcher; vector< DMatch > allMatches; matcher.match( descriptorsObject, descriptorsScene, allMatches ); cout<<"number of matches before filtering: "<<allMatches.size()<<endl; //-- 计算关键点间的最大最小距离 double maxDist = 0; double minDist = 100; for( int i = 0; i < descriptorsObject.rows; i++ ) { double dist = allMatches[i].distance; if( dist < minDist ) minDist = dist; if( dist > maxDist ) maxDist = dist; } printf(" max dist : %f \n", maxDist ); printf(" min dist : %f \n", minDist ); //-- 过滤匹配点,保留好的匹配点(这里采用的标准:distance<3*minDist) vector< DMatch > goodMatches; for( int i = 0; i < descriptorsObject.rows; i++ ) { if( allMatches[i].distance < 2*minDist ) goodMatches.push_back( allMatches[i]); } cout<<"number of matches after filtering: "<<goodMatches.size()<<endl; //-- 显示匹配结果 Mat resultImg; drawMatches( imgObject, keypointsObject, imgScene, keypointsScene, goodMatches, resultImg, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS //不显示未匹配的点 ); //-- 输出匹配点的对应关系 for( int i = 0; i < goodMatches.size(); i++ ) printf( " good match %d: keypointsObject [%d] -- keypointsScene [%d]\n", i, goodMatches[i].queryIdx, goodMatches[i].trainIdx ); ///-- Step 4: 使用findHomography找出相应的透视变换 vector<Point2f> object; vector<Point2f> scene; for( size_t i = 0; i < goodMatches.size(); i++ ) { //-- 从好的匹配中获取关键点: 匹配关系是关键点间具有的一 一对应关系,可以从匹配关系获得关键点的索引 //-- e.g. 这里的goodMatches[i].queryIdx和goodMatches[i].trainIdx是匹配中一对关键点的索引 object.push_back( keypointsObject[ goodMatches[i].queryIdx ].pt ); scene.push_back( keypointsScene[ goodMatches[i].trainIdx ].pt ); } Mat H = findHomography( object, scene, CV_RANSAC ); ///-- Step 5: 使用perspectiveTransform映射点群,在场景中获取目标位置 std::vector<Point2f> objCorners(4); objCorners[0] = cvPoint(0,0); objCorners[1] = cvPoint( imgObject.cols, 0 ); objCorners[2] = cvPoint( imgObject.cols, imgObject.rows ); objCorners[3] = cvPoint( 0, imgObject.rows ); std::vector<Point2f> sceneCorners(4); perspectiveTransform( objCorners, sceneCorners, H); //-- 在被检测到的目标四个角之间划线 line( resultImg, sceneCorners[0] + Point2f( imgObject.cols, 0), sceneCorners[1] + Point2f( imgObject.cols, 0), Scalar(0, 255, 0), 4 ); line( resultImg, sceneCorners[1] + Point2f( imgObject.cols, 0), sceneCorners[2] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); line( resultImg, sceneCorners[2] + Point2f( imgObject.cols, 0), sceneCorners[3] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); line( resultImg, sceneCorners[3] + Point2f( imgObject.cols, 0), sceneCorners[0] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); //-- 显示检测结果 imshow("detection result", resultImg ); double end = clock(); cout<<"\nSURF--elapsed time: "<<(end - begin)/CLOCKS_PER_SEC*1000<<" ms\n"; waitKey(0); return 0; }
实验结果:
/** * @概述: 采用SIFT算子在场景中进行已知目标检测 * @类和函数: SiftFeatureDetector + SiftDescriptorExtractor + FlannBasedMatcher + findHomography + perspectiveTransform * @实现步骤: * Step 1: 在图像中使用SIFT算法SiftFeatureDetector检测关键点 * Step 2: 对检测到的每一个关键点使用SiftDescriptorExtractor计算其特征向量(也称描述子) * Step 3: 使用FlannBasedMatcher通过特征向量对关键点进行匹配,使用阈值剔除不好的匹配 * Step 4: 利用findHomography基于匹配的关键点找出相应的透视变换 * Step 5: 利用perspectiveTransform函数映射点群,在场景中获取目标的位置 * @author: holybin */ #include <ctime> #include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/nonfree/features2d.hpp" //SiftFeatureDetector实际在该头文件中 #include "opencv2/features2d/features2d.hpp" //FlannBasedMatcher实际在该头文件中 #include "opencv2/calib3d/calib3d.hpp" //findHomography所需头文件 using namespace cv; using namespace std; int main( int argc, char** argv ) { Mat imgObject = imread( "D:\\opencv_pic\\cat3d120.jpg", CV_LOAD_IMAGE_GRAYSCALE ); Mat imgScene = imread( "D:\\opencv_pic\\cat0.jpg", CV_LOAD_IMAGE_GRAYSCALE ); if( !imgObject.data || !imgScene.data ) { cout<< " --(!) Error reading images "<<endl; return -1; } double begin = clock(); ///-- Step 1: 使用SIFT算子检测特征点 //int minHessian = 400; SiftFeatureDetector detector;//( minHessian ); vector<KeyPoint> keypointsObject, keypointsScene; detector.detect( imgObject, keypointsObject ); detector.detect( imgScene, keypointsScene ); cout<<"object--number of keypoints: "<<keypointsObject.size()<<endl; cout<<"scene--number of keypoints: "<<keypointsScene.size()<<endl; ///-- Step 2: 使用SIFT算子提取特征(计算特征向量) SiftDescriptorExtractor extractor; Mat descriptorsObject, descriptorsScene; extractor.compute( imgObject, keypointsObject, descriptorsObject ); extractor.compute( imgScene, keypointsScene, descriptorsScene ); ///-- Step 3: 使用FLANN法进行匹配 FlannBasedMatcher matcher; vector< DMatch > allMatches; matcher.match( descriptorsObject, descriptorsScene, allMatches ); cout<<"number of matches before filtering: "<<allMatches.size()<<endl; //-- 计算关键点间的最大最小距离 double maxDist = 0; double minDist = 100; for( int i = 0; i < descriptorsObject.rows; i++ ) { double dist = allMatches[i].distance; if( dist < minDist ) minDist = dist; if( dist > maxDist ) maxDist = dist; } printf(" max dist : %f \n", maxDist ); printf(" min dist : %f \n", minDist ); //-- 过滤匹配点,保留好的匹配点(这里采用的标准:distance<3*minDist) vector< DMatch > goodMatches; for( int i = 0; i < descriptorsObject.rows; i++ ) { if( allMatches[i].distance < 2*minDist ) goodMatches.push_back( allMatches[i]); } cout<<"number of matches after filtering: "<<goodMatches.size()<<endl; //-- 显示匹配结果 Mat resultImg; drawMatches( imgObject, keypointsObject, imgScene, keypointsScene, goodMatches, resultImg, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS //不显示未匹配的点 ); //-- 输出匹配点的对应关系 for( int i = 0; i < goodMatches.size(); i++ ) printf( " good match %d: keypointsObject [%d] -- keypointsScene [%d]\n", i, goodMatches[i].queryIdx, goodMatches[i].trainIdx ); ///-- Step 4: 使用findHomography找出相应的透视变换 vector<Point2f> object; vector<Point2f> scene; for( size_t i = 0; i < goodMatches.size(); i++ ) { //-- 从好的匹配中获取关键点: 匹配关系是关键点间具有的一 一对应关系,可以从匹配关系获得关键点的索引 //-- e.g. 这里的goodMatches[i].queryIdx和goodMatches[i].trainIdx是匹配中一对关键点的索引 object.push_back( keypointsObject[ goodMatches[i].queryIdx ].pt ); scene.push_back( keypointsScene[ goodMatches[i].trainIdx ].pt ); } Mat H = findHomography( object, scene, CV_RANSAC ); ///-- Step 5: 使用perspectiveTransform映射点群,在场景中获取目标位置 std::vector<Point2f> objCorners(4); objCorners[0] = cvPoint(0,0); objCorners[1] = cvPoint( imgObject.cols, 0 ); objCorners[2] = cvPoint( imgObject.cols, imgObject.rows ); objCorners[3] = cvPoint( 0, imgObject.rows ); std::vector<Point2f> sceneCorners(4); perspectiveTransform( objCorners, sceneCorners, H); //-- 在被检测到的目标四个角之间划线 line( resultImg, sceneCorners[0] + Point2f( imgObject.cols, 0), sceneCorners[1] + Point2f( imgObject.cols, 0), Scalar(0, 255, 0), 4 ); line( resultImg, sceneCorners[1] + Point2f( imgObject.cols, 0), sceneCorners[2] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); line( resultImg, sceneCorners[2] + Point2f( imgObject.cols, 0), sceneCorners[3] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); line( resultImg, sceneCorners[3] + Point2f( imgObject.cols, 0), sceneCorners[0] + Point2f( imgObject.cols, 0), Scalar( 0, 255, 0), 4 ); //-- 显示检测结果 imshow("detection result", resultImg ); double end = clock(); cout<<"\nSIFT--elapsed time: "<<(end - begin)/CLOCKS_PER_SEC*1000<<" ms\n"; waitKey(0); return 0; }
实验结果:
可以看出,SURF的速度比SIFT慢了,主要是由于匹配点较多计算复杂度高造成的,但是匹配点个数比SIFT多,所以准确度比SIFT高。