总述

问题:

现在手上有两幅图像,我们希望把这两副图像进行在图像的公共区域内进行拼接,该如何实现?

opencv图像拼接缝如何消除 opencv图像拼接算法_opencv图像拼接缝如何消除

图像拼接算法大概步骤:

  1. 使用特征点检测算法计算出特征点和特征描述符;
    - 特征点检测算法有:sift surf orb fast lbp
    - 这些算法都同属于一个父类,并且父类的方法里有:creat()detectAndCompute()直接调用
  2. 进行图像匹配
    - 图像匹配算法里有BFMatcher(暴力匹配)和FlannBasedMatcher(最近邻匹配)
    - 这两种匹配都同属于DescriptorMatcher父类,共同的接口函数是:match()
  3. 去除掉错误匹配点
  4. 求单应性矩阵
    - 单应性矩阵,不同于仿射矩阵,它至少需要两幅图像4个特征点才能求出来,有两个函数可以求单应性矩阵,findHomographygetPerspectiveTransform;前者可以用大于四个特征点求解最优的单应矩阵,一般使用Ransac方法,后者只能通过四个点计算;
  5. 进行透视变换,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);

opencv图像拼接缝如何消除 opencv图像拼接算法_特征点_02

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);

opencv图像拼接缝如何消除 opencv图像拼接算法_特征点_03

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)));

结果图

opencv图像拼接缝如何消除 opencv图像拼接算法_opencv图像拼接缝如何消除_04