【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 : %f\n", maxDist);
    printf("min distance : %f\n", minDist);

    vector<DMatch> goodMatches;
    //过滤相同的点
    for (int i = 0; i < descriptor_obj.rows; i++) {
        double dist = matches[i].distance;//相识度
        printf("distance : %f\n", 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()

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值