《OpenCV3和Qt5计算机视觉应用开发》学习笔记第7章

第6章主要从图像内容和像素的角度介绍了有关图像处理的一些内容,包括如何对图像进行滤波和变换操作,或以不同的方式对像素值进行处理。对于模板匹配,我们仅利用原始像素内容来获取结果,以确定特定对象是否存在于图像的某一部分中。但是,我们尚未学习如何设计算法来区分不同类型的对象。为此目的,不仅要利用原始像素,而且还要利用图像基于特定特征所呈现出的集体含义。对于人类来说,假定不是极端相似,识别和区分不同类型的人脸、汽车、手写字体以及几乎任何可见的对象是一件微不足道的事情。人类在处理这些内容时,几乎都是在无意识的状态下进行的。我们甚至可以区分两张非常相似的人脸,这是因为我们的大脑几乎自动地接收人脸上细微而独特的可区分信息,当我们再次看到这些人脸时,就可以再次使用这些信息来识别它们。以不同的汽车品牌为例,我们的大脑几乎存储了绝大多数主要汽车制造商的标志。使用这些标志就能很容易地区分不同的车型(或制造商)。
本章将介绍以下主题:

❑ OpenCV中有哪些算法

❑ 如何使用已有的OpenCV算法

❑ 使用FeatureDetector类检测特征(或关键点)

❑ 使用DescriptorExtractor类提取描述符

❑ 如何匹配描述符并用它执行检测

❑ 如何绘制描述符匹配结果

❑ 如何根据具体情况选择算法

1.OpenCV中有哪些算法

OpenCV 中 的 所 有 ( 至 少 是 那 些 不 太 简 短 的 ) 算 法 都 是 作 为cv::Algorithm类的子类创建的。与通常期望不同,该类不是一个抽象类,这意味着可以创建该类的实例,但这个实例什么都不做。尽管这可能在未来的某个时候发生变化,但访问和使用该类的方式并不会受此 影 响 。 如 果 想 创 建 自 己 的 算 法 , 那 么 应 采 用 cv::Algorithm 类 在OpenCV中的通行方式,这也是创建自己算法的推荐方式,即首先创建cv::Algorithm的子类,它包含用于实现特定目标的所有必需的成员函数;然后,再次对这个新创建的子类进行子类化,以创建相同算法的不同实现。为了更好地理解这一点,可以看一看cv::Algorithm类的细节,其OpenCV源代码的内容大致如下:

/** @brief This is a base class for all more or less complex algorithms in OpenCV

especially for classes of algorithms, for which there can be multiple implementations. The examples
are stereo correspondence (for which there are algorithms like block matching, semi-global block
matching, graph-cut etc.), background subtraction (which can be done using mixture-of-gaussians
models, codebook-based algorithm etc.), optical flow (block matching, Lucas-Kanade, Horn-Schunck
etc.).

Here is example of SimpleBlobDetector use in your application via Algorithm interface:
@snippet snippets/core_various.cpp Algorithm
*/
class CV_EXPORTS_W Algorithm
{
public:
    Algorithm();
    virtual ~Algorithm();

    /** @brief Clears the algorithm state
    */
    CV_WRAP virtual void clear() {}

    /** @brief Stores algorithm parameters in a file storage
    */
    CV_WRAP virtual void write(FileStorage& fs) const { CV_UNUSED(fs); }

    /**
    * @overload
    */
    CV_WRAP void write(FileStorage& fs, const String& name) const;
#if CV_VERSION_MAJOR < 5
    /** @deprecated */
    void write(const Ptr<FileStorage>& fs, const String& name = String()) const;
#endif

    /** @brief Reads algorithm parameters from a file storage
    */
    CV_WRAP virtual void read(const FileNode& fn) { CV_UNUSED(fn); }

    /** @brief Returns true if the Algorithm is empty (e.g. in the very beginning or after unsuccessful read
    */
    CV_WRAP virtual bool empty() const { return false; }

    /** @brief Reads algorithm from the file node

    This is static template method of Algorithm. It's usage is following (in the case of SVM):
    @code
    cv::FileStorage fsRead("example.xml", FileStorage::READ);
    Ptr<SVM> svm = Algorithm::read<SVM>(fsRead.root());
    @endcode
    In order to make this method work, the derived class must overwrite Algorithm::read(const
    FileNode& fn) and also have static create() method without parameters
    (or with all the optional parameters)
    */
    template<typename _Tp> static Ptr<_Tp> read(const FileNode& fn)
    {
        Ptr<_Tp> obj = _Tp::create();
        obj->read(fn);
        return !obj->empty() ? obj : Ptr<_Tp>();
    }

    /** @brief Loads algorithm from the file

    @param filename Name of the file to read.
    @param objname The optional name of the node to read (if empty, the first top-level node will be used)

    This is static template method of Algorithm. It's usage is following (in the case of SVM):
    @code
    Ptr<SVM> svm = Algorithm::load<SVM>("my_svm_model.xml");
    @endcode
    In order to make this method work, the derived class must overwrite Algorithm::read(const
    FileNode& fn).
    */
    template<typename _Tp> static Ptr<_Tp> load(const String& filename, const String& objname=String())
    {
        FileStorage fs(filename, FileStorage::READ);
        CV_Assert(fs.isOpened());
        FileNode fn = objname.empty() ? fs.getFirstTopLevelNode() : fs[objname];
        if (fn.empty()) return Ptr<_Tp>();
        Ptr<_Tp> obj = _Tp::create();
        obj->read(fn);
        return !obj->empty() ? obj : Ptr<_Tp>();
    }

    /** @brief Loads algorithm from a String

    @param strModel The string variable containing the model you want to load.
    @param objname The optional name of the node to read (if empty, the first top-level node will be used)

    This is static template method of Algorithm. It's usage is following (in the case of SVM):
    @code
    Ptr<SVM> svm = Algorithm::loadFromString<SVM>(myStringModel);
    @endcode
    */
    template<typename _Tp> static Ptr<_Tp> loadFromString(const String& strModel, const String& objname=String())
    {
        FileStorage fs(strModel, FileStorage::READ + FileStorage::MEMORY);
        FileNode fn = objname.empty() ? fs.getFirstTopLevelNode() : fs[objname];
        Ptr<_Tp> obj = _Tp::create();
        obj->read(fn);
        return !obj->empty() ? obj : Ptr<_Tp>();
    }

    /** Saves the algorithm to a file.
    In order to make this method work, the derived class must implement Algorithm::write(FileStorage& fs). */
    CV_WRAP virtual void save(const String& filename) const;

    /** Returns the algorithm string identifier.
    This string is used as top level xml/yml node tag when the object is saved to a file or string. */
    CV_WRAP virtual String getDefaultName() const;

protected:
    void writeFormat(FileStorage& fs) const;
};

enum struct Param {
    INT=0, BOOLEAN=1, REAL=2, STRING=3, MAT=4, MAT_VECTOR=5, ALGORITHM=6, FLOAT=7,
    UNSIGNED_INT=8, UINT64=9, UCHAR=11, SCALAR=12
};

2.如何使用已有的OpenCV算法

在cv::Algorithm类(以及其他很多OpenCV类)中用到的FileStorage和File-Node类是什么,然后认识cv::Algorithm类中的方法:
·FileStorage 类可以用来轻松地实现对XML、YAML以及JSON文件的写入和读取。FileStorage类可以存储由很多算法产生或所需的各种类型的信息,在OpenCV中使用广泛。该类的用法与其他文件读/写类基本相似,区别在于FileStorage类适用于固定类型的文件。
·FileNode 类本身是Node类的一个子类,用来表示FileStorage类中的 单 个 元 素 。 FileNode 类 可 以 是 FileNode 元 素 集 合 中 的 一 个 叶 子 节点,也可以是其他FileNode元素的容器。
除 了 上 面 列 出 的 两 个 类 之 外 , OpenCV 还 有 另 外 一 个 名 为FileNodeIterator的类。顾名思义,与循环一样,该类可用于遍历STL中的节点。下面通过一个例子来说明上述类的具体用法:

OpenCV中的上述代码将导致创建一个JSON文件,如下所示:

{
	"matvalue":{
		"type_id":"opencv-matrix",
		"rows":3,
		"cols":3,
		"dt":"d",
		"data":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]
	},
	"doublevalue":9.990009999999998e+02,
	"strvalue":"a rondom note"
}

可以看到,为了确保JSON文件结构正确,并且所有内容所采用的存储方式能方便以后轻松地进行检索,FileStorage类自动处理与之相关的所有事情。通常情况下,最好通过利用isOpened函数检查文件是否成功地打开。为简便起见,我们跳过这部分内容。整个过程被称为类或数据的结构序列化。通过下列代码,执行读回:

为便于阅读,也为了展示FileStorage类实际读取和创建FileNode类实例的过程,已经将每个值赋给FileNode,然后再赋给变量本身。但很明显,可以直接将读取节点的结果赋给合适类型的变量。这两个类的功 能 远 不 止 于 此 , 值 得 深 入 探 究 , 但 上 面 介 绍 的 内 容 对 于 解 释cv::Algorithm类如何使用这两个类来说已经足够了。至此,我们知道这些类可以用来轻松地存储和检索不同的类,甚至是OpenCV特有的类型。在此基础上,可以对cv::Algorithm进行更深入的研究。正如之前看到的,在其声明及实现中,cv:Algorithm类使用上述类来存储和检索算法的状态、基本参数的含义、输入或输出值等等。为了完成这个工作,它提供了几个方法,下面将会简单地介绍一下这些方法。

目前,不必担心这些类用法上的详细细节,因为实际上会在子类中重新实现它们,而且它们中的大部分的工作方式实际上依赖于实现时的特定算法。因此,应该只关注结构及其在OpenCV中的组织方式。
下面是由cv::Algorithm类提供的方法:
·read :该方法有一些重载的版本,可以用来读取算法的状态。
·write :该方法与read方法类似,但用来保存算法的状态。
·clear :该方法可以用来清除算法的状态。
·empty :该方法可以用来确定算法的状态是否为空,从而可以判断算法是否被成功加载(读取)。
·load :该方法与read方法几乎相同。
·loadFromString :该方法与load方法以及read方法非常相似,区别是loadFromString方法从字符串读取并加载算法的状态。
浏览一下OpenCV网站上cv::Algorithm的文档页面(尤其是其继承关系图),就会立刻注意到OpenCV中大量的类重新实现了cv::Algorithm。
可以想到,它们都具有上面介绍的那些函数。除此之外,它们中的每个类都提供了自身特定的方法和函数。

二维特征框架

正如本章前面介绍的,OpenCV提供了一些类来执行由世界各地的计算机视觉研究人员所创建的各种特征检测与描述符提取算法。与在OpenCV中实现的所有其他复杂算法一样,特征检测器和描述符提取器也是通过子类化cv::Algorithm类创建的。该子类称为Feature2D,它包含所有特征检测和描述符提取类共用的各种函数。基本上,任何用于检测特征和提取描述符的类都应该是Featured2D的子类。OpenCV使用下面两个类类型来实现这一目的:

❑ FeatureDetector

❑ DescriptorExtractor需要重点注意的是,这两个类实际上都只是Feature2D的不同的名称,因为它们是在OpenCV内使用下面的typedef语句创建的(稍后,将在本节中讨论其原因):

typedef Feature2D FeatureDetector;
typedef Feature2D DescriptorExtractor;

最好浏览一下Feature2D类的声明:

/************************************ Base Classes ************************************/

/** @brief Abstract base class for 2D image feature detectors and descriptor extractors
*/
#ifdef __EMSCRIPTEN__
class CV_EXPORTS_W Feature2D : public Algorithm
#else
class CV_EXPORTS_W Feature2D : public virtual Algorithm
#endif
{
public:
    virtual ~Feature2D();

    /** @brief Detects keypoints in an image (first variant) or image set (second variant).

    @param image Image.
    @param keypoints The detected keypoints. In the second variant of the method keypoints[i] is a set
    of keypoints detected in images[i] .
    @param mask Mask specifying where to look for keypoints (optional). It must be a 8-bit integer
    matrix with non-zero values in the region of interest.
     */
    CV_WRAP virtual void detect( InputArray image,
                                 CV_OUT std::vector<KeyPoint>& keypoints,
                                 InputArray mask=noArray() );

    /** @overload
    @param images Image set.
    @param keypoints The detected keypoints. In the second variant of the method keypoints[i] is a set
    of keypoints detected in images[i] .
    @param masks Masks for each input image specifying where to look for keypoints (optional).
    masks[i] is a mask for images[i].
    */
    CV_WRAP virtual void detect( InputArrayOfArrays images,
                         CV_OUT std::vector<std::vector<KeyPoint> >& keypoints,
                         InputArrayOfArrays masks=noArray() );

    /** @brief Computes the descriptors for a set of keypoints detected in an image (first variant) or image set
    (second variant).

    @param image Image.
    @param keypoints Input collection of keypoints. Keypoints for which a descriptor cannot be
    computed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypoint
    with several dominant orientations (for each orientation).
    @param descriptors Computed descriptors. In the second variant of the method descriptors[i] are
    descriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is the
    descriptor for keypoint j-th keypoint.
     */
    CV_WRAP virtual void compute( InputArray image,
                                  CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints,
                                  OutputArray descriptors );

    /** @overload

    @param images Image set.
    @param keypoints Input collection of keypoints. Keypoints for which a descriptor cannot be
    computed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypoint
    with several dominant orientations (for each orientation).
    @param descriptors Computed descriptors. In the second variant of the method descriptors[i] are
    descriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is the
    descriptor for keypoint j-th keypoint.
    */
    CV_WRAP virtual void compute( InputArrayOfArrays images,
                          CV_OUT CV_IN_OUT std::vector<std::vector<KeyPoint> >& keypoints,
                          OutputArrayOfArrays descriptors );

    /** Detects keypoints and computes the descriptors */
    CV_WRAP virtual void detectAndCompute( InputArray image, InputArray mask,
                                           CV_OUT std::vector<KeyPoint>& keypoints,
                                           OutputArray descriptors,
                                           bool useProvidedKeypoints=false );

    CV_WRAP virtual int descriptorSize() const;
    CV_WRAP virtual int descriptorType() const;
    CV_WRAP virtual int defaultNorm() const;

    CV_WRAP void write( const String& fileName ) const;

    CV_WRAP void read( const String& fileName );

    virtual void write( FileStorage&) const CV_OVERRIDE;

    // see corresponding cv::Algorithm method
    CV_WRAP virtual void read( const FileNode&) CV_OVERRIDE;

    //! Return true if detector object is empty
    CV_WRAP virtual bool empty() const CV_OVERRIDE;
    CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;

    // see corresponding cv::Algorithm method
    CV_WRAP inline void write(FileStorage& fs, const String& name) const { Algorithm::write(fs, name); }
#if CV_VERSION_MAJOR < 5
    inline void write(const Ptr<FileStorage>& fs, const String& name) const { CV_Assert(fs); Algorithm::write(*fs, name); }
#endif
};

/** Feature detectors in OpenCV have wrappers with a common interface that enables you to easily switch
between different algorithms solving the same problem. All objects that implement keypoint detectors
inherit the FeatureDetector interface. */
typedef Feature2D FeatureDetector;

/** Extractors of keypoint descriptors in OpenCV have wrappers with a common interface that enables you
to easily switch between different algorithms solving the same problem. This section is devoted to
computing descriptors represented as vectors in a multidimensional space. All objects that implement
the vector descriptor extractors inherit the DescriptorExtractor interface.
 */
typedef Feature2D DescriptorExtractor;

让我们了解一下Feature2D类的声明中包含了哪些内容。首先,前面提到过,它是cv::Algorithm的子类。read函数、write函数以及empty函数只是cv::Algorithm中已有函数的重新实现。但以下函数是全新的,在cv::Algorithm中不存在,它们主要是特征检测器和描述符提取器需要添加的函数:

❑ detect函数可以用来从一个图像或一组图像中检测特征(即关键点)。

❑ compute函数可以用来从关键点中提取(即计算)描述符。

❑ detectAndCompute函数可以用来以单个函数同时执行检测和计算。

❑ descriptorSize函数、descriptorType函数以及defaultNorm函数是依赖于算法的值,它们是在每个能够提取描述符的Feature2D子类中重新实现的。

尽管这看起来可能很奇怪,基于单个类对特征检测器和描述符进行分类是有缘由的,这是因为部分(不是全部)算法同时为特征检测和描述符提取这二者提供函数。随着我们继续讨论为这个目的创建很多算法,这将变得更加清晰。下面开始介绍OpenCV二维特征框架中现有的Feature2D类和算法。

检测特征

OpenCV有很多类可用于从图像中检测特征(关键点),每个类都拥有其实现,这取决于它所实现的特定算法,并且可能需要一组不同的参数才能正确执行,或获得最好的性能。但是,它们都有一个共同点,即都有前面介绍过的detect函数(因为它们都是Feature2D的子类),用来检测图像中的一组关键点。OpenCV中的关键点或特征是KeyPoint类实例,其中包含需要为合适的关键点(关键点和特征这两个术语在含义上可以混用)存储的大部分信息。下面是KeyPoint类的成员及其定义:

❑ pt:即点,包含图像中关键点的位置(X和Y)。

❑ angle:指的是关键点的顺时针旋转角度(0到360度)。如果检测关键点的算法能够找到它,则设置它,否则设置为-1。

❑ response:指的是关键点的强度,可对关键点排序或滤除强度较弱的关键点等等。

❑ size:这是一个直径,用于指定关键点的邻域,以便做进一步处理。

❑ octave:指的是图像分组层(或金字塔层),从中检测出该特定关键点。这是一个非常强大而实用的概念,广泛用于在检测关键点以及使用关键点进一步检测图像上可能大小不同的对象时,实现尺度独立,或者说尺度不变性。为了实现该功能,用相同的算法处理同一图像的不同尺度版本(仅缩小版本),称每一个尺度为金字塔的一个分组或一层。

为方便使用,KeyPoint类还提供了其他成员和方法,读者可根据需要自己查看,但为了进一步使用这些重要特性,我们将学习所有需要熟悉的重要属性。现在,让我们看一看现有OpenCV特征检测器类的列表及其简要描述,还有如何使用这些特征检测器类的例子:❑ AgastFeatureDetector包含AGAST(自适应及通用加速分割测试)算法的实现,可以用来检测图像中的角点。在配置其行为时需要三个参数(均可省略以使用默认值)。

提取和匹配描述符

计算机视觉中,描述符是一种描述关键点的方法,它完全依赖于用来提取描述符的特定算法,并且与关键点(在KeyPoint类中定义)不同,除了每一个描述符表示一个关键点这一点之外,描述符没有共同的结构。OpenCV中的描述符存储在Mat类中,由此产生的描述符Mat类的每一行都表示关键点的描述符。与上一节中学到的内容一样,可以使用任意一个FeatureDetector子类的detect函数来检测图像中的一组关键点。类似地,可以使用任意一个DescriptorExtractor子类的compute函数从关键点中提取描述符。

由于OpenCV中特征检测器与描述提取符组织方法的缘故(与本章前面介绍的一样,都是Feature2D子类),将它们联合使用是一件极其容易的事情。事实上,你会在本节中看到,我们将使用同样的类(或更准确地说,是那些也提供了描述符提取方法的类)从上一节我们利用各种类所找到的关键点中提取特征描述符,以定位场景图像内的对象。需要注意的是,提取的关键点并非全部与所有描述符兼容,同时并非所有算法(在本例中是Feature2D子类)都提供detect函数和compute函数。但完成的算法也提供了一个detectAndCompute函数,可以同时进行关键点检测及特征提取,而且比单独调用两个函数更快。下面开始介绍第一个示例案例,以便让所有这些内容变得更清晰。这也是在对两个独立图像的特征进行匹配时要执行哪些所需步骤的示例,可以将其用于执行检测、比对等。

完整插件代码如下:


QT       += widgets

TARGET = Keypoint_Plugin
TEMPLATE = lib

CONFIG += plugin

DEFINES += KEYPOINT_PLUGIN_LIBRARY

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

#win环境配置
win32:{
    CONFIG(debug, debug|release){
        #插件生成目录
        DESTDIR += $$PWD/../../bin/win64d/cvplugins
        LIBS += -L$$PWD/../../lib/win64d -lopencv_world480d
    }else{
        DESTDIR += $$PWD/../../bin/win64/cvplugins
        LIBS += -L$$PWD/../../lib/win64 -lopencv_world480
    }
}

INCLUDEPATH += ../../Include

INCLUDEPATH += ../cvplugininterface

SOURCES += \
        keypoint_plugin.cpp

HEADERS += \
        keypoint_plugin.h \
        keypoint_plugin_global.h

unix {
    target.path = /usr/lib
    INSTALLS += target
}

win32: {
    include("c:/dev/opencv/opencv.pri")
}

unix: !macx{
    CONFIG += link_pkgconfig
    PKGCONFIG += opencv
}

unix: macx{
INCLUDEPATH += "/usr/local/include"
LIBS += -L"/usr/local/lib" \
    -lopencv_world
}

FORMS += \
    plugin.ui
keypoint_plugin_global.h
#ifndef KEYPOINT_PLUGIN_GLOBAL_H
#define KEYPOINT_PLUGIN_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(KEYPOINT_PLUGIN_LIBRARY)
#  define KEYPOINT_PLUGINSHARED_EXPORT Q_DECL_EXPORT
#else
#  define KEYPOINT_PLUGINSHARED_EXPORT Q_DECL_IMPORT
#endif

#endif // KEYPOINT_PLUGIN_GLOBAL_H

keypoint_plugin.h

#ifndef KEYPOINT_PLUGIN_H
#define KEYPOINT_PLUGIN_H

#include "keypoint_plugin_global.h"
#include "cvplugininterface.h"

namespace Ui {
    class PluginGui;
}

class KEYPOINT_PLUGINSHARED_EXPORT Keypoint_Plugin: public QObject, public CvPluginInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "com.computervision.cvplugininterface")
    Q_INTERFACES(CvPluginInterface)
public:
    Keypoint_Plugin();
    ~Keypoint_Plugin();

    QString title();
    QString version();
    QString description();
    QString help();
    void setupUi(QWidget *parent);
    void processImage(const cv::Mat &inputImage, cv::Mat &outputImage);

signals:
    void updateNeeded();
    void errorMessage(QString msg);
    void infoMessage(QString msg);

private slots:
    void on_toolBox_currentChanged(int index);

    void on_agastThreshSpin_valueChanged(int arg1);

    void on_agastNonmaxCheck_toggled(bool checked);

    void on_agastTypeCombo_currentIndexChanged(int index);

    void on_kazeAcceleratedCheck_toggled(bool checked);

    void on_kazeExtendCheck_toggled(bool checked);

    void on_kazeUprightCheck_toggled(bool checked);

    void on_akazeDescriptCombo_currentIndexChanged(int index);

    void on_kazeThreshSpin_valueChanged(double arg1);

    void on_kazeOctaveSpin_valueChanged(int arg1);

    void on_kazeLayerSpin_valueChanged(int arg1);

    void on_kazeDiffCombo_currentIndexChanged(int index);

    void on_briskThreshSpin_valueChanged(int arg1);

    void on_briskOctaveSpin_valueChanged(int arg1);

    void on_briskScaleSpin_valueChanged(double arg1);

    void on_fastThreshSpin_valueChanged(int arg1);

    void on_fastNonmaxCheck_toggled(bool checked);

    void on_fastTypeCombo_currentIndexChanged(int index);

    void on_harrisCheck_toggled(bool checked);

    void on_harrisKSpin_valueChanged(double arg1);

    void on_gfttBlockSpin_valueChanged(int arg1);

    void on_gfttMaxSpin_valueChanged(int arg1);

    void on_gfttDistSpin_valueChanged(double arg1);

    void on_gfttQualitySpin_valueChanged(double arg1);

    void on_orbFeaturesSpin_valueChanged(int arg1);

    void on_orbScaleSpin_valueChanged(double arg1);

    void on_orbLevelsSpin_valueChanged(int arg1);

    void on_orbPatchSpin_valueChanged(int arg1);

    void on_orbWtaSpin_valueChanged(int arg1);

    void on_orbFastCheck_toggled(bool checked);

    void on_orbFastSpin_valueChanged(int arg1);

    void on_browseBtn_pressed();

    void on_keypointCombo_currentIndexChanged(int index);

    void on_descriptorCombo_currentIndexChanged(int index);

    void on_matcherCombo_currentIndexChanged(int index);

private:
    Ui::PluginGui *ui;

    cv::Mat secondImage;

    void fillFeature2D(QString algName, cv::Ptr<cv::Feature2D> &algorithm);

};

#endif // KEYPOINT_PLUGIN_H

keypoint_plugin.cpp

#include "keypoint_plugin.h"

#include "ui_plugin.h"

#include <QFileDialog>
#include <QDebug>

Keypoint_Plugin::Keypoint_Plugin()
{
    // Insert initialization codes here ...
}

Keypoint_Plugin::~Keypoint_Plugin()
{
    // Insert cleanup codes here ...
}

QString Keypoint_Plugin::title()
{
    return this->metaObject()->className();
}

QString Keypoint_Plugin::version()
{
    return "1.0.0";
}

QString Keypoint_Plugin::description()
{
    return "Feature Detection Plugin";
}

QString Keypoint_Plugin::help()
{
    return "Feature Detection Plugin";
}

void Keypoint_Plugin::setupUi(QWidget *parent)
{
    ui = new Ui::PluginGui;
    ui->setupUi(parent);

    ui->agastTypeCombo->addItems(QStringList()
                                 << "AGAST_5_8"
                                 << "AGAST_7_12d"
                                 << "AGAST_7_12s"
                                 << "OAST_9_16");

    ui->fastTypeCombo->addItems(QStringList()
                                << "TYPE_5_8"
                                << "TYPE_7_12"
                                << "TYPE_9_16");

    ui->akazeDescriptCombo->addItems(QStringList()
                                     << "DESCRIPTOR_KAZE_UPRIGHT"
                                     << "DESCRIPTOR_KAZE"
                                     << "DESCRIPTOR_MLDB_UPRIGHT"
                                     << "DESCRIPTOR_MLDB");

    ui->kazeDiffCombo->addItems(QStringList()
                                << "DIFF_PM_G1"
                                << "DIFF_PM_G2"
                                << "DIFF_WEICKERT"
                                << "DIFF_CHARBONNIER");

    ui->keypointCombo->addItems(QStringList()
                                << "AGAST"
                                << "KAZE"
                                << "BRISK"
                                << "FAST"
                                << "GFTT/Harris"
                                << "ORB");

    ui->descriptorCombo->addItems(QStringList()
                                  << "KAZE"
                                  << "BRISK"
                                  << "ORB");

    ui->matcherCombo->addItems(QStringList()
                               << "FLANNBASED"
                               << "BRUTEFORCE"
                               << "BRUTEFORCE_L1"
                               << "BRUTEFORCE_HAMMING"
                               << "BRUTEFORCE_HAMMINGLUT"
                               << "BRUTEFORCE_SL2");

    connect(ui->toolBox, SIGNAL(currentChanged(int)), this, SLOT(on_toolBox_currentChanged(int)));
    connect(ui->agastNonmaxCheck, SIGNAL(toggled(bool)), this, SLOT(on_agastNonmaxCheck_toggled(bool)));
    connect(ui->agastThreshSpin, SIGNAL(valueChanged(int)), this, SLOT(on_agastThreshSpin_valueChanged(int)));
    connect(ui->agastTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_agastTypeCombo_currentIndexChanged(int)));

    connect(ui->kazeAcceleratedCheck, SIGNAL(toggled(bool)), this, SLOT(on_kazeAcceleratedCheck_toggled(bool)));
    connect(ui->kazeExtendCheck, SIGNAL(toggled(bool)), this, SLOT(on_kazeExtendCheck_toggled(bool)));
    connect(ui->kazeUprightCheck, SIGNAL(toggled(bool)), this, SLOT(on_kazeUprightCheck_toggled(bool)));
    connect(ui->kazeLayerSpin, SIGNAL(valueChanged(int)), this, SLOT(on_kazeLayerSpin_valueChanged(int)));
    connect(ui->kazeOctaveSpin, SIGNAL(valueChanged(int)), this, SLOT(on_kazeOctaveSpin_valueChanged(int)));
    connect(ui->kazeThreshSpin, SIGNAL(valueChanged(double)), this, SLOT(on_kazeThreshSpin_valueChanged(double)));
    connect(ui->kazeDiffCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_kazeDiffCombo_currentIndexChanged(int)));
    connect(ui->akazeDescriptCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_akazeDescriptCombo_currentIndexChanged(int)));
    ui->kazeAcceleratedCheck->setChecked(true);

    connect(ui->briskOctaveSpin, SIGNAL(valueChanged(int)), this, SLOT(on_briskOctaveSpin_valueChanged(int)));
    connect(ui->briskScaleSpin, SIGNAL(valueChanged(double)), this, SLOT(on_briskScaleSpin_valueChanged(double)));
    connect(ui->briskThreshSpin, SIGNAL(valueChanged(int)), this, SLOT(on_briskThreshSpin_valueChanged(int)));

    connect(ui->fastNonmaxCheck, SIGNAL(toggled(bool)), this, SLOT(on_fastNonmaxCheck_toggled(bool)));
    connect(ui->fastThreshSpin, SIGNAL(valueChanged(int)), this, SLOT(on_fastThreshSpin_valueChanged(int)));
    connect(ui->fastTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_fastTypeCombo_currentIndexChanged(int)));

    connect(ui->harrisCheck, SIGNAL(toggled(bool)), this, SLOT(on_harrisCheck_toggled(bool)));
    connect(ui->harrisKSpin, SIGNAL(valueChanged(double)), this, SLOT(on_harrisKSpin_valueChanged(double)));
    connect(ui->gfttBlockSpin, SIGNAL(valueChanged(int)), this, SLOT(on_gfttBlockSpin_valueChanged(int)));
    connect(ui->gfttDistSpin, SIGNAL(valueChanged(double)), this, SLOT(on_gfttDistSpin_valueChanged(double)));
    connect(ui->gfttMaxSpin, SIGNAL(valueChanged(int)), this, SLOT(on_gfttMaxSpin_valueChanged(int)));
    connect(ui->gfttQualitySpin, SIGNAL(valueChanged(double)), this, SLOT(on_gfttQualitySpin_valueChanged(double)));

    connect(ui->orbFastCheck, SIGNAL(toggled(bool)), this, SLOT(on_orbFastCheck_toggled(bool)));
    connect(ui->orbFastSpin, SIGNAL(valueChanged(int)), this, SLOT(on_orbFastSpin_valueChanged(int)));
    connect(ui->orbFeaturesSpin, SIGNAL(valueChanged(int)), this, SLOT(on_orbFeaturesSpin_valueChanged(int)));
    connect(ui->orbLevelsSpin, SIGNAL(valueChanged(int)), this, SLOT(on_orbLevelsSpin_valueChanged(int)));
    connect(ui->orbPatchSpin, SIGNAL(valueChanged(int)), this, SLOT(on_orbPatchSpin_valueChanged(int)));
    connect(ui->orbWtaSpin, SIGNAL(valueChanged(int)), this, SLOT(on_orbWtaSpin_valueChanged(int)));
    connect(ui->orbScaleSpin, SIGNAL(valueChanged(double)), this, SLOT(on_orbScaleSpin_valueChanged(double)));

    connect(ui->browseBtn, SIGNAL(pressed()), this, SLOT(on_browseBtn_pressed()));
    connect(ui->keypointCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_keypointCombo_currentIndexChanged(int)));
    connect(ui->descriptorCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_descriptorCombo_currentIndexChanged(int)));
    connect(ui->matcherCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_matcherCombo_currentIndexChanged(int)));
}

void Keypoint_Plugin::processImage(const cv::Mat &inputImage, cv::Mat &outputImage)
{
    if(secondImage.empty())
    {
        emit errorMessage("Select a secondary image first!");
        return;
    }

    using namespace cv;
    using namespace std;

    Ptr<Feature2D> keypDetector;
    Ptr<Feature2D> descExtractor;
    Ptr<DescriptorMatcher> descMather;

    fillFeature2D(ui->keypointCombo->currentText(), keypDetector);
    fillFeature2D(ui->descriptorCombo->currentText(), descExtractor);

    vector<KeyPoint> keypoints1, keypoints2;
    Mat descriptor1, descriptor2;
    vector<DMatch> matches;

    // fast and safe version
    if(ui->keypointCombo->currentText() == ui->descriptorCombo->currentText())
    {
        descExtractor->detectAndCompute(inputImage, Mat(), keypoints1, descriptor1);
        descExtractor->detectAndCompute(secondImage, Mat(), keypoints2, descriptor2);
    }
    else
    {
        // we need to check
        if(ui->keypointCombo->currentText() == "KAZE"
                || ui->keypointCombo->currentText() == "AKAZE"
                || ui->descriptorCombo->currentText() == "KAZE"
                || ui->descriptorCombo->currentText() == "AKAZE")
        {
            emit errorMessage("Use KAZE/AKAZE keypoints with KAZE/AKAZE descriptors only!");
            return;
        }

        keypDetector->detect(inputImage, keypoints1);
        descExtractor->compute(inputImage, keypoints1, descriptor1);
        keypDetector->detect(secondImage, keypoints2);
        descExtractor->compute(secondImage, keypoints2, descriptor2);
    }

    // Also check for the correct type of matching
    descMather = DescriptorMatcher::create((DescriptorMatcher::MatcherType)(ui->matcherCombo->currentIndex()+1));
    descMather->match(descriptor1, descriptor2, matches);

    // Find good matches (AKAZE)
    vector<DMatch> goodMatches;
    double matchThresh = 0.1;
    for(int i=0; i<descriptor1.rows; i++)
    {
        if(matches[i].distance < matchThresh)
            goodMatches.push_back(matches[i]);
    }

    drawMatches(inputImage,
                keypoints1,
                secondImage,
                keypoints2,
                goodMatches,
                outputImage);

    vector<Point2f> goodP1, goodP2;
    for(size_t i=0; i<goodMatches.size(); i++)
    {
        goodP1.push_back(keypoints1[goodMatches[i].queryIdx].pt);
        goodP2.push_back(keypoints2[goodMatches[i].trainIdx].pt);
    }
    Mat homoChange = findHomography(goodP1, goodP2);

    vector<Point2f> corners1(4), corners2(4);
    corners1[0] = Point2f(0,0);
    corners1[1] = Point2f(inputImage.cols-1, 0);
    corners1[2] = Point2f(inputImage.cols-1, inputImage.rows-1);
    corners1[3] = Point2f(0, inputImage.rows-1);

    perspectiveTransform(corners1, corners2, homoChange);

    Point2f offset(inputImage.cols, 0);

    line(outputImage, corners2[0] + offset, corners2[1] + offset, Scalar::all(255), 2);
    line(outputImage, corners2[1] + offset, corners2[2] + offset, Scalar::all(255), 2);
    line(outputImage, corners2[2] + offset, corners2[3] + offset, Scalar::all(255), 2);
    line(outputImage, corners2[3] + offset, corners2[0] + offset, Scalar::all(255), 2);
}

void Keypoint_Plugin::fillFeature2D(QString algName, cv::Ptr<cv::Feature2D> &algorithm)
{
    using namespace cv;
    using namespace std;

    if(algName == "AGAST")
    {
        Ptr<AgastFeatureDetector> agast = AgastFeatureDetector::create();
        agast->setThreshold(ui->agastThreshSpin->value());
        agast->setNonmaxSuppression(ui->agastNonmaxCheck->isChecked());
        agast->setType((AgastFeatureDetector::DetectorType)ui->agastTypeCombo->currentIndex());
        algorithm = agast;
    }
    else if(algName == "KAZE")
    {
        if(ui->kazeAcceleratedCheck->isChecked())
        {
            Ptr<AKAZE> akaze = AKAZE::create();
            akaze->setDescriptorChannels(3);
            akaze->setDescriptorSize(0);
            akaze->setDescriptorType((AKAZE::DescriptorType)(ui->akazeDescriptCombo->currentIndex() + 2));
            akaze->setDiffusivity((KAZE::DiffusivityType)(ui->kazeDiffCombo->currentIndex()));
            akaze->setNOctaves(ui->kazeOctaveSpin->value());
            akaze->setNOctaveLayers(ui->kazeLayerSpin->value());
            akaze->setThreshold(ui->kazeThreshSpin->value());
            algorithm = akaze;
        }
        else
        {
            Ptr<KAZE> kaze = KAZE::create();
            kaze->setUpright(ui->kazeUprightCheck->isChecked());
            kaze->setExtended(ui->kazeExtendCheck->isChecked());
            kaze->setDiffusivity((KAZE::DiffusivityType)(ui->kazeDiffCombo->currentIndex()));
            kaze->setNOctaves(ui->kazeOctaveSpin->value());
            kaze->setNOctaveLayers(ui->kazeLayerSpin->value());
            kaze->setThreshold(ui->kazeThreshSpin->value());
            algorithm = kaze;
        }

    }
    else if(algName == "BRISK")
    {
        Ptr<BRISK> brisk =
                BRISK::create(ui->briskThreshSpin->value(),
                              ui->briskOctaveSpin->value(),
                              ui->briskScaleSpin->value());
        algorithm = brisk;
    }
    else if(algName == "FAST")
    {
        Ptr<FastFeatureDetector> fast = FastFeatureDetector::create();
        fast->setThreshold(ui->fastThreshSpin->value());
        fast->setNonmaxSuppression(ui->fastNonmaxCheck->isChecked());
        fast->setType((FastFeatureDetector::DetectorType)(ui->fastTypeCombo->currentIndex()));
        algorithm = fast;
    }
    else if(algName == "GFTT/Harris")
    {
        Ptr<GFTTDetector> gftt = GFTTDetector::create();
        gftt->setHarrisDetector(ui->harrisCheck->isChecked());
        gftt->setK(ui->harrisKSpin->value());
        gftt->setBlockSize(ui->gfttBlockSpin->value());
        gftt->setMaxFeatures(ui->gfttMaxSpin->value());
        gftt->setMinDistance(ui->gfttDistSpin->value());
        gftt->setQualityLevel(ui->gfttQualitySpin->value());
        algorithm = gftt;
    }
    else if(algName == "ORB")
    {
        Ptr<ORB> orb = ORB::create();
        orb->setMaxFeatures(ui->orbFeaturesSpin->value());
        orb->setScaleFactor(ui->orbScaleSpin->value());
        orb->setNLevels(ui->orbLevelsSpin->value());
        orb->setPatchSize(ui->orbPatchSpin->value());
        orb->setEdgeThreshold(ui->orbPatchSpin->value()); // same as patch size
        orb->setWTA_K(ui->orbWtaSpin->value());
        orb->setScoreType(ui->orbFastCheck->isChecked() ?
                              ORB::HARRIS_SCORE
                            :
                              ORB::FAST_SCORE);
        orb->setPatchSize(ui->orbPatchSpin->value());
        orb->setFastThreshold(ui->orbFastSpin->value());
        algorithm = orb;
    }
}

void Keypoint_Plugin::on_toolBox_currentChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_agastThreshSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_agastNonmaxCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_agastTypeCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeAcceleratedCheck_toggled(bool checked)
{
    ui->kazeExtendCheck->setVisible(!checked);
    ui->kazeUprightCheck->setVisible(!checked);
    ui->akazeDescriptCombo->setVisible(checked);
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeExtendCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeUprightCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_akazeDescriptCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeThreshSpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeOctaveSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeLayerSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_kazeDiffCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_briskThreshSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_briskOctaveSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_briskScaleSpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_fastThreshSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_fastNonmaxCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_fastTypeCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_harrisCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_harrisKSpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_gfttBlockSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_gfttMaxSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_gfttDistSpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_gfttQualitySpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbFeaturesSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbScaleSpin_valueChanged(double)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbLevelsSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbPatchSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbWtaSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbFastCheck_toggled(bool)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_orbFastSpin_valueChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_browseBtn_pressed()
{
    QString fileName = QFileDialog::getOpenFileName(ui->tabWidget,
                                                    tr("Open Input Image"),
                                                    QDir::currentPath(),
                                                    tr("Images") + " (*.jpg *.png *.bmp)");

    using namespace cv;
    secondImage = imread(fileName.toStdString());
    if(!secondImage.empty())
    {
        emit updateNeeded();
    }
}

void Keypoint_Plugin::on_keypointCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_descriptorCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

void Keypoint_Plugin::on_matcherCombo_currentIndexChanged(int)
{
    emit updateNeeded();
}

plugin.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>PluginGui</class>
 <widget class="QWidget" name="PluginGui">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>333</width>
    <height>601</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0">
    <widget class="QTabWidget" name="tabWidget">
     <property name="currentIndex">
      <number>0</number>
     </property>
     <widget class="QWidget" name="tab">
      <attribute name="title">
       <string>Feature2D Settings</string>
      </attribute>
      <layout class="QGridLayout" name="gridLayout_8">
       <item row="0" column="0">
        <widget class="QToolBox" name="toolBox">
         <property name="currentIndex">
          <number>1</number>
         </property>
         <widget class="QWidget" name="page">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>263</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>AGAST</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_2">
           <item row="0" column="1">
            <widget class="QSpinBox" name="agastThreshSpin">
             <property name="maximum">
              <number>255</number>
             </property>
             <property name="value">
              <number>10</number>
             </property>
            </widget>
           </item>
           <item row="0" column="0">
            <widget class="QLabel" name="label">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Threshold :</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0" colspan="2">
            <widget class="QCheckBox" name="agastNonmaxCheck">
             <property name="text">
              <string>Nonmax Suppression</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="label_2">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Type :</string>
             </property>
            </widget>
           </item>
           <item row="2" column="1">
            <widget class="QComboBox" name="agastTypeCombo"/>
           </item>
          </layout>
         </widget>
         <widget class="QWidget" name="page_2">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>264</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>KAZE &amp;&amp; AKAZE</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_3">
           <item row="2" column="0" colspan="2">
            <widget class="QCheckBox" name="kazeUprightCheck">
             <property name="text">
              <string>Upright</string>
             </property>
            </widget>
           </item>
           <item row="5" column="0">
            <widget class="QLabel" name="label_4">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Nr. of Octaves :</string>
             </property>
             <property name="wordWrap">
              <bool>true</bool>
             </property>
            </widget>
           </item>
           <item row="6" column="0">
            <widget class="QLabel" name="label_5">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Nr. of Octave Layers :</string>
             </property>
             <property name="wordWrap">
              <bool>true</bool>
             </property>
            </widget>
           </item>
           <item row="3" column="0">
            <widget class="QLabel" name="label_6">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Descriptor Type :</string>
             </property>
             <property name="wordWrap">
              <bool>true</bool>
             </property>
            </widget>
           </item>
           <item row="6" column="1">
            <widget class="QSpinBox" name="kazeLayerSpin">
             <property name="maximum">
              <number>999</number>
             </property>
             <property name="value">
              <number>4</number>
             </property>
            </widget>
           </item>
           <item row="3" column="1">
            <widget class="QComboBox" name="akazeDescriptCombo"/>
           </item>
           <item row="1" column="0" colspan="2">
            <widget class="QCheckBox" name="kazeExtendCheck">
             <property name="text">
              <string>Extended</string>
             </property>
            </widget>
           </item>
           <item row="5" column="1">
            <widget class="QSpinBox" name="kazeOctaveSpin">
             <property name="maximum">
              <number>999</number>
             </property>
             <property name="value">
              <number>4</number>
             </property>
            </widget>
           </item>
           <item row="0" column="0" colspan="2">
            <widget class="QCheckBox" name="kazeAcceleratedCheck">
             <property name="text">
              <string>Accelerated</string>
             </property>
            </widget>
           </item>
           <item row="4" column="0">
            <widget class="QLabel" name="label_3">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Threshold :</string>
             </property>
             <property name="wordWrap">
              <bool>true</bool>
             </property>
            </widget>
           </item>
           <item row="4" column="1">
            <widget class="QDoubleSpinBox" name="kazeThreshSpin">
             <property name="decimals">
              <number>6</number>
             </property>
             <property name="maximum">
              <double>10.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>0.000100000000000</double>
             </property>
             <property name="value">
              <double>0.000100000000000</double>
             </property>
            </widget>
           </item>
           <item row="7" column="0">
            <widget class="QLabel" name="label_7">
             <property name="text">
              <string>Diffusivity :</string>
             </property>
             <property name="wordWrap">
              <bool>true</bool>
             </property>
            </widget>
           </item>
           <item row="7" column="1">
            <widget class="QComboBox" name="kazeDiffCombo"/>
           </item>
          </layout>
         </widget>
         <widget class="QWidget" name="page_3">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>263</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>BRISK</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_4">
           <item row="0" column="0">
            <widget class="QLabel" name="label_8">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Threshold :</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1">
            <widget class="QSpinBox" name="briskThreshSpin">
             <property name="maximum">
              <number>999</number>
             </property>
             <property name="value">
              <number>30</number>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_9">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Octaves :</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="label_10">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Pattern Scale :</string>
             </property>
            </widget>
           </item>
           <item row="1" column="1">
            <widget class="QSpinBox" name="briskOctaveSpin">
             <property name="value">
              <number>3</number>
             </property>
            </widget>
           </item>
           <item row="2" column="1">
            <widget class="QDoubleSpinBox" name="briskScaleSpin">
             <property name="value">
              <double>1.000000000000000</double>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
         <widget class="QWidget" name="page_4">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>263</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>FAST</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_5">
           <item row="2" column="0">
            <widget class="QLabel" name="label_12">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Type :</string>
             </property>
            </widget>
           </item>
           <item row="0" column="0">
            <widget class="QLabel" name="label_11">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Threshold :</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0" colspan="3">
            <widget class="QCheckBox" name="fastNonmaxCheck">
             <property name="text">
              <string>Nonmax Suppression</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1" colspan="2">
            <widget class="QSpinBox" name="fastThreshSpin">
             <property name="value">
              <number>10</number>
             </property>
            </widget>
           </item>
           <item row="2" column="1" colspan="2">
            <widget class="QComboBox" name="fastTypeCombo"/>
           </item>
          </layout>
         </widget>
         <widget class="QWidget" name="page_5">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>263</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>GFTT &amp;&amp; Harris</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_6">
           <item row="0" column="0" colspan="2">
            <widget class="QCheckBox" name="harrisCheck">
             <property name="text">
              <string>Use Harris</string>
             </property>
            </widget>
           </item>
           <item row="4" column="0">
            <widget class="QLabel" name="label_16">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Min Distance :</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="label_14">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Block Size :</string>
             </property>
            </widget>
           </item>
           <item row="3" column="0">
            <widget class="QLabel" name="label_15">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Max Features :</string>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_13">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>K :</string>
             </property>
            </widget>
           </item>
           <item row="5" column="0">
            <widget class="QLabel" name="label_17">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Quality Level :</string>
             </property>
            </widget>
           </item>
           <item row="2" column="1">
            <widget class="QSpinBox" name="gfttBlockSpin">
             <property name="value">
              <number>3</number>
             </property>
            </widget>
           </item>
           <item row="1" column="1">
            <widget class="QDoubleSpinBox" name="harrisKSpin">
             <property name="decimals">
              <number>5</number>
             </property>
             <property name="singleStep">
              <double>0.010000000000000</double>
             </property>
             <property name="value">
              <double>0.040000000000000</double>
             </property>
            </widget>
           </item>
           <item row="3" column="1">
            <widget class="QSpinBox" name="gfttMaxSpin">
             <property name="maximum">
              <number>999999</number>
             </property>
             <property name="value">
              <number>1000</number>
             </property>
            </widget>
           </item>
           <item row="5" column="1">
            <widget class="QDoubleSpinBox" name="gfttQualitySpin">
             <property name="decimals">
              <number>5</number>
             </property>
             <property name="maximum">
              <double>999.000000000000000</double>
             </property>
             <property name="singleStep">
              <double>0.010000000000000</double>
             </property>
             <property name="value">
              <double>0.010000000000000</double>
             </property>
            </widget>
           </item>
           <item row="4" column="1">
            <widget class="QDoubleSpinBox" name="gfttDistSpin">
             <property name="decimals">
              <number>5</number>
             </property>
             <property name="maximum">
              <double>99999.000000000000000</double>
             </property>
             <property name="value">
              <double>1.000000000000000</double>
             </property>
            </widget>
           </item>
          </layout>
          <zorder>label_13</zorder>
          <zorder>label_14</zorder>
          <zorder>label_15</zorder>
          <zorder>label_16</zorder>
          <zorder>label_17</zorder>
          <zorder>gfttBlockSpin</zorder>
          <zorder>harrisKSpin</zorder>
          <zorder>gfttMaxSpin</zorder>
          <zorder>gfttQualitySpin</zorder>
          <zorder>gfttDistSpin</zorder>
          <zorder>harrisCheck</zorder>
         </widget>
         <widget class="QWidget" name="page_6">
          <property name="geometry">
           <rect>
            <x>0</x>
            <y>0</y>
            <width>263</width>
            <height>318</height>
           </rect>
          </property>
          <attribute name="label">
           <string>ORB</string>
          </attribute>
          <layout class="QGridLayout" name="gridLayout_7">
           <item row="3" column="1">
            <widget class="QSpinBox" name="orbPatchSpin">
             <property name="value">
              <number>31</number>
             </property>
            </widget>
           </item>
           <item row="1" column="0">
            <widget class="QLabel" name="label_19">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Scale Factor :</string>
             </property>
            </widget>
           </item>
           <item row="4" column="1">
            <widget class="QSpinBox" name="orbWtaSpin">
             <property name="value">
              <number>2</number>
             </property>
            </widget>
           </item>
           <item row="6" column="0">
            <widget class="QLabel" name="label_24">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Fast Threshold :</string>
             </property>
            </widget>
           </item>
           <item row="3" column="0">
            <widget class="QLabel" name="label_21">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Patch Size :</string>
             </property>
            </widget>
           </item>
           <item row="2" column="0">
            <widget class="QLabel" name="label_20">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Nr. of Levels :</string>
             </property>
            </widget>
           </item>
           <item row="1" column="1">
            <widget class="QDoubleSpinBox" name="orbScaleSpin">
             <property name="value">
              <double>1.200000000000000</double>
             </property>
            </widget>
           </item>
           <item row="0" column="0">
            <widget class="QLabel" name="label_18">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Nr. of Features :</string>
             </property>
            </widget>
           </item>
           <item row="0" column="1">
            <widget class="QSpinBox" name="orbFeaturesSpin">
             <property name="maximum">
              <number>9999</number>
             </property>
             <property name="value">
              <number>500</number>
             </property>
            </widget>
           </item>
           <item row="2" column="1">
            <widget class="QSpinBox" name="orbLevelsSpin">
             <property name="value">
              <number>8</number>
             </property>
            </widget>
           </item>
           <item row="4" column="0">
            <widget class="QLabel" name="label_22">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>WTA K:</string>
             </property>
            </widget>
           </item>
           <item row="5" column="0" colspan="2">
            <widget class="QCheckBox" name="orbFastCheck">
             <property name="text">
              <string>FAST (Instead of Harris)</string>
             </property>
            </widget>
           </item>
           <item row="6" column="1">
            <widget class="QSpinBox" name="orbFastSpin">
             <property name="maximum">
              <number>999</number>
             </property>
             <property name="value">
              <number>20</number>
             </property>
            </widget>
           </item>
          </layout>
         </widget>
        </widget>
       </item>
      </layout>
     </widget>
     <widget class="QWidget" name="tab_2">
      <attribute name="title">
       <string>Matching</string>
      </attribute>
      <layout class="QGridLayout" name="gridLayout_9">
       <item row="1" column="0">
        <layout class="QHBoxLayout" name="horizontalLayout_4">
         <item>
          <widget class="QPushButton" name="browseBtn">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>Browse for Second Image</string>
           </property>
          </widget>
         </item>
        </layout>
       </item>
       <item row="2" column="0">
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
          <widget class="QLabel" name="label_23">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>Keypoint Detector:</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QComboBox" name="keypointCombo"/>
         </item>
        </layout>
       </item>
       <item row="3" column="0">
        <layout class="QHBoxLayout" name="horizontalLayout_2">
         <item>
          <widget class="QLabel" name="label_25">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>Descriptor Extractor :</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QComboBox" name="descriptorCombo"/>
         </item>
        </layout>
       </item>
       <item row="4" column="0">
        <layout class="QHBoxLayout" name="horizontalLayout_3">
         <item>
          <widget class="QLabel" name="label_26">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>Descriptor Matcher :</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QComboBox" name="matcherCombo"/>
         </item>
        </layout>
       </item>
      </layout>
     </widget>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

运行效果

❑ 如何根据具体情况选择算法
正如前面介绍的那样,没有任何一种算法可以不做调整即可很容易地适用于所有情况,主要原因涉及与软件和硬件相关的各种因素。一个算法可能是非常精确的,但同时可能需要大量的资源(例如,内存或可用的CPU)。另一种算法可能需要很少的参数(这总是一种解脱),但话说回来,这可能无法实现最高的性能。我们甚至不能列出在选择最佳Feature2D(或者特征检测器和描述符提取器)算法或最佳匹配算法时所有可能的影响因素,但是我们仍然可以考虑一些主要的和更众所周知的因素,这也是OpenCV和大多数计算机视觉算法以这种方式创建的原因。下面就是这些因素:❑ 精度

❑ 速度

❑ 资源利用率(内存、磁盘空间等)

❑ 可用性 

注意,“性能”这个词通常指的是精确度、速度和资源利用率的组合。因此,我们实际上寻找的是这样一种算法,即它能满足所需的性能,并且在应用程序工作的平台上可用。最需要注意的是,作为一名工程师,你也能影响这些参数,比如你可以缩小用例到刚好满足需要。下面逐一解释这些因素。

精度

首先,精度是一种误导,因为一旦看到某些精度下降,我们通常倾向于放弃这个算法,但正确的方式是首先弄清楚用例对于精度的需求。如果查看由知名公司生产的基于计算机视觉的机器的数据指标,就会马上注意到如像超过95%等等这些内容。这并不意味着机器是不完美的,恰恰相反——这意味着机器的精度定义良好,用户可以期望一定程度的精度,同时忍受一定的低误差。话虽如此,以百分百的精度为目标既是好事,也被推荐。

除了查阅算法的文献和引用之外,没有更好的方式为用例选择精确的算法,而更好的办法是自己试一试。应当确保使用Qt中适当的控件创建用户界面,这样,就可以轻松地使用现有的(甚至可能是你自己的)算法进行实验。创建一些基准,并确保当任意一个特定算法的阈值或其他参数发生变化时,你完全了解它的行为。另外,确保根据需要的尺度和旋转独立性来选择算法。例如,使用标准的AKAZE描述符类型(非垂直)时,在AKAZE中算法允许旋转独立性,因此甚至可以匹配旋转的对象。或者,请使用更高的分组层(或金字塔层)数,因为这有助于匹配不同大小的图像,从而取得尺度独立性。

速度

开发一个实时应用程序时,如果FPS值(每秒帧数,或帧率)必须尽可能地高,则算法执行速度是特别重要的。因此,和精度一样,也需要搞清楚速度需求。如果匹配两个图像,并向用户显示某些匹配结果,即使延迟半秒(500毫秒)也是可以接受的,但是,当使用高FPS值时,每帧延迟半秒就非常高了。可以使用OpenCV中的TickMeter类或getTickFrequency函数和getTickCount函数来度量一个计算机视觉过程(或者任何处理这个问题的过程)的执行时间。首先,来看一看较旧的方法是如何工作的:

资源利用率

特别是在最近的高端设备和计算机上,这通常不是一个大问题,但仍然可能是计算机的一个问题,因为计算机的磁盘和内存空间有限,例如,嵌入式计算机。请尝试使用那些预先安装于操作系统上的资源监控应用程序。例如,在Windows上,可以使用任务管理应用程序查看所使用的资源,如内存。在macOS上,可以使用活动监视器应用程序查看每个程序使用的电量(能量)的数量,以及内存和其他资源的使用信息。在Linux上,可以使用各种工具(例如,系统监视器),目的完全一样。

可用性

即使OpenCV和Qt都是跨平台的框架,但算法(甚至某个类或函数)仍然依赖于特定于平台的功能,特别是由于性能方面的原因。需要注意的很重要的一点是,需要确保所使用的算法在你希望发布应用程序的平台上可用。最好的资源通常是OpenCV和Qt框架中的底层类的文档页面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OpenCV3和Qt5计算机视觉应用开发》是一本介绍如何结合OpenCV3和Qt5进行计算机视觉应用开发的书籍。本书共分为八章,内容丰富而全面。 第一章是对计算机视觉和相关技术的概述,引导读者了解计算机视觉的基础知识,以及OpenCV3和Qt5的基本概念和使用方法。 第二到第五章依次介绍了OpenCV3和Qt5的基础知识和使用方法。其中,在OpenCV3的章节中,读者能够学习到如何使用OpenCV3进行图像处理、特征提取、目标检测等计算机视觉任务。而在Qt5的章节中,读者将学习到如何使用Qt5进行图形界面设计,以及如何将OpenCV3与Qt5进行桥接,实现计算机视觉应用的图形化界面。 第六章介绍了如何在Qt5中导入OpenCV3库,并给出了一些在Qt中使用OpenCV进行图像处理的示例代码。读者可以通过这一章的学习,了解如何在Qt中调用OpenCV函数,实现各种图像处理功能。 第七章是一个完整的计算机视觉应用案例,案例中介绍了一个基于OpenCV3和Qt5开发的人脸识别系统。通过阅读这一章的内容,读者可以了解到如何运用OpenCV3和Qt5构建一个实际的计算机视觉应用系统,并了解到其中的原理和细节。 第八章是有关Qt5的高级使用和扩展。本章内容较为高级,主要介绍了如何使用Qt5进行多线程编程、网络编程和数据库操作等高级技术,并给出了一些示例代码。 总之,《OpenCV3和Qt5计算机视觉应用开发》是一本非常实用的书籍,适合计算机视觉爱好者和开发者阅读,通过学习本书,读者能够掌握使用OpenCV3和Qt5进行计算机视觉应用开发的技巧和方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值