基于SIFT特征的全景图像拼接

基于SIFT特征的全景图像拼接

分类: image Machine learning2013-07-05 13:33 2554人阅读 评论(3) 收藏 举报

基于SIFT特征的全景图像拼接

分类: 计算机视觉/OpenCV2013-07-04 21:43 91人阅读 评论(0) 收藏 举报

主要分为以下几个步骤:

(1) 读入两张图片并分别提取SIFT特征

(2) 利用k-d tree和BBF算法进行特征匹配查找

(3) 利用RANSAC算法筛选匹配点并计算变换矩阵

(3) 图像融合

SIFT算法以及RANSAC算法都是利用的RobHess的SIFT源码,前三个步骤RobHess的源码中都有自带的示例。

(1) SIFT特征提取

直接调用RobHess源码中的sift_features()函数进行默认参数的SIFT特征提取,主要代码如下:

[cpp] view plaincopy

  1. img1_Feat = cvCloneImage(img1);//复制图1,深拷贝,用来画特征点
  2. img2_Feat = cvCloneImage(img2);//复制图2,深拷贝,用来画特征点
  3. //默认提取的是LOWE格式的SIFT特征点
  4. //提取并显示第1幅图片上的特征点
  5. n1 = sift_features( img1, &feat1 );//检测图1中的SIFT特征点,n1是图1的特征点个数
  6. export_features("feature1.txt",feat1,n1);//将特征向量数据写入到文件
  7. draw_features( img1_Feat, feat1, n1 );//画出特征点
  8. cvNamedWindow(IMG1_FEAT);//创建窗口
  9. cvShowImage(IMG1_FEAT,img1_Feat);//显示
  10. //提取并显示第2幅图片上的特征点
  11. n2 = sift_features( img2, &feat2 );//检测图2中的SIFT特征点,n2是图2的特征点个数
  12. export_features("feature2.txt",feat2,n2);//将特征向量数据写入到文件
  13. draw_features( img2_Feat, feat2, n2 );//画出特征点
  14. cvNamedWindow(IMG2_FEAT);//创建窗口
  15. cvShowImage(IMG2_FEAT,img2_Feat);//显示

检测出的SIFT特征点如下:

                 

(2) 利用k-d tree和BBF算法进行特征匹配查找,并根据最近邻和次近邻距离比值进行初步筛选

也是调用RobHess源码中的函数,加上之后的一些筛选处理,主要代码如下:

[cpp] view plaincopy

  1. //根据图1的特征点集feat1建立k-d树,返回k-d树根给kd_root
  2. kd_root = kdtree_build( feat1, n1 );
  3. Point pt1,pt2;//连线的两个端点
  4. double d0,d1;//feat2中每个特征点到最近邻和次近邻的距离
  5. int matchNum = 0;//经距离比值法筛选后的匹配点对的个数
  6. //遍历特征点集feat2,针对feat2中每个特征点feat,选取符合距离比值条件的匹配点,放到feat的fwd_match域中
  7. for(int i = 0; i < n2; i++ )
  8. {
  9. feat = feat2+i;//第i个特征点的指针
  10. //在kd_root中搜索目标点feat的2个最近邻点,存放在nbrs中,返回实际找到的近邻点个数
  11. int k = kdtree_bbf_knn( kd_root, feat, 2, &nbrs, KDTREE_BBF_MAX_NN_CHKS );
  12. if( k == 2 )
  13. {
  14. d0 = descr_dist_sq( feat, nbrs[0] );//feat与最近邻点的距离的平方
  15. d1 = descr_dist_sq( feat, nbrs[1] );//feat与次近邻点的距离的平方
  16. //若d0和d1的比值小于阈值NN_SQ_DIST_RATIO_THR,则接受此匹配,否则剔除
  17. if( d0 < d1 * NN_SQ_DIST_RATIO_THR )
  18. {   //将目标点feat和最近邻点作为匹配点对
  19. pt2 = Point( cvRound( feat->x ), cvRound( feat->y ) );//图2中点的坐标
  20. pt1 = Point( cvRound( nbrs[0]->x ), cvRound( nbrs[0]->y ) );//图1中点的坐标(feat的最近邻点)
  21. pt2.x += img1->width;//由于两幅图是左右排列的,pt2的横坐标加上图1的宽度,作为连线的终点
  22. cvLine( stacked, pt1, pt2, CV_RGB(255,0,255), 1, 8, 0 );//画出连线
  23. matchNum++;//统计匹配点对的个数
  24. feat2[i].fwd_match = nbrs[0];//使点feat的fwd_match域指向其对应的匹配点
  25. }
  26. }
  27. free( nbrs );//释放近邻数组
  28. }
  29. //显示并保存经距离比值法筛选后的匹配图
  30. cvNamedWindow(IMG_MATCH1);//创建窗口
  31. cvShowImage(IMG_MATCH1,stacked);//显示

匹配结果如下:

(3) 利用RANSAC算法筛选匹配点并计算变换矩阵

此部分最主要的是RobHess源码中的ransac_xform()函数,此函数实现了用RANSAC算法筛选匹配点,返回结果是计算好的变换矩阵。

此部分中,我利用匹配点的坐标关系,对输入的两幅图像的左右关系进行了判断,并根据结果选择使用矩阵H或H的逆阵进行变换。

所以读入的两幅要拼接的图像的左右位置关系可以随意,程序中可自动调整。

主要代码如下:

[cpp] view plaincopy

  1. //利用RANSAC算法筛选匹配点,计算变换矩阵H,
  2. //无论img1和img2的左右顺序,计算出的H永远是将feat2中的特征点变换为其匹配点,即将img2中的点变换为img1中的对应点
  3. H = ransac_xform(feat2,n2,FEATURE_FWD_MATCH,lsq_homog,4,0.01,homog_xfer_err,3.0,&inliers,&n_inliers);
  4. //若能成功计算出变换矩阵,即两幅图中有共同区域
  5. if( H )
  6. {
  7. qDebug()<<tr("经RANSAC算法筛选后的匹配点对个数:")<<n_inliers<<endl; //输出筛选后的匹配点对个数
  8. int invertNum = 0;//统计pt2.x > pt1.x的匹配点对的个数,来判断img1中是否右图
  9. //遍历经RANSAC算法筛选后的特征点集合inliers,找到每个特征点的匹配点,画出连线
  10. for(int i=0; i<n_inliers; i++)
  11. {
  12. feat = inliers[i];//第i个特征点
  13. pt2 = Point(cvRound(feat->x), cvRound(feat->y));//图2中点的坐标
  14. pt1 = Point(cvRound(feat->fwd_match->x), cvRound(feat->fwd_match->y));//图1中点的坐标(feat的匹配点)
  15. //qDebug()<<"pt2:("<<pt2.x<<","<<pt2.y<<")--->pt1:("<<pt1.x<<","<<pt1.y<<")";//输出对应点对
  16. //统计匹配点的左右位置关系,来判断图1和图2的左右位置关系
  17. if(pt2.x > pt1.x)
  18. invertNum++;
  19. pt2.x += img1->width;//由于两幅图是左右排列的,pt2的横坐标加上图1的宽度,作为连线的终点
  20. cvLine(stacked_ransac,pt1,pt2,CV_RGB(255,0,255),1,8,0);//在匹配图上画出连线
  21. }
  22. cvNamedWindow(IMG_MATCH2);//创建窗口
  23. cvShowImage(IMG_MATCH2,stacked_ransac);//显示经RANSAC算法筛选后的匹配图
  24. /*程序中计算出的变换矩阵H用来将img2中的点变换为img1中的点,正常情况下img1应该是左图,img2应该是右图。
  25. 此时img2中的点pt2和img1中的对应点pt1的x坐标的关系基本都是:pt2.x < pt1.x
  26. 若用户打开的img1是右图,img2是左图,则img2中的点pt2和img1中的对应点pt1的x坐标的关系基本都是:pt2.x > pt1.x
  27. 所以通过统计对应点变换前后x坐标大小关系,可以知道img1是不是右图。
  28. 如果img1是右图,将img1中的匹配点经H的逆阵H_IVT变换后可得到img2中的匹配点*/
  29. //若pt2.x > pt1.x的点的个数大于内点个数的80%,则认定img1中是右图
  30. if(invertNum > n_inliers * 0.8)
  31. {
  32. CvMat * H_IVT = cvCreateMat(3, 3, CV_64FC1);//变换矩阵的逆矩阵
  33. //求H的逆阵H_IVT时,若成功求出,返回非零值
  34. if( cvInvert(H,H_IVT) )
  35. {
  36. cvReleaseMat(&H);//释放变换矩阵H,因为用不到了
  37. H = cvCloneMat(H_IVT);//将H的逆阵H_IVT中的数据拷贝到H中
  38. cvReleaseMat(&H_IVT);//释放逆阵H_IVT
  39. //将img1和img2对调
  40. IplImage * temp = img2;
  41. img2 = img1;
  42. img1 = temp;
  43. ui->mosaicButton->setEnabled(true);//激活全景拼接按钮
  44. }
  45. else//H不可逆时,返回0
  46. {
  47. cvReleaseMat(&H_IVT);//释放逆阵H_IVT
  48. QMessageBox::warning(this,tr("警告"),tr("变换矩阵H不可逆"));
  49. }
  50. }
  51. else
  52. ui->mosaicButton->setEnabled(true);//激活全景拼接按钮
  53. }
  54. else //无法计算出变换矩阵,即两幅图中没有重合区域
  55. {
  56. QMessageBox::warning(this,tr("警告"),tr("两图中无公共区域"));
  57. }

经RANSAC筛选后的匹配结果如下图:

(3) 图像融合

这里有两种拼接方法:

① 简易拼接方法的过程是:首先将右图img2经变换矩阵H变换到一个新图像中,然后直接将左图img1加到新图像中,这样拼接出来会有明显的拼接缝,但也是一个初步的成品了。

② 另一种方法首先也是将右图img2经变换矩阵H变换到一个新图像中,然后图像的融合过程将目标图像分为三部分,最左边完全取自img1中的数据,中间的重合部分是两幅图像的加权平均,重合区域右边的部分完全取自img2经变换后的图像。加权平均的权重选择也有好多方法,比如可以使用最基本的取两张图像的平均值,但这样会有明显的拼接缝。这里首先计算出拼接区域的宽度,设d1,d2分别是重叠区域中的点到重叠区域左边界和右边界的距离,则使用如下公式计算重叠区域的像素值:

,这样就可以实现平滑过渡。

主要代码如下:

[cpp] view plaincopy

  1. //若能成功计算出变换矩阵,即两幅图中有共同区域,才可以进行全景拼接
  2. if(H)
  3. {
  4. //拼接图像,img1是左图,img2是右图
  5. CalcFourCorner();//计算图2的四个角经变换后的坐标
  6. //为拼接结果图xformed分配空间,高度为图1图2高度的较小者,根据图2右上角和右下角变换后的点的位置决定拼接图的宽度
  7. xformed = cvCreateImage(cvSize(MIN(rightTop.x,rightBottom.x),MIN(img1->height,img2->height)),IPL_DEPTH_8U,3);
  8. //用变换矩阵H对右图img2做投影变换(变换后会有坐标右移),结果放到xformed中
  9. cvWarpPerspective(img2,xformed,H,CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS,cvScalarAll(0));
  10. cvNamedWindow(IMG_MOSAIC_TEMP); //显示临时图,即只将图2变换后的图
  11. cvShowImage(IMG_MOSAIC_TEMP,xformed);
  12. //简易拼接法:直接将将左图img1叠加到xformed的左边
  13. xformed_simple = cvCloneImage(xformed);//简易拼接图,可笼子xformed
  14. cvSetImageROI(xformed_simple,cvRect(0,0,img1->width,img1->height));
  15. cvAddWeighted(img1,1,xformed_simple,0,0,xformed_simple);
  16. cvResetImageROI(xformed_simple);
  17. cvNamedWindow(IMG_MOSAIC_SIMPLE);//创建窗口
  18. cvShowImage(IMG_MOSAIC_SIMPLE,xformed_simple);//显示简易拼接图
  19. //处理后的拼接图,克隆自xformed
  20. xformed_proc = cvCloneImage(xformed);
  21. //重叠区域左边的部分完全取自图1
  22. cvSetImageROI(img1,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  23. cvSetImageROI(xformed,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  24. cvSetImageROI(xformed_proc,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  25. cvAddWeighted(img1,1,xformed,0,0,xformed_proc);
  26. cvResetImageROI(img1);
  27. cvResetImageROI(xformed);
  28. cvResetImageROI(xformed_proc);
  29. cvNamedWindow(IMG_MOSAIC_BEFORE_FUSION);
  30. cvShowImage(IMG_MOSAIC_BEFORE_FUSION,xformed_proc);//显示融合之前的拼接图
  31. //采用加权平均的方法融合重叠区域
  32. int start = MIN(leftTop.x,leftBottom.x) ;//开始位置,即重叠区域的左边界
  33. double processWidth = img1->width - start;//重叠区域的宽度
  34. double alpha = 1;//img1中像素的权重
  35. for(int i=0; i<xformed_proc->height; i++)//遍历行
  36. {
  37. const uchar * pixel_img1 = ((uchar *)(img1->imageData + img1->widthStep * i));//img1中第i行数据的指针
  38. const uchar * pixel_xformed = ((uchar *)(xformed->imageData + xformed->widthStep * i));//xformed中第i行数据的指针
  39. uchar * pixel_xformed_proc = ((uchar *)(xformed_proc->imageData + xformed_proc->widthStep * i));//xformed_proc中第i行数据的指针
  40. for(int j=start; j<img1->width; j++)//遍历重叠区域的列
  41. {
  42. //如果遇到图像xformed中无像素的黑点,则完全拷贝图1中的数据
  43. if(pixel_xformed[j*3] < 50 && pixel_xformed[j*3+1] < 50 && pixel_xformed[j*3+2] < 50 )
  44. {
  45. alpha = 1;
  46. }
  47. else
  48. {   //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比
  49. alpha = (processWidth-(j-start)) / processWidth ;
  50. }
  51. pixel_xformed_proc[j*3] = pixel_img1[j*3] * alpha + pixel_xformed[j*3] * (1-alpha);//B通道
  52. pixel_xformed_proc[j*3+1] = pixel_img1[j*3+1] * alpha + pixel_xformed[j*3+1] * (1-alpha);//G通道
  53. pixel_xformed_proc[j*3+2] = pixel_img1[j*3+2] * alpha + pixel_xformed[j*3+2] * (1-alpha);//R通道
  54. }
  55. }
  56. cvNamedWindow(IMG_MOSAIC_PROC);//创建窗口
  57. cvShowImage(IMG_MOSAIC_PROC,xformed_proc);//显示处理后的拼接图
  58. /*重叠区域取两幅图像的平均值,效果不好
  59. //设置ROI,是包含重叠区域的矩形
  60. cvSetImageROI(xformed_proc,cvRect(MIN(leftTop.x,leftBottom.x),0,img1->width-MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  61. cvSetImageROI(img1,cvRect(MIN(leftTop.x,leftBottom.x),0,img1->width-MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  62. cvSetImageROI(xformed,cvRect(MIN(leftTop.x,leftBottom.x),0,img1->width-MIN(leftTop.x,leftBottom.x),xformed_proc->height));
  63. cvAddWeighted(img1,0.5,xformed,0.5,0,xformed_proc);
  64. cvResetImageROI(xformed_proc);
  65. cvResetImageROI(img1);
  66. cvResetImageROI(xformed); //*/
  67. /*对拼接缝周围区域进行滤波来消除拼接缝,效果不好
  68. //在处理前后的图上分别设置横跨拼接缝的矩形ROI
  69. cvSetImageROI(xformed_proc,cvRect(img1->width-10,0,img1->width+10,xformed->height));
  70. cvSetImageROI(xformed,cvRect(img1->width-10,0,img1->width+10,xformed->height));
  71. cvSmooth(xformed,xformed_proc,CV_MEDIAN,5);//对拼接缝周围区域进行中值滤波
  72. cvResetImageROI(xformed);
  73. cvResetImageROI(xformed_proc);
  74. cvShowImage(IMG_MOSAIC_PROC,xformed_proc);//显示处理后的拼接图 */
  75. /*想通过锐化解决变换后的图像失真的问题,对于扭曲过大的图像,效果不好
  76. double a[]={  0, -1,  0, -1,  5, -1, 0, -1,  0  };//拉普拉斯滤波核的数据
  77. CvMat kernel = cvMat(3,3,CV_64FC1,a);//拉普拉斯滤波核
  78. cvFilter2D(xformed_proc,xformed_proc,&kernel);//滤波
  79. cvShowImage(IMG_MOSAIC_PROC,xformed_proc);//显示处理后的拼接图*/
  80. }

右图经变换后的结果如下图:

简易拼接结果如下图:

使用第二种方法时,重合区域融合之前如下图:

加权平均融合之后如下图:

用Qt做了个简单的界面,如下:

还有很多不足,经常有黑边无法去除,望大家多多指正。

源码下载:基于SIFT特征的全景图像拼接,Qt工程:http://download.csdn.net/detail/masikkk/5702681

时间: 2024-08-10 16:47:41

基于SIFT特征的全景图像拼接的相关文章

SIFT特征原理简析(HELU版)

SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以<Distinctive Image Features from Scale-Invariant Keypoints[J]>发表于IJCV中.开源算法库OpenCV中进行了实现.扩展和使用. 本文主要依据原始论文和网络上相关专业分析,对SIFT特征提取的算法流程进行简单分析.由于涉及到的知识概念较多,本人

[blog]基于SURF特征的图像与视频拼接技术的研究和实现(一)

基于SURF特征的图像与视频拼接技术的研究和实现(一) 一直有计划研究实时图像拼接,但是直到最近拜读西电2013年张亚娟的<基于SURF特征的图像与视频拼接技术的研究和实现>,条理清晰.内容完整.实现的技术具有市场价值.因此定下决心以这篇论文为基础脉络,结合实际情况,进行“基于SURF特征的图像与视频拼接技术的研究和实现”. 一.基于opencv的surf实现 3.0以后,surf被分到了"opencv_contrib-master"中去,操作起来不习惯,这里仍然选择一直在

SIFT特征点提取

一. SIFT算法 1.算法简介 尺度不变特征转换即SIFT (Scale-invariant feature transform)是一种计算机视觉的算法.它用来侦测与描述影像中的局部性特征, 它在空间尺度中寻找极值点,并提取出其位置.尺度.旋转不变量,此算法由 David Lowe在1999年所发表,2004年完善总结. 其应用范围包含物体辨识.机器人地图感知与导航.影像缝合.3D模型建立.手势辨识.影像追踪和动作比对. 局部影像特征的描述与侦测可以帮助辨识物体,SIFT特征是基于物体上的一些

【HEVC帧间预测论文】P1.1 基于运动特征的HEVC快速帧间预测算法

基于运动特征的 HEVC 快速帧间预测算法/Fast Inter-Frame Prediction Algorithm for HEVC Based on Motion Features <HEVC标准介绍.HEVC帧间预测论文笔记>系列博客,目录见:http://www.cnblogs.com/DwyaneTalk/p/5711333.html 上海大学学报(自然科学版)第19卷第3期. 利用当前深度CU与时域对应位置已编码CU的亮度像素值的差值平方和均值来判断当前CU的运动特征.属于A类算

PrimeSense 三维重建开发小谈 (2) - 基于灰度特征的关系对配准算法

1 综述 与多点云处理有关的任务,点云的配准(Registration)是一个绕不开的问题.如何采用适当的算法,对特定的点云数据进行相对更优的配准,是点云配准过程中的关键任务. 配准结果通常被表达为一个代表缩放,旋转,平移的刚体变换的矩阵,该矩阵代表了两个点云的位置关系,即将其中一个点云(源点云,Source)施以这个刚体变换可以使它与另一个点云(目标点云,Target)配准. 图 1   目标点云(Target) 图 2   源点云(Source) 图 3   配准结果(该结果即通过本文所述的

[Computer Vision] SIFT特征学后感

SIFT(Scale Invariant Feature Transform),尺度空间不变特征,目前手工设计的最好vision特征. 以下是学习http://blog.csdn.net/zddblog/article/details/7521424后的收获. 一.尺度空间 gaussian pyramid的产生: 1.为避免对第一组第一层图片(原始图片)做高斯滤波导致损失,在其基础上将尺度扩大一倍作为-1层,方法是用=0.5做高斯滤波. 2.对每组(octave)倒数第三张图片做降采样,产生下

基于Haar特征的Adaboost级联人脸检测分类器

基于Haar特征的Adaboost级联人脸检测分类器,简称haar分类器.通过这个算法的名字,我们可以看到这个算法其实包含了几个关键点:Haar特征.Adaboost.级联.理解了这三个词对该算法基本就掌握了. 1        算法要点 Haar分类器 = Haar-like特征 + 积分图方法 + AdaBoost +级联: Haar分类器算法的要点如下: a)        使用Haar-like特征做检测. b)       使用积分图(IntegralImage)对Haar-like特

Sift特征

Sift特征 Sift特征包含两个部分,一个是关键点(frame或者keypoint),另外一个就是在关键点处的描述子(descriptor,或者Keypoint descriptor) 在面部特征点的检测中,经常提取Sift特征.这里的Sift特征指的就是Sift描述子,在一个点处提取的Sift特征一般为128维,即4*4*8=128,4*4表示4*4的区域,8表示每个区域统计的方向. 在Vfleat中:frame 表示Keypoint,descriptor 表示Keypoint descri

OpenCV2.4.4中调用SIFT特征检测器进行图像匹配

OpenCV中一些相关结构说明: 特征点类: class KeyPoint { Point2f pt; //坐标 float size; //特征点邻域直径 float angle; //特征点的方向,值为[0,360),负值表示不使用 float response; // int octave; //特征点所在的图像金字塔的组 int class_id; //用于聚类的id } 存放匹配结果的结构: 1 struct DMatch 2 { 3 //三个构造函数 4 DMatch(): quer