本文接着上篇FLANN特征匹配,从上篇可以知道,如果特征匹配时全部是用线进行匹配,那么真的让人看着很窝心。那么,可不可以把匹配到的结果用矩形或圆表示出来呢?当然可以,这就是平面对象识别。是上一章节的更进一步。这里主要用到两个新的API:
1、findHomography() ------>发现两个平面的透视变幻,生成透视变换矩阵
2、perspectiveTransform() ---------->透视变换
因为拍摄的照片因为角度问题而导致轮廓可能是这样的:
这是上一章的代码,与上一章毫无差别:
现在通过透视变换变成这样子:
#include
#include
#include
#include
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 detector = SURF::create(minHesssion); //也可以用SIRF,但是效率比SURF低
vector keypoints_obj; //存放img1的特征值
vector 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;
vectormatches;
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 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(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS
);
imshow("input image1", img1);
imshow("input image2", img2);
imshow("FlannBasedMatcher demo", resultImg);
接着上面的代码写平面对象识别的代码:
主要用到的API的表现代码:
findHomography():
//生成透视变换矩阵
vector obj;
vector 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 obj_corner(4);//源图片4个角的坐标
vector 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
#include
#include
#include
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 detector = SURF::create(minHesssion); //也可以用SIRF,但是效率比SURF低
vector keypoints_obj; //存放img1的特征值
vector 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;
vectormatches;
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 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(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS
);
imshow("input image1", img1);
imshow("input image2", img2);
imshow("FlannBasedMatcher demo", resultImg);
//-------------------上面部分是FLANN特征匹配的内容--------------------------
//-------------------平面对象识别(将匹配到的内容替换为矩形)--------------------------
//生成透视变换矩阵
vector obj;
vector 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 obj_corner(4);//源图片4个角的坐标
vector 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
中的 “3”可以取得稍微大一点,如果取小了,比如说:
vector 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