OpenCV学习之仿射变换 & SURF特征点描述合辑

放射变换相关函数warpAffine和getRotationMatrix2D,SURF(特征点描述)算法在

OpenCV中进一步的体现与应用。

一、仿射变换

1.1 初识仿射变换

仿射变换(Affine Transformation或Affine Map),又称仿射映射,是指在几何中,一个向量

空间进行一次线性变换并接上一个平移,保持了二维图形的“平直性”(即:直线经过变换以后依然是直线)

和“平行性”(即:二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)


一个任意的仿射变化都能表示为乘以一个矩阵(线性变换)接着在加上一个向量(平移)的形式。

则,常见的仿射变换有如下三种:

旋转,rotation(线性变换)

平移,translation(向量加)

缩放,scale(线性变换)

仿射变换代表两幅图之间的一种映射关系。


1.2、仿射变换相关的函数使用

OpenCV仿射变换相关的函数一般涉及到warpAffine和getRotationMatrix2D:

使用warpAffine来实现一些简单的重映射。

使用getRotationMatrix2D来获得旋转矩阵


二、warpAffine函数详解

warpAffine函数的作用是依据如下式子,对图像做仿射变换。

dst(x,y)= src(M11x + M12y +M13,M21x + M22y +M23)

函数原型如下:

void warpAffine( InputArray src, OutputArray dst, InputArray M, Size dsize, 

int flags=INTER_LINEAR, intborderMode=BORDER_CONSTANT,const Scalar& borderValue=Scalar())

第一个参数:src,输入图像,Mat类对象

第二个参数:dst,输出图像,需要和源图像有一样的尺寸和类型

第三个参数:InputArray类型的M矩阵,2*3的变换矩阵。

第四个参数:Size类型的dsize,表示输出图像的尺寸。

第五个参数:int类型的flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值),

可选的插值方式如下:

    INTER_NEAREST - 最近邻插值

    INTER_LINEAR - 线性插值(默认值)

    INTER_AREA - 区域插值

    INTER_CUBIC-三次样条插值

    INTER_LANCZOS4-Lanczos插值

    CV_WARP_FILL_OUTLIERS-填充所有输出图像的像素。如果部分像素落在图像的边界外,

    那么它们的值设定为fillval

    CV_WARP_INVERSE_MAP-表示M为输出图像到输入图像的反变换,可以直接用来做像素插值。

    否则,warpAffine函数从M矩阵得到反变换。

第六个参数,int类型的borderMode,边界像素模式,默认值为BORDER_CONSTANT。

第七个参数,const Scalar&类型的borderValue,在恒定的边界情况下取的值,默认值为

Scalar(),即0。


三、getRotationMatrix2D

计算二维旋转变换矩阵。变换会将旋转中心映射到它自身。

Mat getRotationMatrix2D(Point2f center,double angle,double sclae)

第一个参数,Point2f类型的center,表示源图像的旋转中心。

第二个参数,double类型的angle,旋转角度,角度为正值表示向逆时针旋转(坐标原点是左上角)

第三个参数,double类型的scale,缩放系数。

#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

//------------------------- 宏定义部分 -------------------------------//
#define WindowName1 "源始图窗口"
#define WindowName2 "经过warp后的图像窗口"
#define WindowName3 "经过warp和rotate后的图像"

//---------------------------全局函数声明部分  --------------------------------//
static void HelpText();

// 描述:控制台应用程序入口函数,程序从这里开始
int main()
{
	HelpText();
	
	// 参数准备
	// 定义两组点,代表两个三角形
	Point2f srcTriangle[3];
	Point2f dstTriangle[3];
	//定义一些Mat变量
	Mat rotMat(2,3,CV_32FC1); // 通过Mat类的构造函数对rotMat进行初始化
	Mat warpMat(2,3,CV_32FC1);
	Mat srcImage,dstImage_warp,dstImage_warp_rotate;

	// 加载源图像并做一些初始化
	srcImage = imread("D:\\photo\\bird.jpg");
	if(!srcImage.data) { cout << "srcImage not load " << endl; return false;} 
	// 设置目标图像的大小、类型和源图像一致
	dstImage_warp = Mat::zeros(srcImage.size(),srcImage.type());

	// 设置源图像和目标图像上的三组点以计算仿射变换
	srcTriangle[0] = Point2f(0,0);
	srcTriangle[1] = Point2f(static_cast<float>(srcImage.cols - 1),0);
	srcTriangle[2] = Point2f(0,static_cast<float>(srcImage.rows - 1));

	dstTriangle[0] = Point2f(static_cast<float>(srcImage.cols * 0.0),static_cast<float>(srcImage.rows * 0.33));
	dstTriangle[1] = Point2f(static_cast<float>(srcImage.cols * 0.56),static_cast<float>(srcImage.rows * 0.23));
	dstTriangle[2] = Point2f(static_cast<float>(srcImage.cols * 0.15),static_cast<float>(srcImage.rows * 0.63));

	// 求得仿射变换矩阵
	warpMat = getAffineTransform( srcTriangle,dstTriangle);
	// 对源图像应用刚刚求得的仿射变换
	warpAffine(srcImage,dstImage_warp,warpMat,dstImage_warp.size());

	// 对图像进行缩放后再旋转
	// 计算绕图像中点逆时针旋转45°,缩放因子为0.5d的旋转矩阵
	Point center = Point( dstImage_warp.cols/2, dstImage_warp.rows/2);
	double angle = 45.0;
	double scale = 0.5;
	// 通过上面的旋转细节信息求得旋转矩阵
	rotMat = getRotationMatrix2D( center,angle,scale);
	// 旋转已缩放后的图像
	warpAffine( dstImage_warp, dstImage_warp_rotate,rotMat,dstImage_warp_rotate.size());

	
	// 显示窗口
	imshow( WindowName1,srcImage);
	imshow( WindowName2,dstImage_warp);
	imshow( WindowName3,dstImage_warp_rotate);

	waitKey(0);
	return 0;
}


static void HelpText()
{
	// 输出一些帮助信息
	cout << "欢迎来到【仿射变换】示例程序" << endl;
}





四、SURF特征点描述

    SURF,英文全称为SpeededUp Robust Features,直译为“加速版的具有鲁棒性的特征”算法,

由Bay在2006年首次提出。SURF是尺度不变特征变换算法(SIFT算法)的加速版。一般来说,

标准的SURF算子比SIFT算子快好几倍,并且在多幅图片下具有更好的稳定性。SURF最大的特点

在用采用了harr特征以及积分图像的概念,这大大加快了程序的运行时间。SURF可以应用于计算机

视觉的物体识别以及3D重构中。

SURF算法部分,常常涉及到SURF、SurfFeatureDetector、SurfDescriptorExtractor这三个 类 ,后面两个是前一个的别名,三者是等价的。


drawKeypoints函数详解

void drawKeypoints( const Mat& image,const vector<KeyPoint>& keypoints, Mat& outImage,

constScalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT)

第一个参数,const Mat&类型的src,输入图像。

第二个参数,const vector<KeyPoint>&类型的keypoints,根据源图像得到的特征点,它是一个

输出参数。

第三个参数,Mat&类型的outImage,输出图像,其内容取决于第五个参数标识符flags。

第四个参数,const Scalar&类型的color,关键点的颜色,有默认值Scalar::all(-1).

第五个参数,int类型的flags,绘制关键点的特征标识符,有默认值DrawMatchesFlags::DEFAULT.

struct DrawMatchesFlags
{
    enum
    {
        DEFAULT = 0, // Output image matrix will be created (Mat::create),
                     // i.e. existing memory of output image may be reused.
                     // Two source images, matches, and single keypoints
                     // will be drawn.
                     // For each keypoint, only the center point will be
                     // drawn (without a circle around the keypoint with the
                     // keypoint size and orientation).
        DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
                       // created (using Mat::create). Matches will be drawn
                       // on existing content of output image.
        NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.
        DRAW_RICH_KEYPOINTS = 4 // For each keypoint, the circle around
                       // keypoint with keypoint size and orientation will
                       // be drawn.
    };
};

SURF特征点检测的程序的流程:

(1)使用FeatureDetector接口来发现感兴趣点。

(2)使用SurFeatureDetector以及其函数detect来实现检测过程

(3)使用函数drawKeypoints绘制检测到的关键点

//-------------------------------- SURF特征点检测 -------------------------------------------//
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/nonfree/nonfree.hpp>

using namespace std;
using namespace cv;

int main(int argc,char** argv)
{
	Mat srcImage = imread("D:\\photo\\bird1.jpg");
	if(!srcImage.data){ cout << "读取图片错误,请确定目录下是否有指定图片存在" << endl;}

	imshow("源始图",srcImage);

	// 定义需要用到的变量和类
	// minHessian的值一般在400~800之间。minHessian是一个阈值,它决定了哪些值是你接受的关键点。minHessian值越高,得到的关键点越少,但是关键点也就更好,反之,minHessian值越低,得到的关键点越多,关键点质量越差。
	int minHessian = 600; // 定义SURF中hessian阈值特征点检测算子
	// 定义一个SurfFeatureDetector(SURF)特征检测类对象
	SurfFeatureDetector detector( minHessian);
	std::vector<KeyPoint> keypoints; // vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据

	// 调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect( srcImage,keypoints);

	//	绘制特征关键点
	Mat img_keypoints;
	drawKeypoints( srcImage,keypoints,img_keypoints,Scalar::all(-1),DrawMatchesFlags::DEFAULT);

	// 显示效果图
	imshow("特征点检测效果图",img_keypoints);

	waitKey(0);
	return 0;
}

四、SURF特征匹配

SURF算法为每个检测到的特征定义了位置和尺度,尺度值可用于定义围绕特征点的窗口大小,

不论物体的尺度在窗口是什么样的,都将包含相同视觉信息,这些信息用于表示特征点

以使得他们与众不同。

在特征匹配中,特征描述子通常是用于N维向量,在光照不变以及少许透视变形的情况下很理想。

另外,优质的描述子可以通过简单的距离测量进行比较,比如欧式距离,在特征匹配算法中,用处很大。


在OpenCV中,使用SURF进行特征点描述主要是drawMatches方法和BruteForceMatcher类运用。

drawMatches用于绘制出相匹配的两个图像的关键点:

void drawMatches(const Mat& img1, constvector<KeyPoint>& keypoints1, const Mat& img2,

 constvector<KeyPoint>& keypoints2, constvector<DMatch>& matches1to2, 

 Mat& outImage, const Scalar&matchColor=Scalar::all(-1), const Scalar&singlePointColor=Scalar::all(-1),

  const vector<char>&matchesMask=vector<char>(), intflags=DrawMatchesFlags::DEFAULT);

C++: void drawMatches(const Mat& img1,
 constvector<KeyPoint>& keypoints1,
 const Mat& img2,
 constvector<KeyPoint>& keypoints2,
 const vector<vector<DMatch>>&matches1to2,
 Mat& outImg,
 const Scalar&matchColor=Scalar::all(-1),
 const Scalar&singlePointColor=Scalar::all(-1),
 constvector<vector<char>>& matchesMask=vector<vector<char>>(),
 intflags=DrawMatchesFlags::DEFAULT )

除了第五个参数matches1to2和第九个参数matchesMask有细微差别以外,两个版本基本上相同。

第一个参数,const Mat&类型的img1,第一幅源图像。

第二个参数,const vector<KeyPoint>&类型的keypoints1,根据第一幅图像

得到的特征点,它是一个输出参数。

第三个参数,const Mat&类型的img2,第二幅源图像。

第四个参数,const vector<KeyPoint>&类型的keypoints2,根据第二幅源图像

得到的特征点,它是一个输出参数。

第五个参数,matches1to2,第一幅图像到第二幅图像的匹配点,即表示每一个图1中

的特征点都在图2中有一一对应的点

第六个参数,Mat&类型的outImg,输出图像,其内容取决于第五个参数标识符flags

第七个参数,const Scalar&类型的matchColor,匹配的输出颜色,即线和关键点的颜色。

它有默认值Scalar::all(-1),表示颜色是随机生成的。

第八个参数,const Scalar&类型的singlePointColor,单一特征点的颜色,它也有表示

随机生成颜色的默认值Scalar::all(-1)。

第九个参数,matchesMask,确定哪些匹配是会绘制出来的掩膜,如果掩膜为空

表示所有匹配都进行绘制。

第十个参数,int类型的flags,特征绘制的标识符,有默认值DrawMatchesFlags::DEFAULT。

可以在如下这个DrawMatchesFlags结构体中选取值。

struct DrawMatchesFlags
{
   enum
    {
       DEFAULT = 0, // Output image matrix will be created (Mat::create),
                     // i.e. existing memory ofoutput image may be reused.
                     // Two source images,matches, and single keypoints
                     // will be drawn.
                     // For each keypoint, only the center pointwill be
                     // drawn (without a circlearound the keypoint with the
                     // keypoint size andorientation).
       DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
                       // created (usingMat::create). Matches will be drawn
                       // on existing contentof output image.
       NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.
       DRAW_RICH_KEYPOINTS = 4 // For each keypoint, the circle around
                       // keypoint withkeypoint size and orientation will
                       // be drawn.
   };
};


在示例程序中,利用了SURF特征的特征描述办法,其操作封装在类SurfFeatureDetector中,

利用类内的detect函数可以检测出SURF特征的关键点,保存在vector容器中。

第二步4利用SurfDescriptorExtractor类进行特征向量的相关计算。将之前的vector变量变成

向量矩阵形式保存在Mat中。最后强行匹配两幅图像的特征向量,利用了类

BruteForceMatcher中的函数match。

程序核心思想:

(1)使用DescriptorExtractor接口来寻找关键点对应的特征向量。

(2)使用SurfDescriptorExtractor以及它的函数compute来完成特定的计算。

(3)使用BruteForceMatcher来匹配特征向量。

(4)使用函数drawMatches来绘制检测到匹配点。

//-------------------------------- SURF特征匹配 -------------------------------------------//
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <opencv2/legacy/legacy.hpp>

using namespace std;
using namespace cv;

int main(int argc,char** argv)
{
	// 1.载入图片
	Mat srcImage1 = imread("D:\\photo\\bird.jpg");
	Mat srcImage2 = imread("D:\\photo\\bird2.jpg");
	if( !srcImage1.data || !srcImage2.data)
	{
		cout << "图片读取错误!" << endl;
	}

	// 2.使用SURF算子检测关键点
	int minHessian = 700; 
	SurfFeatureDetector detector( minHessian);  // 定义一个特征检测类对象detector
	vector<KeyPoint> keyPoints1,keyPoints2;   // vector模板类,存放任意类型的动态数组

	// 3.调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect( srcImage1, keyPoints1);
	detector.detect( srcImage2, keyPoints2);

	// 4.计算描述符(特征向量)
	SurfDescriptorExtractor extractor;
	Mat descriptors1, descriptors2;
	extractor.compute( srcImage1, keyPoints1,descriptors1);
	extractor.compute( srcImage2, keyPoints2,descriptors2);

	// 5. 使用BruteForce进行匹配
	// 实例化一个匹配器
	BruteForceMatcher<L2<float>> matcher;
	vector<DMatch> matches;
	// 匹配两幅图中的描述子(descriptors)
	matcher.match( descriptors1,descriptors2,matches);

	// 6. 绘制从两个图像中匹配出关键点
	Mat imgMatches;
	drawMatches( srcImage1,keyPoints1,srcImage2,keyPoints2,matches,imgMatches); 

	// 7. 显示效果图
	namedWindow("匹配图",0);
	imshow("匹配图",imgMatches);
	
	waitKey(0);
	return 0;
}










  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值