网上的SIFT代码都是基于Opencv写的,源代码的看不是很清晰,不利于初学者。
最近,闲得无聊研究了一下SIFT,找到了RobHess维护的代码,整了一番。
然后,再借鉴很多前辈的博客,改了一段代码。
利用Sift寻找特征点,kd数BBF进行特征点的匹配,再用随机抽一致性就行选择特征点匹配组。
写的很不错,只可惜代码是用QT,改了好久才可以用。
不废话,上代码!!
#include "imgfeatures.h"
#include "kdtree.h"
#include "minpq.h"
#include "sift.h"
#include "utils.h"
#include "xform.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
struct PointKey
{
int x;
int y;
} leftTop, rightTop, leftBottom,rightBottom;
char img1_file[] = "C:\\Users\\Lvmeng\\Desktop\\photo-sample\\Image1.jpg";
char img2_file[] = "C:\\Users\\Lvmeng\\Desktop\\photo-sample\\Image2.jpg";
//在k-d树上进行BBF搜索的最大次数
/* the maximum number of keypoint NN candidates to check during BBF search */
#define KDTREE_BBF_MAX_NN_CHKS 200
//目标点与最近邻和次近邻的距离的比值的阈值,若大于此阈值,则剔除此匹配点对
//通常此值取0.6,值越小找到的匹配点对越精确,但匹配数目越少
/* threshold on squared ratio of distances between NN and 2nd NN */
#define NN_SQ_DIST_RATIO_THR 0.6
/*监测特征点的函数
然后画出特征点方向,
*/
void SiftMatch_features()
{
struct feature* feat1, *feat2, *feat;
int n1, n2;
int n_inliers;//经RANSAC算法筛选后的内点个数,即feat2中具有符合要求的特征点的个数
struct kd_node *kd_root;
IplImage* img1, *img2, *stacked;
struct feature** nbrs; //储存特征点的数组,用来kd进行匹配
struct feature **inliers;//精RANSAC筛选后的内点数组
CvPoint pt1, pt2;//连线的两个端点
double d0, d1;//feat2中每个特征点到最近邻和次近邻的距离
int matchNum = 0;//经距离比值法筛选后的匹配点对的个数
int invertNum = 0;//统计pt2.x > pt1.x的匹配点对的个数,来判断img1中是否右图
img1 = cvLoadImage(img1_file, 1);//复制图1,深拷贝,用来画特征点
img2 = cvLoadImage(img2_file, 1);//复制图2,深拷贝,用来画特征点
n1 = sift_features(img1, &feat1);//检测图1中的SIFT特征点,n1是图1的特征点个数
export_features("feature1.txt", feat1, n1);//将特征向量数据写入到文件
draw_features(img1, feat1, n1);//画出特征点
cvNamedWindow("Image1-Feature", 1);//创建窗口
cvShowImage("Image1", img1);//显示
//下面这个应该是用来保存图像的,画出来的特征点的图像进行保存
///QString name1_Feat = name1;//文件名,原文件名加"_Feat"
/// cvSaveImage(name1_Feat.insert(name1_Feat.lastIndexOf(".", -1), "_Feat").toAscii().data(), img1_Feat);//保存图片
//提取并显示第2幅图片上的特征点
n2 = sift_features(img2, &feat2);//检测图2中的SIFT特征点,n2是图2的特征点个数
export_features("feature2.txt", feat2, n2);//将特征向量数据写入到文件
draw_features(img2, feat2, n2);//画出特征点
cvNamedWindow("Image2-Feature", 1);//创建窗口
cvShowImage("Image2", img2);//显示
//下面这个应该是用来保存图像的,画出来的特征点的图像进行保存
// QString name2_Feat = name2;//文件名,原文件名加"_Feat"
// cvSaveImage(name2_Feat.insert(name2_Feat.lastIndexOf(".", -1), "_Feat").toAscii().data(), img2_Feat);//保存图片
stacked = stack_imgs(img1, img2);//实现两幅图像叠加
//根据图1的特征点集feat1建立k-d树,返回k-d树根给kd_root
kd_root = kdtree_build(feat1, n1);
//遍历特征点集feat2,针对feat2中每个特征点feat,选取符合距离比值条件的匹配点,放到feat的fwd_match域中
for (int i = 0; i < n2; i++)
{
feat = feat2 + i;//第i个特征点的指针
//在kd_root中搜索目标点feat的2个最近邻点,存放在nbrs中,返回实际找到的近邻点个数
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]);//feat与最近邻点的距离的平方
d1 = descr_dist_sq(feat, nbrs[1]);//feat与次近邻点的距离的平方
//若d0和d1的比值小于阈值NN_SQ_DIST_RATIO_THR,则接受此匹配,否则剔除
if (d0 < d1 * NN_SQ_DIST_RATIO_THR)
{ //将目标点feat和最近邻点作为匹配点对
pt2 = cvPoint(cvRound(feat->x), cvRound(feat->y));//图2中点的坐标
pt1 = cvPoint(cvRound(nbrs[0]->x), cvRound(nbrs[0]->y));//图1中点的坐标(feat的最近邻点)
if (0)//垂直排列 这里设为0或者决定了匹配的图像是上下匹配还是左右的问题。也就是两张图是左右排列,还是上下排列
pt2.y += img1->height;//由于两幅图是上下排列的,pt2的纵坐标加上图1的高度,作为连线的终点
else
pt2.x += img1->width;//由于两幅图是左右排列的,pt2的横坐标加上图1的宽度,作为连线的终点
cvLine(stacked, pt1, pt2, CV_RGB(0, 255, 0), 1, 8, 0);//画出连线
matchNum++;//统计匹配点对的个数
feat2[i].fwd_match = nbrs[0];//使点feat的fwd_match域指向其对应的匹配点
}
}
free(nbrs);//释放近邻数组
}
cout << ("经距离比值法筛选后的匹配点对个数:") << matchNum << endl;
//显示并保存经距离比值法筛选后的匹配图
cvNamedWindow("Match-feature");//创建窗口
cvShowImage("Match-feature", stacked);//显示
cvSaveImage("Match-feature.jpg", stacked);
CvMat * H;//RANSAC算法求出的变换矩阵
//利用RANSAC算法筛选匹配点,计算变换矩阵H,
//无论img1和img2的左右顺序,H永远是将feat2中的特征点变换为其匹配点,即将img2中的点变换为img1中的对应点
H = ransac_xform(feat2, n2, FEATURE_FWD_MATCH, lsq_homog, 4, 0.01, homog_xfer_err, 3.0, &inliers, &n_inliers);
//输出H矩阵
cout << H << endl;
IplImage *stacked_ransac;//显示匹配结果的合成图像,显示经RANSAC算法筛选后的匹配结果
int i;
//若能成功计算出变换矩阵,即两幅图中有共同区域
if (H)
{
cout << "经RANSAC算法筛选后的匹配点对个数:" << n_inliers << endl;
// cin >> i;
// if (i)//将2幅图片合成1幅图片,img1在上,img2在下
// stacked_ransac = stack_imgs(img1, img2);//合成图像,显示经RANSAC算法筛选后的匹配结果
// else
stacked_ransac = stack_imgs_horizontal(img1, img2);//合成图像,显示经RANSAC算法筛选后的匹配结果
//img1LeftBound = inliers[0]->fwd_match->x;//图1中匹配点外接矩形的左边界
//img1RightBound = img1LeftBound;//图1中匹配点外接矩形的右边界
//img2LeftBound = inliers[0]->x;//图2中匹配点外接矩形的左边界
//img2RightBound = img2LeftBound;//图2中匹配点外接矩形的右边界
//遍历经RANSAC算法筛选后的特征点集合inliers,找到每个特征点的匹配点,画出连线
for (int i = 0; i < n_inliers; i++)
{
feat = inliers[i];//第i个特征点
pt2 = Point(cvRound(feat->x), cvRound(feat->y));//图2中点的坐标
pt1 = Point(cvRound(feat->fwd_match->x), cvRound(feat->fwd_match->y));//图1中点的坐标(feat的匹配点)
//qDebug()<<"pt2:("<<pt2.x<<","<<pt2.y<<")--->pt1:("<<pt1.x<<","<<pt1.y<<")";//输出对应点对
/*找匹配点区域的边界
if(pt1.x < img1LeftBound) img1LeftBound = pt1.x;
if(pt1.x > img1RightBound) img1RightBound = pt1.x;
if(pt2.x < img2LeftBound) img2LeftBound = pt2.x;
if(pt2.x > img2RightBound) img2RightBound = pt2.x;//
*/
//统计匹配点的左右位置关系,来判断图1和图2的左右位置关系
if (pt2.x > pt1.x)
invertNum++;
if (0)//垂直排列
pt2.y += img1->height;//由于两幅图是上下排列的,pt2的纵坐标加上图1的高度,作为连线的终点
else//水平排列
pt2.x += img1->width;//由于两幅图是左右排列的,pt2的横坐标加上图1的宽度,作为连线的终点
cvLine(stacked_ransac, pt1, pt2, CV_RGB(0, 0, 255), 1, 8, 0);//在匹配图上画出连线
}
//绘制图1中包围匹配点的矩形
//cvRectangle(stacked_ransac,cvPoint(img1LeftBound,0),cvPoint(img1RightBound,img1->height),CV_RGB(0,255,0),2);
//绘制图2中包围匹配点的矩形
//cvRectangle(stacked_ransac,cvPoint(img1->width+img2LeftBound,0),cvPoint(img1->width+img2RightBound,img2->height),CV_RGB(0,0,255),2);
cvNamedWindow("显示经RANSAC图"); //创建窗口
cvShowImage("显示经RANSAC图", stacked_ransac); //显示经RANSAC算法筛选后的匹配图
cvSaveImage("显示经RANSAC图.jpg", stacked_ransac);
}
else
{
cout << "H 矩阵为0,不能使用RANSAC算法" << endl;
}
}
int main(void)
{
SiftMatch_features();
cvWaitKey(0);
return 0;
}
未经过随机采样一致性选择匹配点的图片
使用随机一直采样的算法!!
不过,感觉代码有点小问题,没有opencv自带的Sift算法好用。有的时候,进行矩阵投影会出现问题!!