关于特征子中经常用的的函数:findHomography,estimateRigidTransform
关于opencv的特征描述子,我们需要了解DMatch与KPoints的数据结构,如下:
DMatch结构体
/*
* Struct for matching: query descriptor index, train descriptor index, train image index and distance between descriptors.
*/
/*
* DMatch主要用来储存匹配信息的结构体,query是要匹配的描述子,train是被匹配的描述子,在Opencv中进行匹配时
* void DescriptorMatcher::match( const Mat& queryDescriptors, const Mat& trainDescriptors, vector<DMatch>& matches, const Mat& mask ) const
* match函数的参数中位置在前面的为query descriptor,后面的是 train descriptor
* 例如:query descriptor的数目为20,train descriptor数目为30,则DescriptorMatcher::match后的vector<DMatch>的size为20
* 若反过来,则vector<DMatch>的size为30
*
*/
1
struct DMatch
{ //三个构造函数
DMatch():
queryIdx(-1),trainIdx(-1),imgIdx(-1),distance(std::numeric_limits<float>::max()) {}
DMatch(int _queryIdx, int _trainIdx, float _distance ) :
queryIdx( _queryIdx),trainIdx( _trainIdx), imgIdx(-1),distance( _distance) {}
DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance ) : queryIdx(_queryIdx), trainIdx( _trainIdx), imgIdx( _imgIdx),distance( _distance) {}
int queryIdx; //此匹配对应的查询图像的特征描述子索引
int trainIdx; //此匹配对应的训练(模板)图像的特征描述子索引
int imgIdx; //训练图像的索引(若有多个)
float distance; //两个特征向量之间的欧氏距离,越小表明匹配度越高。
bool operator < (const DMatch &m) const;
};
2
struct CV_EXPORTS_W_SIMPLE DMatch
{
//默认构造函数,FLT_MAX是无穷大
//#define FLT_MAX 3.402823466e+38F /* max value */
CV_WRAP DMatch() : queryIdx(-1), trainIdx(-1), imgIdx(-1), distance(FLT_MAX) {}
//DMatch构造函数
CV_WRAP DMatch( int _queryIdx, int _trainIdx, float _distance ) :
queryIdx(_queryIdx), trainIdx(_trainIdx), imgIdx(-1), distance(_distance) {}
//DMatch构造函数
CV_WRAP DMatch( int _queryIdx, int _trainIdx, int _imgIdx, float _distance ) :
queryIdx(_queryIdx), trainIdx(_trainIdx), imgIdx(_imgIdx), distance(_distance) {}
//queryIdx为query描述子的索引,match函数中前面的那个描述子
CV_PROP_RW int queryIdx; // query descriptor index
//trainIdx为train描述子的索引,match函数中后面的那个描述子
CV_PROP_RW int trainIdx; // train descriptor index
//imgIdx为进行匹配图像的索引
//例如已知一幅图像的sift描述子,与其他十幅图像的描述子进行匹配,找最相似的图像,则imgIdx此时就有用了。
CV_PROP_RW int imgIdx; // train image index
//distance为两个描述子之间的距离
CV_PROP_RW float distance;
//DMatch比较运算符重载,比较的是DMatch中的distance,小于为true,否则为false
// less is better
bool operator<( const DMatch &m ) const
{
return distance < m.distance;
}
};
KeyPoint特征点类
保存特征点各种信息的KeyPoint类在使用中是不透明的,我们来看看KeyPoint类的主要属性:
class KeyPoint
{
Point2f pt; //特征点坐标
float size; //特征点邻域直径
float angle; //特征点的方向,值为0~360,负值表示不使用
float response; //特征点的响应强度,代表了该点是特征点的程度,可以用于后续处理中特征点排序
int octave; //特征点所在的图像金字塔的组
int class_id; //用于聚类的id
}
主要包含的特征点信息有:位置、邻域直径、特征的方向、响应强度、多尺度信息和分类等。特征点匹配的实现就是通过逐个匹配特征点的这些信息。
drawKeypoints特征点绘制
opencv提供了一个快速绘制特征点的函数drawKeypoints,函数原型:
void drawKeypoints(
const Mat& image,
const vector<KeyPoint>& keypoints,
CV_OUT Mat& outImage,
const Scalar& color=Scalar::all(-1),
int flags=DrawMatchesFlags::DEFAULT );
第一个参数image:原始图像,可以使三通道或单通道图像;
第二个参数keypoints:特征点向量,向量内每一个元素是一个KeyPoint对象,包含了特征点的各种属性信息;
第三个参数outImage:特征点绘制的画布图像,可以是原图像;
第四个参数color:绘制的特征点的颜色信息,默认绘制的是随机彩色;
第五个参数flags:特征点的绘制模式,其实就是设置特征点的那些信息需要绘制,那些不需要绘制,有以下几种模式可选:
DEFAULT:只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标。
DRAW_OVER_OUTIMG:函数不创建输出的图像,而是直接在输出图像变量空间绘制,要求本身输出图像变量就 是一个初始化好了的,size与type都是已经初始化好的变量
NOT_DRAW_SINGLE_POINTS:单点的特征点不被绘制
DRAW_RICH_KEYPOINTS:绘制特征点的时候绘制的是一个个带有方向的圆,这种方法同时显示图像的坐 标,size,和方向,是最能显示特征的一种绘制方式。
#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d/nonfree.hpp>
//在使用SurfFeatureDetector类时候,opencv3.x新版本需要加相关头文件与命名空间
#include <iostream>
using namespace cv;
using namespace std;
using namespace xfeatures2d;
int main(int argc, char *argv[]){
Ptr<SurfFeatureDetector> detector = SurfFeatureDetector::create(800);
Mat image01 = imread("1.png");
Mat image02 = imread("2.png");
imshow("原始测试图像", image01);
imshow("基准图像", image02);
//灰度图转换
Mat srcImage1, srcImage2;
cvtColor(image01, srcImage1, CV_RGB2GRAY);
cvtColor(image02, srcImage2, CV_RGB2GRAY);
vector<cv::KeyPoint> key_points_1, key_points_2;
Mat dstImage1, dstImage2;
detector->detectAndCompute(srcImage1, Mat(), key_points_1, dstImage1);
detector->detectAndCompute(srcImage2, Mat(), key_points_2, dstImage2);//可以分成detect和compute
Mat img_keypoints_1, img_keypoints_2;
drawKeypoints(srcImage1, key_points_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
drawKeypoints(srcImage2, key_points_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("FlannBased");
vector<DMatch>mach;
matcher->match(dstImage1, dstImage2, mach);
sort(mach.begin(), mach.end()); //特征点排序
double Max_dist = 0;
double Min_dist = 100;
for (int i = 0; i < dstImage1.rows; i++){
double dist = mach[i].distance;
if (dist < Min_dist)Min_dist = dist;
if (dist > Max_dist)Max_dist = dist;
}
cout << "最短距离" << Min_dist << endl;
cout << "最长距离" << Max_dist << endl;
vector<DMatch>goodmaches;
for (int i = 0; i < dstImage1.rows; i++){
if (mach[i].distance < 2 * Min_dist)
goodmaches.push_back(mach[i]);
}
Mat img_maches;
drawMatches(srcImage1, key_points_1, srcImage2, key_points_2, goodmaches, img_maches);
vector<Point2f> imagePoints1, imagePoints2;
for (int i = 0; i<10; i++){
imagePoints1.push_back(key_points_1[mach[i].queryIdx].pt);
imagePoints2.push_back(key_points_2[mach[i].trainIdx].pt);
}
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;
}
#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d/nonfree.hpp>
//在使用SurfFeatureDetector类时候,opencv3.x新版本需要加相关头文件与命名空间
#include <iostream>
using namespace cv;
using namespace xfeatures2d;
using namespace std;
int main()
{
Mat srcImage1 = imread("1.jpg");
Mat srcImage2 = imread("2.png");
imshow("【原图1】", srcImage1);
imshow("【原图2】", srcImage2);
Mat grayImage1, grayImage2;
cvtColor(srcImage1, grayImage1, CV_BGR2GRAY);
cvtColor(srcImage2, grayImage2, CV_BGR2GRAY);
//首先对两幅图像进行特征点的检测
//先准备参数
vector<KeyPoint> g_vKeyPoint1;
vector<KeyPoint> g_vKeyPoint2;
//SURF surf(400);
Ptr<SurfFeatureDetector> detector = SurfFeatureDetector::create(400);
//利用得到的特征点计算特征描述子
//目的:对得到的每个特征点进行特征描述,整合到Mat类型的矩阵中(计算结果是Mat类型的)
//该得到的结果矩阵的行数就是特征点的个数,因为是对每个点进行描述,所以每行都会有一个描述的字子向量,共同构成Mat矩阵
Mat descriImage1, descriImage2;
detector->detectAndCompute(srcImage1, Mat(), g_vKeyPoint1, descriImage1);
detector->detectAndCompute(srcImage2, Mat(), g_vKeyPoint2, descriImage2);
//surf.cmpute(grayImage1, g_vKeyPoint1, descriImage1);
//surf.compute(grayImage2, g_vKeyPoint2, descriImage2);
//正式开始在两幅图像中进行匹配
//先得到一个匹配向量
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("FlannBased");
//FlannBasedMatcher FLMatcher;
vector<DMatch> g_vMatches;
//g_vMatches就是得到的匹配向量
matcher->match(descriImage1, descriImage2, g_vMatches);
//FLMatcher.match(descriImage1, descriImage2, g_vMatches);
//用找最大最小值的方式找到 两幅图像中匹配的点的距离的最大值和最小值
//这里的 keyPoint1.size() 和 descriImage1.rows是一样的值,因为descriImage1的行数就是检测到的特征点的个数
double minDistance = g_vMatches[0].distance, maxDistance = g_vMatches[0].distance;
for (size_t i = 0; i < g_vKeyPoint1.size(); i++)
{
double currDistance = g_vMatches[i].distance;
if (currDistance < minDistance)
minDistance = currDistance;
if (currDistance > maxDistance)
maxDistance = currDistance;
}
//定义一个新的变量,用来存储 通过距离检测后 通过阀值的点
vector<DMatch> newMatches;
for (size_t i = 0; i < g_vKeyPoint1.size(); i++)
{
if (g_vMatches[i].distance < 2 * minDistance)
newMatches.push_back(g_vMatches[i]);
}
//用绘制函数对匹配向量进行绘制
Mat dstImage;
drawMatches(srcImage1, g_vKeyPoint1, srcImage2, g_vKeyPoint2, newMatches, dstImage
, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255))
, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), Mat(), 2);
imshow("【特征提取后的图像】", dstImage);
/*****************************************************正式开始寻找已知物体*************************************************/
//为了调用 得到H矩阵findHomography函数,所以需要得到 匹配点所对应的特征点 然后作为参数传递给计算H矩阵的函数
//所以首先是进行 匹配点和对应的特征点的转换步骤
//将得到的点放入新的容器中,所以需要定义新的容器
vector<Point2f> g_vSrcPoint2f1;
vector<Point2f> g_vSrcPoint2f2;
for (size_t i = 0; i < newMatches.size(); i++)
{
g_vSrcPoint2f1.push_back(g_vKeyPoint1[newMatches[i].queryIdx].pt);
g_vSrcPoint2f2.push_back(g_vKeyPoint2[newMatches[i].trainIdx].pt);
}
//将得到的对应的特征点 计算H矩阵
Mat H = findHomography(g_vSrcPoint2f1, g_vSrcPoint2f2, 0);
////用得到的H矩阵 来进行透视矩阵变换 用到的是perspectiveTransform函数
//vector<Point2f> g_vCorners1(4);
//vector<Point2f> g_vCorners2(4);
//g_vCorners1[0] = Point2f(0, 0);
//g_vCorners1[1] = Point2f((float)srcImage1.cols, 0);
//g_vCorners1[2] = Point2f((float)srcImage1.cols, (float)srcImage1.rows);
//g_vCorners1[3] = Point2f(0, (float)srcImage1.rows);
//perspectiveTransform(g_vCorners1, g_vCorners2, H);
////在得到的两幅图像的合成图中绘制检测到的物体的直线
//line(dstImage, (Point)g_vCorners2[0] + Point(srcImage1.cols, 0), (Point)g_vCorners2[1] + Point(srcImage1.cols, 0)
// , Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
//line(dstImage, (Point)g_vCorners2[1] + Point(srcImage1.cols, 0), (Point)g_vCorners2[2] + Point(srcImage1.cols, 0)
// , Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
//line(dstImage, (Point)g_vCorners2[2] + Point(srcImage1.cols, 0), (Point)g_vCorners2[3] + Point(srcImage1.cols, 0)
// , Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
//line(dstImage, (Point)g_vCorners2[3] + Point(srcImage1.cols, 0), (Point)g_vCorners2[0] + Point(srcImage1.cols, 0)
// , Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 2);
//imshow("【检测物体后的图像】", dstImage);
//进行角点检测
////开始进行强角点检测
////先配置需要的函数参数
vector<Point2f> dstPoint2f1;
goodFeaturesToTrack(grayImage1, dstPoint2f1, 200, 0.01, 10, Mat(), 3);
vector<Point2f> dstPoint2f2(dstPoint2f1.size());
//进行透视变换
perspectiveTransform(dstPoint2f1, dstPoint2f2, H);
//在计算得到的点中寻找最小包围矩形
//rectPoint变量中得到了矩形的四个顶点坐标
RotatedRect rectPoint = minAreaRect(dstPoint2f2);
//定义一个存储以上四个点的坐标的变量
Point2f fourPoint2f[4];
//将rectPoint变量中存储的坐标值放到 fourPoint的数组中
rectPoint.points(fourPoint2f);
//根据得到的四个点的坐标 绘制矩形
for (int i = 0; i < 3; i++)
{
line(srcImage2, fourPoint2f[i], fourPoint2f[i + 1]
, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 3);
}
line(srcImage2, fourPoint2f[0], fourPoint2f[3]
, Scalar(theRNG().uniform(0, 255), theRNG().uniform(0, 255), theRNG().uniform(0, 255)), 3);
imshow("【检测到的物体】", srcImage2);
waitKey(0);
return 0;
}