admin管理员组文章数量:1622542
目录
- 写在前面
- 配准实验结果
- 图像配准
- 特征点提取及匹配
- 特征检测
- 特征匹配
- 对称检测
- 单应性矩阵Homography matrix
- 理解
- opencv函数接口
- 图像配准
- 单应性变换
- 拼接
- 优化函数
- 参考
- 完
写在前面
1、文中所有资源、参考已给出来源链接,如有侵权请联系删除
2、为了尽量描述每个问题,文章内涉及分支较多,建议先整体阅读,想对某一块知识深究原理的再点击相应链接继续学习
3、本文实验环境:win10+vs2019+opencv440(vs2019配置opencv+contrib-440 + PCL1.10.0 + 源码单步调试https://blog.csdn/qq_41102371/article/details/108727224)
4、码字不易,转载本文请注明出处,本文链接:https://blog.csdn/qq_41102371/article/details/116031738
配准实验结果
原图来自opencv(\opencv-4.4.0\samples\data目录下)https://github/opencv/opencv/tree/master/samples/data
graf3.png(左图)graf3.png(右图)
特征匹配
求单应性矩阵并RANSAC剔除误匹配点后
使用单应性变换矩阵将右图配准至左图效果
完整项目文件免费下载地址:
[share_noel/Opencv&Image Processing/202105image registration]https://blog.csdn/qq_41102371/article/details/125646840
愿意用c币支持的朋友也可在此下载:
202105image_registration.zip https://download.csdn/download/qq_41102371/18717986
(上述下载链接中csdn与网盘的文件完全相同,只不过网盘免费下载)
图像配准
图像配准(Image registration)就是将不同时间、不同传感器(成像设备)或不同条件下(天候、照度、摄像位置和角度等)获取的两幅或多幅图像进行匹配、叠加的过程。
图像配准方法分为三个主要类别:基于灰度信息法、变换域法和基于特征法。
本文使用基于特征的方法,流程为:
首先对两幅图像进行特征提取得到特征点;
通过进行相似性度量找到匹配的特征点对;
然后通过匹配的特征点对得到图像A但图像B空间坐标变换参数(单应性变换矩阵);
使用单应性矩阵将图A进行变换,将图B复制到变换后图A的特定位置;
对重叠边界进行处理使配准图像更自然。
百度百科:图像配准 https://baike.baidu/item/%E5%9B%BE%E5%83%8F%E9%85%8D%E5%87%86/9020482?fr=aladdin#5
OpenCV探索之路(二十四)图像拼接和图像融合技术 https://wwwblogs/skyfsm/p/7411961.html
特征点提取及匹配
特征检测
特征点的检测方法有很多,比较常用的有SIFT,ORB,SURF,Harris等;(OpenCV探索之路(二十三):特征检测和特征匹配方法汇总 https://wwwblogs/skyfsm/p/7401523.html)在追求速度的场景中,用的较多的是ORB,比如视觉SLAM,但ORB不具备尺度不变性;追求精度的场景更多用SIFT、SURF,比如图像配准、基于SFM的影像三维重建等。
本文使用opencv440中的SIFT特征检测算子,关于SIFT算法讲解的一些文章、博客,总结得特别全:RobHess的SIFT源码分析:综述 https://blog.csdn/masibuaa/article/details/9191309
在opencv440版本中的SIFT算法使用方法如下,创建一个SIFT对象siftdetector,使用detectAndCompute()函数同时检测特征点及生成对应的特征描述子
Ptr<Feature2D> siftdetector = cv::SIFT::create(0, 3, 0.04, 10);// SIFT提取特征点参数0, 3, 0.04, 10
vector<KeyPoint> keyPoint1, keyPoint2;//特征点
cv::Mat imageDesc1, imageDesc2;//描述子
//特征点检测与描述,为下边的特征点匹配做准备
siftdetector->detectAndCompute(image1, noArray(), keyPoint1, imageDesc1);
siftdetector->detectAndCompute(image2, noArray(), keyPoint2, imageDesc2);
特征匹配
在特征检测阶段得到的特征描述子就是用来特征匹配用的,原理就是判定两个特征点的欧氏距离;特征匹配使用k近邻搜索,设置k=2,返回图A的特征点
F
A
F_A
FA在图B的2个近邻点:最近邻特征
F
B
1
F_{B1}
FB1以及次近邻特征点
F
B
2
F_{B2}
FB2;使用Lowe的比率阈值剔除错误匹配:对于图A中的特征点
F
A
F_A
FA,其在B图与最近邻特征点
F
B
1
F_{B1}
FB1的为距离
d
1
d_1
d1,与次近邻特征点
F
B
2
F_{B2}
FB2的距离为
d
2
d_2
d2,若
d
1
<
α
∗
d
2
d_1<\alpha*d_2
d1<α∗d2,则认为
F
B
1
F_{B1}
FB1是
F
A
F_A
FA的特征匹配点。其中
α
\alpha
α是一个常数,Lowe原文给出的是0.8([share_noel/papers/SIFT-Lowe2004-Distinctive Image Features from Scale-Invariant Keypoints.pdf]https://blog.csdn/qq_41102371/article/details/125646840
但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:
ratio=0. 4:对于准确度要求高的匹配;
ratio=0. 6:对于匹配点数目要求比较多的匹配;
ratio=0. 5:一般情况下
(OpenCV探索之路(二十三):特征检测和特征匹配方法汇总 https://wwwblogs/skyfsm/p/7401523.html)
特征点匹配方法(SIFT匹配)的一点见解 https://blog.csdn/holybin/article/details/28597349
特征匹配代码:
FlannBasedMatcher matcher;//创建一个特征匹配的对象
//匹配点对容器的容器,每个容器里面装最近邻和次近邻的匹配点对
vector<vector<DMatch> > matchePoints;
//匹配点对容器,装匹配点对
vector<DMatch> GoodMatchePoints;
//向matcher传入特征描述子
vector<Mat> train_desc(1, imageDesc1);
matcher.add(train_desc);
matcher.train();
//使用k近邻(knn)查找imageDesc2的每个特征点在imageDesc1中的最近邻和次近邻点,所以最后一个参数设置为2
matcher.knnMatch(imageDesc2, matchePoints, 2);
cout << "total match points: " << matchePoints.size() << endl;
// Lowe's algorithm,获取优秀匹配点
for (int i = 0; i < matchePoints.size(); i++)
{
//判定最近邻与次近邻的比值是不是小于alpha,是就保留该配对的最近点对
if (matchePoints[i][0].distance < 0.4 * matchePoints[i][1].distance)
{
//由上可知[i][0]是最近邻,[i][1]是次近邻,满足比值的情况下保留最近邻
GoodMatchePoints.push_back(matchePoints[i][0]);
}
}
对称检测
在得到对应特征点对后,还可以使用对称检测来剔除误匹配点对,主要思想为:如果图A中特征点 F A F_A FA在图B中的对应特征点是 F B F_B FB,那么同样使用k近邻的方法检测 F B F_B FB的对应特征点,如果还是 F A F_A FA,那么就保留,如果不是,那么剔除这对特征点对;本文实验并未使用此方法,因为为了方便观察和调试,本文特意设置 α = 0.4 \alpha=0.4 α=0.4以得到较少的特征点,这些点还是比较可靠的。此方法的使用可参考此文的代码实现:SfM多视图三维点云重建–【VS2015+OpenCV3.4+PCL1.8】 https://blog.csdn/YunLaowang/article/details/88388130
// 消除误匹配:对称性检测
void symmetryTest(const vector<DMatch>& matches1, const vector<DMatch>& matches2, vector<DMatch>& symMatches)
{
symMatches.clear();
for (vector<DMatch>::const_iterator matchIterator1 = matches1.begin(); matchIterator1 != matches1.end(); ++matchIterator1)
for (vector<DMatch>::const_iterator matchIterator2 = matches2.begin(); matchIterator2 != matches2.end(); ++matchIterator2)
if ((*matchIterator1).queryIdx == (*matchIterator2).trainIdx && (*matchIterator1).trainIdx == (*matchIterator2).queryIdx)
symMatches.push_back(*matchIterator1);
}
特征匹配及第一次剔除误匹配后的点对
单应性矩阵Homography matrix
理解
单应性矩阵
H
H
H就是描述平面与平面之间变换关系的
3
×
3
3\times3
3×3矩阵,可以想象从不同视角去观察一个平面得到的结果是不一样的,比如对一面涂鸦墙
A
A
A进行两个
C
B
C_{B}
CB和
C
C
C_C
CC两个角度的拍摄,得到图像
B
B
B和
C
C
C,实际上
B
B
B和
C
C
C也是平面,只不过是图像平面,当我们想知道
B
B
B的内容在
C
C
C_C
CC的视角下是什么样时,我们就可以用单应性矩阵
H
H
H来表示平面与平面之间的两两转换关系。比如
B
B
B到
C
C
C之间的单应性变换就可以用
H
B
C
H_{BC}
HBC来表示:
B
C
=
H
B
C
∗
B
B_C=H_{BC}*B
BC=HBC∗B
此时
C
C
C和
B
C
B_C
BC是同一视角下的图像,拼接在一起就有了更多的信息,于是就有了图像配准这样的应用;
并且单应性矩阵是可逆的,我们可以使用
H
C
B
=
H
B
C
−
1
H_{CB}=H_{BC}^{-1}
HCB=HBC−1来表示视角
C
C
C_C
CC变换到
C
B
C_B
CB的单应性变换。(上述单应性矩阵与图像矩阵相乘的表达是不严谨的,仅为了方便描述,实际上使用时是对图像的齐次坐标做单应性变换)
以上描述的单应性变换关系由简单的旋转、平移、缩放、透视组成,具体剖析以及代码示例见此文:
图像旋转平移、仿射变换、透视变换 https://blog.csdn/qq_41102371/article/details/116245483
那么单应性矩阵怎么求呢?由上文的工作,我们检测到了两图像之间的特征点以及对应点对,既然是两平面之间存在单应性变换,那么平面上的对应点肯定也是遵循同样的单应性变换,只要我们有比较好的对应点对,就能够反求解出单应性矩阵。
opencv函数接口
findHomography()函数在\opencv-4.4.0\modules\calib3d\src\fundam.cpp line:350
Mat cv::findHomography (
InputArray srcPoints,
InputArray dstPoints,
int method = 0,
double ransacReprojThreshold = 3,
OutputArray mask = noArray(),
const int maxIters = 2000,
const double confidence = 0.995
)
本文使用opencv提供的单应性矩阵求解函数,opencv的官方文档中有对函数接口的解释以及单应性变换的描述
opencv官方文档findHomography–https://docs.opencv/4.4.0/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780
关于单应性矩阵的更详细公式、推导、代码讲解放在了这篇文章里面:待完成…
关于RANSAC算法及其在单应性矩阵求解中的应用、代码讲解放在了这篇文章:待完成…
现在使用得到的特征点对进行homography matrix的求解,直接使用opencv的函数
输入参数:imagePoints特征点对,优化方法使用RANSAC,重投影误差为3个像素,空的掩膜矩阵
输出:函数返回单应性矩阵,同时更新了掩膜矩阵
//获取图像1到图像2的投影映射矩阵 尺寸为3*3
Mat homo = findHomography(imagePoints1, imagePoints2, cv::RANSAC, 3.0, mask);
cout << "1变换矩阵为:\n" << homo << endl << endl; //输出映射矩阵
/*使用掩膜矩阵剔除错误的匹配点对
本程序为了方便后面的调试分析, 将误匹配的特征点对以及相应的特征点都删除了
留下的正确匹配对按顺序一一对应
*/
maskout_points(imagePoints1, mask);
maskout_points(imagePoints2, mask);
maskout_keypoints(keyPoint1, mask);
maskout_keypoints(keyPoint2, mask);
maskout_matches(GoodMatchePoints, mask);
//将RANSAC后的特征匹配可视化
cv::Mat second_match;
drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, second_match);
cv::namedWindow("second_match", WINDOW_NORMAL);
imshow("second_match", second_match);
imwrite("second_match.jpg", second_match);
cv::waitKey(0);
/*计算配准图的四个顶点坐标
* 图像经过单应性变换后,边界可能会超出之前的范围
* 因此只要算出四个顶点变换的像素坐标就能知道变换后图像的范围
*/
CalcCorners(homo, image01);
cout << "left_top:" << corners.left_top << endl;
cout << "left_bottom:" << corners.left_bottom << endl;
cout << "right_top:" << corners.right_top << endl;
cout << "right_bottom:" << corners.right_bottom << endl;
RANSAC剔除误匹配后的特征匹配
图像配准
单应性变换
使用得到的单应性矩阵对图像1进行单应性变换,变换后的图像为imageTransform1,变换后的尺寸与之前求出顶点的范围有关,图像的高度不变,使用image02.rows,超出去的部分会被忽略,图像的宽度为右上顶点与右下顶点中x坐标最大的那个
//图像配准
Mat imageTransform1, imageTransform2;
warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.x, corners.right_bottom.x), image02.rows));
//warpPerspective(image01, imageTransform1, homo, Size(3200, 1280));
//warpPerspective(image01, imageTransform2, adjustMat*homo, Size(image02.cols*1.3, image02.rows*1.8));
imshow("直接经过透视矩阵变换", imageTransform1);
imwrite("trans1.jpg", imageTransform1);
拼接
现在图1已经转到图2的视角,将两图放在同一块画布上即可实现图像的拼接,但是由于误差,接缝处会效果不是太好,使用权重插值的方法,对边界处进行优化处理
//创建拼接后的图,需提前计算图的大小
int dst_width = imageTransform1.cols; //取最右点的长度为拼接图的长度
int dst_height = image02.rows;
Mat dst(dst_height, dst_width, CV_8UC3);
dst.setTo(0);//图像初始化为每个像素是0
//将两幅图像复制进同一目标图像
imageTransform1.copyTo(dst(Rect(0, 0, imageTransform1.cols, imageTransform1.rows)));
imshow("b_dst0", dst);
image02.copyTo(dst(Rect(0, 0, image02.cols, image02.rows)));
cv::namedWindow("b_dst", WINDOW_NORMAL);
imshow("b_dst", dst);
imwrite("dst0.jpg", dst);
//接缝优化
OptimizeSeam(image02, imageTransform1, dst);
imshow("dst", dst);
imwrite("dst.jpg", dst);
waitKey();
接缝优化前
优化函数
函数通过对左右图像的像素分配不同的权重,使图像衔接更加完美,加权公式以及详细叙述在此文:基于SIFT特征的全景图像拼接https://blog.csdn/masibuaa/article/details/9246493
//优化两图的连接处,使得拼接自然
void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
{
int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界
//忽略小于0的部分
if (start < 0)
{
start = 0;
}
double processWidth = img1.cols - start;//重叠区域的宽度
int rows = dst.rows;
int cols = img1.cols; //注意,是列数*通道数
double alpha = 1;//img1中像素的权重
for (int i = 0; i < rows; i++)
{
uchar* p = img1.ptr<uchar>(i); //获取第i行的首地址
uchar* t = trans.ptr<uchar>(i);
uchar* d = dst.ptr<uchar>(i);
for (int j = start; j < cols; j++)
{
//如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
{
alpha = 1;
}
else
{
//img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好
alpha = (processWidth - (j - start)) / processWidth;
}
d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
}
}
}
优化后
参考
vs2019配置opencv+contrib-440 + PCL1.10.0 + 源码单步调试
https://github/opencv/opencv/tree/master/samples/data
百度百科:图像配准
OpenCV探索之路(二十四)图像拼接和图像融合技术 https://wwwblogs/skyfsm/p/7411961.html
OpenCV探索之路(二十三):特征检测和特征匹配方法汇总
Lowe2004-Distinctive Image Features from Scale-Invariant Keypoints
RobHess的SIFT源码分析:综述
特征点匹配方法(SIFT匹配)的一点见解 https://blog.csdn/holybin/article/details/28597349
SfM多视图三维点云重建–【VS2015+OpenCV3.4+PCL1.8】 https://blog.csdn/YunLaowang/article/details/88388130
图像旋转平移、仿射变换、透视变换 https://blog.csdn/qq_41102371/article/details/116245483
opencv官方文档findHomography–https://docs.opencv/4.4.0/d9/d0c/group__calib3d.html#ga4abc2ece9fab9398f2e560d53c8c9780
基于SIFT特征的全景图像拼接https://blog.csdn/masibuaa/article/details/9246493
Opencv FlOAT64类型Mat访问错误 https://blog.csdn/weixin_41108706/article/details/88566069
单应性矩阵的理解及求解https://blog.csdn/liubing8609/article/details/85340015
完
如有错漏,敬请指正
--------------------------------------------------------------------------------------------诺有缸的高飞鸟202105
版权声明:本文标题:基于sift算法的图像配准、Homograph Matrix、RANSAC 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1728871680a1177341.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论