之前的写了好几篇文,什么特征点检测,匹配,RANSAC之类的乱七八糟的,就是为了做这个应用。了解原理之后用NI Vision实现,数图的课程设计算是交差了~~全景图像融合使用到SIFT算子(特征点检测和匹配)、单应矩阵(立体几何)和RANSAC(随机抽样一致性)之类的内容,了解其中的领域和原理还是需要花点时间的。
霸气侧漏的全景图
1.单应矩阵
X是空间中的一点,左右两边是射影平面(摄像头)
单应(Homography)是射影几何中的概念,又称为射影变换。它把一个射影平面上的点(三维齐次矢量)映射到另一个射影平面上。单应是关于三维齐次矢量的一种线性变换,可以用一个3×3的非奇异矩阵H表示,这个矩阵H称为单应矩阵。使用这个矩阵,就可以将射影平面上的一个点投影到另一个平面上(图中的 m 投影到 m‘)。
线性变换
平面上的点为三维齐次矢量,即
单应矩阵H可以将两幅图像关联起来
2.与基础矩阵的区别
基础矩阵体现的是两个图像间的对极约束(详细见我之前的一篇文章)。两个图像之间的对极约束与场景的结构无关,也就是说你拍摄的物体可以是一个球,或者其他奇形怪状的物体。基础矩阵不能给出两幅图像的像点的一一对应的关系,只能给出像点到另一幅图像的对极线的映射关系。
基础矩阵F描述的实际是一种点和线的映射关系,而不是点对点的关系,不能给出另一个点的确切位置。
也就说,三维点如果不是在同一个平面上,可以使用基础矩阵F来计算图像上像点在另一幅图像上对应的对极线,而不能使用单应矩阵H得到对应点的确切位置。在实际应用中,当被拍摄物体深度Z比较大的时候,可以视为一个平面来处理,也可以使用单应矩阵来进行点对点的映射。
从公式推导中,我们可以得到,当射影平面之间只有旋转无平移时,也可以使用单应矩阵来进行映射,这里不进行推导。
如果拍摄物体不为平面(或不能视为平面来处理)并且射影平面之间不只有旋转关系时时,强行估算单应矩阵,会产生巨大偏差。
假设强行使用单应矩阵
p'应映射到x2',但被映射到了x2
通过平面P上的匹配点得到了单应矩阵H之后,再用来估计不在平面P上的点 p' 的位置,就会出现这样的情况。
3.通过匹配点来计算单应矩阵
关于特征点匹配的内容可以看我之前的文章(为了今天算是磨刀三月- -),为了提高匹配准确率,这里使用的是SIFT算子配合上RANSAC算法的方式进行估计。
两图像上的像点 p1(x1,y1) p2(x2,y2) 是一对匹配的点对,其单应矩阵为H,则有
矩阵形式
展开得
第三个方程为约束条件
那么就至少需要4对匹配点(4个方程组)进行计算(任意三点不共线)
4.代码实现
/******************************************************************** * Created by 杨帮杰 on 10/12/18 * Right to use this code in any way you want without * warranty, support or any guarantee of it working * E-mail: yangbangjie1998@qq.com * Association: SCAU 华南农业大学 ********************************************************************/#include <iostream>#include <vector>#include <opencv2/core.hpp>#include <opencv2/imgproc.hpp>#include <opencv2/highgui.hpp>#include <opencv2/features2d.hpp>#include <opencv2/calib3d.hpp>#include <opencv2/xfeatures2d.hpp>#include <opencv2/stitching.hpp>#define PARLIAMENT01 "/home/jacob/图片/images/parliament1.jpg"#define PARLIAMENT02 "/home/jacob/图片/images/parliament2.jpg"using namespace cv; using namespace std;int main() { Mat image1= imread(PARLIAMENT01,0); Mat image2= imread(PARLIAMENT02,0); if (!image1.data || !image2.data) return 0; imshow("Image 1",image1); imshow("Image 2",image2); vector<KeyPoint> keypoints1; vector<KeyPoint> keypoints2; Mat descriptors1, descriptors2; //创建SIFT检测器 Ptr<Feature2D> ptrFeature2D = xfeatures2d::SIFT::create(74); //检测SIFT特征并生成描述子 ptrFeature2D->detectAndCompute(image1, noArray(), keypoints1, descriptors1); ptrFeature2D->detectAndCompute(image2, noArray(), keypoints2, descriptors2); cout << "Number of feature points (1): " << keypoints1.size() << endl; cout << "Number of feature points (2): " << keypoints2.size() << endl; //使用欧氏距离和交叉匹配策略进行图像匹配 BFMatcher matcher(NORM_L2, true); vector<DMatch> matches; matcher.match(descriptors1,descriptors2,matches); Mat imageMatches; drawMatches(image1,keypoints1, // 1st image and its keypoints image2,keypoints2, // 2nd image and its keypoints matches, // the matches imageMatches, // the image produced Scalar(255,255,255), // color of the lines Scalar(255,255,255), // color of the keypoints vector<char>(), 2); imshow("Matches (pure rotation case)",imageMatches); //将keypoints类型转换为Point2f vector<Point2f> points1, points2; for (vector<DMatch>::const_iterator it= matches.begin(); it!= matches.end(); ++it) { float x= keypoints1[it->queryIdx].pt.x; float y= keypoints1[it->queryIdx].pt.y; points1.push_back(Point2f(x,y)); x= keypoints2[it->trainIdx].pt.x; y= keypoints2[it->trainIdx].pt.y; points2.push_back(Point2f(x,y)); } cout << "number of points: " << points1.size() << " & " << points2.size() << endl; //使用RANSAC算法估算单应矩阵 vector<char> inliers; Mat homography= findHomography( points1,points2, // corresponding points inliers, // outputed inliers matches RANSAC, // RANSAC method 1.); // max distance to reprojection point //画出局内匹配项 drawMatches(image1, keypoints1, // 1st image and its keypoints image2, keypoints2, // 2nd image and its keypoints matches, // the matches imageMatches, // the image produced Scalar(255, 255, 255), // color of the lines Scalar(255, 255, 255), // color of the keypoints inliers, 2); imshow("Homography inlier points", imageMatches); //用单应矩阵对图像进行变换 Mat result; warpPerspective(image1, // input image result, // output image homography, // homography Size(2*image1.cols,image1.rows)); // size of output image //拼接 Mat half(result,Rect(0,0,image2.cols,image2.rows)); image2.copyTo(half); imshow("Image mosaic",result); waitKey(); return 0; }
结果如下
兴趣点匹配
拼接结果
可以看到通过变换视角,可以对图像进行拼接。当然距离真正的全景图像的合成还有点距离,比如说有明显边界,扭曲严重等问题。OpenCV3中提供了一个函数叫stitcher,可以得到比较好的拼接效果。接下来的一段时间就需要我去研究一下里面的实现了,敬请期待吧 _!
作者:Jacob杨帮帮
链接:https://www.jianshu.com/p/549ce9168b0e
共同学习,写下你的评论
评论加载中...
作者其他优质文章