对象形变与位置变换
Mat findHomography( // 发现两个平面的透视变换,生成变换矩阵。即根据srcPoints的顶点数据与dstPoints的顶点数据,返回有srcPoints变换到dstPoints的变换矩阵
InputArray srcPoints, // srcPoints 与 dstPoints 应该是同尺寸
InputArray dstPoints,
int method = 0, // 发现变换矩阵的算法 RANSAC
double ransacReprojThreshold = 3,
OutputArray mask=noArray(),
const int maxIters = 2000,
const double confidence = 0.995
);
void perspectiveTransform( // 透视变换
InputArray src,
OutputArray dst,
InputArray m // 变换矩阵
);
代码
#include "../common/common.hpp"
using namespace cv::xfeatures2d;
void main(int argc, char** argv)
{
Mat img1 = imread(getCVImagesPath("images/box.png"), IMREAD_GRAYSCALE);
Mat img2 = imread(getCVImagesPath("images/box_in_scene.png"), IMREAD_GRAYSCALE);
imshow("object image", img1);
imshow("object in scene", img2);
// surf featurs extraction
int minHessian = 400;
Ptr<SURF> detector = SURF::create(minHessian); // 也可以用 SIFT 特征
vector<KeyPoint> keypoints_obj; // 保存特征点
vector<KeyPoint> keypoints_scene;
Mat descriptor_obj, descriptor_scene; // 特征描述子
detector->detectAndCompute(img1, Mat(), keypoints_obj, descriptor_obj, false); // SURF特征检测,同时计算生成对应描述子
detector->detectAndCompute(img2, Mat(), keypoints_scene, descriptor_scene, false);
cout << "keypoints_obj.size=" << keypoints_obj.size() << endl; // keypoints_obj.size=786
cout << "keypoints_scene.size=" << keypoints_scene.size() << endl; // keypoints_scene.size = 1040
// descriptor_obj depth=5, type=5, size=[64 x 786] CV_32F单通道 每个关键点用64来描述?
cout << "descriptor_obj depth=" << descriptor_obj.depth() << ", type=" << descriptor_obj.type() << ", size=" << descriptor_obj.size() << endl;
// descriptor_scene depth = 5, type = 5, size = [64 x 1040]
cout << "descriptor_scene depth=" << descriptor_scene.depth() << ", type=" << descriptor_scene.type() << ", size=" << descriptor_scene.size() << endl;
// matching
FlannBasedMatcher matcher; // Flann 匹配类
vector<DMatch> matches; // 保存匹配结果
// 在descriptor_scene中匹配descriptor_obj的特征描述子,结果放到matches中, matches的长度与descriptor_obj的行数一致
matcher.match(descriptor_obj, descriptor_scene, matches); // descriptor_obj中的特征描述子都会在descriptor_scene找到一个匹配点(不管是否真的准确)
//descriptor_obj rows=786, cols=64
cout << "descriptor_obj rows=" << descriptor_obj.rows << ", cols=" << descriptor_obj.cols << endl;
cout << "matches.size=" << matches.size() << endl; // matches.size = 786
// find good matched points
double minDist = 1000;
double maxDist = 0;
for (int i = 0; i < descriptor_obj.rows; i++) {
cout << "matches[" << i << "].queryIdx=" << matches[i].queryIdx << ", "; // matches[i].queryIdx 要查询的特征描述子的下标
cout << "matches[" << i << "].trainIdx=" << matches[i].trainIdx << ", "; // matches[i].trainIdx 匹配到的要训练的特征描述子的下标
cout << "matches[" << i << "].distance=" << matches[i].distance << endl; // 这两个描述特征子的距离 匹配度?
double dist = matches[i].distance; // 要查询的特征描述子与匹配到的要训练的特征描述子之间的距离, 距离越小,匹配的越准确
if (dist > maxDist) {
maxDist = dist;
}
if (dist < minDist) {
minDist = dist;
}
}
printf("max distance=%f\n", maxDist); // max distance=0.671626
printf("min distance=%f\n", minDist); // min distance=0.055168
vector<DMatch> goodMatches; // 保存匹配度高的匹配点
for (int i = 0; i < descriptor_obj.rows; i++) {
double dist = matches[i].distance;
if (dist < max(3 * minDist, 0.02)) { // 阈值的选取 实际情况实际分析?
goodMatches.push_back(matches[i]);
}
}
Mat matchesImg; // 显示匹配的结果
drawMatches(img1, keypoints_obj, img2, keypoints_scene, goodMatches, matchesImg, Scalar::all(-1),
Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); // Single keypoints will not be drawn.
imshow("Flann Matching Result", matchesImg);
// 发现查询描述子的特征所在的顶点与训练描述子的特征所在的顶点 之间的变换矩阵
vector<Point2f> obj;
vector<Point2f> objInScene;
for (size_t t = 0; t < goodMatches.size(); t++) {
obj.push_back(keypoints_obj[goodMatches[t].queryIdx].pt); // 获取要查询的描述子的关键点的位置
objInScene.push_back(keypoints_scene[goodMatches[t].trainIdx].pt); // 获取要训练的描述子的关键点的位置
}
Mat H = findHomography(obj, objInScene, RANSAC); // 发现 透视变换矩阵
cout << "goodMatches.size=" << goodMatches.size() << endl; // goodMatches.size=31
// H depth = 6, type = 6, size = [3 x 3] CV_64F 单通道
cout << "H depth=" << H.depth() << ", type=" << H.type() << ", size=" << H.size() << endl;
vector<Point2f> obj_corners(4); // 书本的四个顶点
vector<Point2f> scene_corners(4); // 保存训练图像中书本的顶点位置
obj_corners[0] = Point(0, 0); // 因为图1中书本的顶点就是图像的四个角,所以就这么设置以下的值了
obj_corners[1] = Point(img1.cols, 0);
obj_corners[2] = Point(img1.cols, img1.rows);
obj_corners[3] = Point(0, img1.rows);
perspectiveTransform(obj_corners, scene_corners, H); // 透视变换,通过变换矩阵找到训练图像中书本的顶点位置
// draw line 因为matchesImg是两张图像的合成,所以若要在matchesImg上显示找到的书本的位置,x坐标需要偏移img1.cols
line(matchesImg, scene_corners[0] + Point2f(img1.cols, 0), scene_corners[1] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[1] + Point2f(img1.cols, 0), scene_corners[2] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[2] + Point2f(img1.cols, 0), scene_corners[3] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
line(matchesImg, scene_corners[3] + Point2f(img1.cols, 0), scene_corners[0] + Point2f(img1.cols, 0), Scalar(0, 0, 255), 2, 8, 0);
Mat dst;
cvtColor(img2, dst, COLOR_GRAY2BGR);
line(dst, scene_corners[0], scene_corners[1], Scalar(0, 0, 255), 2, 8, 0); // 在训练图像上绘制找到的书本
line(dst, scene_corners[1], scene_corners[2], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[2], scene_corners[3], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[3], scene_corners[0], Scalar(0, 0, 255), 2, 8, 0);
imshow("find known object demo", matchesImg); // 在合成的matchesImg上显示找到的书本
imshow("Draw object", dst); // 在原训练图上显示找到的书本
waitKey(0);
}
效果图