总述
问题:
现在手上有两幅图像,我们希望把这两副图像进行在图像的公共区域内进行拼接,该如何实现?
图像拼接算法大概步骤:
- 使用特征点检测算法计算出特征点和特征描述符;
- 特征点检测算法有:sift surf orb fast lbp
等
- 这些算法都同属于一个父类,并且父类的方法里有:creat()
、detectAndCompute()
直接调用 - 进行图像匹配
- 图像匹配算法里有BFMatcher
(暴力匹配)和FlannBasedMatcher
(最近邻匹配)
- 这两种匹配都同属于DescriptorMatcher
父类,共同的接口函数是:match()
- 去除掉错误匹配点
- 求单应性矩阵
- 单应性矩阵,不同于仿射矩阵,它至少需要两幅图像4个特征点才能求出来,有两个函数可以求单应性矩阵,findHomography
和getPerspectiveTransform
;前者可以用大于四个特征点求解最优的单应矩阵,一般使用Ransac方法,后者只能通过四个点计算; - 进行透视变换,
warpPerspective
代码分解
1. sift + Flann匹配
Mat ref_img = imread("ImageA.png", -1);
Mat src_img = imread("ImageB.png", -1);
//sift
auto detector_ref = SIFT::create(600);
auto detector_src = SIFT::create(600);
vector<KeyPoint> keypoints_ref, keypoints_src;
Mat descriptor_ref, descriptor_src;
detector_ref->detectAndCompute(ref_img, cv::Mat(), keypoints_ref, descriptor_ref);
detector_src->detectAndCompute(src_img, cv::Mat(), keypoints_src, descriptor_src);
//1、使用最近邻算法进行匹配
auto matcher = FlannBasedMatcher::create();
vector<DMatch> matches;
matcher->match(descriptor_ref, descriptor_src, matches);
Mat dst;
drawMatches(ref_img, keypoints_ref, src_img, keypoints_src, matches, dst);
2. 去除掉错误匹配点
//判断条件:小于最小的距离的3倍
double minDist = matches[0].distance, maxDist = 0;
for (size_t i = 0; i < matches.size(); i++)
{
double dist = matches[i].distance;
if (dist > maxDist)
maxDist = dist;
if (dist < minDist)
minDist = dist;
}
vector<DMatch> goodMatches;
for (size_t i = 0; i < matches.size(); i++)
{
double dist = matches[i].distance;
if (dist < 3 * minDist)
{
goodMatches.push_back(matches[i]);
}
}
Mat matchesImage;
drawMatches(ref_img, keypoints_ref, src_img, keypoints_src, goodMatches, matchesImage, Scalar::all(-1), \
Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
3. 求单应性矩阵
首先使用函数matches2points
把过滤后的鲁棒的特征点一一对应,然后调用findHomography函数计算出单应性矩阵H。
//Converts matching indices to xy points
void matches2points(const vector<KeyPoint>& train, const vector<KeyPoint>& query,
const std::vector<cv::DMatch>& matches, std::vector<cv::Point2f>& pts_train,
std::vector<Point2f>& pts_query)
{
pts_train.clear();
pts_query.clear();
pts_train.reserve(matches.size());
pts_query.reserve(matches.size());
size_t i = 0;
for (; i < matches.size(); i++)
{
const DMatch& dmatch = matches[i];
pts_query.push_back(query[dmatch.queryIdx].pt);
pts_train.push_back(train[dmatch.trainIdx].pt);
}
}
vector<Point2f> ref_points, src_points;
//matches2points(keypoints_ref, keypoints_src, goodMatches, ref_points, src_points);
matches2points(keypoints_src, keypoints_ref, goodMatches, src_points, ref_points);
//求单应性矩阵,注意是右图在前,左图在后,计算的是右图关于左图的透视变换矩阵
Mat H;
if (goodMatches.size() > 4) {
H = findHomography(src_points, ref_points, RANSAC);
//也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差
}
else {
cout << "matcher size < 4, can not compute homography ...." << endl;
}
4. 进行透视变换
- 使用
CalcCorners
计算出透视变换后的上下左右四个角点的坐标 - 计算右图的仿射变换
- 左右图进行拼接
typedef struct
{
Point2f left_top;
Point2f left_bottom;
Point2f right_top;
Point2f right_bottom;
}four_corners_t;
four_corners_t corners;
void CalcCorners(const Mat& H, const Mat& src)
{
double v2[] = { 0, 0, 1 };//左上角
double v1[3];//变换后的坐标值
Mat V2 = Mat(3, 1, CV_64FC1, v2); //列向量
Mat V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
//左上角(0,0,1)
cout << "V2: " << V2 << endl;
cout << "V1: " << V1 << endl;
corners.left_top.x = v1[0] / v1[2];
corners.left_top.y = v1[1] / v1[2];
//左下角(0,src.rows,1)
v2[0] = 0;
v2[1] = src.rows;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.left_bottom.x = v1[0] / v1[2];
corners.left_bottom.y = v1[1] / v1[2];
//右上角(src.cols,0,1)
v2[0] = src.cols;
v2[1] = 0;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.right_top.x = v1[0] / v1[2];
corners.right_top.y = v1[1] / v1[2];
//右下角(src.cols,src.rows,1)
v2[0] = src.cols;
v2[1] = src.rows;
v2[2] = 1;
V2 = Mat(3, 1, CV_64FC1, v2); //列向量
V1 = Mat(3, 1, CV_64FC1, v1); //列向量
V1 = H * V2;
corners.right_bottom.x = v1[0] / v1[2];
corners.right_bottom.y = v1[1] / v1[2];
}
CalcCorners(H, src_img);
Mat src_pt;
//使用透视变换
warpPerspective(src_img, src_pt, H, Size(MAX(corners.right_top.x, corners.right_bottom.x), ref_img.rows));
//warpPerspective(src_img, src_pt, H, ref_img.size());
ref_img.copyTo(src_pt(Rect(0, 0, ref_img.cols, ref_img.rows)));
结果图