图像拼接算法总结(二)

来源:互联网 发布:微动力cms 编辑:程序博客网 时间:2024/06/09 18:12

2、特征提取与匹配

OpenCV中关于SURF算法的部分,常常涉及到的是SURF、SurfFeatureDetector、SurfDescriptorExtractor这三个类;

features2d.hpp头文件中,有:typedef SURF SurfFeatureDetector;和typedef SURF SurfDescriptorExtractor;typedef声明是为现有类型创建一个新的名字,类型别名,即SURF类有了两个新名字SurfFeatureDetector以及SurfDescriptorExtractor。

也就是说,SurfFeatureDetector类和SurfDescriptorExtractor类,其实就是SURF类,他们三者等价。

因此包含必要的头文件:

#include<iostream>

#include<stdio.h>

#include"highgui/highgui.hpp"  

#include"opencv2/nonfree/nonfree.hpp"  

#include"opencv2/legacy/legacy.hpp" 

usingnamespace cv;

usingnamespace std;

 

2.1 拼接图像的加载:

实例:

MatimageRgbLeft = imread("img_left_1.JPG");

MatimageRgbRight = imread("img_right_1.JPG");

 

//灰度图转换,在特征提取与匹配模块使用灰度图像,因此需要将图像转为灰度图

MatimageGrayLeft, imageGrayRight;

cvtColor(imageRgbLeft,imageGrayLeft, CV_RGB2GRAY);

cvtColor(imageRgbRight,imageGrayRight, CV_RGB2GRAY);

 

2.2 提取特征点   

实例:

intminHessian = 800; //设定阈值minHessian,关系到最后提取的特征点,这个是surf算法的一个参数;

SurfFeatureDetectorsurfDetector(minHessian);   // 海塞矩阵阈值

vector<KeyPoint>keyPointLeft, keyPointRight;// 定义两个KeyPoint 向keypoints_object, keypoints_scene来存放检测出来的特征点;

surfDetector.detect(imageGrayLeft,keyPointLeft);

surfDetector.detect(imageGrayRight,keyPointRight);

 

2.3 drawKeypoints()

作用:绘制关键点。

形式:void drawKeypoints(const Mat&image, constvector<KeyPoint>& keypoints, Mat& outImage, constScalar&color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT );

参数:

image:const Mat&类型的src,输入图像;

keypoints:const vector<KeyPoint>&类型的keypoints,根据源图像得到的特征点,它是一个输出参数;

outImage:Mat&类型的outImage,输出图像,其内容取决于第五个参数标识符falgs;

color:const Scalar&类型的color,关键点的颜色,有默认值Scalar::all(-1);

flags:int类型的flags,绘制关键点是的特征标识符,有默认值DrawMatchesFlags::DEFAULT;

实例:

MatimageLeftPoint, imageRightPoint;

drawKeypoints(imageRgbLeft,keyPointLeft, imageLeftPoint, Scalar::all(-1), DrawMatchesFlags::DEFAULT);

imshow("imageLeftPoint",imageLeftPoint);

drawKeypoints(imageRgbRight,keyPointRight, imageRightPoint, Scalar::all(-1), DrawMatchesFlags::DEFAULT);

imshow("imageRightPoint",imageRightPoint);

waitKey(0);

特征点提取的效果图如下:

图9-3:左图图像特征点提取结果

图9-4:右图图像特征点提取结果

2.4特征点描述,为特征点匹配做准备   

实例:

SurfDescriptorExtractorsurfDescriptor;

MatimageDescLeft, imageDescRight;

surfDescriptor.compute(imageGrayLeft,keyPointLeft, imageDescLeft);

surfDescriptor.compute(imageRgbRight,keyPointRight, imageDescRight);

 

2.5获得匹配特征点,并提取最优配对    

用BruteForceMatcher类中的函数match来匹配两幅图像的特征向量

实例:

BruteForceMatchermatcher;

vector<DMatch>matchePoints;

matcher.match(imageDescLeft,imageDescRight, matchePoints, Mat());

BruteForceMatcher是由DescriptorMatcher派生出来的一个类,而DescriptorMatcher定义了不同的匹配策略的共同接口。调用match方法后,在其第三个参数输出一个cv::DMatch向量。于是我们定义一个std::vector<DMatch>类型的matches。

用FlannBasedMatcher类中的函数match来匹配两幅图像的特征向量

实例:

FlannBasedMatchermatcher;

vector<DMatch>matchePoints;

matcher.match(imageDescLeft,imageDescRight, matchePoints, Mat());

FlannBasedMatcher算法更快但是找到的是最近邻近似匹配,所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。当然也可以通过调整FlannBasedMatcher的参数来提高匹配的精度或者提高算法速度,但是相应地算法速度或者算法精度会受到影响。

 

2.6 drawMatches()

//作用:从两幅图片中画出发现的匹配的关键点。

形式:void drawMatches(const Mat& img1, constvector<KeyPoint>& keypoints1, const Mat& img2, constvector<KeyPoint>& keypoints2, const vector<DMatch>&matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1),const Scalar& singlePointColor=Scalar::all(-1), constvector<char>& matchesMask=vector<char>(), int flags=DrawMatchesFlags::DEFAULT);

参数:

img1、img2:两幅源图像;

keypoints1、keypoints2:两幅源图像中的关键点;

matches1to2:从第一幅图像来匹配第二幅图像,即从keypoints1[i]中寻找与keypoints2[i]的对应点;

outImg:输出图像;它的值取决于flags--在图像中绘制的内容;

matchColor:匹配颜色(线和点的颜色),如果matchColor==Scalar::all(-1),颜色随机生成;

singlePointColor:单个关键点的颜色,即没有关键点与之匹配,如果matchColor==Scalar::all(-1),颜色随机生成;

matchesMask:掩码决定画哪个匹配的关键点,如果掩码为空,画出所有的匹配的关键点;

flags:设置绘图功能,可能标志位的值由“DrawMatchesFlags”确定;

实例:

Matfirstmatches;

drawMatches(imageRgbLeft,keyPointLeft, imageRgbRight, keyPointRight,

    matchePoints, firstmatches,Scalar::all(-1), Scalar::all(-1),

    vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

imshow("first_matches",firstmatches);

waitKey(0);

画出匹配效果图如下:

图9-5:特征匹配结果

2.7匹配特征点sort排序

sort方法可以对匹配点进行从小到大的排序,用sort排序之前,每个匹配点对间的距离(即匹配稳健性程度)是随机分布的,排序之后,距离按由小到大的顺序排列,越靠前的,匹配度越高,可以通过排序后把靠前的匹配提取出来。本例中提取前20个最优匹配。

实例:

sort(matchePoints.begin(),matchePoints.end()); //特征点排序   

//获取排在前N个的最优匹配特征点 

vector<Point2f>imagePointsLeft, imagePointsRight;

intj = 0;

for(int i = 0; i<matchePoints.size(); i++)

{

    j++;

   imagePointsLeft.push_back(keyPointLeft[matchePoints[i].queryIdx].pt);

   imagePointsRight.push_back(keyPointRight[matchePoints[i].trainIdx].pt);

    if (j > 20)

        break;

}

3、图像配准

3.1 findHomography()和RANSAC去除误匹配

   作用:寻找两个平面匹配上的关键点的变换。

形式:Mat findHomography(InputArray srcPoints, InputArray dstPoints, intmethod=0, double ransacReprojThreshold=3, OutputArray mask=noArray() );

参数:

srcPoints:原始平面中点的坐标,即一个CV_32FC2 or vector<Point2f> 型矩阵;

dstPoints:目标平面中点的坐标,即一个CV_32FC2 or vector<Point2f> 型矩阵;

method:用于计算单应矩阵的方法--0:使用所有点的常规方法;

CV_RANSAC:基于RANSAC的鲁棒方法;

CV_LMEDS:最不平均方法;

ransacReprojThreshold:仅当使用CV_RANSAC方法时,把一双点作为内窗的最大投影误差;

mask:鲁棒方法的可选输出掩码设置;

//获取图像1到图像2的投影映射矩阵,尺寸为3*3 

vector<unsignedchar> inliersMask(imagePointsLeft.size());

Mathomo = findHomography(imagePointsLeft, imagePointsRight, CV_RANSAC, 5, inliersMask);//使用CV_RANSAC来去除误匹配

vector<DMatch>matches_ransac;

//手动的保留RANSAC过滤后的匹配点对 

for(int i = 0; i<inliersMask.size(); i++)

{

    //cout<<inliersMask[i]<<endl; 

    cout << (int)(inliersMask[i])<< endl;

    if (inliersMask[i])

    {

       matches_ransac.push_back(matchePoints[i]);

    }

}

Matsecondmatches;

drawMatches(imageRgbLeft,keyPointLeft, imageRgbRight, keyPointRight,

        matches_ransac, secondmatches,Scalar::all(-1), Scalar::all(-1),

vector<char>(),DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);//使用drawMatches画出RANSAC之后的匹配点

imshow("secondmatches",secondmatches);

waitKey(0);

匹配效果图如下:

图9-6:RANSAC去除误匹配后特征匹配结果

H矩阵修正:如果直接使用H矩阵进行图像的拼接将使得在左图没有重叠区域的像素点的坐标变化为负值,这是由于左图是按照右图的坐标系进行配准,所以左右相对于右图的横坐标在非重叠区域都是负值,所以需要引用校正矩阵,其中H矩阵的第一行数据影响水平(x)方向的偏移,第二行数据影响垂直(y)方向的偏移。所以水平偏移需要修正H矩阵的第一行数据,垂直偏移需要修正H矩阵的第二行数据。H矩阵数值的调整可以通过用一个3*3的矩阵相乘实现,水平方向的修正矩阵:

MatadjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue,0,1.0,0,0,0,1.0);

垂直方向上的修正矩阵:

MatadjustMat=(Mat_<double>(3,3)<<1.0,0,0,0,1.0,adjustValue,0,0,1.0);

水平和垂直两个方向上的修正矩阵:

MatadjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue1,0,1.0,adjustValue2,0,0,1.0);

实例:

//计算修正矩阵,首先定义原始图像矩形的4个顶点坐标,然后进行放射变化操作,计算配准后顶点坐标,由于左图非重叠区域坐标会变为负值,所以取(0,0)点的变化后横坐标绝对值即为非重叠区域的宽度。

vector<Point2f>obj_corners(4);

obj_corners[0]= Point(0, 0); obj_corners[1] = Point(imageRgbLeft.cols, 0);

obj_corners[2]= Point(imageRgbLeft.cols, imageRgbLeft.rows); obj_corners[3] = Point(0,imageRgbLeft.rows);

vector<Point2f>scene_corners(4);

perspectiveTransform(obj_corners,scene_corners, homo);

MatadjustMat = (Mat_<double>(3, 3) << 1.0, 0, abs(scene_corners[0].x),0, 1.0, 0, 0, 0, 1.0);

MatadjustHomo = adjustMat*homo;

 

3.2透视变化

voidwarpPerspective( InputArray src, OutputArray dst,

                                   InputArrayM, Size dsize,

                                   intflags=INTER_LINEAR,

                                   intborderMode=BORDER_CONSTANT,

                                   constScalar& borderValue=Scalar());

src:输入2通道或3通道的浮点阵列,每个元素是一个将被转换的2D/3D向量;

dst:与输入有相同大小和类型的输出矩阵;

M:3x3 或4x4的浮点转换矩阵;

Dsize:拼接图像完成图像大小;

MatimageTransformLeft;

warpPerspective(imageRgbLeft,imageTransformLeft, adjustHomo, Size(imageRgbRight.cols +abs(scene_corners[0].x), imageRgbRight.rows));

效果图如下:

图9-7:图像配准前原图

图9-8:图像配准结果图

4、图像融合

4.1 定位重叠区域

//在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变 

Matimage1Overlap, image2Overlap; //图1和图2的重叠部分    

image1Overlap= imageTransformLeft(Rect(Point(abs(scene_corners[0].x), 0), Point(abs(scene_corners[0].x)+ (scene_corners[2].x), imageRgbRight.rows)));

image2Overlap= imageRgbRight(Rect(0, 0, image1Overlap.cols, image1Overlap.rows));

Matimage1ROICopy = image1Overlap.clone(); //复制一份图1的重叠部分 

4.2重叠区域加权融合

for(int i = 0; i<image1Overlap.rows; i++)

{

    for (int j = 0; j<image1Overlap.cols;j++)

    {

        double weight;

        weight = (double)j /image1Overlap.cols;  //随距离改变而改变的叠加系数 

        image1Overlap.at<Vec3b>(i, j)[0]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[0] +weight*image2Overlap.at<Vec3b>(i, j)[0];

        image1Overlap.at<Vec3b>(i, j)[1]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[1] +weight*image2Overlap.at<Vec3b>(i, j)[1];

        image1Overlap.at<Vec3b>(i, j)[2]= (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[2] +weight*image2Overlap.at<Vec3b>(i, j)[2];

    }

}

4.3 非重叠区域融合

MatROIMat = imageRgbRight(Rect(Point(image1Overlap.cols, 0),Point(imageRgbRight.cols, imageRgbRight.rows)));  //图2中不重合的部分 

ROIMat.copyTo(Mat(imageTransformLeft,Rect(abs(scene_corners[0].x) + (scene_corners[2].x), 0, ROIMat.cols,imageRgbRight.rows))); //不重合的部分直接衔接上去 

namedWindow("拼接结果", 0);

imshow("拼接结果", imageTransformLeft);

//imwrite("D:\\拼接结果.jpg", imageTransform1);

waitKey(0);

图像融合后效果图如下:

图9-9:图像融合结果




原创粉丝点击