OpenCv-C++-平面物件識別(接FLANN特徵匹配)
本文接著上篇FLANN特徵匹配,從上篇可以知道,如果特徵匹配時全部是用線進行匹配,那麼真的讓人看著很窩心。那麼,可不可以把匹配到的結果用矩形或圓表示出來呢?當然可以,這就是平面物件識別。是上一章節的更進一步。這裡主要用到兩個新的API:
1、findHomography() ------>發現兩個平面的透視變幻,生成透視變換矩陣
2、perspectiveTransform() ---------->透視變換
因為拍攝的照片因為角度問題而導致輪廓可能是這樣的:
這是上一章的程式碼,與上一章毫無差別:
現在通過透視變換變成這樣子:
#include<opencv2/opencv.hpp> #include<iostream> #include<opencv2/xfeatures2d.hpp> #include<math.h> using namespace cv; using namespace std; using namespace cv::xfeatures2d; //檢測計算和繪製時,最好將源圖(img1)在前面,目標影象(img2)在後面 Mat img1, img2; int main(int argc, char**argv) { img1 = imread("D:/test/box.png", 0); img2 = imread("D:/test/box_in_scene.png", 0); if (!img1.data || !img2.data) { cout << "圖片為空!" << endl; return -1; } int minHesssion = 400; Ptr<SURF> detector = SURF::create(minHesssion); //也可以用SIRF,但是效率比SURF低 vector<KeyPoint> keypoints_obj; //存放img1的特徵值 vector<KeyPoint> keypoints_scene; //存放img2的特徵值 Mat descript_obj, descript_scene; detector->detectAndCompute(img1, Mat(), keypoints_obj, descript_obj); //檢測並計算特徵描述子 detector->detectAndCompute(img2, Mat(), keypoints_scene, descript_scene); FlannBasedMatcher fbmatcher; vector<DMatch>matches; fbmatcher.match(descript_obj, descript_scene, matches); //特徵描述子匹配 //找出最優特徵點 double minDist = 1000; //初始化最大最小距離 double maxDist = 0; for (int i = 0; i < descript_obj.rows; i++) { double dist = matches[i].distance; if (dist > maxDist) { maxDist = dist; } if (dist < minDist) { minDist = dist; } } printf("maxDist:%f\n", maxDist); printf("minDist:%f\n", minDist); vector<DMatch> goodMatches; for (int i = 0; i < descript_obj.rows; i++) { double dist = matches[i].distance; if (dist < max(3 * minDist, 0.02)) { goodMatches.push_back(matches[i]); } } Mat resultImg; drawMatches(img1, keypoints_obj, img2, keypoints_scene, goodMatches, resultImg, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); imshow("input image1", img1); imshow("input image2", img2); imshow("FlannBasedMatcher demo", resultImg);
接著上面的程式碼寫平面物件識別的程式碼:
主要用到的API的表現程式碼:
findHomography():
//生成透視變換矩陣 vector<Point2f> obj; vector<Point2f> objinscene; for (size_t i = 0; i < goodMatches.size(); i++) { obj.push_back(keypoints_obj[goodMatches[i].queryIdx].pt); // queryIdx:是測試影象(源影象)的特徵點描述符(descriptor)的下標,同時也是描述符對應特徵點(keypoint)的下標。 objinscene.push_back(keypoints_scene[goodMatches[i].trainIdx].pt); //trainIdx:是樣本影象(目標影象)的特徵點描述符的下標,同樣也是相應的特徵點的下標。 } Mat H = findHomography(obj, objinscene, RANSAC); //生成透視變換矩陣
perspectiveTransform():
vector<Point2f> obj_corner(4);//源圖片4個角的座標 vector<Point2f> objinscene_corner(4); obj_corner[0] = Point(0, 0); //左上角(第一點)座標 obj_corner[1] = Point(img1.cols, 0); //右上角(第二點)座標 obj_corner[2] = Point(img1.cols, img1.rows); //右下角(第三點)座標 obj_corner[3] = Point(0, img1.rows); //坐下角(第四點)座標 //------------------透視變換--------------------- perspectiveTransform(obj_corner, objinscene_corner, H);
接下來將透視變換後獲取到的4個角點用線連線起來(這裡畫矩形):
cvtColor(img2, dst, COLOR_GRAY2BGR);
line(resultImg, objinscene_corner[0] + Point2f(img1.cols, 0), objinscene_corner[1] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[1] + Point2f(img1.cols, 0), objinscene_corner[2] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[2] + Point2f(img1.cols, 0), objinscene_corner[3] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[3] + Point2f(img1.cols, 0), objinscene_corner[0] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
為什麼要 + Point2f(img1.cols, 0)?因為FLANN特徵匹配顯示的圖片是這樣的:
下面貼上所有原始碼:
#include<opencv2/opencv.hpp>
#include<iostream>
#include<opencv2/xfeatures2d.hpp>
#include<math.h>
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
//檢測計算和繪製時,最好將源圖(img1)在前面,目標影象(img2)在後面
Mat img1, img2;
int main(int argc, char**argv)
{
img1 = imread("D:/test/box.png", 0);
img2 = imread("D:/test/box_in_scene.png", 0);
if (!img1.data || !img2.data)
{
cout << "圖片為空!" << endl;
return -1;
}
int minHesssion = 400;
Ptr<SURF> detector = SURF::create(minHesssion); //也可以用SIRF,但是效率比SURF低
vector<KeyPoint> keypoints_obj; //存放img1的特徵值
vector<KeyPoint> keypoints_scene; //存放img2的特徵值
Mat descript_obj, descript_scene;
detector->detectAndCompute(img1, Mat(), keypoints_obj, descript_obj); //檢測並計算特徵描述子
detector->detectAndCompute(img2, Mat(), keypoints_scene, descript_scene);
FlannBasedMatcher fbmatcher;
vector<DMatch>matches;
fbmatcher.match(descript_obj, descript_scene, matches); //特徵描述子匹配
//找出最優特徵點
double minDist = 1000; //初始化最大最小距離
double maxDist = 0;
for (int i = 0; i < descript_obj.rows; i++)
{
double dist = matches[i].distance;
if (dist > maxDist)
{
maxDist = dist;
}
if (dist < minDist)
{
minDist = dist;
}
}
printf("maxDist:%f\n", maxDist);
printf("minDist:%f\n", minDist);
vector<DMatch> goodMatches;
for (int i = 0; i < descript_obj.rows; i++)
{
double dist = matches[i].distance;
if (dist < max(3 * minDist, 0.02)) {
goodMatches.push_back(matches[i]);
}
}
Mat resultImg;
drawMatches(img1, keypoints_obj, img2, keypoints_scene, goodMatches, resultImg, Scalar::all(-1),
Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS
);
imshow("input image1", img1);
imshow("input image2", img2);
imshow("FlannBasedMatcher demo", resultImg);
//-------------------上面部分是FLANN特徵匹配的內容--------------------------
//-------------------平面物件識別(將匹配到的內容替換為矩形)--------------------------
//生成透視變換矩陣
vector<Point2f> obj;
vector<Point2f> objinscene;
for (size_t i = 0; i < goodMatches.size(); i++)
{
obj.push_back(keypoints_obj[goodMatches[i].queryIdx].pt); // queryIdx:是測試影象(源影象)的特徵點描述符(descriptor)的下標,同時也是描述符對應特徵點(keypoint)的下標。
objinscene.push_back(keypoints_scene[goodMatches[i].trainIdx].pt); //trainIdx:是樣本影象(目標影象)的特徵點描述符的下標,同樣也是相應的特徵點的下標。
}
Mat H = findHomography(obj, objinscene, RANSAC); //生成透視變換矩陣
vector<Point2f> obj_corner(4);//源圖片4個角的座標
vector<Point2f> objinscene_corner(4);
obj_corner[0] = Point(0, 0); //左上角(第一點)座標
obj_corner[1] = Point(img1.cols, 0); //右上角(第二點)座標
obj_corner[2] = Point(img1.cols, img1.rows); //右下角(第三點)座標
obj_corner[3] = Point(0, img1.rows); //坐下角(第四點)座標
//------------------透視變換---------------------
perspectiveTransform(obj_corner, objinscene_corner, H);
//繪製線
Mat dst;
cvtColor(img2, dst, COLOR_GRAY2BGR);
line(resultImg, objinscene_corner[0] + Point2f(img1.cols, 0), objinscene_corner[1] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[1] + Point2f(img1.cols, 0), objinscene_corner[2] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[2] + Point2f(img1.cols, 0), objinscene_corner[3] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(resultImg, objinscene_corner[3] + Point2f(img1.cols, 0), objinscene_corner[0] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
//也可以將匹配到的結果單獨拉出來
line(dst, objinscene_corner[0] , objinscene_corner[1] , Scalar(0, 0, 255), 2, 8, 0);
line(dst, objinscene_corner[1], objinscene_corner[2] , Scalar(0, 0, 255), 2, 8, 0);
line(dst, objinscene_corner[2] , objinscene_corner[3] , Scalar(0, 0, 255), 2, 8, 0);
line(dst, objinscene_corner[3] , objinscene_corner[0] , Scalar(0, 0, 255), 2, 8, 0);
imshow("perspectiveTransform demo", resultImg);
imshow("single perspectiveTransform demo", dst);
waitKey(0);
return 0;
}
最後的結果是這樣的:
注意:這裡為了更好地匹配到物件,
vector<DMatch> goodMatches; for (int i = 0; i < descript_obj.rows; i++) { double dist = matches[i].distance; if (dist < max(3 * minDist, 0.02)) { goodMatches.push_back(matches[i]); } }
中的 “3”可以取得稍微大一點,如果取小了,比如說:
vector<DMatch> goodMatches;
for (int i = 0; i < descript_obj.rows; i++)
{
double dist = matches[i].distance;
if (dist < max(2 * minDist, 0.02)) {
goodMatches.push_back(matches[i]);
}
}
那麼結果就會變成這樣:
結果就會變得不夠精準,當然了,還是要適當問題適當分析。跟上一章一樣,特徵點變少了,結果當然就不準了,特徵點稍微多一點,結果就會更精準一點,但也不能太多,不然就變成跟“暴力匹配”一樣了…
這裡對for (size_t i = 0; i < goodMatches.size(); i++) { obj.push_back(keypoints_obj[goodMatches[i].queryIdx].pt); // queryIdx:是測試影象(源影象)的特徵點描述符(descriptor)的下標,同時也是描述符對應特徵點(keypoint)的下標。 objinscene.push_back(keypoints_scene[goodMatches[i].trainIdx].pt); //trainIdx:是樣本影象(目標影象)的特徵點描述符的下標,同樣也是相應的特徵點的下標。 }
中的引數做一下說明:
1、int queryIdx –>是測試影象的特徵點描述符(descriptor)的下標,同時也是描述符對應特徵點(keypoint)的下標。
2、int trainIdx –> 是樣本影象的特徵點描述符的下標,同樣也是相應的特徵點的下標。
3、int imgIdx –>當樣本是多張影象的話有用。
4、float distance –>代表這一對匹配的特徵點描述符(本質是向量)的歐氏距離,數值越小也就說明兩個特徵點越相像。
最後,
也就是一個小於操作符的過載,用於比較和排序。 比較的是上述的distance,當然是越小越好。
引數說明參考文章:https://blog.csdn.net/qq_23845067/article/details/51926856