機器人視覺專案:視覺檢測識別+機器人跟隨(21)
影象特徵提取+基於opencv的特徵匹配
OpenCV的feature2d module中提供了從區域性影象特徵(Local image feature)的檢測、特徵向量(feature vector)的提取,到特徵匹配的實現。其中的區域性影象特徵包括了常用的幾種區域性影象特徵檢測與描述運算元,如FAST、SURF、SIFT、以及ORB。對於高維特徵向量之間的匹配,OpenCV主要有兩種方式:1)BruteForce窮舉法;2)FLANN近似K近鄰演算法(包含了多種高維特徵向量匹配的演算法,例如隨機森林等)。
localFeature.h
// 區域性影象特徵提取與匹配 // Author: www.icvpr.com // Blog : http://blog.csdn.net/icvpr #ifndef _FEATURE_H_ #define _FEATURE_H_ #include <iostream> #include <vector> #include <string> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; class Feature { public: Feature(); ~Feature(); Feature(const string& detectType, const string& extractType, const string& matchType); public: void detectKeypoints(const Mat& image, vector<KeyPoint>& keypoints); // 檢測特徵點 void extractDescriptors(const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptor); // 提取特徵向量 void bestMatch(const Mat& queryDescriptor, Mat& trainDescriptor, vector<DMatch>& matches); // 最近鄰匹配 void knnMatch(const Mat& queryDescriptor, Mat& trainDescriptor, vector<vector<DMatch>>& matches, int k); // K近鄰匹配 void saveKeypoints(const Mat& image, const vector<KeyPoint>& keypoints, const string& saveFileName = ""); // 儲存特徵點 void saveMatches(const Mat& queryImage, const vector<KeyPoint>& queryKeypoints, const Mat& trainImage, const vector<KeyPoint>& trainKeypoints, const vector<DMatch>& matches, const string& saveFileName = ""); // 儲存匹配結果到圖片中 private: Ptr<FeatureDetector> m_detector; Ptr<DescriptorExtractor> m_extractor; Ptr<DescriptorMatcher> m_matcher; string m_detectType; string m_extractType; string m_matchType; }; #endif
LocalFeature.cpp
// 區域性影象特徵提取與匹配 // Author: www.icvpr.com // Blog : http://blog.csdn.net/icvpr #include "LocalFeature.h" Feature::Feature() { m_detectType = "SIFT"; m_extractType = "SIFT"; m_matchType = "FruteForce"; initModule_nonfree(); } Feature::~Feature() { } Feature::Feature(const string& detectType, const string& extractType, const string& matchType) { assert(!detectType.empty()); assert(!extractType.empty()); assert(!matchType.empty()); m_detectType = detectType; m_extractType = extractType; m_matchType = matchType; initModule_nonfree(); } void Feature::detectKeypoints(const Mat& image, std::vector<KeyPoint>& keypoints) { assert(image.type() == CV_8UC1); assert(!m_detectType.empty()); keypoints.clear(); m_detector = FeatureDetector::create(m_detectType); m_detector->detect(image, keypoints); } void Feature::extractDescriptors(const Mat& image, std::vector<KeyPoint>& keypoints, Mat& descriptor) { assert(image.type() == CV_8UC1); assert(!m_extractType.empty()); m_extractor = DescriptorExtractor::create(m_extractType); m_extractor->compute(image, keypoints, descriptor); } void Feature::bestMatch(const Mat& queryDescriptor, Mat& trainDescriptor, std::vector<DMatch>& matches) { assert(!queryDescriptor.empty()); assert(!trainDescriptor.empty()); assert(!m_matchType.empty()); matches.clear(); m_matcher = DescriptorMatcher::create(m_matchType); m_matcher->add(std::vector<Mat>(1, trainDescriptor)); m_matcher->train(); m_matcher->match(queryDescriptor, matches); } void Feature::knnMatch(const Mat& queryDescriptor, Mat& trainDescriptor, std::vector<std::vector<DMatch>>& matches, int k) { assert(k > 0); assert(!queryDescriptor.empty()); assert(!trainDescriptor.empty()); assert(!m_matchType.empty()); matches.clear(); m_matcher = DescriptorMatcher::create(m_matchType); m_matcher->add(std::vector<Mat>(1, trainDescriptor)); m_matcher->train(); m_matcher->knnMatch(queryDescriptor, matches, k); } void Feature::saveKeypoints(const Mat& image, const vector<KeyPoint>& keypoints, const string& saveFileName) { assert(!saveFileName.empty()); Mat outImage; cv::drawKeypoints(image, keypoints, outImage, Scalar(255,255,0), DrawMatchesFlags::DRAW_RICH_KEYPOINTS ); // string saveKeypointsImgName = saveFileName + "_" + m_detectType + ".jpg"; imwrite(saveKeypointsImgName, outImage); } void Feature::saveMatches(const Mat& queryImage, const vector<KeyPoint>& queryKeypoints, const Mat& trainImage, const vector<KeyPoint>& trainKeypoints, const vector<DMatch>& matches, const string& saveFileName) { assert(!saveFileName.empty()); Mat outImage; cv::drawMatches(queryImage, queryKeypoints, trainImage, trainKeypoints, matches, outImage, Scalar(255, 0, 0), Scalar(0, 255, 255), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); // string saveMatchImgName = saveFileName + "_" + m_detectType + "_" + m_extractType + "_" + m_matchType + ".jpg"; imwrite(saveMatchImgName, outImage); }
基於特徵的匹配方法有FAST、SIFT、SURF、ORB等演算法,利用Surf演算法進行影象匹配其一般流程為:檢測物體特徵點->計算特徵點描述子->使用BurteForceMatcher或FLANN進行特徵點匹配->匹配到的特徵點進行透視變換findHomography()->透視矩陣變換perspectiveTransform()->繪製匹配物體輪廓。
影象匹配實際步驟:
一種是使用OpenCV的cv::FeatureDetector介面實現SURF特徵檢測,可以改變檢測閾值實現不同的效果。
// 設定兩個用於存放特徵點的向量 std::vector<cv::KeyPoint> keypoint1; std::vector<cv::KeyPoint> keypoint2; // 構造SURF特徵檢測器 cv::SurfFeatureDetector surf(3000); // 閾值 // 對兩幅圖分別檢測SURF特徵 surf.detect(image1,keypoint1); surf.detect(image2,keypoint2);
再構造SURF描述子提取器,輸出矩陣的形式。矩陣的行數與特徵向量中的元素的個數相同,每一行都是一個N維描述子的向量。在SURF演算法中,預設的描述子維度是64。兩個特徵點越相似,特徵向量也就越接近。
cv::SurfDescriptorExtractor surfDesc;
// 對兩幅影象提取SURF描述子
cv::Mat descriptor1, descriptor2;
surfDesc.compute(image1,keypoint1,descriptor1);
surfDesc.compute(image2,keypoint2,descriptor2);
提取出特徵描述子後需要進行匹配,用cv::BruteForceMatcher構造匹配器。cv::BruteForceMatcher是類cv::DescriptorMatcher的一個子類,定義了不同的匹配策略的共同介面,結果返回一個cv::DMatch向量,它將被用於表示一對匹配的描述子。之後通過排序篩選出理想的匹配結果,可以加上匹配結果的視覺化(也沒必要)。
完整程式碼:
#include <QCoreApplication>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 以下兩圖比之
// 輸入兩張要匹配的圖
cv::Mat image1= cv::imread("c:/Fig12.18(a1).jpg",0);
cv::Mat image2= cv::imread("c:/Fig12.18(a2).jpg",0);
if (!image1.data || !image2.data)
qDebug() << "Error!";
cv::namedWindow("Right Image");
cv::imshow("Right Image", image1);
cv::namedWindow("Left Image");
cv::imshow("Left Image", image2);
// 存放特徵點的向量
std::vector<cv::KeyPoint> keypoint1;
std::vector<cv::KeyPoint> keypoint2;
// 構造SURF特徵檢測器
cv::SurfFeatureDetector surf(3000); // 閾值
// 對兩幅圖分別檢測SURF特徵
surf.detect(image1,keypoint1);
surf.detect(image2,keypoint2);
// 輸出帶有詳細特徵點資訊的兩幅影象
cv::Mat imageSURF;
cv::drawKeypoints(image1,keypoint1,
imageSURF,
cv::Scalar(255,255,255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("Right SURF Features");
cv::imshow("Right SURF Features", imageSURF);
cv::drawKeypoints(image2,keypoint2,
imageSURF,
cv::Scalar(255,255,255),
cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::namedWindow("Left SURF Features");
cv::imshow("Left SURF Features", imageSURF);
// 構造SURF描述子提取器
cv::SurfDescriptorExtractor surfDesc;
// 對兩幅影象提取SURF描述子
cv::Mat descriptor1, descriptor2;
surfDesc.compute(image1,keypoint1,descriptor1);
surfDesc.compute(image2,keypoint2,descriptor2);
// 構造匹配器
cv::BruteForceMatcher< cv::L2<float> > matcher;
// 將兩張圖片的描述子進行匹配,只選擇25個最佳匹配
std::vector<cv::DMatch> matches;
matcher.match(descriptor1, descriptor2, matches);
std::nth_element(matches.begin(), // 初始位置
matches.begin()+24, // 排序元素的位置
matches.end()); // 終止位置
// 移除25位後的所有元素
matches.erase(matches.begin()+25, matches.end());
// 以下操作將匹配結果視覺化
cv::Mat imageMatches;
cv::drawMatches(image1,keypoint1, // 第一張圖片和檢測到的特徵點
image2,keypoint2, // 第二張圖片和檢測到的特徵點
matches, // 輸出的匹配結果
imageMatches, // 生成的影象
cv::Scalar(128,128,128)); // 畫直線的顏色
cv::namedWindow("Matches"); //, CV_WINDOW_NORMAL);
cv::imshow("Matches",imageMatches);
return a.exec();
}
另一種匹配方法則是使用 cv::FlannBasedMatcher 介面以及函式 FLANN 實現快速高效匹配(快速最近鄰逼近搜尋函式庫(Fast Approximate Nearest Neighbor Search Library))。
現在的一個疑問是在怎麼樣將sift,surf,orb,flann等特徵檢測和匹配的演算法加到我們行人跟隨的檢測程式碼中,這個需要就是說結合程式碼思考一下。