SIFT图像拼接

2023-05-16

SIFT图像拼接

文章目录

    • SIFT图像拼接
  • 前言
  • 二、相关工作
    • 1.SIFT
    • 2.RANSAC拟合
    • 3单应性变换
    • 4.SVD分解
  • 三、实现步骤
  • 四、效果
  • 总结
  • 代码

前言

结合SIFT、单应性变换、Ransac和SVD等算法实现指定几幅图像之间的拼接,并理解其原理。实验用图如图1,我们需要将图像按照(a)-(b)-©-(d)的顺序拼接起来,将图像都变换到同一个像素坐标系。实验原理如图所示:
实验原理图:
在这里插入图片描述

待拼接图像1
待拼接图像2待拼接图像3

二、相关工作

1.SIFT

SIFT即尺度不变特征变换,是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。SIFT算法具有稳定性、不变性、区分性多量性、高速性和可扩展性等优良特点,因此在图像特征提取方面广泛。
SIFT特征检测主要包括以下5个基本步骤:
⦁ 生成高斯金字塔:生成高斯金字塔可以分为两个过程,一是高斯模糊,二是降采样。如图2,Octave表示一幅图像可产生的图像组数。另外,降采样时,高斯金字塔上一组图像的初始图像(底层图像)是由前一组图像的倒数第三张图像隔点采样得到的。
⦁ 尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。
⦁ 关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
⦁ 方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
⦁ 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。
如果要实现图像之间的特征点匹配,要通过特征描述子集之间比对完成。常见的匹配器有暴力匹配器快速近似最邻近算法匹配器。暴力匹配器就是将两幅图像中的特征描述子全都匹配一遍,得到最优的结果,它的优点是精度高,但是缺点也是显而易见的,在大量的匹配时,匹配时间会很长。快速近似最邻近算法匹配器,故名思意,它只搜索邻近的点,找到邻近的最优匹配,它的匹配准确度会比暴力匹配器低,但是它的匹配时间大大的缩减了,可用于对匹配准确度要求不高的场合。
网图,金字塔模型

2.RANSAC拟合

RANSAC是Random Sample Consensus的缩写,其基本操作是通过迭代的方法得出一组包含噪声数据的数学模型参数。其基本的思想如下:
1)选定数学模型,准备足够多的点。
2)根据点确定模型参数。
3)判定数据集中其他的点是否符合模型,并进行统计。
4)反复以上操作,直到模型满足最多的点符合模型为止。
RANSAC算法不是一种确定的算法,只能在一定概率下产生结果,这个概率是会随着数据集的增大而增加的。
在RANSAC拟合过程中选取的点不能太过于靠近。理论上说迭代的次数越大它的获取好样本概率就越大,但是考虑到算法力求简单快捷,我们可以根据需要的样本概率等参数来决定迭代次数。

其中,K为需要采样的次数;z为获取一个好样本的概率,一般设为99%; w为点集中内点的比例,一般可以在初始时设置一个较小值,如0.1,然后迭代更新; n为模型参数估计需要的最小点个数,直线拟合最少需要2个点。除了迭代次数之外,我们还要设置一个距离阀值,当点距直线距离小于这个值时,被即为内点(即符合要求的点),当点距离直线的距离大于这个点就称为外点。
在实验中, SIFT算法得到的匹配点中还是存在比较多的误匹配点,我们需要利用RANSAC算法剔除误差比较大的匹配点。

3单应性变换

由一张世界平面诱导的两幅图之间的射影变换

单应性变换,也常被称为射影变换,可简单理解为世界坐标系中一平面上的点在不同像素坐标系之间的映射关系,对应的矩阵称为单应性矩阵,是一个3*3的非奇异矩阵,经常用H表示。单应性变换用H可表示为:
在这里插入图片描述

可简要表示为,其中,表示第一幅图像中一点的齐次坐标,表示该点在另一幅图像中的对应点,H为第一幅图到第二幅图的单应性变换矩阵,值得注意的是第二幅图到第一幅图的单应性变换矩阵为。为了求解出单应性矩阵的各个元素,我们将式(1-1)展开,可得到以下两个方程:
在这里插入图片描述
由于H是一个齐次方程,我们只需知道他的比率即可。因此将归一化之后,我们只需要求8个未知元素,需要8个非线性方程。一组对应点可以分解出两组方程,应此我们至少需要四组对应点,但是要注意,我们取的四组对应点必须三三不共线,否则方程组没有唯一解。

4.SVD分解

在H的求解过程中,通过RANSAC筛选到的点对不可能完全匹配,所以我们要尽可能把所有筛选到的点都使用上,减少随机误差。假设我们筛选出n对匹配点,那我们需要列出2n个方程,表示为AX=B的形式有:
在这里插入图片描述

理想状态下,方程应该有唯一解,但是由于随机误差的影响方程很可能无解。因此,我们应该求其极小范数最小二乘解。。通过SVD分解 (2-3)
在这里插入图片描述

其中U是一个nn的正交阵,V为一个88的正交阵为一个n*8的矩阵。
!

表示A的广义逆。可以求解x:

三、实现步骤

1)利用SIFT算法,提取四幅图的特征点和特征描述子,相邻两个图用暴力匹配器进行匹配,生成3个特征点对集合;
2)RANSAC筛选可靠点对;
3)SVD分解求极小范数最小二乘解,分别解出一二幅图,二三幅图,三四幅图的单应性矩阵H12、H23、H34;
将所有图片转换到第三幅图像所在像素平面,进行图像拼接

四、效果

SIFT提取特征点,再通过暴力匹配器匹配后的结果:
在这里插入图片描述
RANSAC筛选后的点:
在这里插入图片描述

拼接后的图像:
在这里插入图片描述

总结

通过实验得到的拼接后的图像效果还行,但是仍然存在两个问题:一是照片的亮度不协调;另一个是还是会有部分拼接的不到位。照片亮度不协调是由于,相机为了呈现好的拍照效果,自动调节参数,导致有的相片亮有的相片暗。拼接部分不到位可能会有两个原因:求解的H有误差,还有就是场景深度差异大,用一个H难以描述。

代码

#include <iostream>  
#include <stdio.h>  
#include "opencv2/core.hpp"  
#include "opencv2/core/utility.hpp"  
#include "opencv2/core/ocl.hpp"  
#include "opencv2/imgcodecs.hpp"  
#include "opencv2/highgui.hpp"  
#include "opencv2/features2d.hpp"  
#include "opencv2/calib3d.hpp"  
#include "opencv2/imgproc.hpp"  
#include"opencv2/flann.hpp"  
#include"opencv2/features2d.hpp"  
#include"opencv2/ml.hpp"  
#include <opencv2/imgproc/types_c.h>
#include "opencv2/core/operations.hpp"
#include "opencv2/core/core.hpp"
#include<cmath>
#include <opencv.hpp>
#include "opencv2/features2d.hpp"
#include "opencv2/core/types_c.h"

using namespace cv;
using namespace std;
typedef struct
{
	Point2f left_top;
	Point2f left_bottom;
	Point2f right_top;
	Point2f right_bottom;
}four_corners_t;
four_corners_t corners[10];

void goodMatches(vector<DMatch>& matches, Mat& imageDesc1, vector< DMatch >& good_matches, int n);
void fitLineRansac(vector<KeyPoint>& keyPoints1,vector<KeyPoint>& keyPoints2,vector<DMatch>& matches,vector<DMatch>& inlierMatch,int iterations,	double sigma);
void FITSVD(vector<KeyPoint>& keyPoints1, vector<KeyPoint>& keyPoints2, vector<DMatch>& matches, Mat& H);
void CalcCorners(const Mat& H, const Mat& src,int i)
{
	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[i].left_top.x = v1[0] / v1[2];
	corners[i].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[i].left_bottom.x = v1[0] / v1[2];
	corners[i].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[i].right_top.x = v1[0] / v1[2];
	corners[i].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[i].right_bottom.x = v1[0] / v1[2];
	corners[i].right_bottom.y = v1[1] / v1[2];
}
int main(int argc, char* argv)
{
	//读取图像
	Mat image01 = imread("01.jpg", 1);
	Mat image02 = imread("02.jpg", 1);
	Mat image03 = imread("03.jpg", 1);
	Mat image04 = imread("04.jpg", 1);
    if (image01.empty() || image02.empty()||image03.empty()||image04.empty())
		{
			cout << "Can't read image" << endl;
			return -1;
		}
    //缩减图像大小并灰度化
	Mat img1, img2,img3,img4,img1_gray,img2_gray,img3_gray,img4_gray;
	resize(image01, img1, Size(image01.rows * 0.5, image01.cols * 0.5));
	resize(image02, img2, Size(image02.rows * 0.5, image02.cols * 0.5));
	resize(image03, img3, Size(image03.rows * 0.5, image03.cols * 0.5));
	resize(image04, img4, Size(image04.rows * 0.5, image04.cols * 0.5));
	cvtColor(img1, img1_gray, CV_BGR2GRAY);
    cvtColor(img2, img2_gray, CV_BGR2GRAY);
	cvtColor(img3, img3_gray, CV_BGR2GRAY);
	cvtColor(img4, img4_gray, CV_BGR2GRAY);
	
	
	
    vector<KeyPoint>keyPoints1;
    vector<KeyPoint>keyPoints2;
	vector<KeyPoint>keyPoints3;
	vector<KeyPoint>keyPoints4;
	
	int numFeatures = 800;
	Ptr <cv::SIFT> sift = sift->create(numFeatures);;
	sift->detect(img1_gray, keyPoints1);
	sift->detect(img2_gray, keyPoints2);
	sift->detect(img3_gray, keyPoints3);
	sift->detect(img4_gray, keyPoints4);

	//提取特征点
	Mat outimg1,outimg2,outimg3,outimg4;
	 drawKeypoints(img1, keyPoints1, outimg1);
     imshow("image01 keypoints", outimg1);
     drawKeypoints(img2, keyPoints2, outimg2);
     imshow("image02 keypoints", outimg2);
	 drawKeypoints(img1, keyPoints1, outimg3);
	 imshow("image03 keypoints", outimg3);
	 drawKeypoints(img2, keyPoints2, outimg4);
	 imshow("image04 keypoints", outimg4);

	 //提取特征描述子
	     Mat imageDesc1, imageDesc2, imageDesc3, imageDesc4;
		 cv::Ptr<SiftDescriptorExtractor>extractor = SiftDescriptorExtractor::create(1800);
		 extractor->compute(img1_gray, keyPoints1, imageDesc1);
		 extractor->compute(img2_gray, keyPoints2, imageDesc2);
		 extractor->compute(img3_gray, keyPoints3, imageDesc3);
		 extractor->compute(img4_gray, keyPoints4, imageDesc4);

		 //特征点匹配
		 BFMatcher matcher;
		 vector<DMatch> matches12,matches23,matches34;
		 matcher.match(imageDesc1, imageDesc2,matches12);
		 matcher.match(imageDesc2, imageDesc3, matches23);
		 matcher.match(imageDesc3, imageDesc4, matches34);

		/* Mat image_match2;
		 drawMatches(img1, keyPoints1, img2, keyPoints2, matches, image_match2);
		 imshow("没有经过Rancsanc处理后图片", image_match2);
		 imwrite("E:/A/没有经过Rancsanc处理后图片.jpg", image_match2);*/

		/* FlannBasedMatcher matcher1;
		 vector< DMatch > matches1;
		 matcher1.match(imageDesc1, imageDesc2, matches1);*/
		
		 //去除匹配误差比较大的点
		 vector<DMatch> good_matches12, good_matches23, good_matches34;
		 goodMatches(matches12, imageDesc1, good_matches12, 5);
		 goodMatches(matches23, imageDesc2, good_matches23, 6);
		 goodMatches(matches34, imageDesc3, good_matches34,5);
		 Mat image_match12;
		 drawMatches(img1, keyPoints1, img2, keyPoints2, good_matches12, image_match12);
		 imshow("match12后图片", image_match12);
		  imwrite("E:/A/image_match12.jpg", image_match12);
		  Mat image_match23;
		  drawMatches(img2, keyPoints2, img3, keyPoints3, good_matches23, image_match23);
		  imshow("match23", image_match23);
		  imwrite("E:/A/image_match23.jpg", image_match23);
		  Mat image_match34;
		  drawMatches(img3, keyPoints3, img4, keyPoints4, good_matches34, image_match34);
		  imshow("match43", image_match34);
		  imwrite("E:/A/image_match34.jpg", image_match34);
		  /*sort(inlinerMatch12.begin(), inlinerMatch12.end());*/
	

		
 		 Mat image_match3;
		 vector<DMatch> inlinerMatch12, inlinerMatch23, inlinerMatch34;
		 fitLineRansac(keyPoints1, keyPoints2, good_matches12, inlinerMatch12, 3000, 10);
		 fitLineRansac(keyPoints2, keyPoints3, good_matches23, inlinerMatch23, 3000, 10);
		 fitLineRansac(keyPoints3, keyPoints4, good_matches34, inlinerMatch34, 3000, 10);
		
	/*	 Mat image_linermatch12;
		 drawMatches(img1, keyPoints1, img2, keyPoints2, inlinerMatch12, image_linermatch12);
		 imshow("linermatch12", image_linermatch12);
		 imwrite("E:/A/image_matchliner12.jpg", image_linermatch12);
		 Mat image_linermatch23;
		 drawMatches(img2, keyPoints2, img3, keyPoints3, inlinerMatch23, image_linermatch23);
		 imshow("linermatch23", image_linermatch23);
		 imwrite("E:/A/image_matchliner23.jpg", image_linermatch23);
		 Mat image_linermatch34;
		 drawMatches(img3, keyPoints3, img4, keyPoints4, inlinerMatch34, image_linermatch34);
		 imshow("linermatch34", image_linermatch34);
		 imwrite("E:/A/image_linermatch34.jpg", image_linermatch34);*/
		 /*int a12_1=img1.at<cv::Vec3b>(keyPoints1[inlinerMatch12[1].queryIdx].pt.x, keyPoints1[inlinerMatch12[1].queryIdx].pt.y)[0] / img2.at<cv::Vec3b>(keyPoints2[inlinerMatch12[1].trainIdx].pt.x, keyPoints2[inlinerMatch12[1].trainIdx].pt.y)[0];
		 int a12_2 = img1.at<cv::Vec3b>(keyPoints1[inlinerMatch12[1].queryIdx].pt.x, keyPoints1[inlinerMatch12[1].queryIdx].pt.y)[1] / img2.at<cv::Vec3b>(keyPoints2[inlinerMatch12[1].trainIdx].pt.x, keyPoints2[inlinerMatch12[1].trainIdx].pt.y)[1];
		 int a12_3 = img1.at<cv::Vec3b>(keyPoints1[inlinerMatch12[1].queryIdx].pt.x, keyPoints1[inlinerMatch12[1].queryIdx].pt.y)[2] / img2.at<cv::Vec3b>(keyPoints2[inlinerMatch12[1].trainIdx].pt.x, keyPoints2[inlinerMatch12[1].trainIdx].pt.y)[2];
		 int b23_1= img2.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[0] / img3.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[0];
		 int b23_2 = img2.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[1] / img3.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[1];
		 int b23_3 = img2.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[2] / img3.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[2];
		 int c34_1= img3.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[0] / img4.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[0];
		 int c34_2 = img3.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[1] / img4.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[1];
		 int c34_3 = img3.at<cv::Vec3b>(keyPoints1[inlinerMatch23[1].queryIdx].pt.x, keyPoints1[inlinerMatch23[1].queryIdx].pt.y)[2] / img4.at<cv::Vec3b>(keyPoints2[inlinerMatch23[1].trainIdx].pt.x, keyPoints2[inlinerMatch23[1].trainIdx].pt.y)[2];*/
		      Mat H12, H23, H34;
			 FITSVD(keyPoints1, keyPoints2, inlinerMatch12, H12);
			 FITSVD(keyPoints2, keyPoints3, inlinerMatch23, H23);
			 FITSVD(keyPoints3, keyPoints4, inlinerMatch34, H34);

			 //没有平移时图一变换后左角点的位置
			 CalcCorners(H12*H23, img1, 0);
			

				Mat adjustMat = (Mat_<double>(3, 3) << 1.0, 0,MIN(abs(corners[0].left_top.x), abs(corners[0].left_bottom.x)), 0, 1.0, corners[0].left_top.x, 0, 0, 1.0);
				Mat adjustHomo13= adjustMat * H23 * H12;
				Mat adjustHomo23 = adjustMat * H23;
				Mat adjustHomo43 = adjustMat*H34.inv();
				
				CalcCorners(adjustHomo13, img1,1);
				CalcCorners(adjustHomo23, img2,2);
				CalcCorners(adjustMat, img3, 3);
				CalcCorners(adjustHomo43, img4,4);

				//	cout << "left_top:" << corners.left_top << endl;
				//	cout << "left_bottom:" << corners.left_bottom << endl;
				//	cout << "right_top:" << corners.right_top << endl;
				//	cout << "right_bottom:" << corners.right_bottom << endl;

						
				Mat imageTransform1, imageTransform2, imageTransform3, imageTransform4;
				float trans_bottom = MIN(corners[1].right_bottom.y, corners[1].left_bottom.y, corners[2].right_bottom.y, corners[2].left_bottom.y, corners[3].right_bottom.y, corners[3].left_bottom.y, corners[4].right_bottom.y, corners[4].left_bottom.y);
				
			 warpPerspective(img1, imageTransform1, adjustHomo13, Size(MIN(corners[1].right_top.x, corners[1].right_bottom.x), trans_bottom));
			 warpPerspective(img3, imageTransform3, adjustMat, Size(MIN(corners[3].right_top.x, corners[3].right_bottom.x), trans_bottom));
			 warpPerspective(img2, imageTransform2, adjustHomo23, Size(MIN(corners[2].right_top.x, corners[2].right_bottom.x),trans_bottom));
			 warpPerspective(img4, imageTransform4, adjustHomo43, Size(MIN(corners[4].right_top.x, corners[4].right_bottom.x),trans_bottom));
			 imshow("trans1", imageTransform1);
			 imshow("trans2", imageTransform2);
			 imwrite("E:/A/trans2.jpg", imageTransform2);
			 imwrite("E:/A/trans1.jpg", imageTransform1);
			
			 imshow("trans3", imageTransform3);
			 imwrite("E:/A/trans3.jpg", imageTransform3);
			 imshow("trans4", imageTransform4);
			 imwrite("E:/A/trans4.jpg", imageTransform4);

			 Mat imageTransform12(Size(imageTransform2.cols, img2.rows), CV_8UC3);
			 imageTransform2.copyTo(imageTransform12(Rect(0, 0, imageTransform2.cols, imageTransform2.rows)));
			 imageTransform1.copyTo(imageTransform12(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
			
			 Mat imageTransform123(Size(imageTransform3.cols, img2.rows), CV_8UC3);
			 imageTransform3.copyTo(imageTransform123(Rect(0, 0, imageTransform3.cols, imageTransform3.rows)));

			 imageTransform12.copyTo(imageTransform123(Rect(0, 0,imageTransform12.cols, imageTransform12.rows)));
 Mat imageTransform1234(Size(imageTransform4.cols, img2.rows), CV_8UC3);
			 imageTransform4.copyTo(imageTransform1234(Rect(0, 0, imageTransform4.cols, imageTransform4.rows)));
			 imageTransform123.copyTo(imageTransform1234(Rect(0, 0, imageTransform123.cols, imageTransform123.rows)));
			 imshow("trans1234", imageTransform1234);
			 imwrite("E:/A/trans121234.jpg", imageTransform1234);
			

				waitKey(0);
				return 0;
}
//去除误差比较大的点
void goodMatches(vector<DMatch>& matches, Mat& imageDesc1, vector< DMatch >& good_matches, int n)
{
	double max_dist = 0; double min_dist = 100;//最小距离和最大距离  
	for (int i = 0; i < imageDesc1.rows; i++)
	{
		double dist = matches[i].distance;
		if (dist < min_dist) min_dist = dist;
		if (dist > max_dist) max_dist = dist;
	}
	//【7】存下匹配距离小于n*min_dist的点对  
	for (int i = 0; i < matches.size(); i++)
	{
		if (matches[i].distance < n* min_dist)
		{
			good_matches.push_back(matches[i]);
		}
	}
}

//Ransanc
void fitLineRansac( vector<KeyPoint>& keyPoints1,vector<KeyPoint>& keyPoints2, vector<DMatch>&matches,vector<DMatch>&inlierMatch,int iterations ,double sigma)
{
	unsigned int n = matches.size();

	if (n < 4)//如果点数小于4则不能实现拟合,四对点才能确定一H矩阵
	{
		fprintf(stderr, "Warning: too few points in Matches(), %s line %d\n", __FILE__, __LINE__);
	}
	double bestScore = -1.;
	cv::RNG rng;
	for (int k = 0; k < iterations; k++)
	{
		int i1 = 0, i2 = 0, i3 = 0, i4 = 0;		
			i1 =rand()% matches.size();
			i2 = rand() % matches.size();
			i3 = rand() % matches.size();
			i4 = rand() % matches.size();
	
		double score = 0;
		vector<Point2f> imagePoints12_1, imagePoints12_2;
		imagePoints12_1.push_back(keyPoints1[matches[i1].queryIdx].pt);
		imagePoints12_1.push_back(keyPoints1[matches[i2].queryIdx].pt);
		imagePoints12_1.push_back(keyPoints1[matches[i3].queryIdx].pt);
		imagePoints12_1.push_back(keyPoints1[matches[i4].queryIdx].pt);
	
		imagePoints12_2.push_back((keyPoints2[matches[i1].trainIdx].pt));
		imagePoints12_2.push_back((keyPoints2[matches[i2].trainIdx].pt));
		imagePoints12_2.push_back((keyPoints2[matches[i3].trainIdx].pt));
		imagePoints12_2.push_back((keyPoints2[matches[i4].trainIdx].pt));
		Mat homo1to2 = findHomography(imagePoints12_1, imagePoints12_2);
		vector<DMatch> inlierMatch12;
		

		for (int i = 0; i < n; i++)
		{   
			Mat kp2 = Mat::zeros(3, 1, CV_64FC1);
			Mat kp1 = (Mat_<double>(3, 1) << keyPoints1[matches[i].queryIdx].pt.x, keyPoints1[matches[i].queryIdx].pt.y, 1);
			kp2 = homo1to2 * kp1;
			float x = 0, x1 = 0, x2 = 0, y = 0, y1 = 0, y2 = 0, d;
			x1 = kp2.at<double>(0, 0) / kp2.at<double>(2, 0);
			y1 = kp2.at<double>(1, 0) / kp2.at<double>(2, 0);
			x2 = keyPoints2[matches[i].trainIdx].pt.x;
			y2=	keyPoints2[matches[i].trainIdx].pt.y;

			x = fabs(x2 - x1);
			y = fabs(y2 - y1);
			d = sqrt(x * x + y * y );
			if (d < sigma)
			{
				score += 1;
				inlierMatch12.push_back(matches[i]);
			}

		}
		if (score > bestScore)
		{
			vector<DMatch>().swap(inlierMatch);
			inlierMatch = inlierMatch12;
			bestScore = score;
		}
		else {
			vector<DMatch>().swap(inlierMatch12);
		}
	}
}
//SVD分解求H矩阵
void FITSVD(vector<KeyPoint>& keyPoints1,vector<KeyPoint>& keyPoints2, vector<DMatch>&matches, Mat&H  )
{
	int n = matches.size();
	if (n < 4)
	{
		fprintf(stderr, "Warning: too few points in lsq_homog(), %s line %d\n", __FILE__, __LINE__);
		return ;
	}

	
	Mat A = Mat::zeros(2 * n, 8, CV_64FC1);//2*n行,8列
	Mat B = Mat::zeros(2 * n, 1, CV_64FC1);


	for (int i = 0; i < n; i++)
	{
		A.at<double>(i, 0) = keyPoints1[matches[i].queryIdx].pt.x;
		A.at<double>(i + n, 3) = keyPoints1[matches[i].queryIdx].pt.x;
		A.at<double>(i, 1) = keyPoints1[matches[i].queryIdx].pt.y;
		A.at<double>(i + n, 4) = keyPoints1[matches[i].queryIdx].pt.y;
		A.at<double>(i, 2) = 1.0;
		A.at<double>(i + n, 5) = 1.0;
		A.at<double>(i, 6) = -keyPoints1[matches[i].queryIdx].pt.x * keyPoints2[matches[i].trainIdx].pt.x;
		A.at<double>(i, 7) = -keyPoints1[matches[i].queryIdx].pt.y * keyPoints2[matches[i].trainIdx].pt.x;
		A.at<double>(i + n, 6) = -keyPoints1[matches[i].queryIdx].pt.x * keyPoints2[matches[i].trainIdx].pt.y;
		A.at<double>(i + n, 7) = -keyPoints1[matches[i].queryIdx].pt.y * keyPoints2[matches[i].trainIdx].pt.y;

		B.at<double>(i, 0) = keyPoints2[matches[i].trainIdx].pt.x;
		B.at<double>(i + n, 0) = keyPoints2[matches[i].trainIdx].pt.y;

	}
	Mat S = Mat::zeros(8, 1, CV_64FC1);
	Mat W = Mat::zeros(8, 2 * n, CV_64FC1);
	Mat VT = Mat::zeros(8, 8, CV_64FC1);
	Mat U = Mat::zeros(2 * n, 2 * n, CV_64FC1);
	Mat X = Mat::zeros(8, 1, CV_64FC1);
	H = Mat::zeros(3, 3, CV_64FC1);

	SVD thissvd;
	thissvd.compute(A, S, U, VT, SVD::FULL_UV);
	for (int j = 0; j < 8; j++)
	W.at<double>(j, j) = 1 / S.at<double>(j, 0);
	X = VT.t() * W * U.t() * B;


	H.at<double>(0, 0) = X.at<double>(0, 0);
	H.at<double>(0, 1) = X.at<double>(1, 0);
	H.at<double>(0, 2) = X.at<double>(2, 0);
	H.at<double>(1, 0) = X.at<double>(3, 0);
	H.at<double>(1, 1) = X.at<double>(4, 0);
	H.at<double>(1, 2) = X.at<double>(5, 0);
	H.at<double>(2, 0) = X.at<double>(6, 0);
	H.at<double>(2, 1) = X.at<double>(7, 0);
	H.at<double>(2, 2) = 1;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SIFT图像拼接 的相关文章

  • 深度学习环境配置记录——RTX3050

    一 下载 首先需要先了解一下深度学习环境需要的各个软件之间的关系 xff1a 从源代码构建 TensorFlow google cn 然后了解自己的电脑 NVIDIA控制面板中查看显卡驱动 xff0c 注意这个只是显卡驱动的版本 xff0c
  • RT-Thread— 知识点总结(RTT认证+面试题汇总)

    RT Thread 知识点总结 内核 RO xff1a 只读数据段 xff0c 存放程序中定义的常量 RO Size xff1a code 43 RO Data gt 占用flash大小 RW xff1a 读写数据段 xff0c 存放非0全
  • 建立本地分支与远程分支关联

    文章目录 全过程使用的指令1 1 更新 remote 版本1 2 建立一个新的分支与远程分支对应1 3 关联远程仓库分支 全过程使用的指令 span class token function git span fetch span clas
  • 遥感卫星飞行控制系统设计

    文章目录 1 卫星姿态控制模块组成2 转动惯量和地球自转角速度3 初始姿态和目标姿态4 欧拉角转四元数及四元数转欧拉角5 仿真6 绘图分析 1 卫星姿态控制模块组成 其中执行机构为零动量反作用飞轮 xff0c 此处略去 xff1b 传感器测
  • Objects Track Benchmarks

    MOT 2D MOT MOT challengeTAOCaltech Roadside PedestriansBDD100KWaymoAOTPANDAArgoVerseHiEve Multi person Motion TrackingUA
  • 树莓派4B全40管脚对应功能示意图

    以下两图中 xff0c 图1是树莓派引脚功能图 xff0c 其对应图2红框标注的部分 xff0c 黄色数字标注了对应的管脚 谢谢评论区指正 xff0c 实在抱歉 xff0c 实物图部分误用了树莓派1代的实物图 xff0c 但树莓派4b整体布
  • 【数据结构与算法】车辆路径问题(Vehicle Routing Problem,VRP)

    车辆路径问题 xff08 Vehicle Routing Problem VRP xff09 什么是车辆路径问题 车辆路线问题 xff08 VRP xff09 是指一定数量的客户 xff0c 各自有不同数量的货物需求 xff0c 配送中心向
  • 护士实习自我鉴定

    医院实习护士的评价 篇一 短短一个月的泌尿外科实习生活已接近尾声 xff0c 回顾这段时间的实习生活 xff0c 我感受很深 泌尿外科是我实习的第二站 xff0c 相对肝胆外科就不是那么的忙碌 在泌尿外科经历的业务学习是让我印象最深刻的 x
  • uni-app 实现照片水印并上传照片

    话不多说 xff0c 直接代码 lt template gt lt view class 61 34 wrap 34 gt lt u form model 61 34 model 34 ref 61 34 uForm 34 gt lt u
  • 文档服务器minio 可通过文件路径进行访问

    方法一 登陆服务器 xff0c 直接修改桶的权限 xff1a 1 2 3 4 方法二 创建桶的时候设置桶的策略 xff08 注 xff1a minio版本过高 xff0c 可能没有此方法 xff09 创建一个名为managertest的文件
  • JAVA 视频压缩

    项目依赖 开发引入Windows依赖 xff0c 生产引入linux依赖 xff1b 建议一次都引入 lt 视频压缩 gt lt dependency gt lt groupId gt ws schild lt groupId gt lt
  • Elasticsearch 7.X以上依赖自带jdk

    将 elasticsearch env 中的环境依赖修改为 set JAVA 61 ES HOME jdk bin java exe
  • JPA时间注解的使用

    JPA时间注解 64 Temporal注解 格式化时间日期 xff0c 页面直接得到格式化类型的值 64 Temporal TemporalType DATE 只代表年月日 xff0c 没有时分秒 在页面端取值 xff1a 2019 03
  • python 无人机、飞机轨迹(含姿态角)可视化方法

    无人机 飞机轨迹 含姿态角 可视化方法 目标 xff1a 在三维直角坐标系中画出包含无人机位置pos 偏航角yaw 俯仰角pitch 滚转角roll等姿态的飞行轨迹 思路 xff1a 同时建立机体坐标系和直角坐标系 xff0c 飞机的所有点
  • 【报错记录】import keras时出现AlreadyExistsError: Another metric with the same name already exists.

    AlreadyExistsError Another metric with the same name already exists AlreadyExistError 已存在另一个同名度量 keras版本与tensorflow版本不对应
  • vscode 选择python解释器

    当python环境不止一个时 xff0c vscode可以选择指定的python解释器 xff0c 具体为 xff1a vscode设置中打开Command Palette 键入 Python Select Interpreter
  • 统计代码量方法

    文章目录 方法一 xff1a 直接使用正则表达式方法二 xff1a 使用 96 cloc 96 文件2 1 在Windows下使用代码量统计工具 不设置环境变量 设置环境变量 2 2 96 Linux 96 下使用代码量统计工具 方法三 x
  • Makefile教程(掌握这里足够)

    众所周知 xff0c 在Linux环境下进行项目开发那就少了使用make来构建和管理自己的工程 如果想要更加深入的学习 xff0c 我在这里推荐一本书 https www jianguoyun com p DZWKrLIQjKL5Bxi0z
  • Docker:Docker的安装与镜像加速

    文章目录 一 Docker教程1 1 Docker的优点1 2 Docker容器技术和传统虚拟机技术的性能比较1 3 Docker的相关链接 二 Docker的安装2 1 使用官方安装脚本自动安装2 2 使用Docker仓库进行安装 三 D
  • 字节的高低位交换

    文章目录 一 字节的高低位交换1 移位操作2 蝶式交换法3 查表法 一 字节的高低位交换 问题 xff1a 对一字节的数据 xff0c 进行逐个高低位交换 例如0xCF 11001111B 经过0 7 1 6 2 5 3 4对应位置的交换

随机推荐

  • Typora中的emoji图标标签

    转载自 xff1a https www cnblogs com wangjs jacky p 12011208 html People x1f604 smile x1f606 laughing x1f60a blush x1f603 smi
  • [MFC]使用编辑框来设置IP地址

    我们除了使用IP控件来设置控件之外还可以使用编辑框来设置IP xff0c 这样的话 xff0c 就需要来进行判断我们输入的IP是否合法 判断IP地址合法的标准 xff1a 字符串中必须包含3个符号 被符号 分隔的4个字符串的长度必须小于或等
  • [MFC控件]IP地址控件

    文章目录 使用场景 xff1a 96 CIPAddressCtrl 96 类的成员的属性 xff1a 1 空内容判断 96 CIPAddressCtrl IsBlank 96 2 清空控件 96 CIPAddressCtrl ClearAd
  • VS项目字符集

    在使用VS进行编码过程中 xff0c 查看项目属性看到项目的默认值下有一个字符集选项 xff0c 看下图 xff1a 多字节字符集 在最初的Internet上只有一种字符集 那就是ASCII字符集 xff0c 它相信大家都知道 xff0c
  • 查看当前自己电脑的线程数

    我们在学习线程 xff0c 那我们知道自己电脑中的CPU的线程数吗 xff1f 方法一 xff1a 任务管理器 启动任务管理器点击任务管理器的 性能 选项如下图所示 xff0c 我的电脑是双核四线程的 如果上图看的不明确 xff0c 可以看
  • [MFC控件]获取Edit编辑框内容

    文章目录 一 设置编辑框变量二 通过ID获取 一 设置编辑框变量 1 为编辑框控件添加一个类型为CEdit的变量m Edit CString str span class token punctuation span m Edit span
  • python pip

    简介 pip 是最常用的Python包管理工具 xff0c 该工具提供了对Python 包的查找 下载 安装 卸载的功能 目前Python 2 7 9 43 或 Python 3 4 43 以上版本都自带 pip 工具 xff0c 或者co
  • Julia入门-0、在Windows下安装Julia

    文章目录 0 前言1 相关网站2 Windows 系统下安装Julia3 Julia 交互式命令窗口 0 前言 Julia 是一个面向科学计算的高性能动态高级程序设计语言 Julia 最初是为了满足高性能数值分析和计算科学的需要而设计的 x
  • Julia入门-2、Julia中的全局模块对象

    在 Julia 中 xff0c 有几个关键的全局模块对象 xff1a jl main module 表示当前正在执行的模块 xff0c 也称为 顶层模块 xff08 top level module xff09 或 主模块 xff08 ma
  • Julia入门-3、Julia包管理工具

    文章目录 0 Julia 的包管理工具是 96 Pkg 96 1 使用Julia包管理工具过慢 0 Julia 的包管理工具是Pkg Julia 的包管理工具是Pkg xff0c 可以用于安装 更新 卸载和管理 Julia 中的软件包 以下
  • 华三snmp3配置

    snmp agent 开启SNMP协议 snmp agent local engineid 0000000000 系统自动生成 xff0c 无需配置 snmp agent community read h3c acl 2001 只读属性为h
  • 如何合理的选择加密软件?驱动层加密与应用层加密那个更具优势?

    合理的使用文档加密软件至关重要 站在信息安全的角度来看 xff0c 目前要做的是 xff0c 人员需要正确的对待加密软件为基础 xff0c 然后依据企业的实际办公需求 xff0c 去合理的使用文档加密软件产品来帮助企业达到数据安全保护的要求
  • 系统调用的概念及原理

    系统调用与内核函数 内核函数与普通函数形式上没有什么区别 xff0c 只不过前者在内核实现 xff0c 因此要满足一些内核编程的要求 系统调用是用户进程进入内核的接口层 xff0c 它本身并非内核函数 xff0c 但它是由内核函数实现的 x
  • ubuntu18.04下 安装SLAM-Pangolin(亲测有效)

    首先安装Pangolin所需依赖 sudo apt install libgl1 mesa dev sudo apt install libglew dev sudo apt install cmake sudo apt install l
  • 【树莓派开发日记2 】树莓派安装Ubuntu22系统及启动黑屏等问题的踩坑记录

    树莓派安装Ubuntu22系统及启动黑屏等问题的踩坑记录 在成功进行了组装后 xff0c 就到了最为关键的部分了 xff0c 进行树莓派的系统烧录 虽然树莓派有自己对应的系统 xff0c raspbian xff0c 但是绝大部分的开发者还
  • Docker 安装 MySQL

    Docker 安装 MySQL 1 拉取指定版本mysql2 创建并运行mysql容器3 通过 navicat 远程连接 docker安装教程 1 拉取指定版本mysql 拉取latest版本 xff08 最新版本 xff09 MySQL
  • 智能控制知识点总结

    智能控制知识点总结 一 选择 xff08 20分 xff09 二 填空 xff08 20分 xff09 1 智能控制系统要有自适应和自学习的能力 智能控制必须具有模拟人类学习 Learning 和自适应 xff08 Adaptation 的
  • git new repository上传

    Command line instructions Git global setup span class token function git span config global user name span class token s
  • ROS学习:ROS文件系统--package.xml

    package xml 也是一个 catkin的package 必备文件 xff0c 它是这个软件包的描述文件 xff0c 在较早的ROS 版本 rosbuild 编译系统 中 xff0c 这个文件叫做 manifest xml xff0c
  • SIFT图像拼接

    SIFT图像拼接 文章目录 SIFT图像拼接 前言二 相关工作1 SIFT2 RANSAC拟合3单应性变换4 SVD分解 三 实现步骤四 效果总结代码 前言 结合SIFT 单应性变换 Ransac和SVD等算法实现指定几幅图像之间的拼接 x