您的位置:首页 > 运维架构

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

2018-07-13 16:06 183 查看

放射变换相关函数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;
}










阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: