OpenCV 单应矩阵应用:全景图像融合原理

Jacob的全景图像融合算法系列
OpenCV 尺度不变特征检测:SIFT、SURF、BRISK、ORB
OpenCV 匹配兴趣点:SIFT、SURF 和二值描述子
OpenCV 估算图像的投影关系:基础矩阵和RANSAC
OpenCV 单应矩阵应用:全景图像融合原理
图像融合:拉普拉斯金字塔融合算法

之前的写了好几篇文,什么特征点检测,匹配,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,可以得到比较好的拼接效果。接下来的一段时间就需要我去研究一下里面的实现了,敬请期待吧 →_→!

References:
SLAM入门之视觉里程计(5):单应矩阵
Opencv Sift和Surf特征实现图像无缝拼接生成全景图像
opencv计算机视觉编程攻略(第三版) —— Robert Laganiere

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271