在特征點檢測過後,完成圖像間特征點的比對是非常重要的。對于圖像配準工作而言。特征點比對的準确度是最值得關注的點,甯願少比對,也不能誤比對。
我在此圖像配準中使用的是KNN比對:
下面引用自百度百科。
K最近鄰(kNN,k-NearestNeighbor)分類算法是資料挖掘分類技術中最簡單的方法之一,所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lcl3aU9Eer1WZxkzVlZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TOwgDOyYDM5AzMwITM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
如上圖所示,如果k=5,那麼對于未知點 xu ,離它最近的5個點有4個屬于 w1 ,1個屬于 w3 ,那麼我們可以認為這個未知點 xu 是屬于 w1 的。
在特征點比對的場景中,Lowe設定的比對規則是1NN,即尋找特征向量離得最近的點進行比對。但是這樣會産生誤比對,是以它又定制了一個規則及1NN/2NN < ratio時,該比對才生效。Lowe推薦的ratio為0.8。
得到比對的一系列點後,接下來就是要産生兩圖像的對應仿射矩陣了。這裡采用的是RANSAC法。
最終圖像比對源碼為:
#include<iostream>
//#include <opencv2/xfeatures2d.hpp>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void main()
{
double start, duration_ms;
cv::Mat mask1, mask2, imgshowcanny;
cv::Mat imgShow1, imgShow2, imgShow3;
// 聲明并從data檔案夾裡讀取兩個rgb與深度圖
cv::Mat rgb1 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\1.jpg", );
cv::Mat rgb2 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\2.jpg", );
cv::Mat rgb3 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\3.jpg", );
cv::Mat rgb4 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\4.jpg", );
cv::Mat rgb5 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\5.jpg", );
cv::Mat rgb6 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\6.jpg", );
cv::Mat rgb7 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\7.jpg", );
cv::Mat rgb8 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\8.jpg", );
cv::Mat rgb9 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\9.jpg", );
cv::Mat rgb10 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\10.jpg", );
vector< cv::KeyPoint > kp1, kp2, kp3, kp4, kp5, kp6, kp7, kp8, kp9, kp10; //關鍵點
mask1 = rgb1.clone();
//邊緣檢測
//cout << "Canny edge detection" << endl;
//mask1 = Mat::zeros(rgb1.size(), CV_8UC1);
//mask2 = Mat::zeros(rgb2.size(), CV_8UC1);
cv::Canny(mask1, mask1, , , );
//cv::Canny(rgb2, mask2, 50, 150, 3);
cv::Mat element = getStructuringElement(MORPH_RECT, Size(, ));
dilate(mask1, mask1, element);
cv::imwrite("F:/study work/Visual Odometry dev/VO_practice_2016.4/算法測試/KAZE算法/data/mask1.jpg", mask1);
imgshowcanny = mask1.clone();
cv::resize(imgshowcanny, imgshowcanny, cv::Size(imgshowcanny.cols / , imgshowcanny.rows / ));
cv::imshow("mask", imgshowcanny);
//特征檢測算法:AKAZE
cv::Ptr<cv::AKAZE> akaze = AKAZE::create(AKAZE::DESCRIPTOR_MLDB, , , , , , KAZE::DIFF_PM_G2);
Mat descriptors_1, descriptors_2, descriptors_3, descriptors_4, descriptors_5, descriptors_6, descriptors_7, descriptors_8, descriptors_9, descriptors_10;
start = double(getTickCount());
akaze->detectAndCompute(rgb1, mask1, kp1, descriptors_1, false);
duration_ms = (double(getTickCount()) - start) * / getTickFrequency();//計時
std::cout << "It took " << duration_ms << " ms to detect features in pic1 using AKAZE." << std::endl;
start = double(getTickCount());
akaze->detectAndCompute(rgb2, cv::Mat(), kp2, descriptors_2, false);
duration_ms = (double(getTickCount()) - start) * / getTickFrequency();//計時
std::cout << "It took " << duration_ms << " ms to detect features in pic2 using AKAZE." << std::endl;
akaze->detectAndCompute(rgb3, cv::Mat(), kp3, descriptors_3, false);
akaze->detectAndCompute(rgb4, cv::Mat(), kp4, descriptors_4, false);
akaze->detectAndCompute(rgb5, cv::Mat(), kp5, descriptors_5, false);
akaze->detectAndCompute(rgb6, cv::Mat(), kp6, descriptors_6, false);
akaze->detectAndCompute(rgb7, cv::Mat(), kp7, descriptors_7, false);
akaze->detectAndCompute(rgb8, cv::Mat(), kp8, descriptors_8, false);
akaze->detectAndCompute(rgb9, cv::Mat(), kp9, descriptors_9, false);
akaze->detectAndCompute(rgb10, cv::Mat(), kp10, descriptors_10, false);
// 可視化, 顯示特征點
cout << "Key points of two AKAZE images: " << kp1.size() << ", " << kp2.size() << endl;
cv::drawKeypoints(rgb1, kp1, imgShow1, cv::Scalar::all(-), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::drawKeypoints(rgb2, kp2, imgShow2, cv::Scalar::all(-), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::drawKeypoints(rgb3, kp3, imgShow3, cv::Scalar::all(-), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imwrite("F:/study work/Visual Odometry dev/VO_practice_2016.4/算法測試/KAZE算法/data/akaze1.jpg", imgShow1);
cv::imwrite("F:/study work/Visual Odometry dev/VO_practice_2016.4/算法測試/KAZE算法/data/akaze2.jpg", imgShow2);
cv::resize(imgShow2, imgShow2, cv::Size(imgShow2.cols / , imgShow2.rows / ));
cv::resize(imgShow1, imgShow1, cv::Size(imgShow1.cols / , imgShow1.rows / ));
cv::imshow("AKAZE_keypoints2", imgShow2);
cv::imshow("AKAZE_keypoints1", imgShow1);
cv::waitKey(); //暫停等待一個按鍵
//用Matcher比對特征 :
BFMatcher bf_matcher;
FlannBasedMatcher flann_matcher;
cv::Ptr<cv::DescriptorMatcher> knn_matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
std::vector< DMatch > flann_matches;
std::vector< DMatch > bf_matches1;
std::vector< DMatch > knn_match;
std::vector< std::vector<cv::DMatch> > knn_matches1, knn_matches2, knn_matches3, knn_matches4, knn_matches5, knn_matches6, knn_matches7, knn_matches8, knn_matches9;
start = double(getTickCount());
bf_matcher.match(descriptors_1, descriptors_2, bf_matches1);
//flann_matcher.match(descriptors_1, descriptors_2, flann_matches);
duration_ms = (double(getTickCount()) - start) * / getTickFrequency();//計時
std::cout << "It took " << duration_ms << " ms to do brutal-force-match on AKAZE features." << std::endl;
start = double(getTickCount());
knn_matcher->knnMatch(descriptors_1, descriptors_2, knn_matches1, );
duration_ms = (double(getTickCount()) - start) * / getTickFrequency();//計時
std::cout << "It took " << duration_ms << " ms to do knn-match on AKAZE features." << std::endl;
knn_matcher->knnMatch(descriptors_1, descriptors_3, knn_matches2, );
knn_matcher->knnMatch(descriptors_1, descriptors_4, knn_matches3, );
knn_matcher->knnMatch(descriptors_1, descriptors_5, knn_matches4, );
knn_matcher->knnMatch(descriptors_1, descriptors_6, knn_matches5, );
knn_matcher->knnMatch(descriptors_1, descriptors_7, knn_matches6, );
knn_matcher->knnMatch(descriptors_1, descriptors_8, knn_matches7, );
knn_matcher->knnMatch(descriptors_1, descriptors_9, knn_matches8, );
knn_matcher->knnMatch(descriptors_1, descriptors_10, knn_matches9, );
//用RANSAC求取單應矩陣的方式去掉不可靠的BF比對
vector< DMatch > inliers1, inliers2;
cout << "Computing homography (RANSAC)" << endl;
vector<Point2f> points1(bf_matches1.size());
vector<Point2f> points2(bf_matches1.size());
for (size_t i = ; i < bf_matches1.size(); i++)
{
points1[i] = kp1[bf_matches1[i].queryIdx].pt;
points2[i] = kp2[bf_matches1[i].trainIdx].pt;
}
/*
for (int i = 0; i < points1.size(); i++) {
cout << "points1_x :" << points1[i].x << endl;
cout << "points1_y :" << points1[i].y << endl;
}
*/
//計算單應矩陣并找出inliers
vector<uchar> flag1(points1.size(), );
Mat H1 = findHomography(points1, points2, CV_RANSAC, , flag1);
Mat H2 = findHomography(points2, points1, CV_RANSAC, , flag1);
cout << "points1_shape: " << points1.size() << endl;
cout << "points2_shape: " << points2.size() << endl;
cout << "H1_shape: " << H2.size() << endl;
/*for (int i = 0; i < H1.rows; i++) {
for (int j = 0; j < H1.cols; j++) {
cout << "H1_value" << H1[i][i] << endl;
}
}*/
//std::cout << H2 << std::endl;
//cout << H << endl << endl;
for (int i = ; i < bf_matches1.size(); i++)
{
if (flag1[i])
{
inliers1.push_back(bf_matches1[i]);
}
}
cout << "AKAZE BF matches inliers size = " << inliers1.size() << endl;
//去掉不可靠的HAMMING-KNN比對
vector< cv::DMatch > goodMatches1;
for (size_t i = ; i < knn_matches1.size(); i++)
{
if (knn_matches1[i][].distance < *
knn_matches1[i][].distance)
goodMatches1.push_back(knn_matches1[i][]);
}
vector< cv::DMatch > goodMatches2;
for (size_t i = ; i < knn_matches2.size(); i++)
{
if (knn_matches2[i][].distance < *
knn_matches2[i][].distance)
goodMatches2.push_back(knn_matches2[i][]);
}
vector< cv::DMatch > goodMatches3;
for (size_t i = ; i < knn_matches3.size(); i++)
{
if (knn_matches3[i][].distance < *
knn_matches3[i][].distance)
goodMatches3.push_back(knn_matches3[i][]);
}
vector< cv::DMatch > goodMatches4;
for (size_t i = ; i < knn_matches4.size(); i++)
{
if (knn_matches4[i][].distance < *
knn_matches4[i][].distance)
goodMatches4.push_back(knn_matches4[i][]);
}
vector< cv::DMatch > goodMatches5;
for (size_t i = ; i < knn_matches5.size(); i++)
{
if (knn_matches5[i][].distance < *
knn_matches5[i][].distance)
goodMatches5.push_back(knn_matches5[i][]);
}
vector< cv::DMatch > goodMatches6;
for (size_t i = ; i < knn_matches6.size(); i++)
{
if (knn_matches6[i][].distance < *
knn_matches6[i][].distance)
goodMatches6.push_back(knn_matches6[i][]);
}
vector< cv::DMatch > goodMatches7;
for (size_t i = ; i < knn_matches7.size(); i++)
{
if (knn_matches7[i][].distance < *
knn_matches7[i][].distance)
goodMatches7.push_back(knn_matches7[i][]);
}
vector< cv::DMatch > goodMatches8;
for (size_t i = ; i < knn_matches8.size(); i++)
{
if (knn_matches8[i][].distance < *
knn_matches8[i][].distance)
goodMatches8.push_back(knn_matches8[i][]);
}
vector< cv::DMatch > goodMatches9;
for (size_t i = ; i < knn_matches9.size(); i++)
{
if (knn_matches9[i][].distance < *
knn_matches9[i][].distance)
goodMatches9.push_back(knn_matches9[i][]);
}
cout << "AKAZE good knn matches size = " << goodMatches1.size() << endl;
cout << "I am before count point" << endl;
vector<Point2f> points3(goodMatches1.size());
vector<Point2f> points4(goodMatches1.size());
for (size_t i = ; i < goodMatches1.size(); i++)
{
points3[i] = kp1[goodMatches1[i].queryIdx].pt;
points4[i] = kp2[goodMatches1[i].trainIdx].pt;
}
cout << "I am before count point 1" << endl;
vector<Point2f> points5(goodMatches2.size());
vector<Point2f> points6(goodMatches2.size());
for (size_t i = ; i < goodMatches2.size(); i++)
{
points5[i] = kp1[goodMatches2[i].queryIdx].pt;
points6[i] = kp3[goodMatches2[i].trainIdx].pt;
}
cout << "I am before count point 2" << endl;
vector<Point2f> points7(goodMatches3.size());
vector<Point2f> points8(goodMatches3.size());
cout << "goodMatchers3.size" << goodMatches3.size() << endl;
for (size_t i = ; i < goodMatches3.size(); i++)
{
points7[i] = kp1[goodMatches3[i].queryIdx].pt;
points8[i] = kp4[goodMatches3[i].trainIdx].pt;
}
cout << "I am before count point 3" << endl;
vector<Point2f> points9(goodMatches4.size());
vector<Point2f> points10(goodMatches4.size());
for (size_t i = ; i < goodMatches4.size(); i++)
{
points9[i] = kp1[goodMatches4[i].queryIdx].pt;
points10[i] = kp5[goodMatches4[i].trainIdx].pt;
}
cout << "I am before count point 4" << endl;
vector<Point2f> points11(goodMatches5.size());
vector<Point2f> points12(goodMatches5.size());
for (size_t i = ; i < goodMatches5.size(); i++)
{
points11[i] = kp1[goodMatches5[i].queryIdx].pt;
points12[i] = kp6[goodMatches5[i].trainIdx].pt;
}
vector<Point2f> points13(goodMatches6.size());
vector<Point2f> points14(goodMatches6.size());
for (size_t i = ; i < goodMatches6.size(); i++)
{
points13[i] = kp1[goodMatches6[i].queryIdx].pt;
points14[i] = kp7[goodMatches6[i].trainIdx].pt;
}
vector<Point2f> points15(goodMatches7.size());
vector<Point2f> points16(goodMatches7.size());
for (size_t i = ; i < goodMatches7.size(); i++)
{
points15[i] = kp1[goodMatches7[i].queryIdx].pt;
points16[i] = kp8[goodMatches7[i].trainIdx].pt;
}
cout << "I am before count point 4" << endl;
vector<Point2f> points17(goodMatches8.size());
vector<Point2f> points18(goodMatches8.size());
for (size_t i = ; i < goodMatches8.size(); i++)
{
points17[i] = kp1[goodMatches8[i].queryIdx].pt;
points18[i] = kp9[goodMatches8[i].trainIdx].pt;
}
vector<Point2f> points19(goodMatches9.size());
vector<Point2f> points20(goodMatches9.size());
for (size_t i = ; i < goodMatches9.size(); i++)
{
points19[i] = kp1[goodMatches9[i].queryIdx].pt;
points20[i] = kp10[goodMatches9[i].trainIdx].pt;
}
cout << "I am after count point" << endl;
Mat H3 = findHomography(points4, points3, CV_RANSAC, , flag1);
Mat H4 = findHomography(points6, points5, CV_RANSAC, , flag1);
Mat H5 = findHomography(points8, points7, CV_RANSAC, , flag1);
Mat H6 = findHomography(points10, points9, CV_RANSAC, , flag1);
Mat H7 = findHomography(points12, points11, CV_RANSAC, , flag1);
Mat H8 = findHomography(points14, points13, CV_RANSAC, , flag1);
Mat H9 = findHomography(points16, points15, CV_RANSAC, , flag1);
Mat H10 = findHomography(points18, points17, CV_RANSAC, , flag1);
Mat H11 = findHomography(points20, points19, CV_RANSAC, , flag1);
cv::Mat imdst_2, imdst_3, imdst_4, imdst_5, imdst_6, imdst_7, imdst_8, imdst_9, imdst_10, imsrc_1, imsrc_2, imsrc_3, imsrc_4, imsrc_5, imsrc_6, imsrc_7, imsrc_8, imsrc_9, imsrc_10, warp_dst, imresult;
imsrc_1 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\1.jpg");
imsrc_2 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\2.jpg");
imsrc_3 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\3.jpg");
imsrc_4 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\4.jpg");
imsrc_5 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\5.jpg");
imsrc_6 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\6.jpg");
imsrc_7 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\7.jpg");
imsrc_8 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\8.jpg");
imsrc_9 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\9.jpg");
imsrc_10 = cv::imread("C:\\opencv-cpp\\opencvtest\\img_source\\1\\10.jpg");
//warpAffine(im_src, warp_dst, H2, im_src.size());
warpPerspective(imsrc_2, imdst_2, H3, imsrc_2.size());
warpPerspective(imsrc_3, imdst_3, H4, imsrc_3.size());
warpPerspective(imsrc_4, imdst_4, H5, imsrc_4.size());
warpPerspective(imsrc_5, imdst_5, H6, imsrc_5.size());
warpPerspective(imsrc_6, imdst_6, H7, imsrc_6.size());
warpPerspective(imsrc_7, imdst_7, H8, imsrc_7.size());
warpPerspective(imsrc_8, imdst_8, H9, imsrc_8.size());
warpPerspective(imsrc_9, imdst_9, H10, imsrc_9.size());
warpPerspective(imsrc_10, imdst_10, H11, imsrc_10.size());
cv::Mat imsrc_1_, imdst_2_, imdst_3_, imdst_4_, imdst_5_, imdst_6_, imdst_7_, imdst_8_, imdst_9_, imdst_10_;
imsrc_1.convertTo(imsrc_1_, CV_32F, / );
imdst_2.convertTo(imdst_2_, CV_32F, / );
imdst_3.convertTo(imdst_3_, CV_32F, / );
imdst_4.convertTo(imdst_4_, CV_32F, / );
imdst_5.convertTo(imdst_5_, CV_32F, / );
imdst_6.convertTo(imdst_6_, CV_32F, / );
imdst_7.convertTo(imdst_7_, CV_32F, / );
imdst_8.convertTo(imdst_8_, CV_32F, / );
imdst_9.convertTo(imdst_9_, CV_32F, / );
imdst_10.convertTo(imdst_10_, CV_32F, / );
imresult = (imsrc_1_ + imdst_2_ + imdst_3_ + imdst_4_ + imdst_5_ + imdst_6_ + imdst_7_ + imdst_8_ + imdst_9_ + imdst_10_) / ;
cv::imshow("result", imresult);
cv::imwrite("D:\\opencv_cpp\\opencv_cpp\\source_images\\after_transactions\\2_after_transaction.jpg", imdst_2);
cv::imwrite("D:\\opencv_cpp\\opencv_cpp\\source_images\\after_transactions\\3_after_transaction.jpg", imdst_3);
// 可視化:顯示比對的特征
cv::Mat AKAZE_BF_imgMatches;
cv::Mat AKAZE_KNN_imgMatches;
篩選比對,把距離太大的去掉
這裡使用的準則是去掉大于四倍最小距離的比對
//vector< cv::DMatch > goodMatches;
//double minDis = 9999;
//for (size_t i = 0; i<matches.size(); i++)
//{
// if (matches[i].distance < minDis)
// minDis = matches[i].distance;
//}
//for (size_t i = 0; i<matches.size(); i++)
//{
// if (matches[i].distance < 8 * minDis)
// goodMatches.push_back(matches[i]);
//}
// 顯示 good matches
cv::drawMatches(rgb1, kp1, rgb2, kp2, inliers1, AKAZE_BF_imgMatches);
cv::imwrite("F:/study work/Visual Odometry dev/VO_practice_2016.4/算法測試/KAZE算法/data/AKAZE-BF-Matches-inliers.jpg", AKAZE_BF_imgMatches);
cv::drawMatches(rgb1, kp1, rgb2, kp2, goodMatches1, AKAZE_KNN_imgMatches);
cv::imwrite("F:/study work/Visual Odometry dev/VO_practice_2016.4/算法測試/KAZE算法/data/AKAZE-Good-KNN-Matches.jpg", AKAZE_KNN_imgMatches);
cv::resize(AKAZE_BF_imgMatches, AKAZE_BF_imgMatches,
Size(AKAZE_BF_imgMatches.cols / , AKAZE_BF_imgMatches.rows / ));
cv::imshow("AKAZE-BF-Matches-inliers", AKAZE_BF_imgMatches);
cv::resize(AKAZE_KNN_imgMatches, AKAZE_KNN_imgMatches, Size(AKAZE_KNN_imgMatches.cols / , AKAZE_KNN_imgMatches.rows / ));
cv::imshow("AKAZE-Good-KNN-Matches", AKAZE_KNN_imgMatches);
cv::waitKey();
}
代碼中選用了10張圖檔進行配準。最後結果為: