Opencv中使用Surf特征实现图像配准及对透视变换矩阵H的平移修正
图像配准需要将一张测试图片按照第二张基准图片的尺寸、角度等形态信息进行透视(仿射)变换匹配,本例通过Surf特征的定位和匹配实现图像配准。
配准流程:
- 1. 提取两幅图像的Surf特征
- 2. 对Surf特征进行匹配,找到最匹配的特征点对
- 3. 提取最优配对点的坐标,生成透视变换矩阵
- 4. 对测试图像经过透视变换,生成配准图像
以下是Opencv代码实现:
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char *argv[])
{
Mat image01=imread(argv[1]);
Mat image02=imread(argv[2]);
imshow("原始测试图像",image01);
imshow("基准图像",image02);
//灰度图转换
Mat image1,image2;
cvtColor(image01,image1,CV_RGB2GRAY);
cvtColor(image02,image2,CV_RGB2GRAY);
//提取特征点
SurfFeatureDetector surfDetector(800); // 海塞矩阵阈值
vector<KeyPoint> keyPoint1,keyPoint2;
surfDetector.detect(image1,keyPoint1);
surfDetector.detect(image2,keyPoint2);
//特征点描述,为下边的特征点匹配做准备
SurfDescriptorExtractor SurfDescriptor;
Mat imageDesc1,imageDesc2;
SurfDescriptor.compute(image1,keyPoint1,imageDesc1);
SurfDescriptor.compute(image2,keyPoint2,imageDesc2);
//获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
sort(matchePoints.begin(),matchePoints.end()); //特征点排序
//获取排在前N个的最优匹配特征点
vector<Point2f> imagePoints1,imagePoints2;
for(int i=0;i<10;i++)
{
imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
}
//获取图像1到图像2的投影映射矩阵 尺寸为3*3
Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
////也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差
//Mat homo=getPerspectiveTransform(imagePoints1,imagePoints2);
cout<<"变换矩阵为:\n"<<homo<<endl<<endl; //输出映射矩阵
//图像配准
Mat imageTransform1,imageTransform2;
warpPerspective(image01,imageTransform1,homo,Size(image02.cols,image02.rows));
imshow("经过透视矩阵变换后",imageTransform1);
waitKey();
return 0;
}
测试图像:
配准基准(目标)图像:
配准效果:
可以看到,使用Surf特征自动配准的效果还算可以,也不需要手动选取特征点,但是有点小遗憾是配准后原图的部分图像缺失了,如原始图像的左上角和左下角区域。
怎么解释这个现象呢?其实是因为原图像缺失部分经过透视矩阵变换后在新图像中位置坐标变成负的了,导致这部分直接被截取掉了。
拿原始图像中的坐标为左上角的点(x0,y0)=(1,1)为例,本例中生成的透视变换矩阵H为:
经过矩阵H变换后目标图像的对应坐标为X(x,y):
所以变换后的目标点X(x,y)=(135/-4,-54/-4)= (-34,13) ,即x的坐标等于-34,小于零,所以直接被截取干掉了。
解决办法之一可以通过修正变换矩阵,对其加一个水平或垂直方向上的平移分量,保证变换后的目标坐标有一个偏移,偏移后的坐标大于0即可。其中H矩阵的第一行数据影响水平(x)方向的偏移,第二行数据影响垂直(y)方向的偏移。所以水平偏移需要修正H矩阵的第一行数据,垂直偏移需要修正H矩阵的第二行数据。
H矩阵数值的调整可以通过用一个3*3的矩阵相乘实现,水平方向的修正矩阵:
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue,0,1.0,0,0,0,1.0);
垂直方向上的修正矩阵:
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,0,0,1.0,adjustValue,0,0,1.0);
水平和垂直两个方向上的修正矩阵:
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,adjustValue1,0,1.0,adjustValue2,0,0,1.0);
上边的修正(平移)系数adjustValue数值越大,在目标图像上坐标的偏移也就越大,大于零的adjustValue对应正方向上的平移,小于零的adjustValue对应负方向的平移。
以下是经过adjustMat矩阵修正过的H矩阵数据变化对比:
经过水平和垂直方向的平移修正之后的透视效果,两个角落区域的内容都可以显示出来了:
以下是对透视变换矩阵平移修正的完整代码,有兴趣可以参考试一试。
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char *argv[])
{
Mat image01=imread(argv[1]);
Mat image02=imread(argv[2]);
imshow("原始测试图像",image01);
imshow("基准图像",image02);
//灰度图转换
Mat image1,image2;
cvtColor(image01,image1,CV_RGB2GRAY);
cvtColor(image02,image2,CV_RGB2GRAY);
//提取特征点
SurfFeatureDetector surfDetector(800); // 海塞矩阵阈值
vector<KeyPoint> keyPoint1,keyPoint2;
surfDetector.detect(image1,keyPoint1);
surfDetector.detect(image2,keyPoint2);
//特征点描述,为下边的特征点匹配做准备
SurfDescriptorExtractor SurfDescriptor;
Mat imageDesc1,imageDesc2;
SurfDescriptor.compute(image1,keyPoint1,imageDesc1);
SurfDescriptor.compute(image2,keyPoint2,imageDesc2);
//获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
sort(matchePoints.begin(),matchePoints.end()); //特征点排序
//获取排在前N个的最优匹配特征点
vector<Point2f> imagePoints1,imagePoints2;
for(int i=0;i<10;i++)
{
imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
}
//获取图像1到图像2的投影映射矩阵 尺寸为3*3
Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
////也可以使用getPerspectiveTransform方法获得透视变换矩阵,不过要求只能有4个点,效果稍差
//Mat homo=getPerspectiveTransform(imagePoints1,imagePoints2);
cout<<"变换矩阵为:\n"<<homo<<endl<<endl; //输出映射矩阵
double adjustValue=image1.cols;
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,35,0,1.0,65,0,0,1.0);
cout<<"调整矩阵为:\n"<<adjustMat<<endl<<endl;
cout<<"调整后变换矩阵为:\n"<<adjustMat*homo<<endl;
//图像配准
Mat imageTransform1,imageTransform2;
warpPerspective(image01,imageTransform1,homo,Size(image02.cols,image02.rows));
warpPerspective(image01,imageTransform2,adjustMat*homo,Size(image02.cols*1.3,image02.rows*1.8));
imshow("直接经过透视矩阵变换",imageTransform1);
imshow("经过平移修正后的透视矩阵变换",imageTransform2);
waitKey();
return 0;
}
顺便贴一下特征点的匹配效果:
Opencv中使用Surf特征实现图像配准及对透视变换矩阵H的平移修正的更多相关文章
- Opencv Sift和Surf特征实现图像无缝拼接生成全景图像
Sift和Surf算法实现两幅图像拼接的过程是一样的,主要分为4大部分: 1. 特征点提取和描述 2. 特征点配对,找到两幅图像中匹配点的位置 3. 通过配对点,生成变换矩阵,并对图像1应用变换矩阵生 ...
- 基于SURF特征的图像与视频拼接技术的研究和实现(一)
基于SURF特征的图像与视频拼接技术的研究和实现(一) 一直有计划研究实时图像拼接,但是直到最近拜读西电2013年张亚娟的<基于SURF特征的图像与视频拼接技术的研究和实现>,条 ...
- OpenCV中IplImage图像格式与BYTE图像数据的转换
最近在将Karlsruhe Institute of Technology的Andreas Geiger发表在ACCV2010上的Efficent Large-Scale Stereo Matchin ...
- OpenCV中的SURF算法介绍
SURF:speed up robust feature,翻译为快速鲁棒特征.首先就其中涉及到的特征点和描述符做一些简单的介绍: 特征点和描述符 特征点分为两类:狭义特征点和广义特征点.狭义特征点的位 ...
- 【OpenCV新手教程之十八】OpenCV仿射变换 & SURF特征点描写叙述合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/33320997 作者:毛星云(浅墨) ...
- OpenCV中基于Haar特征和级联分类器的人脸检测
使用机器学习的方法进行人脸检测的第一步需要训练人脸分类器,这是一个耗时耗力的过程,需要收集大量的正负样本,并且样本质量的好坏对结果影响巨大,如果样本没有处理好,再优秀的机器学习分类算法都是零. 今年3 ...
- OpenCV中基于HOG特征的行人检测
目前基于机器学习方法的行人检测的主流特征描述子之一是HOG(Histogram of Oriented Gradient, 方向梯度直方图).HOG特征是用于目标检测的特征描述子,它通过计算和统计图像 ...
- [OpenCV-Python] OpenCV 中图像特征提取与描述 部分 V (一)
部分 V图像特征提取与描述 OpenCV-Python 中文教程(搬运)目录 29 理解图像特征 目标本节我会试着帮你理解什么是图像特征,为什么图像特征很重要,为什么角点很重要等.29.1 解释 我相 ...
- OpenCV教程(47) sift特征和surf特征
在前面三篇教程中的几种角检测方法,比如harris角检测,都是旋转无关的,即使我们转动图像,依然能检测出角的位置,但是图像缩放后,harris角检测可能会失效,比如下面的图像,图像放大之前可 ...
随机推荐
- C++源文件到可运行文件的过程
一.四个步骤 对于C/C++编写的程序,从源码到可运行文件,一般经过以下四个步骤: 1).预处理,产生.ii文件 2).编译,产生汇编文件(.s文件) 3).汇编,产生目标文件(.o或.obj文 ...
- OR1200指令Cache使用举例
下面内容摘自<步步惊芯--软核处理器内部设计分析>一书 12.4 ICache中的特殊寄存器 通过ICache的接口可知其具有特殊寄存器,而且是不可读的特殊寄存器,OR1200处理器中IC ...
- 【CS Round #43 A】Expected Dice
[链接]https://csacademy.com/contest/round-43/task/expected-dice/ [题意] 大水题 [题解] 把36种可能的结果都存下来. 然后把重复出现的 ...
- fatfs输出目录
利用fatfs文件系统, 如何输出SD卡根目录下的各个文件夹名字呢? 程序如下: u8 Dirname_i; u8 Dirname_j; DIR dir; //读取txt里的目录用,还是要把fatfs ...
- Day1:用户交互与格式化输出
一.用户交互 1.input()方法 #!/usr/bin/env python # -*- coding:utf-8 -*- # Author:Hiuhung Wan username = inpu ...
- SVN—怎样安装SVNclient软件
一.怎样安装TortoiseSVN-1.7.12.24070-win32-svn-1.7.9版本号的SVNclient软件: a.下载TortoiseSVN-1.7.12 ...
- BaaS简介
SaaS(软件即服务:Software as a Service).IaaS(基础设施即服务:Infrastructure as a Service)和PaaS(平台即服务:Platform as a ...
- 29、从零写USB摄像头驱动之通过urb接受数据后上报数据是函数中fid的作用
原因分析如下: 视频数据是由一帧一帧数据组成,为了防止数据错乱,会给每一帧数据分配一个frameid,从第0帧开始,接着是第1帧,接着又是第0帧这样交错进行的,对usb摄像头来说每一帧数据来源于多个包 ...
- [Angular2Fire] Firebase auth (Google, Github)
To do auth, first you need to go firebase.console.com to enable the auth methods, for example, enabl ...
- h5 video 点击自动全屏
加上如下属性 https://blog.csdn.net/weixin_40974504/article/details/79639478 可阻止自动全屏播放,感谢 https://blog.csdn ...