我是靠谱客的博主 饱满鱼,最近开发中收集的这篇文章主要介绍【opencv 450 samples】 AffineFeature 检测器/提取器的示例用法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/calib3d.hpp>
#include <iostream>
#include <iomanip>
using namespace std;
using namespace cv;
static void help(char** argv)
{
cout
<< "这是 AffineFeature 检测器/提取器的示例用法。n"
<< "这是 samples/python/asift.py 的 C++ 版本n"
<< "Usage: " << argv[0] << "n"
<< "
[ --feature=<sift|orb|brisk> ]
# Feature to use.n"
<< "
[ --flann ]
# use Flann-based matcher instead of bruteforce.n"
<< "
[ --maxlines=<number(50 as default)> ] # The maximum number of lines in visualizing the matching result.n"
<< "
[ --image1=<image1(aero1.jpg as default)> ]n"
<< "
[ --image2=<image2(aero3.jpg as default)> ] # Path to images to compare."
<< endl;
}
static double timer()
{
return getTickCount() / getTickFrequency();
}
int main(int argc, char** argv)
{
vector<String> fileName;//文件名矢量
cv::CommandLineParser parser(argc, argv,
"{help h ||}"
"{feature|brisk|}"
"{flann||}"
"{maxlines|50|}"
"{image1|aero1.jpg|}{image2|aero3.jpg|}");
if (parser.has("help"))
{
help(argv);
return 0;
}
string feature = parser.get<string>("feature");//特征类型: 默认brisk
bool useFlann = parser.has("flann");//使用 快速最近邻搜索包
int maxlines = parser.get<int>("maxlines");//最多显示50条映射线条
fileName.push_back(samples::findFile(parser.get<string>("image1")));
fileName.push_back(samples::findFile(parser.get<string>("image2")));
if (!parser.check())//解释错误
{
parser.printErrors();
cout << "See --help (or missing '=' between argument name and value?)" << endl;//在参数名和值之间是否缺少等号
return 1;
}
Mat img1 = imread(fileName[0], IMREAD_GRAYSCALE);//读取灰度图1
Mat img2 = imread(fileName[1], IMREAD_GRAYSCALE);//灰度图2
if (img1.empty())
{
cerr << "Image " << fileName[0] << " is empty or cannot be found" << endl;
return 1;
}
if (img2.empty())
{
cerr << "Image " << fileName[1] << " is empty or cannot be found" << endl;
return 1;
}
Ptr<Feature2D> backend;//特征检测器
Ptr<DescriptorMatcher> matcher;//描述子匹配器
if (feature == "sift")
{
backend = SIFT::create();//创建sift特征检测器
if (useFlann)
matcher = DescriptorMatcher::create("FlannBased");
else
matcher = DescriptorMatcher::create("BruteForce");
}
else if (feature == "orb")
{
backend = ORB::create();
if (useFlann)
matcher = makePtr<FlannBasedMatcher>(makePtr<flann::LshIndexParams>(6, 12, 1));
else
matcher = DescriptorMatcher::create("BruteForce-Hamming");
}
else if (feature == "brisk")
{
backend = BRISK::create();
if (useFlann)
matcher = makePtr<FlannBasedMatcher>(makePtr<flann::LshIndexParams>(6, 12, 1));
else
matcher = DescriptorMatcher::create("BruteForce-Hamming");
}
else
{
cerr << feature << " is not supported. See --help" << endl;
return 1;
}
cout << "extracting with " << feature << "..." << endl;//正在提取特征……
Ptr<AffineFeature> ext = AffineFeature::create(backend); //AffineFeature用于实现使检测器和提取器具有仿射不变的包装器的类
vector<KeyPoint> kp1, kp2;//关键点向量
Mat desc1, desc2;//描述子
ext->detectAndCompute(img1, Mat(), kp1, desc1);//检测特征点和计算描述子
ext->detectAndCompute(img2, Mat(), kp2, desc2);
cout << "img1 - " << kp1.size() << " features, "
<< "img2 - " << kp2.size() << " features"
<< endl;
cout << "matching with " << (useFlann ? "flann" : "bruteforce") << "..." << endl;//特征匹配……
double start = timer();
// match and draw 匹配和绘制
vector< vector<DMatch> > rawMatches;
vector<Point2f> p1, p2;//匹配点?
vector<float> distances;//点距离矢量
matcher->knnMatch(desc1, desc2, rawMatches, 2);//描述子匹配器 匹配两个图像提取的描述子。为每个查询描述子找到 2 个最佳匹配项(按距离递增的顺序)
// filter_matches
过滤匹配对象
for (size_t i = 0; i < rawMatches.size(); i++)
{
const vector<DMatch>& m = rawMatches[i];
if (m.size() == 2 && m[0].distance < m[1].distance * 0.75)//best匹配比better匹配距离更近
{
p1.push_back(kp1[m[0].queryIdx].pt);//描述子1匹配点矢量
p2.push_back(kp2[m[0].trainIdx].pt);//描述子2匹配点矢量
distances.push_back(m[0].distance);//最佳匹配距离
}
}
vector<uchar> status;
vector< pair<Point2f, Point2f> > pointPairs;
Mat H = findHomography(p1, p2, status, RANSAC);// 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列)
//裁掉status[i] 为零的点对距离?
int inliers = 0;
for (size_t i = 0; i < status.size(); i++)
{
if (status[i])
{
pointPairs.push_back(make_pair(p1[i], p2[i]));//点对 集合
distances[inliers] = distances[i];//距离矢量
// CV_Assert(inliers <= (int)i);
inliers++;
}
}
distances.resize(inliers);//裁掉status[i] 为零的点对距离?
cout << "execution time: " << fixed << setprecision(2) << (timer()-start)*1000 << " ms" << endl;//执行时间
cout << inliers << " / " << status.size() << " inliers/matched" << endl;
cout << "visualizing..." << endl;
vector<int> indices(inliers);//索引矢量
cv::sortIdx(distances, indices, SORT_EVERY_ROW+SORT_ASCENDING);//根据距离重新排列 索引矢量
每行升序
// explore_match 探索匹配
int h1 = img1.size().height;
int w1 = img1.size().width;
int h2 = img2.size().height;
int w2 = img2.size().width;
Mat vis = Mat::zeros(max(h1, h2), w1+w2, CV_8U);//拼接图初始化
img1.copyTo(Mat(vis, Rect(0, 0, w1, h1)));
img2.copyTo(Mat(vis, Rect(w1, 0, w2, h2)));
cvtColor(vis, vis, COLOR_GRAY2BGR);//灰度图转BGR
vector<Point2f> corners(4);//4个角点
corners[0] = Point2f(0, 0);
corners[1] = Point2f((float)w1, 0);
corners[2] = Point2f((float)w1, (float)h1);
corners[3] = Point2f(0, (float)h1);
vector<Point2i> icorners;
perspectiveTransform(corners, corners, H);//映射4个角点
transform(corners, corners, Matx23f(1,0,(float)w1,0,1,0));//平移w1宽度
Mat(corners).convertTo(icorners, CV_32S);//浮点数 转 整型
polylines(vis, icorners, true, Scalar(255,255,255));//绘制四个角点 多边形
//在拼接视图上绘制映射线条
for (int i = 0; i < min(inliers, maxlines); i++)//遍历点对
{
int idx = indices[i];//点对索引
const Point2f& pi1 = pointPairs[idx].first;//第一幅图的点
const Point2f& pi2 = pointPairs[idx].second;//第二幅图的点
circle(vis, pi1, 2, Scalar(0,255,0), -1);//绘制圆点
circle(vis, pi2 + Point2f((float)w1,0), 2, Scalar(0,255,0), -1);
line(vis, pi1, pi2 + Point2f((float)w1,0), Scalar(0,255,0));//在拼接视图上绘制直线
}
if (inliers > maxlines)
cout << "only " << maxlines << " inliers are visualized" << endl;
imshow("affine find_obj", vis);//显示拼接视图
// Mat vis2 = Mat::zeros(max(h1, h2), w1+w2, CV_8U);
// Mat warp1;
// warpPerspective(img1, warp1, H, Size(w1, h1));
// warp1.copyTo(Mat(vis2, Rect(0, 0, w1, h1)));
// img2.copyTo(Mat(vis2, Rect(w1, 0, w2, h2)));
// imshow("warped", vis2);
waitKey();
cout << "done" << endl;
return 0;
}

笔记:


一、. OpenCV学习笔记-FLANN匹配器
https://blog.csdn.net/qq_36387683/article/details/80578480
FLANN是快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)的简称。它是一个对大数据集和高维特征进行最近邻搜索的算法的集合,而且这些算法都已经被优化过了。在面对大数据集是它的效果要好于BFMatcher。
使用FLANN匹配,我们需要传入两个字典作为参数。这两个用来确定要使用的算法和其他相关参数等。
第一个是indexParams。配置我们要使用的算法
1、 随机k-d树算法(The Randomized k-d TreeAlgorithm)
a. Classick-d tree
找出数据集中方差最高的维度,利用这个维度的数值将数据划分为两个部分,对每个子集重复相同的过程。
参考http://www.cnblogs.com/eyeszjwang/articles/2429382.html。
b.
Randomizedk-d tree
建立多棵随机k-d树,从具有最高方差的N_d维中随机选取若干维度,用来做划分。在对随机k-d森林进行搜索时候,所有的随机k-d树将共享一个优先队列。
增加树的数量能加快搜索速度,但由于内存负载的问题,树的数量只能控制在一定范围内,比如20,如果超过一定范围,那么搜索速度不会增加甚至会减慢
2、
优先搜索k-means树算法(The Priority Search K-MeansTree Algorithm)
随机k-d森林在许多情形下都很有效,但是对于需要高精度的情形,优先搜索k-means树更加有效。 K-means tree 利用了数据固有的结构信息,它根据数据的所有维度进行聚类,而随机k-d tree一次只利用了一个维度进行划分。
2.1
算法描述
步骤1 建立优先搜索k-means tree:
(1)
建立一个层次化的k-means 树;
(2)
每个层次的聚类中心,作为树的节点;
(3)
当某个cluster内的点数量小于K时,那么这些数据节点将做为叶子节点。
步骤2 在优先搜索k-means tree中进行搜索:
(1)
从根节点N开始检索;
(2)
如果是N叶子节点则将同层次的叶子节点都加入到搜索结果中,count += |N|;
(3)
如果N不是叶子节点,则将它的子节点与query Q比较,找出最近的那个节点Cq,同层次的其他节点加入到优先队列中;
(4)
对Cq节点进行递归搜索;
(5)
如果优先队列不为空且 count<L,那么从取优先队列的第一个元素赋值给N,然后重复步骤(1)。
聚类的个数K,也称为branching factor 是个非常主要的参数。
建树的时间复杂度 = O( ndKI ( log(n)/log(K) ))
n为数据点的总个数,I为K-means的迭代次数。搜索的时间复杂度 = O( L/K * Kd * ( log(n)/(log(K) ) ) = O(Ld ( log(n)/(log(K) ) )。
3 、层次聚类树 (The Hierarchical ClusteringTree)
层次聚类树采用k-medoids的聚类方法,而不是k-means。即它的聚类中心总是输入数据的某个点,但是在本算法中,并没有像k-medoids聚类算法那样去最小化方差求聚类中心,而是直接从输入数据中随机选取聚类中心点,这样的方法在建立树时更加简单有效,同时又保持多棵树之间的独立性。
同时建立多棵树,在搜索阶段并行地搜索它们能大大提高搜索性能(归功于随机地选择聚类中心,而不需要多次迭代去获得更好的聚类中心)。建立多棵随机树的方法对k-d tree也十分有效,但对于k-means tree却不适用。
比如我们使用SIFT,我们可以传入参数:
index_params=dict(algorithm = FLANN_INDEX_KDTREE,trees=5)
第二个字典是SearchParams。它用来指定递归遍历的次数。值越高结果越准确,但是消耗的时间也越多。如果想修改这个值,可以传入参数:
search_params=dict( checks = 10)
二、计算机视觉 OpenCV Android | 特征检测与匹配 之 Feature2D中的检测器与描述子
前面提到的SURF与SIFT特征检测器与描述子,
其实都是OpenCV扩展模块xfeature2d中的内容,
而在OpenCV本身包含的feature2d模块中也包含了几个非常有用的特征检测器与描述子,
其所支持的特征点检测器(FeatureDetector)如下:
FAST=1
STAR=2
ORB=5
MSER=6
GFTT=7
HARRIS=8
SIMPLEBLOB=9
DENSE=10
BRISK=11
AKAZE=12
其中,3、4本来是SIFT与SURF的,但在OpenCV3.x中,它们已经被移到扩展模块中了。
如果使用OpenCV官方编译好的OpenCV4Android 3.x版本的SDK,
则当声明与使用这两个类型的时候,它会告诉你不支持。
描述子类型
feature2d支持的特征点检测器还支持以下的描述子类型:
DescriptorExtractor.ORB=3
DescriptorExtractor.BRIEF=4
DescriptorExtractor.BRISK=5
DescriptorExtractor.FREAK=6
DescriptorExtractor.AKAZE=7
这里其实还有1与2分别是SIFT与SURF,
但其已经被移到扩展模块了,所以如果声明使用会抛出不支持的错误提示。
简单介绍几种特征提取方法
在feature2d模块中同时具有特征点检测与描述子功能的方法有ORB、BRISK、AKAZE。
下面我们简单介绍一下这三种特征提取方法。
1.ORB检测器与描述子
ORB(Oriented FAST and Rotated BRIEF)是OpenCV实验室于2011年开发出来的一种新的特征提取算法,
相比较于SIFT与SURF,
ORB的一大好处是没有专利限制,
可以免费自由使用,
同时具有旋转不变性与尺度不变性。
OpenCV4Android中创建ORB检测器与描述子的代码:
FeatureDetector detector = FeatureDetector.create(FeatureDetector.ORB);
DescriptorExtractor descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.ORB);
2.BRISK检测器与描述子
BRISK(Binary Robust Invariant Scalable Keypoint)特征检测与描述子是在2011年由几位作者联合提出的一种新的特征提取算法,
OpenCV4Android中创建ORB检测器与描述子的代码如下:
FeatureDetector detector = FeatureDetector.create(FeatureDetector.BRISK);
DescriptorExtractor descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.BRISK);
3.AKAZE检测器与描述子
AKAZE算法是SIFT算法之后,
具有尺度不变性与旋转不变性算法领域的再一次突破,
它是KAZE特征提取算法的加速版本;
其算法原理有别于前面提到的几种方法,
其是通过正则化PM方程与AOS(加性算子分裂)方法来求解非线性扩散,
从而得到 尺度空间 的 每一层;
采样的方法与SIFT类似,
对每一层实现候选点的定位与过滤以实现关键点的提取;
然后再使用与SURF求解方向角度类似的方法实现旋转不变性特征,
最终生成AKAZE描述子。
AKAZE算法的原理本身比较复杂,笔者所读的书中亦无详细解说,
感兴趣的小伙伴阅读相关论文去深入了解。
在OpenCV4Android中创建AKAZE特征检测器与描述子的代码如下:
FeatureDetector detector = FeatureDetector.create(FeatureDetector.AKAZE);
DescriptorExtractor descriptorExtractor = DescriptorExtractor.create(DescriptorExtractor.AKAZE);
4. OpenCV4Android中feature2d检测器与描述子的使用
基于feature2d中的检测器对象实现对象关键点检测的演示代码:
FeatureDetector detector = null;
if(type == 1) {
  detector = FeatureDetector.create(FeatureDetector.ORB);
} else if(type == 2) {
  detector = FeatureDetector.create(FeatureDetector.BRISK);
} else if(type == 3) {
  detector = FeatureDetector.create(FeatureDetector.FAST);
} else if(type == 4){
  detector = FeatureDetector.create(FeatureDetector.AKAZE);
} else {detector = FeatureDetector.create(FeatureDetector.HARRIS);
}
MatOfKeyPoint keyPoints = new MatOfKeyPoint();
detector.detect(src, keyPoints);
Features2d.drawKeypoints(src, keyPoints, dst);
以AKAZE为例,在feature2d中实现图像特征检测、描述子计算、特征匹配的演示代码如下:
private void descriptorDemo(Mat src, Mat dst) {
  String boxFile = fileUri.getPath().replaceAll("box_in_scene", "box");
  Mat boxImage = Imgcodecs.imread(boxFile);
  FeatureDetector detector = FeatureDetector.create(FeatureDetector.AKAZE);
  DescriptorExtractor descriptorExtractor = DescriptorExtractor.create
(DescriptorExtractor.AKAZE);
  // 关键点检测
  MatOfKeyPoint keyPoints_box = new MatOfKeyPoint();
  MatOfKeyPoint keyPoints_scene = new MatOfKeyPoint();
  detector.detect(boxImage, keyPoints_box);
  detector.detect(src, keyPoints_scene);
  // 描述子生成
  Mat descriptor_box = new Mat();
  Mat descriptor_scene = new Mat();
  descriptorExtractor.compute(boxImage, keyPoints_box, descriptor_box);
  descriptorExtractor.compute(src, keyPoints_scene, descriptor_scene);
  // 特征匹配
  MatOfDMatch matches = new MatOfDMatch();
  DescriptorMatcher descriptorMatcher = DescriptorMatcher.create
(DescriptorMatcher.BRUTEFORCE_HAMMING);
  descriptorMatcher.match(descriptor_box, descriptor_scene, matches);
  Features2d.drawMatches(boxImage, keyPoints_box, src, keyPoints_scene, matches, dst);
  // 释放内存
  keyPoints_box.release();
  keyPoints_scene.release();
  descriptor_box.release();
  descriptor_scene.release();
  matches.release();
}
SIFT 代表Scale Invariant Feature Transform,它是一种特征提取方法(其中包括HOG 特征提取),其中将图像内容转换为对平移、尺度和其他图像变换不变的局部特征坐标。
三、 OpenCV SIFT特征算法详解与使用
SIFT概述
SIFT特征是非常稳定的图像特征,在图像搜索、特征匹配、图像分类检测等方面应用十分广泛,但是它的缺点也是非常明显,就是计算量比较大,很难实时,
所以对一些实时要求比较高的常见SIFT算法还是无法适用。如今SIFT算法在深度学习特征提取与分类检测网络大行其道的背景下,已经越来越有鸡肋的感觉,
但是它本身的算法知识还是很值得我们学习,对我们也有很多有益的启示,本质上SIFT算法是很多常见算法的组合与巧妙衔接,这个思路对我们自己处理问
题可以带来很多有益的帮助。特别是SIFT特征涉及到尺度空间不变性与旋转不变性特征,是我们传统图像特征工程的两大利器,可以扩展与应用到很多图像
特征提取的算法当中,比如SURF、HOG、HAAR、LBP等。夸张一点的说SIFT算法涵盖了图像特征提取必备的精髓思想,从特征点的检测到描述子生成,完成了
对图像的准确描述,早期的ImageNet比赛中,很多图像分类算法都是以SIFT与HOG特征为基础,所有SIFT算法还是值得认真详细解读一番的。SIFT特征提取归
纳起来SIFT特征提取主要有如下几步:
构建高斯多尺度金字塔
关键点精准定位与过滤
关键点方向指派
描述子生成
OpenCV中调用
OpenCV已经实现了SIFT算法,但是在OpenCV3.0之后因为专利授权问题,该算法在扩展模块xfeature2d中,需要自己编译才可以使用,OpenCV Python中从3.4.2之后扩展模块也无法使用,需要自己单独编译python SDK才可以使用。首先需要创建一个SIFT检测器对象,通过调用
通过detect方法提取对象关键点
用drawKeypoints绘制关键点
通过compute提取描述子,
通过暴力匹配根据描述子匹配
代码演示如下
import cv2 as cv
box = cv.imread("D:/images/box.png");
box_in_sence = cv.imread("D:/images/box_in_scene.png");
cv.imshow("box", box)
cv.imshow("box_in_sence", box_in_sence)
# 创建SIFT特征检测器
sift = cv.xfeatures2d.SIFT_create()
# 特征点提取与描述子生成
kp1, des1 = sift.detectAndCompute(box,None)
kp2, des2 = sift.detectAndCompute(box_in_sence,None)
# 暴力匹配
bf = cv.DescriptorMatcher_create(cv.DescriptorMatcher_BRUTEFORCE)
matches = bf.match(des1,des2)
# 绘制最佳匹配
matches = sorted(matches, key = lambda x:x.distance)
result = cv.drawMatches(box, kp1, box_in_sence, kp2, matches[:15], None)
cv.imshow("-match", result)
cv.waitKey(0)
cv.destroyAllWindows()
四、 OpenCV2:特征匹配及其优化
https://www.cnblogs.com/wangguchangqing/p/4333873.html#:~:text=Descript,atcher%EF%BC%89%E3%80%82
DescriptorMatcher
DMatcher
4.1 KNN匹配
计算两视图的基础矩阵F,并细化匹配结果
计算两视图的单应矩阵H,并细化匹配结果
DescriptorMatcher 和 DMatcher
DescriptorMatcher是匹配特征向量的抽象类,在OpenCV2中的特征匹配方法都继承自该类(例如:BFmatcher,FlannBasedMatcher)。
.该类主要包含了两组匹配方法:图像对之间的匹配以及图像和一个图像集之间的匹配。
用于图像对之间匹配的方法的声明
// 为每个查询描述子找到一个最佳匹配(如果掩码为空)。Find one best match for each query descriptor (if mask is empty).
CV_WRAP void match( const Mat& queryDescriptors, const Mat& trainDescriptors,
CV_OUT vector<DMatch>& matches, const Mat& mask=Mat() ) const;
// Find k best matches for each query descriptor (in increasing order of distances).
// compactResult is used when mask is not empty. If compactResult is false matches
// vector will have the same size as queryDescriptors rows. If compactResult is true
// matches vector will not contain matches for fully masked out query descriptors.
//为每个查询描述子找到 k 个最佳匹配项(按距离递增的顺序)。 当掩码不为空时使用 compactResult。
//如果 compactResult 为 false 匹配向量将具有与 queryDescriptors 行相同的大小。
//如果 compactResult 为真,则匹配向量将不包含完全屏蔽的查询描述符的匹配项。
CV_WRAP void knnMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
CV_OUT vector<vector<DMatch> >& matches, int k,
const Mat& mask=Mat(), bool compactResult=false ) const;
// Find best matches for each query descriptor which have distance less than
// maxDistance (in increasing order of distances).
//查找距离小于 maxDistance 的每个查询描述子的最佳匹配(按距离递增的顺序)。
void radiusMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
vector<vector<DMatch> >& matches, float maxDistance,
const Mat& mask=Mat(), bool compactResult=false ) const;
方法重载,用于图像和图像集匹配的方法声明
CV_WRAP void match( const Mat& queryDescriptors, CV_OUT vector<DMatch>& matches,
const vector<Mat>& masks=vector<Mat>() );
CV_WRAP void knnMatch( const Mat& queryDescriptors, CV_OUT vector<vector<DMatch> >& matches, int k,
const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
void radiusMatch( const Mat& queryDescriptors, vector<vector<DMatch> >& matches, float maxDistance,
const vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
DMatcher 是用来保存匹配结果的,主要有以下几个属性
CV_PROP_RW int queryIdx; // query descriptor index
CV_PROP_RW int trainIdx; // train descriptor index
CV_PROP_RW int imgIdx;
// train image index
CV_PROP_RW float distance;
在图像匹配时有两种图像的集合,查找集(Query Set)和训练集(Train Set),
对于每个Query descriptor,DMatch中保存了和其最好匹配的Train descriptor。
另外,每个train image会生成多个train descriptor。
如果是图像对之间的匹配的话,由于所有的train descriptor都是由一个train image生成的,
所以在匹配结果DMatch中所有的imgIdx是一样的,都为0.
4.2 KNNMatch
匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点事错误的,图像上的特征点无法匹配。常用的删除错误的匹配有
交叉过滤
如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
OpenCV中的BFMatcher已经包含了这种过滤
BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。
比率测试
KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。
在抽象基类DescriptorMatcher中封装了knnMatch方法,具体使用方法如下:
void FeatureMatchTest::knnMatch(vector<DMatch>& matches) {
const float minRatio = 1.f / 1.5f;
const int k = 2;
vector<vector<DMatch>> knnMatches;
matcher->knnMatch(leftPattern->descriptors, rightPattern->descriptors, knnMatches, k);
for (size_t i = 0; i < knnMatches.size(); i++) {
const DMatch& bestMatch = knnMatches[i][0];
const DMatch& betterMatch = knnMatches[i][1];
float
distanceRatio = bestMatch.distance / betterMatch.distance;
if (distanceRatio < minRatio)
matches.push_back(bestMatch);
}
}
RASIC方法计算基础矩阵,并细化匹配结果
如果已经知道了两视图(图像)间的多个点的匹配,就可以进行基础矩阵F的计算了。OpenCV2中可以使用findFundamentalMat方法,其声明如下:
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS_W Mat findFundamentalMat( InputArray points1, InputArray points2,
int method=FM_RANSAC,
double param1=3., double param2=0.99,
OutputArray mask=noArray());
参数说明:
points1,points2 两幅图像间相匹配的点,点的坐标要是浮点数(float或者double)
第三个参数method是用来计算基础矩阵的具体方法,是一个枚举值。
param1,param2保持默认值即可。
主要来说下mask参数,有N个匹配点用来计算基础矩阵,则该值有N个元素,每个元素的值为0或者1.值为0时,代表该匹配点事错误的匹配(离群值),只在使用RANSAC和LMeds方法时该值有效,
可以使用该值来删除错误的匹配。
另外,在匹配完成后使用得到的匹配点来计算基础矩阵时,首先需要将特征点对齐,
并且将特征点转换为2D点,具体实现如下:
//Align all points
vector<KeyPoint> alignedKps1, alignedKps2;
for (size_t i = 0; i < matches.size(); i++) {
alignedKps1.push_back(leftPattern->keypoints[matches[i].queryIdx]);
alignedKps2.push_back(rightPattern->keypoints[matches[i].trainIdx]);
}
//Keypoints to points
vector<Point2f> ps1, ps2;
for (unsigned i = 0; i < alignedKps1.size(); i++)
ps1.push_back(alignedKps1[i].pt);
for (unsigned i = 0; i < alignedKps2.size(); i++)
ps2.push_back(alignedKps2[i].pt);
五、 OpenCV 4.x API 详解与C++实例-特征检测与描述
https://blog.csdn.net/wujuxKkoolerter/article/details/114162810
OpenCV提供了丰富的特征检测算法,比如SIFT(Scale Invariant Feature Transform)、
AffineFeature、AgastFeatureDetector、AKAZE、BRISK、FastFeatureDetector、GFTTDetector、
KAZE、MSER、ORB、SimpleBlobDetector等
4.1 SIFT SIFT(Scale Invariant Feature Transform)
尺度不变特征变换算法提取图像特征
SIFT类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::SIFT::create(int nfeatures = 0,int nOctaveLayers = 3,double contrastThreshold = 0.04,double edgeThreshold = 10,double sigma = 1.6)
参数名称	参数描述
nfeatures	保留的最佳特征的数量;特征按其得分排名
nOctaveLayers	每个八度中的层数。 3是D.Lowe谁中使用的值。 八度的数量是根据图像分辨率自动计算的。
contrastThreshold	对比度阈值,用于过滤半均匀(低对比度)区域中的弱点。 阈值越大,检测器产生的特征越少。
edgeThreshold	用于过滤边缘特征的阈值。 请注意,其含义与contrastThreshold不同,即edgeThreshold越大,滤除的特征越少(保留的特征越多)。
sigma	高斯的sigma应用于八度为#0的输入图像。 如果使用带软镜头的弱相机拍摄图像,则可能需要减少数量。
注意:应用过滤时,对比度阈值将被nOctaveLayers除。 当nOctaveLayers设置为默认值并且如果要使用D.Lowe论文中使用的值0.03时,请将此参数设置为0.09。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 创建SIFT特征提取器
cv::Ptr<cv::SIFT> feature = cv::SIFT::create(1024,3,0.04,120,1.5);
// 检测特征点
vector<cv::KeyPoint> keypoints;
cv::Mat descriptor;
feature->detectAndCompute(gray,cv::Mat(),keypoints,descriptor);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.2
AffineFeature
用于实现使检测器和提取器具有仿射不变的包装器的类,在[Guoshen Yu and Jean-Michel Morel. Asift: An algorithm for fully affine invariant comparison. Image Processing On Line, 1:11–38, 2011.]中描述为ASIFT。
AffineFeature类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AffineFeature::create(const Ptr< Feature2D > & backend,int maxTilt = 5,int minTilt = 0,float tiltStep = 1.4142135623730951f,float rotateStepBase = 72)
参数	参数描述
backend	用作后端的检测器/提取器。
maxTilt	倾斜系数的最高功率指标。 在纸张中使用5作为倾斜采样范围n。
minTilt	最低的倾斜系数功率指数。 本文使用0。
tiltStep	倾斜采样步骤δt。
rotateStepBase	旋转采样阶跃系数b
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 使用SIFT特征检测算法创建
cv::Ptr<cv::AffineFeature> feature = cv::AffineFeature::create(cv::SIFT::create(128));
// 检测特征点
vector<cv::KeyPoint> keypoints;
cv::Mat descriptor;
feature->detectAndCompute(gray,cv::Mat(),keypoints,descriptor);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.3
AgastFeatureDetector
使用AGAST方法进行特征检测的包装类。
AgastFeatureDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AgastFeatureDetector::create(int threshold = 10,bool nonmaxSuppression = true,AgastFeatureDetector::DetectorType type = AgastFeatureDetector::OAST_9_16)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 创建AgastFeatureDetector特征提取器
cv::Ptr<cv::AgastFeatureDetector> feature = cv::AgastFeatureDetector::create();
// 检测特征点
vector<cv::KeyPoint> keypoints;
cv::Mat descriptor;
feature->detect(gray,keypoints);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.4 AKAZE
实现AKAZE关键点检测器和描述符提取器的类,在[Pablo F Alcantarilla, Jesús Nuevo, and Adrien Bartoli. Fast explicit diffusion for accelerated features in nonlinear scale spaces. Trans. Pattern Anal. Machine Intell, 34(7):1281–1298, 2011.]中进行了描述。
AKAZE类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AKAZE::create (AKAZE::DescriptorType descriptor_type = AKAZE::DESCRIPTOR_MLDB,int descriptor_size = 0,int descriptor_channels = 3,float threshold = 0.001f,int nOctaves = 4,int nOctaveLayers = 4,KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2)
参数名称	参数描述
descriptor_type	提取的描述符的类型:DESCRIPTOR_KAZE,DESCRIPTOR_KAZE_UPRIGHT,DESCRIPTOR_MLDB或DESCRIPTOR_MLDB_UPRIGHT。
descriptor_size	描述符的大小(以位为单位)。 0->全尺寸
descriptor_channels	描述符中的通道数(1、2、3)
threshold	检测器响应阈值以接受点
nOctaves	图像的最大八度演变
nOctaveLayers	每个比例级别的默认子级别数
diffusivity	扩散类型。 DIFF_PM_G1,DIFF_PM_G2,DIFF_WEICKERT或DIFF_CHARBONNIER
4.5 BRISK
实现BRISK关键点检测器和描述符提取器的类,在[Stefan Leutenegger, Margarita Chli, and Roland Yves Siegwart. Brisk: Binary robust invariant scalable keypoints. In Computer Vision (ICCV), 2011 IEEE International Conference on, pages 2548–2555. IEEE, 2011.]中进行了描述。
BRISK类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::BRISK::create(int thresh = 30,int octaves=3,float patternScale=1.0f)
参数名称	参数描述
thresh	AGAST检测阈值得分。
octaves	检测八度。 使用0进行单刻度。
patternScale	将此比例应用于用于采样关键点邻域的模式。
自定义模式的BRISK构造函数。
static Ptr cv::BRISK::create (const std::vector< float > & radiusList,const std::vector< int > & numberList,float dMax = 5.85f,float dMin = 8.2f,const std::vector< int > & indexChange=std::vector< int >())
参数名称	参数描述
numberList	定义采样圆上的采样点数。 必须与radiusList大小相同。
radiusList	定义在关键点周围采样的半径(以像素为单位)(对于关键点比例1)。
dMax	用于描述符形成的短配对的阈值(对于关键点比例1,以像素为单位)。
dMin	用于方向确定的长配对的阈值(对于关键点比例1,以像素为单位)。
indexChange	位的索引重新映射。
static Ptr cv::BRISK::create (int thresh,int octaves,const std::vector< float > & radiusList,const std::vector< int > & numberList,float dMax = 5.85f,float dMin = 8.2f,const std::vector< int > & indexChange = std::vector< int >())
参数名称	参数描述
thresh	AGAST检测阈值得分。
octaves	检测八度。 使用0进行单刻度。
radiusList	定义在关键点周围采样的半径(以像素为单位)(对于关键点比例1)。
numberList	定义采样圆上的采样点数。 必须与radiusList大小相同。
dMax	用于描述符形成的短配对的阈值(对于关键点比例1,以像素为单位)。
dMin	用于方向确定的长配对的阈值(对于关键点比例1,以像素为单位)。
indexChange	位的索引重新映射。
4.6 FastFeatureDetector
使用FAST方法进行特征检测的包装类。FastFeatureDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::FastFeatureDetector::create(int threshold = 10,bool nonmaxSuppression = true,FastFeatureDetector::DetectorType type = FastFeatureDetector::TYPE_9_16)
4.7
GFTTDetector
使用goodFeaturesToTrack函数包装特征的包装类。GFTTDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::GFTTDetector::create(int maxCorners=1000,double qualityLevel = 0.01,double minDistance = 1,int blockSize = 3,bool useHarrisDetector = false,double k = 0.04)
4.8
KAZE
实现KAZE关键点检测器和描述符提取器的类,在[Pablo Fernández Alcantarilla, Adrien Bartoli, and Andrew J Davison. Kaze features. In Computer Vision–ECCV 2012, pages 214–227. Springer, 2012.]中进行了描述。
AKAZE描述符只能与KAZE或AKAZE关键点一起使用。AKAZE类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::KAZE::create(bool extended = false,bool upright = false,float threshold = 0.001f,int nOctaves = 4,int nOctaveLayers = 4,KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2)
参数	参数名称
extended	设置为启用扩展(128字节)描述符的提取。
upright	设置为启用直立描述符(非旋转不变)。
threshold	检测器响应阈值以接受点
nOctaves	图像的最大八度演变
nOctaveLayers	每个比例级别的默认子级别数
diffusivity	扩散类型。 DIFF_PM_G1,DIFF_PM_G2,DIFF_WEICKERT或DIFF_CHARBONNIER
4.9 MSER
最大限度地稳定的极值区域提取。该类封装了MSER提取算法的所有参数。MSER类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::MSER::create(int _delta = 5,int _min_area = 60,int _max_area = 14400,double _max_variation = 0.25,double _min_diversity = .2,int _max_evolution = 200,double _area_threshold = 1.01,double _min_margin = 0.003,int _edge_blur_size = 5)
参数名称	参数描述
_delta	它比较(sizei-sizei-delta)/ sizei-delta
_min_area	修剪小于minArea的区域
_max_area	修剪大于maxArea的区域
_max_variation	修剪该区域的大小与其子面积相似
_min_diversity	对于彩色图像,回溯以切断多样性小于min_diversity的mser
_max_evolution	对于彩色图像,演变步骤
_area_threshold	对于彩色图像,导致重新初始化的面积阈值
_min_margin	对于彩色图像,请忽略过小的边距
_edge_blur_size	对于彩色图像,边缘模糊的光圈大小
4.10 ORB
实现ORB(面向Brief)关键点检测器和描述符提取器的类。
在[Ethan Rublee, Vincent Rabaud, Kurt Konolige, and Gary Bradski. Orb: an efficient alternative to sift or surf. In Computer Vision (ICCV), 2011 IEEE International Conference on, pages 2564–2571. IEEE, 2011.]中描述。 该算法在金字塔中使用FAST来检测稳定的关键点,使用FAST或Harris响应选择最强的特征,使用一阶矩找到它们的方向,并使用Brief来计算描述符(其中随机点对(或k元组)的坐标为 根据测量的方向旋转)。
ORB类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::ORB::create(int nfeatures=500,float scaleFactor = 1.2f,int nlevels = 8,int edgeThreshold = 31,int firstLevel = 0,int WTA_K = 2,ORB::ScoreType scoreType = ORB::HARRIS_SCORE,int patchSize = 31,int fastThreshold = 20)
参数	参数描述
nfeatures	保留的最大特征数量。
scaleFactor	金字塔抽取率大于1。scaleFactor == 2表示经典金字塔,其中每个下一个级别的像素比上一个少4倍,但是如此大的比例因子将大大降低特征匹配分数。 另一方面,太接近1的比例因子意味着要覆盖一定的比例范围,您将需要更多的金字塔等级,因此速度会受到影响。
nlevels	金字塔等级的数量。 最小级别的线性大小等于input_image_linear_size / pow(scaleFactor,nlevels-firstLevel)。
edgeThreshold	未检测到特征的边框的大小。 它应该与patchSize参数大致匹配。
firstLevel	要放置源图像的金字塔等级。 先前的层填充有放大的源图像。
WTA_K	产生定向的Brief描述符的每个元素的点数。 默认值2表示“ BRIEF”,我们采用一个随机点对并比较它们的亮度,因此得到0/1响应。 其他可能的值是3和4。例如,3表示我们取3个随机点(当然,这些点坐标是随机的,但它们是从预定义的种子生成的,因此,BRIEF描述符的每个元素都是从确定的 像素矩形),找到最大亮度的点和获胜者的输出指数(0、1或2)。 这样的输出将占用2位,因此将需要Hamming距离的特殊变体,表示为NORM_HAMMING2(每个bin 2位)。 当WTA_K = 4时,我们取4个随机点来计算每个bin(也将占用2位,可能的值为0、1、2或3)。
scoreType	默认的HARRIS_SCORE表示将Harris算法用于对要素进行排名(分数被写入KeyPoint :: score并用于保留最佳nfeatures要素); FAST_SCORE是该参数的替代值,它产生的稳定关键点会稍少一些,但计算速度稍快一些。
patchSize	定向的Brief描述符使用的补丁大小。 当然,在较小的金字塔层上,特征覆盖的感知图像区域将更大。
fastThreshold	快速阈值
4.11 SimpleBlobDetector
用于从图像中提取斑点的类。 SimpleBlobDetector类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::SimpleBlobDetector::create(const SimpleBlobDetector::Params & parameters = SimpleBlobDetector::Params())
六、 opencv学习——cv2.findHomography()
https://blog.csdn.net/qq_36387683/article/details/98446442
我们之前使用了查询图像,找到其中的一些特征点,我们取另外一个训练图像,找到里面的特征,我们找到它们中间最匹配的。.简单说就是我们在一组图像里找一个目标的某个部分的位置。
我们可以使用一个calib3d模块里的函数,cv2.findHomography().如果我们传了两个图像里的点集合,它会找到那个目标的透视转换。然后我们可以使用cv2.perspectiveTransform()来找目标,它需要至少4个正确的点来找变换。
我们看过可能会有一些匹配是的错误而影响结果。哟啊解决这个问题,算法使用了RANSAC或者LEAST_MEDIAN(由标志决定)。提供正确估计的好的匹配被叫做inliers,而其他的叫做outliers。cv2.findHomography()返回一个掩图来指定inlier和outlier。
https://cloud.tencent.com/developer/article/1435205
https://www.cnblogs.com/kbqLibrary/p/12389254.html
findHomography:
计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) (就是对图片的矫正),使用最小均方误差或者RANSAC方法
函数功能:找到两个平面之间的转换矩阵。
射影变换也叫做单应(Homography)
图1通过H矩阵变换变成图2,就是这个函数的公式
X′=HX
X′代表图2
其操作过程
在“大”图像(目标图像)上选择4个点和“小”图像(被合并图像)的四角做对应,然后根据这4对对应的点计算两幅图像的单应矩阵。
得到单应矩阵H后,利用函数warpPerspective将H应用到“小”图像上,得到图像M
将图像M合并到目标图像中选择的四个点的位置
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
)
参数详解:
srcPoints
源平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型
dstPoints
目标平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector<Point2f>类型
method
计算单应矩阵所使用的方法。不同的方法对应不同的参数,具体如下:
0 - 利用所有点的常规方法
RANSAC - RANSAC-基于RANSAC的鲁棒算法
LMEDS - 最小中值鲁棒算法
RHO - PROSAC-基于PROSAC的鲁棒算法
ransacReprojThreshold
将点对视为内点的最大允许重投影错误阈值(仅用于RANSAC和RHO方法)。如果
则点被认为是个外点(即错误匹配点对)。若srcPoints和dstPoints是以像素为单位的,则该参数通常设置在1到10的范围内。
mask
可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的。
maxIters RANSAC 算法的最大迭代次数,默认值为2000。
confidence 可信度值,取值范围为0到1.
//图片映射矩阵把不同角度的图片矫正
void findHomographyText(){
// Read source image.
Mat src = imread("F:\视觉\opencv\pic\1.png");
// Four corners of the book in source image
vector<Point2f> pts_src;
pts_src.push_back(Point2f(0, 0));
pts_src.push_back(Point2f(src.cols, 0));
pts_src.push_back(Point2f(src.cols, src.rows));
pts_src.push_back(Point2f(0, src.rows));
// Four corners of the book in destination image.
vector<Point2f> pts_dst;
pts_dst.push_back(Point2f(0, 0));
pts_dst.push_back(Point2f(src.cols/4, 0));
pts_dst.push_back(Point2f(src.cols/3, src.rows));
pts_dst.push_back(Point2f(0, src.rows/2));
// Calculate Homography
Mat h = findHomography(pts_src, pts_dst);
// Output image
Mat im_out;
// Warp source image to destination based on homography
warpPerspective(src, im_out, h, src.size());
// Display images
imshow("Source Image", src);
imshow("Warped Source Image", im_out);
waitKey(0);
}
6.2,SURF对图像的识别和标记
1,开发思路
(1)使用SIFT或者SURF进行角点检测,获取两个图像的的角点集合
(2)根据两个集合,使用特征点匹配,匹配类似的点 FlannBasedMatcher
(3)过滤特征点对。
(4)通过特征点对,求出H值
(5)画出特征区域
代码实现:
1,使用SIFT或者SURF进行角点检测,获取两个图像的的角点集合
src = imread("F:\视觉\opencv\pic\11.png");//读图片
src3 = imread("F:\视觉\opencv\pic\5.png");//读图片
int minHessian = 400;
cvtColor(src, src, COLOR_BGR2GRAY);
cvtColor(src3, src3, COLOR_BGR2GRAY);
Ptr<SIFT> detector = SIFT::create(minHessian);
vector<KeyPoint> keypoints_obj;//图片1特征点
vector<KeyPoint> keypoints_scene;//图片2特征点
Mat descriptor_obj, descriptor_scene;
//找出特征点存到keypoints_obj与keypoints_scene点集中
detector->detectAndCompute(src, Mat(), keypoints_obj, descriptor_obj);
detector->detectAndCompute(src3, Mat(), keypoints_scene, descriptor_scene);
// matching 找到特征集合
FlannBasedMatcher matcher;
vector<DMatch> matches;
matcher.match(descriptor_obj, descriptor_scene, matches);
2,过滤相似度高的图像
// find good matched points
double minDist = 1000;
double maxDist = 0;
for (int i = 0; i < descriptor_obj.rows; i++) {
double dist = matches[i].distance;
if (dist > maxDist) {
maxDist = dist;
}
if (dist < minDist) {
minDist = dist;
}
}
printf("max distance : %fn", maxDist);
printf("min distance : %fn", minDist);
vector<DMatch> goodMatches;
//过滤相同的点
for (int i = 0; i < descriptor_obj.rows; i++) {
double dist = matches[i].distance;//相识度
printf("distance : %fn", dist);
if (dist < max(3 * minDist, 0.2)) {
goodMatches.push_back(matches[i]);
}
}
3,求出H
vector<Point2f> obj;
vector<Point2f> objInScene;
for (size_t t = 0; t < goodMatches.size(); t++) {
//把DMatch转成坐标 Point2f
obj.push_back(keypoints_obj[goodMatches[t].queryIdx].pt);
objInScene.push_back(keypoints_scene[goodMatches[t].trainIdx].pt);
}
//用来求取“射影变换”的H转制矩阵函数
X'=H X ,并使用RANSAC消除一些出错的点
Mat H = findHomography(obj, objInScene, RANSAC);
4,使用H求出映射到大图的点
vector<Point2f> obj_corners(4);
vector<Point2f> scene_corners(4);
obj_corners[0] = Point(0, 0);
obj_corners[1] = Point(src.cols, 0);
obj_corners[2] = Point(src.cols, src.rows);
obj_corners[3] = Point(0, src.rows);
//透视变换(把斜的图片扶正)
cout << H << endl;
perspectiveTransform(obj_corners, scene_corners, H);
5,在原图上画线段
Mat dst;
cvtColor(src3, dst, COLOR_GRAY2BGR);
line(dst, scene_corners[0], scene_corners[1], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[1], scene_corners[2], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[2], scene_corners[3], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[3], scene_corners[0], Scalar(0, 0, 255), 2, 8, 0);
imshow("Draw object", dst);
Homography应用:虚拟广告牌
在足球或者棒球体育直播中,经常可以看到球场旁边有虚拟广告,并且还会根据地区,国家的不同播放不同的广告,这是如何做到的?
看完此篇博客,你应该就能知道如何实现了。原理跟前一个差不多,这里直接上代码
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
struct userdata{
Mat im;
vector<Point2f> points;
};
void mouseHandler(int event, int x, int y, int flags, void* data_ptr)
{
if
( event == EVENT_LBUTTONDOWN )
{
userdata *data = ((userdata *) data_ptr);
circle(data->im, Point(x,y),3,Scalar(0,255,255), 5, CV_AA);
imshow("Image", data->im);
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x,y));
}
}
}
int main( int argc, char** argv)
{
// Read in the image.
Mat im_src = imread("first-image.jpg");
Size size = im_src.size();
// Create a vector of points.
vector<Point2f> pts_src;
pts_src.push_back(Point2f(0,0));
pts_src.push_back(Point2f(size.width - 1, 0));
pts_src.push_back(Point2f(size.width - 1, size.height -1));
pts_src.push_back(Point2f(0, size.height - 1 ));
// Destination image
Mat im_dst = imread("times-square.jpg");
// Set data for mouse handler
Mat im_temp = im_dst.clone();
userdata data;
data.im = im_temp;
//show the image
imshow("Image", im_temp);
cout << "Click on four corners of a billboard and then press ENTER" << endl;
//set the callback function for any mouse event
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
// Calculate Homography between source and destination points
Mat h = findHomography(pts_src, data.points);
// Warp source image
warpPerspective(im_src, im_temp, h, im_temp.size());
// Extract four points from mouse data
Point pts_dst[4];
for( int i = 0; i < 4; i++)
{
pts_dst[i] = data.points[i];
}
// Black out polygonal area in destination image.
fillConvexPoly(im_dst, pts_dst, 4, Scalar(0), CV_AA);
// Add warped source image to destination image.
im_dst = im_dst + im_temp;
// Display image.
imshow("Image", im_dst);
waitKey(0);
return 0;
}
Homography是一个3*3的变换矩阵,将一张图中的点映射到另一张图中对应的点
retval, mask = cv.findHomography( srcPoints, dstPoints[, method[, ransacReprojThreshold[, mask[, maxIters[, confidence]]]]] )
参数	描述
srcPoints	原始点
dstPoints	目标点
retval	变换矩阵
mask	Optional output mask set by a robust method ( RANSAC or LMEDS ). Note that the input mask values are ignored.
//七、 Opencv函数介绍: sort、sortIdx函数
cv::sort 负责返回排序后的矩阵,cv::sortIdx 负责返回对应原矩阵的索引。
还有在 MATLAB 里,1 和 2 用来分别指示是对列还是对行进行排序,'ascend' 和 'descend' 用来指示是升序还是降序。在 OpenCV 中,我们用类似于 CV_SORT_EVERY_ROW + CV_SORT_ASCENDING 这样的方式来一并指定对列还是对行以及升序还是降序,其指示值定义如下,所以可以组合出 4 种不同的方式:
#define CV_SORT_EVERY_ROW
0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING
0
#define CV_SORT_DESCENDING
16
//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:对矩阵的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:对矩阵的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:对矩阵的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:对矩阵的每列按照降序排序;
void demo_sort_sortIdx()
{
int testArrLen = 5;
cv::Mat_<int> testArr = cv::Mat::zeros(2, testArrLen, CV_32S);
testArr(0,0) = 87;
testArr(0,1) = 65;
testArr(0,2) = 98;
testArr(0,3) = 12;
testArr(0,4) = 55;
testArr(1,0) = 86;
testArr(1,1) = 66;
testArr(1,2) = 97;
testArr(1,3) = 17;
testArr(1,4) = 54;
cv::Mat_<int> sortArr, sortIdxArr;
cv::sort(testArr, sortArr, CV_SORT_EVERY_ROW
+
CV_SORT_ASCENDING);
cv::sortIdx(testArr, sortIdxArr, CV_SORT_EVERY_ROW
+
CV_SORT_ASCENDING);
std::cout<<"testArr = "<<testArr<<std::endl;
std::cout<<"sortArr = "<<sortArr<<std::endl;
std::cout<<"sortIdxArr = "<<sortIdxArr<<std::endl;
}
testArr = [87, 65, 98, 12, 55;
86, 66, 97, 17, 54]
sortArr = [12, 55, 65, 87, 98;
17, 54, 66, 86, 97]
sortIdxArr = [3, 4, 1, 0, 2;
3, 4, 1, 0, 2]
//八、【OpenCV】透视变换 Perspective Transformation(续)
求解变换公式的函数:
Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[])
输入原始图像和变换之后的图像的对应4个点,便可以得到变换矩阵。之后用求解得到的矩阵输入perspectiveTransform便可以对一组点进行变换:
void perspectiveTransform(InputArray src, OutputArray dst, InputArray m)
注意这里src和dst的输入并不是图像,而是图像对应的坐标。应用前一篇的例子,做个相反的变换:
int main( )
{
Mat img=imread("boy.png");
int img_height = img.rows;
int img_width = img.cols;
vector<Point2f> corners(4);
corners[0] = Point2f(0,0);
corners[1] = Point2f(img_width-1,0);
corners[2] = Point2f(0,img_height-1);
corners[3] = Point2f(img_width-1,img_height-1);
vector<Point2f> corners_trans(4);
corners_trans[0] = Point2f(150,250);
corners_trans[1] = Point2f(771,0);
corners_trans[2] = Point2f(0,img_height-1);
corners_trans[3] = Point2f(650,img_height-1);
Mat transform = getPerspectiveTransform(corners,corners_trans);
cout<<transform<<endl;
vector<Point2f> ponits, points_trans;
for(int i=0;i<img_height;i++){
for(int j=0;j<img_width;j++){
ponits.push_back(Point2f(j,i));
}
}
perspectiveTransform( ponits, points_trans, transform);
Mat img_trans = Mat::zeros(img_height,img_width,CV_8UC3);
int count = 0;
for(int i=0;i<img_height;i++){
uchar* p = img.ptr<uchar>(i);
for(int j=0;j<img_width;j++){
int y = points_trans[count].y;
int x = points_trans[count].x;
uchar* t = img_trans.ptr<uchar>(y);
t[x*3]
= p[j*3];
t[x*3+1]
= p[j*3+1];
t[x*3+2]
= p[j*3+2];
count++;
}
}
imwrite("boy_trans.png",img_trans);
return 0;
}
除了getPerspectiveTransform()函数,OpenCV还提供了findHomography()的函数,不是用点来找,而是直接用透视平面来找变换公式。这个函数在特征匹配的经典例子中有用到,也非常直观:
int main( int argc, char** argv )
{
Mat img_object = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_scene = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_object.data || !img_scene.data )
{ std::cout<< " --(!) Error reading images " << std::endl; return -1; }
//-- Step 1: 使用 SURF Detector 检测关键点 Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector<KeyPoint> keypoints_object, keypoints_scene;
detector.detect( img_object, keypoints_object );
detector.detect( img_scene, keypoints_scene );
//-- Step 2: 计算描述子(特征向量)Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_object, descriptors_scene;
extractor.compute( img_object, keypoints_object, descriptors_object );
extractor.compute( img_scene, keypoints_scene, descriptors_scene );
//-- Step 3:使用 FLANN 匹配器匹配描述子向量 Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_object, descriptors_scene, matches );
double max_dist = 0; double min_dist = 100;
//-- 快速计算关键点之间的最大和最小距离 Quick calculation of max and min distances between keypoints
for( int i = 0; i < descriptors_object.rows; i++ )
{ double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
if( dist > max_dist ) max_dist = dist;
}
printf("-- Max dist : %f n", max_dist );
printf("-- Min dist : %f n", min_dist );
//--仅绘制“好”匹配项(即距离小于 3*min_dist ) Draw only "good" matches (i.e. whose distance is less than 3*min_dist )
std::vector< DMatch > good_matches;
for( int i = 0; i < descriptors_object.rows; i++ )
{ if( matches[i].distance < 3*min_dist )
{ good_matches.push_back( matches[i]); }
}
Mat img_matches;
drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//--在 img_2 中本地化来自 img_1 的对象 Localize the object from img_1 in img_2
std::vector<Point2f> obj;
std::vector<Point2f> scene;
for( size_t i = 0; i < good_matches.size(); i++ )
{
//-- 从好的匹配中获取关键点Get the keypoints from the good matches
obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );
scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );
}
Mat H = findHomography( obj, scene, RANSAC );
//--从 image_1 获取角点(要“检测”的对象) Get the corners from the image_1 ( the object to be "detected" )
std::vector<Point2f> obj_corners(4);
obj_corners[0] = Point(0,0); obj_corners[1] = Point( img_object.cols, 0 );
obj_corners[2] = Point( img_object.cols, img_object.rows ); obj_corners[3] = Point( 0, img_object.rows );
std::vector<Point2f> scene_corners(4);
perspectiveTransform( obj_corners, scene_corners, H);
//--在角落之间画线(场景中的映射对象 - image_2 ) Draw lines between the corners (the mapped object in the scene - image_2 )
Point2f offset( (float)img_object.cols, 0);
line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );
line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );
//-- 显示检测到的匹配项 Show detected matches
imshow( "Good Matches & Object detection", img_matches );
waitKey(0);
return 0;
}
//九、 cv2.polylines()
https://blog.csdn.net/qq_44109682/article/details/118229813
cv2.polylines这一函数的出现,该函数可以画任意的多边形
polylines(img, pts, isClosed, color, thickness=None, lineType=None, shift=None)
参数:
img(array):为ndarray类型(可以为cv.imread)直接读取的数据
pts(array):为所画多边形的顶点坐标,举个简单的例子:当一张图片需要有多个四边形时,该数组ndarray的shape应该为(N,4,2)
isClosed(bool):所画四边形是否闭合,通常为True
color(tuple):RGB三个通道的值
thickness(int):画线的粗细
shift:顶点坐标中小数的位数
polylines函数其实可以不需要将返回值赋给新的变量,它是直接将输入变量上进行操作,所以上面两行代码,我特意进行了一次copy操作,以防将最初的图片覆盖掉。
//十、 opencv 添加 线条 矩形 椭圆 圆 多边形
import cv2 as cv
import numpy as np
img = np.zeros((480,640,3),np.uint8)
b,g,r = cv.split(img)
b[:] = 250
g[:] = 250
r[:] = 250
img2 = cv.merge((b,g,r))
# 绘制线条
cv.line(img2,(0,0),(300,300),(0,255,0),5,4)
cv.line(img2,(40,0),(340,300),(0,0,255),5,16)
# 绘制矩形
cv.rectangle(img2,(10,40),(70,0),(255,255,0),-1)
# 绘制园
cv.circle(img2,(320,240),100,(255,255,0))
# 绘制椭圆
cv.ellipse(img2,(320,240),(100,50),30,0,180,(0,255,0))
# 绘制多边形
pts = np.array([(300,10),(150,100),(450,100)],np.int32)
print(pts)
cv.polylines(img2,[pts],True,(0,0,255))
# 填充多边形
cv.fillPoly(img2,[pts],(0,0,255))
# 画文本
cv.putText(img2,'hello world',(0,100),cv.FONT_HERSHEY_DUPLEX,3,(255,0,0))
cv.imshow('draw',img2)
cv.waitKey(0)
//十一、 opencv for python(3) 用opencv作图,直线,圆,填充字
import numpy as
np
import cv2
img = np.zeros((512,512,3),np.uint8)
#画一条线,参数为图片,起点,终点,颜色,线条类型
cv2.line(img,(0,0),(512,512),(255,0,0),5)
#画矩形,参数为图片,左上角顶点,右下角顶点,颜色,线条类型
cv2.rectangle(img,(384,0),(510,128),(0,255,0),3)
#画圆,参数为图片,中心点坐标,半径,颜色,线条类型:填充
cv2.circle(img,(447,63),63,(0,0,255),-1)
#画椭圆,参数为图片,中心点坐标,长短轴,逆时针旋转的角度,
# 椭圆弧沿顺时针方向的起始角度和结束角度,颜色类型填充
cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1)
# pts = np.array([[10,5],[20,30],[70,20],[50,10]],np.int32)
# pts = pts.reshape((-1,1,2))
#在图片添加文字,参数为,图片,绘制文字,位置,字体类型,字体大小,颜色,线条类型
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,500),font,4,(255,255,255),2)
winname = 'example'
cv2.namedWindow(winname)
cv2.imshow(winname,img)
cv2.waitKey(0)
cv2.destroyAllWindows(winname)
http://www.juzicode.com/opencv-python-polylines-puttext/
OpenCV-Python教程:绘制多边形、输出文字(polylines,putText,解决中文乱码问题)
11.1 多边形
cv2.polylines()用来画多边形。
第1个参数为图像对象;
第2个参数为包含一个三元组元素的列表,包含了多边形的各个顶点;
第3个参数为Bool型参数表示是否闭合;
第4个参数为颜色;
第5个参数为线条宽度,注意不能使用-1表示填充;
方形可以看做是一种特殊的多边形,知道其4个顶点的坐标也可以利用polylines()画出方形。下面这个例子利用polylines()画出一个方形和五角星:
import cv2
import numpy as np
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.ones((512,512,3)) #白色背景
color=(0,255,0)
#绿色
#五角星
pts = np.array([[70,190],[222,190],[280,61],[330,190],[467,190],
[358,260],[392,380],[280,308],[138,380],[195,260]])
pts = pts.reshape((-1,1,2))
#reshape为10x1x2的numpy
print(pts.shape)
# (10, 1, 2)
cv2.polylines(img,[pts],True,color,5)
#方形
pts = np.array([[10,50],[100,50],[100,100],[10,100]])
pts = pts.reshape((-1,1,2))
cv2.polylines(img,[pts],True,color,3)
cv2.imshow('juzicode.com',img)
cv2.waitKey()
11.2 cv2.putText()用来输出文字。
第1个参数为图像对象;
第2个参数为要输出的字符串;
第3个参数为起始点位置;
第4个参数为字体;
第5个参数为字体大小;
第6个参数为颜色;
第7个参数为线条宽度,默认为1;
第8个参数为线型,默认为LINE8;
import cv2
import numpy as np
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.ones((512,512,3)) #白色背景
text = 'VX:juzicode'
start = (10,200)
font = cv2.FONT_HERSHEY_SIMPLEX
fontscale = 2
color=(0,0,255)
#红色
thick = 2
linetype = cv2.LINE_AA
cv2.putText(img,text,start, font, fontscale, color,thick,linetype)
cv2.imshow('juzicode.com',img)
cv2.waitKey()
上面的例子是输出英文,我们试下包含中文的情况,将text内容修改为“VX:桔子code”,输出乱码了:
通过dir()命令可以将所有FONT开头的内容打印出来,OpenCV中并没有包含中文字体:
一个变通的方法是通过pillow转换后显示中文:
import cv2
import numpy as np
from PIL import Image,ImageDraw,ImageFont
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.full((512,512,3),255,np.uint8) #白色背景
img_pil = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
text = 'VX:桔子code'
start = (10,200)
#起始位置
fonttype = 'msyh.ttc' #微软雅黑字体,和具体操作系统相关
fontscale = 30
#字体大小
font = ImageFont.truetype(fonttype,fontscale)
draw = ImageDraw.Draw(img_pil)
draw.text(start,text,font=font,fill=(255,0,0))
#PIL中BGR=(255,0,0)表示红色
img_ocv = np.array(img_pil)
#PIL图片转换为numpy
img = cv2.cvtColor(img_ocv,cv2.COLOR_RGB2BGR)
#PIL格式转换为OpenCV的BGR格式
cv2.imshow('juzicode.com',img)
cv2.waitKey()

最后

以上就是饱满鱼为你收集整理的【opencv 450 samples】 AffineFeature 检测器/提取器的示例用法的全部内容,希望文章能够帮你解决【opencv 450 samples】 AffineFeature 检测器/提取器的示例用法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部