主要分为以下几个步骤:
(1) 读入两张图片并分别提取SIFT特征
(2) 利用k-d tree和BBF算法进行特征匹配查找
(3) 利用RANSAC算法筛选匹配点并计算变换矩阵
(3) 图像融合
SIFT算法以及RANSAC算法都是利用的RobHess的SIFT源码,前三个步骤RobHess的源码中都有自带的示例。
(1) SIFT特征提取
直接调用RobHess源码中的sift_features()函数进行默认参数的SIFT特征提取,主要代码如下:
- img1_Feat = cvCloneImage(img1);
- img2_Feat = cvCloneImage(img2);
-
-
-
- n1 = sift_features( img1, &feat1 );
- export_features("feature1.txt",feat1,n1);
- draw_features( img1_Feat, feat1, n1 );
- cvNamedWindow(IMG1_FEAT);
- cvShowImage(IMG1_FEAT,img1_Feat);
-
-
- n2 = sift_features( img2, &feat2 );
- export_features("feature2.txt",feat2,n2);
- draw_features( img2_Feat, feat2, n2 );
- cvNamedWindow(IMG2_FEAT);
- cvShowImage(IMG2_FEAT,img2_Feat);
检测出的SIFT特征点如下:
(2) 利用k-d tree和BBF算法进行特征匹配查找,并根据最近邻和次近邻距离比值进行初步筛选
也是调用RobHess源码中的函数,加上之后的一些筛选处理,主要代码如下:
-
- kd_root = kdtree_build( feat1, n1 );
-
- Point pt1,pt2;
- double d0,d1;
- int matchNum = 0;
-
-
- for(int i = 0; i < n2; i++ )
- {
- feat = feat2+i;
-
- int k = kdtree_bbf_knn( kd_root, feat, 2, &nbrs, KDTREE_BBF_MAX_NN_CHKS );
- if( k == 2 )
- {
- d0 = descr_dist_sq( feat, nbrs[0] );
- d1 = descr_dist_sq( feat, nbrs[1] );
-
- if( d0 < d1 * NN_SQ_DIST_RATIO_THR )
- {
- pt2 = Point( cvRound( feat->x ), cvRound( feat->y ) );
- pt1 = Point( cvRound( nbrs[0]->x ), cvRound( nbrs[0]->y ) );
- pt2.x += img1->width;
- cvLine( stacked, pt1, pt2, CV_RGB(255,0,255), 1, 8, 0 );
- matchNum++;
- feat2[i].fwd_match = nbrs[0];
- }
- }
- free( nbrs );
- }
-
- cvNamedWindow(IMG_MATCH1);
- cvShowImage(IMG_MATCH1,stacked);
匹配结果如下:
(3) 利用RANSAC算法筛选匹配点并计算变换矩阵
此部分最主要的是RobHess源码中的ransac_xform()函数,此函数实现了用RANSAC算法筛选匹配点,返回结果是计算好的变换矩阵。
此部分中,我利用匹配点的坐标关系,对输入的两幅图像的左右关系进行了判断,并根据结果选择使用矩阵H或H的逆阵进行变换。
所以读入的两幅要拼接的图像的左右位置关系可以随意,程序中可自动调整。
主要代码如下:
-
-
- H = ransac_xform(feat2,n2,FEATURE_FWD_MATCH,lsq_homog,4,0.01,homog_xfer_err,3.0,&inliers,&n_inliers);
-
-
- if( H )
- {
- qDebug()<<tr("经RANSAC算法筛选后的匹配点对个数:")<<n_inliers<<endl;
-
- int invertNum = 0;
-
-
- for(int i=0; i<n_inliers; i++)
- {
- feat = inliers[i];
- pt2 = Point(cvRound(feat->x), cvRound(feat->y));
- pt1 = Point(cvRound(feat->fwd_match->x), cvRound(feat->fwd_match->y));
-
-
-
- if(pt2.x > pt1.x)
- invertNum++;
-
- pt2.x += img1->width;
- cvLine(stacked_ransac,pt1,pt2,CV_RGB(255,0,255),1,8,0);
- }
-
- cvNamedWindow(IMG_MATCH2);
- cvShowImage(IMG_MATCH2,stacked_ransac);
-
-
-
-
-
-
-
-
- if(invertNum > n_inliers * 0.8)
- {
- CvMat * H_IVT = cvCreateMat(3, 3, CV_64FC1);
-
- if( cvInvert(H,H_IVT) )
- {
- cvReleaseMat(&H);
- H = cvCloneMat(H_IVT);
- cvReleaseMat(&H_IVT);
-
- IplImage * temp = img2;
- img2 = img1;
- img1 = temp;
- ui->mosaicButton->setEnabled(true);
- }
- else
- {
- cvReleaseMat(&H_IVT);
- QMessageBox::warning(this,tr("警告"),tr("变换矩阵H不可逆"));
- }
- }
- else
- ui->mosaicButton->setEnabled(true);
- }
- else
- {
- QMessageBox::warning(this,tr("警告"),tr("两图中无公共区域"));
- }
经RANSAC筛选后的匹配结果如下图:
(3) 图像融合
这里有两种拼接方法:
① 简易拼接方法的过程是:首先将右图img2经变换矩阵H变换到一个新图像中,然后直接将左图img1加到新图像中,这样拼接出来会有明显的拼接缝,但也是一个初步的成品了。
② 另一种方法首先也是将右图img2经变换矩阵H变换到一个新图像中,然后图像的融合过程将目标图像分为三部分,最左边完全取自img1中的数据,中间的重合部分是两幅图像的加权平均,重合区域右边的部分完全取自img2经变换后的图像。加权平均的权重选择也有好多方法,比如可以使用最基本的取两张图像的平均值,但这样会有明显的拼接缝。这里首先计算出拼接区域的宽度,设d1,d2分别是重叠区域中的点到重叠区域左边界和右边界的距离,则使用如下公式计算重叠区域的像素值:
,这样就可以实现平滑过渡。
主要代码如下:
-
- if(H)
- {
-
- CalcFourCorner();
-
- xformed = cvCreateImage(cvSize(MIN(rightTop.x,rightBottom.x),MIN(img1->height,img2->height)),IPL_DEPTH_8U,3);
-
- cvWarpPerspective(img2,xformed,H,CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS,cvScalarAll(0));
- cvNamedWindow(IMG_MOSAIC_TEMP);
- cvShowImage(IMG_MOSAIC_TEMP,xformed);
-
-
- xformed_simple = cvCloneImage(xformed);
- cvSetImageROI(xformed_simple,cvRect(0,0,img1->width,img1->height));
- cvAddWeighted(img1,1,xformed_simple,0,0,xformed_simple);
- cvResetImageROI(xformed_simple);
- cvNamedWindow(IMG_MOSAIC_SIMPLE);
- cvShowImage(IMG_MOSAIC_SIMPLE,xformed_simple);
-
-
- xformed_proc = cvCloneImage(xformed);
-
-
- cvSetImageROI(img1,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
- cvSetImageROI(xformed,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
- cvSetImageROI(xformed_proc,cvRect(0,0,MIN(leftTop.x,leftBottom.x),xformed_proc->height));
- cvAddWeighted(img1,1,xformed,0,0,xformed_proc);
- cvResetImageROI(img1);
- cvResetImageROI(xformed);
- cvResetImageROI(xformed_proc);
- cvNamedWindow(IMG_MOSAIC_BEFORE_FUSION);
- cvShowImage(IMG_MOSAIC_BEFORE_FUSION,xformed_proc);
-
-
- int start = MIN(leftTop.x,leftBottom.x) ;
- double processWidth = img1->width - start;
- double alpha = 1;
- for(int i=0; i<xformed_proc->height; i++)
- {
- const uchar * pixel_img1 = ((uchar *)(img1->imageData + img1->widthStep * i));
- const uchar * pixel_xformed = ((uchar *)(xformed->imageData + xformed->widthStep * i));
- uchar * pixel_xformed_proc = ((uchar *)(xformed_proc->imageData + xformed_proc->widthStep * i));
- for(int j=start; j<img1->width; j++)
- {
-
- if(pixel_xformed[j*3] < 50 && pixel_xformed[j*3+1] < 50 && pixel_xformed[j*3+2] < 50 )
- {
- alpha = 1;
- }
- else
- {
- alpha = (processWidth-(j-start)) / processWidth ;
- }
- pixel_xformed_proc[j*3] = pixel_img1[j*3] * alpha + pixel_xformed[j*3] * (1-alpha);
- pixel_xformed_proc[j*3+1] = pixel_img1[j*3+1] * alpha + pixel_xformed[j*3+1] * (1-alpha);
- pixel_xformed_proc[j*3+2] = pixel_img1[j*3+2] * alpha + pixel_xformed[j*3+2] * (1-alpha);
- }
- }
- cvNamedWindow(IMG_MOSAIC_PROC);
- cvShowImage(IMG_MOSAIC_PROC,xformed_proc);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
右图经变换后的结果如下图:
简易拼接结果如下图:
使用第二种方法时,重合区域融合之前如下图:
加权平均融合之后如下图:
用Qt做了个简单的界面,如下:
还有很多不足,经常有黑边无法去除,望大家多多指正。
源码下载:基于SIFT特征的全景图像拼接,Qt工程:http://download.csdn.net/detail/masikkk/5702681