OpenCV3 和 Qt5 计算机视觉 学习笔记 - 特征和描述符

2024-04-13 15:18

本文主要是介绍OpenCV3 和 Qt5 计算机视觉 学习笔记 - 特征和描述符,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

特征和描述符

所有算法的基础 – 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*/virtual void write(FileStorage& fs) const { CV_UNUSED(fs); }/** @brief simplified API for language bindings* @overload*/CV_WRAP void write(const Ptr<FileStorage>& fs, const String& name = String()) const;/** @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 nodeThis is static template method of Algorithm. It's usage is following (in the case of SVM):@codecv::FileStorage fsRead("example.xml", FileStorage::READ);Ptr<SVM> svm = Algorithm::read<SVM>(fsRead.root());@endcodeIn order to make this method work, the derived class must overwrite Algorithm::read(constFileNode& 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){}/** @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):@codePtr<SVM> svm = Algorithm::load<SVM>("my_svm_model.xml");@endcodeIn order to make this method work, the derived class must overwrite Algorithm::read(constFileNode& 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):@codePtr<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;
};

OpenCV 中的所有算法或更好的算法,至少不是不太简短的算法,都被创建为cv::Algorithm类的子类。 与通常所期望的相反,该类不是抽象类,这意味着您可以创建它的实例,而该实例只是不执行任何操作。 即使将来可能会更改它,也不会真正影响我们访问和使用它的方式。 在 OpenCV 中使用cv::Algorithm类的方式,以及如果要创建自己的算法的推荐方法,是首先创建cv::Algorithm的子类,其中包含用于特定目的或目标的所有必需成员函数。 。 然后,可以再次对该新创建的子类进行子类化,以创建同一算法的不同实现。 为了更好地理解这一点,让我们首先详细了解cv::Algorithm类。 大致了解一下 OpenCV 源代码的外观

首先,让我们看看cv::Algorithm类中使用的FileStorageFileNode类是什么(以及许多其他 OpenCV 类),然后介绍cv::Algorithm类中的方法:

/** @brief XML/YAML/JSON file storage class that encapsulates all the information necessary for writing or
reading data to/from a file.*/
class CV_EXPORTS_W FileStorage
{
public://! file storage modeenum Mode{READ        = 0, //!< value, open the file for readingWRITE       = 1, //!< value, open the file for writingAPPEND      = 2, //!< value, open the file for appendingMEMORY      = 4, //!< flag, read data from source or write data to the internal buffer (which is//!< returned by FileStorage::release)FORMAT_MASK = (7<<3), //!< mask for format flagsFORMAT_AUTO = 0,      //!< flag, auto formatFORMAT_XML  = (1<<3), //!< flag, XML formatFORMAT_YAML = (2<<3), //!< flag, YAML formatFORMAT_JSON = (3<<3), //!< flag, JSON formatBASE64      = 64,     //!< flag, write rawdata in Base64 by default. (consider using WRITE_BASE64)WRITE_BASE64 = BASE64 | WRITE, //!< flag, enable both WRITE and BASE64};enum State{UNDEFINED      = 0,VALUE_EXPECTED = 1,NAME_EXPECTED  = 2,INSIDE_MAP     = 4};/** @brief The constructors.The full constructor opens the file. Alternatively you can use the default constructor and thencall FileStorage::open.*/CV_WRAP FileStorage();/** @overload@copydoc open()*/CV_WRAP FileStorage(const String& filename, int flags, const String& encoding=String());//! the destructor. calls release()virtual ~FileStorage();/** @brief Opens a file.See description of parameters in FileStorage::FileStorage. The method calls FileStorage::releasebefore opening the file.@param filename Name of the file to open or the text string to read the data from.Extension of the file (.xml, .yml/.yaml or .json) determines its format (XML, YAML or JSONrespectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz. If bothFileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specifythe output file format (e.g. mydata.xml, .yml etc.). A file name can also contain parameters.You can use this format, "*?base64" (e.g. "file.json?base64" (case sensitive)), as an alternative toFileStorage::BASE64 flag.@param flags Mode of operation. One of FileStorage::Mode@param encoding Encoding of the file. Note that UTF-16 XML encoding is not supported currently andyou should use 8-bit encoding instead of it.*/CV_WRAP virtual bool open(const String& filename, int flags, const String& encoding=String());/** @brief Checks whether the file is opened.@returns true if the object is associated with the current file and false otherwise. It is agood practice to call this method after you tried to open a file.*/CV_WRAP virtual bool isOpened() const;/** @brief Closes the file and releases all the memory buffers.Call this method after all I/O operations with the storage are finished.*/CV_WRAP virtual void release();/** @brief Closes the file and releases all the memory buffers.Call this method after all I/O operations with the storage are finished. If the storage wasopened for writing data and FileStorage::WRITE was specified*/CV_WRAP virtual String releaseAndGetString();/** @brief Returns the first element of the top-level mapping.@returns The first element of the top-level mapping.*/CV_WRAP FileNode getFirstTopLevelNode() const;/** @brief Returns the top-level mapping@param streamidx Zero-based index of the stream. In most cases there is only one stream in the file.However, YAML supports multiple streams and so there can be several.@returns The top-level mapping.*/CV_WRAP FileNode root(int streamidx=0) const;/** @brief Returns the specified element of the top-level mapping.@param nodename Name of the file node.@returns Node with the given name.*/FileNode operator[](const String& nodename) const;/** @overload */CV_WRAP_AS(getNode) FileNode operator[](const char* nodename) const;/*** @brief Simplified writing API to use with bindings.* @param name Name of the written object* @param val Value of the written object*/CV_WRAP void write(const String& name, int val);/// @overloadCV_WRAP void write(const String& name, double val);/// @overloadCV_WRAP void write(const String& name, const String& val);/// @overloadCV_WRAP void write(const String& name, const Mat& val);/// @overloadCV_WRAP void write(const String& name, const std::vector<String>& val);/** @brief Writes multiple numbers.Writes one or more numbers of the specified format to the currently written structure. Usually it ismore convenient to use operator `<<` instead of this method.@param fmt Specification of each array element, see @ref format_spec "format specification"@param vec Pointer to the written array.@param len Number of the uchar elements to write.*/void writeRaw( const String& fmt, const void* vec, size_t len );/** @brief Writes a comment.The function writes a comment into file storage. The comments are skipped when the storage is read.@param comment The written comment, single-line or multi-line@param append If true, the function tries to put the comment at the end of current line.Else if the comment is multi-line, or if it does not fit at the end of the currentline, the comment starts a new line.*/CV_WRAP void writeComment(const String& comment, bool append = false);void startWriteStruct(const String& name, int flags, const String& typeName);void endWriteStruct();/** @brief Returns the normalized object name for the specified name of a file.@param filename Name of a file@returns The normalized object name.*/static String getDefaultObjectName(const String& filename);/** @brief Returns the current format.* @returns The current format, see FileStorage::Mode*/CV_WRAP int getFormat() const;int state;std::string elname;class Impl;Ptr<Impl> p;
};
  • FileStorage类可用于轻松地读写 XML,YAML 和 JSON 文件。 此类在 OpenCV 中广泛使用,以存储许多算法产生或需要的各种类型的信息。 此类几乎与任何其他文件读取器/写入器类一样工作,不同之处在于它可以与所提到的文件类型一起工作。

    /** @brief File Storage Node class.The node is used to store each and every element of the file storage opened for reading. When
    XML/YAML file is read, it is first parsed and stored in the memory as a hierarchical collection of
    nodes. Each node can be a "leaf" that is contain a single number or a string, or be a collection of
    other nodes. There can be named collections (mappings) where each element has a name and it is
    accessed by a name, and ordered collections (sequences) where elements do not have names but rather
    accessed by index. Type of the file node can be determined using FileNode::type method.Note that file nodes are only used for navigating file storages opened for reading. When a file
    storage is opened for writing, no data is stored in memory after it is written.*/
    class CV_EXPORTS_W_SIMPLE FileNode
    {
    public://! type of the file storage nodeenum{NONE      = 0, //!< empty nodeINT       = 1, //!< an integerREAL      = 2, //!< floating-point numberFLOAT     = REAL, //!< synonym or REALSTR       = 3, //!< text string in UTF-8 encodingSTRING    = STR, //!< synonym for STRSEQ       = 4, //!< sequenceMAP       = 5, //!< mappingTYPE_MASK = 7,FLOW      = 8,  //!< compact representation of a sequence or mapping. Used only by YAML writerUNIFORM   = 8,  //!< if set, means that all the collection elements are numbers of the same type (real's or int's).//!< UNIFORM is used only when reading FileStorage; FLOW is used only when writing. So they share the same bitEMPTY     = 16, //!< empty structure (sequence or mapping)NAMED     = 32  //!< the node has a name (i.e. it is element of a mapping).};/** @brief The constructors.These constructors are used to create a default file node, construct it from obsolete structures orfrom the another file node.*/CV_WRAP FileNode();/** @overload@param fs Pointer to the file storage structure.@param blockIdx Index of the memory block where the file node is stored@param ofs Offset in bytes from the beginning of the serialized storage*/FileNode(const FileStorage* fs, size_t blockIdx, size_t ofs);/** @overload@param node File node to be used as initialization for the created file node.*/FileNode(const FileNode& node);FileNode& operator=(const FileNode& node);/** @brief Returns element of a mapping node or a sequence node.@param nodename Name of an element in the mapping node.@returns Returns the element with the given identifier.*/FileNode operator[](const String& nodename) const;/** @overload@param nodename Name of an element in the mapping node.*/CV_WRAP_AS(getNode) FileNode operator[](const char* nodename) const;/** @overload@param i Index of an element in the sequence node.*/CV_WRAP_AS(at) FileNode operator[](int i) const;/** @brief Returns keys of a mapping node.@returns Keys of a mapping node.*/CV_WRAP std::vector<String> keys() const;/** @brief Returns type of the node.@returns Type of the node. See FileNode::Type*/CV_WRAP int type() const;//! returns true if the node is emptyCV_WRAP bool empty() const;//! returns true if the node is a "none" objectCV_WRAP bool isNone() const;//! returns true if the node is a sequenceCV_WRAP bool isSeq() const;//! returns true if the node is a mappingCV_WRAP bool isMap() const;//! returns true if the node is an integerCV_WRAP bool isInt() const;//! returns true if the node is a floating-point numberCV_WRAP bool isReal() const;//! returns true if the node is a text stringCV_WRAP bool isString() const;//! returns true if the node has a nameCV_WRAP bool isNamed() const;//! returns the node name or an empty string if the node is namelessCV_WRAP std::string name() const;//! returns the number of elements in the node, if it is a sequence or mapping, or 1 otherwise.CV_WRAP size_t size() const;//! returns raw size of the FileNode in bytesCV_WRAP size_t rawSize() const;//! returns the node content as an integer. If the node stores floating-point number, it is rounded.operator int() const;//! returns the node content as floatoperator float() const;//! returns the node content as doubleoperator double() const;//! returns the node content as text stringinline operator std::string() const { return this->string(); }static bool isMap(int flags);static bool isSeq(int flags);static bool isCollection(int flags);static bool isEmptyCollection(int flags);static bool isFlow(int flags);uchar* ptr();const uchar* ptr() const;//! returns iterator pointing to the first node elementFileNodeIterator begin() const;//! returns iterator pointing to the element following the last node elementFileNodeIterator end() const;/** @brief Reads node elements to the buffer with the specified format.Usually it is more convenient to use operator `>>` instead of this method.@param fmt Specification of each array element. See @ref format_spec "format specification"@param vec Pointer to the destination array.@param len Number of bytes to read (buffer size limit). If it is greater than number ofremaining elements then all of them will be read.*/void readRaw( const String& fmt, void* vec, size_t len ) const;/** Internal method used when reading FileStorage.Sets the type (int, real or string) and value of the previously created node.*/void setValue( int type, const void* value, int len=-1 );//! Simplified reading API to use with bindings.CV_WRAP double real() const;//! Simplified reading API to use with bindings.CV_WRAP std::string string() const;//! Simplified reading API to use with bindings.CV_WRAP Mat mat() const;//protected:const FileStorage* fs;size_t blockIdx;size_t ofs;
    };
    • FileNode类本身是Node类的子类,用于表示FileStorage类中的单个元素。 FileNode类可以是FileNode元素集合中的单个叶子,也可以是其他FileNode元素的容器。

      2D 特征框架

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

      • FeatureDetector
      • DescriptorExtractor
/** 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的不同名称,因为它们是使用以下typedef语句在 OpenCV 中创建的(我们将在本节后面讨论其原因) ):

看到Feature2D类的声明也是一个好主意:

/** @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 setof keypoints detected in images[i] .@param mask Mask specifying where to look for keypoints (optional). It must be a 8-bit integermatrix 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 setof 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 becomputed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypointwith several dominant orientations (for each orientation).@param descriptors Computed descriptors. In the second variant of the method descriptors[i] aredescriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is thedescriptor 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 becomputed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypointwith several dominant orientations (for each orientation).@param descriptors Computed descriptors. In the second variant of the method descriptors[i] aredescriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is thedescriptor 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 methodCV_WRAP virtual void read( const FileNode&) CV_OVERRIDE;//! Return true if detector object is emptyCV_WRAP virtual bool empty() const CV_OVERRIDE;CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;// see corresponding cv::Algorithm methodCV_WRAP inline void write(const Ptr<FileStorage>& fs, const String& name = String()) const { Algorithm::write(fs, name); }
};

让我们快速回顾一下Feature2D类的声明中的内容。 首先,它是cv::Algorithm的子类,正如我们之前所学。 读,写和空函数只是cv::Algorithm中存在的简单重新实现的函数。 但是,以下函数是cv::Algorithm中的新增函数,不存在,它们基本上是特征检测器和描述符提取器所需的其他函数:

  • detect函数可用于从一个图像或一组图像中检测特征(或关键点)。
  • compute函数可用于从关键点提取(或计算)描述符。
  • detectAndCompute函数可用于执行单个特征的检测和计算。
  • descriptorSizedescriptorTypedefaultNorm是算法相关的值,它们在每个能够提取描述符的Feature2D子类中重新实现。

看起来似乎很奇怪,但是有充分的理由以这种方式对特征检测器和描述符进行分类,并且只有一个类,这是因为某些算法(并非全部)都提供了特征检测和描述符提取函数。 随着我们继续为此目的创建许多算法,这将变得更加清晰。 因此,让我们从 OpenCV 2D 特征框架中现有的Feature2D类和算法开始。

检测特征

OpenCV 提供了许多类来处理图像中的特征(关键点)检测。 每个类都有自己的实现,具体取决于它实现的特定算法,并且可能需要一组不同的参数才能正确执行或具有最佳表现。 但是,它们所有的共同点就是前面提到的detect函数(因为它们都是Feature2D的子类),可用于检测图像中的一组关键点。 OpenCV 中的关键点或特征是KeyPoint类实例,其中包含为正确的关键点需要存储的大多数信息(这些术语,即关键点和特征,可以互换使用,并且很多,因此,请尝试习惯它)。 以下是KeyPoint类的成员及其定义:

/** @brief Data structure for salient point detectors.The class instance stores a keypoint, i.e. a point feature found by one of many available keypoint
detectors, such as Harris corner detector, #FAST, %StarDetector, %SURF, %SIFT etc.The keypoint is characterized by the 2D position, scale (proportional to the diameter of the
neighborhood that needs to be taken into account), orientation and some other parameters. The
keypoint neighborhood is then analyzed by another algorithm that builds a descriptor (usually
represented as a feature vector). The keypoints representing the same object in different images
can then be matched using %KDTree or another method.
*/
class CV_EXPORTS_W_SIMPLE KeyPoint
{
public://! the default constructorCV_WRAP KeyPoint();/**@param _pt x & y coordinates of the keypoint@param _size keypoint diameter@param _angle keypoint orientation@param _response keypoint detector response on the keypoint (that is, strength of the keypoint)@param _octave pyramid octave in which the keypoint has been detected@param _class_id object id*/KeyPoint(Point2f _pt, float _size, float _angle=-1, float _response=0, int _octave=0, int _class_id=-1);/**@param x x-coordinate of the keypoint@param y y-coordinate of the keypoint@param _size keypoint diameter@param _angle keypoint orientation@param _response keypoint detector response on the keypoint (that is, strength of the keypoint)@param _octave pyramid octave in which the keypoint has been detected@param _class_id object id*/CV_WRAP KeyPoint(float x, float y, float _size, float _angle=-1, float _response=0, int _octave=0, int _class_id=-1);size_t hash() const;/**This method converts vector of keypoints to vector of points or the reverse, where each keypoint isassigned the same size and the same orientation.@param keypoints Keypoints obtained from any feature detection algorithm like SIFT/SURF/ORB@param points2f Array of (x,y) coordinates of each keypoint@param keypointIndexes Array of indexes of keypoints to be converted to points. (Acts like a mask toconvert only specified keypoints)*/CV_WRAP static void convert(const std::vector<KeyPoint>& keypoints,CV_OUT std::vector<Point2f>& points2f,const std::vector<int>& keypointIndexes=std::vector<int>());/** @overload@param points2f Array of (x,y) coordinates of each keypoint@param keypoints Keypoints obtained from any feature detection algorithm like SIFT/SURF/ORB@param size keypoint diameter@param response keypoint detector response on the keypoint (that is, strength of the keypoint)@param octave pyramid octave in which the keypoint has been detected@param class_id object id*/CV_WRAP static void convert(const std::vector<Point2f>& points2f,CV_OUT std::vector<KeyPoint>& keypoints,float size=1, float response=1, int octave=0, int class_id=-1);/**This method computes overlap for pair of keypoints. Overlap is the ratio between area of keypointregions' intersection and area of keypoint regions' union (considering keypoint region as circle).If they don't overlap, we get zero. If they coincide at same location with same size, we get 1.@param kp1 First keypoint@param kp2 Second keypoint*/CV_WRAP static float overlap(const KeyPoint& kp1, const KeyPoint& kp2);CV_PROP_RW Point2f pt; //!< coordinates of the keypointsCV_PROP_RW float size; //!< diameter of the meaningful keypoint neighborhoodCV_PROP_RW float angle; //!< computed orientation of the keypoint (-1 if not applicable);//!< it's in [0,360) degrees and measured relative to//!< image coordinate system, ie in clockwise.CV_PROP_RW float response; //!< the response by which the most strong keypoints have been selected. Can be used for the further sorting or subsamplingCV_PROP_RW int octave; //!< octave (pyramid layer) from which the keypoint has been extractedCV_PROP_RW int class_id; //!< object class (if the keypoints need to be clustered by an object they belong to)
};
  • pt或简单地指向:这包含关键点(XY)在图像中的位置。
  • angle:这是指关键点的顺时针旋转(0 到 360 度),即,检测到关键点的算法是否能够找到它; 否则,它将被设置为-1
  • response:这是关键点的强度,可用于排序或过滤弱关键点,依此类推。
  • size:这是指指定可用于进一步处理的关键点邻域的直径。
  • octave:这是图像的八度(或金字塔音阶),从中可以检测到该特定关键点。 这是一个非常强大且实用的概念,在检测关键点或使用它们进一步检测图像上可能具有不同大小的对象时,已广泛用于实现与比例尺(比例尺不变)的独立性。 为此,可以使用相同的算法处理同一图像的不同缩放版本(仅较小版本)。 每个刻度基本上称为octave或金字塔中的一个等级。

为了方便起见,KeyPoint类提供了其他成员和方法,以便您可以自己检查一下,但是为了进一步使用它们,我们肯定经历了我们需要熟悉的所有重要属性。 现在,让我们看一下现有的 OpenCV 特征检测器类的列表,以及它们的简要说明以及如何使用它们的示例:

  • 可以使用AgastFeatureDetector(包括 AGAST自适应和通用加速分段测试)算法的实现)来检测图像中的角。 它需要三个参数(可以省略所有参数以使用默认值)来配置其行为。 这是一个例子:
        Ptr<AgastFeatureDetector> agast = AgastFeatureDetector::create(); vector<KeyPoint> keypoints; agast->detect(inputImage, keypoints); 

如此简单,我们仅将AgastFeatureDetector与默认参数集一起使用。 在深入研究上述操作的结果之前,让我们首先看一下代码本身,因为其中使用了 OpenCV 中最重要和最实用的类之一(称为Ptr)。 如前面的代码所示,我们使用了Ptr类,它是 OpenCV 共享指针(也称为智能指针)的实现。 使用智能指针的优点之一是,您不必担心在使用完该类后释放为该类分配的内存。 另一个优点以及被称为共享指针的原因是,多个Ptr类可以使用(共享)单个指针,并且该指针(分配的内存)仅保留到Ptr指向的最后一个实例被摧毁为止。 在复杂的代码中,这可能意味着极大的简化。

接下来,请务必注意,您需要使用静态create函数来创建AgastFeatureDetector类的共享指针实例。 您将无法创建此类的实例,因为它是抽象类。 其余代码并不是新内容。 我们只需创建KeyPointstd::vector,然后使用 AGAST 的基础算法检测输入Mat图像中的关键点。

在前面的示例中,我们仅使用了默认参数(省略了默认参数),但是为了更好地控制 AGAST 算法的行为,我们需要注意以下参数:

  • 默认情况下设置为10threshold值用于基于像素与围绕它的圆上的像素之间的强度差传递特征。 阈值越高意味着检测到的特征数量越少,反之亦然。

  • NonmaxSuppression可用于对检测到的关键点应用非最大抑制。 默认情况下,此参数设置为true,可用于进一步过滤掉不需要的关键点。

  • 可以将

    type
    

    参数设置为以下值之一,并确定 AGAST 算法的类型:

    • AGAST_5_8
    • AGAST_7_12d
    • AGAST_7_12s
    • OAST_9_16(默认值)

您可以使用适当的 Qt 小部件从用户界面获取参数值。 这是 AGAST 算法的示例用户界面以及其底层代码。 另外,您可以下载本节末尾提供的完整keypoint_plugin源代码,其中包含该源代码以及以下特征检测示例,它们全部集成在一个插件中,与我们全面的computer_vision项目兼容:

在这里插入图片描述

KAZE 和 AKAZE

KAZEAKAZE加速 KAZE)类可用于使用 KAZE 算法(其加速版本)检测特征。 有关 KAZE 和 AKAZE 算法的详细信息,请参考以下参考文献列表中提到的文件。 类似于我们在 AGAST 中看到的那样,我们可以使用默认参数集并简单地调用detect函数,或者我们可以使用适当的 Qt 小部件获取所需的参数并进一步控制算法的行为。 这是一个例子:

AKAZE 和 KAZE 中的主要参数如下:

  • nOctaves或八度的数量(默认为 4)可用于定义图像的最大八度。
  • nOctaveLayers或八度级别的数量(默认为 4)是每个八度(或每个比例级别)的子级别数。
  • 扩散率可以采用下列项之一,它是 KAZE 和 AKAZE 算法使用的非线性扩散方法(如稍后在此算法的参考文献中所述):
    • DIFF_PM_G1
    • DIFF_PM_G2
    • DIFF_WEICKERT
    • DIFF_CHARBONNIER
  • 阈值是接受关键点的响应值(默认为 0.001000)。 阈值越低,检测到的(和接受的)关键点数量越多,反之亦然。
  • 描述符类型参数可以是以下值之一。 请注意,此参数仅存在于 AKAZE 类中:
    • DESCRIPTOR_KAZE_UPRIGHT
    • DESCRIPTOR_KAZE
    • DESCRIPTOR_MLDB_UPRIGHT
  • descriptor_size用于定义描述符的大小。 零值(也是默认值)表示完整尺寸的描述符。
  • descriptor_channels可用于设置描述符中的通道数。 默认情况下,此值设置为 3。

现在,不要理会与描述符相关的参数,例如描述符类型和大小以及通道数,我们将在后面看到。 这些相同的类也用于从特征中提取描述符,并且这些参数将在其中起作用,而不必检测关键点,尤其是detect函数。

这是前面示例用户界面的源代码,其中,根据我们前面示例用户界面中Accelerated复选框的状态,选择了 KAZE(未选中)或 AKAZE(加速):

在这里插入图片描述

BRISK

BRISK类可用于使用 BRISK二进制鲁棒不变可缩放关键点)算法检测图像中的特征。 请确保参考以下文章,以获取有关其工作方式以及 OpenCV 中基础实现的详细信息。 不过,用法与我们在 AGAST 和 KAZE 中看到的用法非常相似,其中使用create函数创建了类,然后设置了参数(如果我们不使用默认值),最后是detect函数被调用。 这是一个简单的例子:

在这里插入图片描述

GFTT

GFTT(需要跟踪的良好特征)仅是特征检测器。 GFTTDetector可用于使用 Harris(以创建者命名)和 GFTT 角检测算法检测特征。 因此,是的,该类别实际上是将两种特征检测方法组合在一起的一个类别,原因是 GFTT 实际上是哈里斯算法的一种修改版本,使用的哪一种将由输入参数决定。 因此,让我们看看如何在示例案例中使用它,然后简要介绍一下参数:

以下是GFTTDetector类的参数及其定义:

  • 如果将useHarrisDetector设置为true,则将使用 Harris 算法,否则将使用 GFTT。 默认情况下,此参数设置为false
  • blockSize可用于设置块大小,该块大小将用于计算像素附近的导数协方差矩阵。 默认为 3。
  • K是 Harris 算法使用的常数参数值。
  • 可以设置maxFeaturesmaxCorners来限制检测到的关键点数量。 默认情况下,它设置为 1000,但是如果关键点的数量超过该数量,则仅返回最强的响应。
  • minDistance是关键点之间的最小可接受值。 默认情况下,此值设置为 1,它不是像素距离,而是欧几里得距离。
  • qualityLevel是阈值级别的值,用于过滤质量指标低于特定级别的关键点。 请注意,实际阈值是通过将该值与图像中检测到的最佳关键点质量相乘得出的。

ORB

最后,ORB 算法,这是我们将在本节中介绍的最后一个特征检测算法。

ORB类可用于使用 ORB(二进制鲁棒独立基本特征)算法检测图像中的关键点。 此类封装了我们已经看到的一些方法(例如 FAST 或 Harris)来检测关键点。 因此,在类构造器中设置或使用设置器函数设置的某些参数与描述符提取有关,我们将在后面学习; 但是,ORB 类可用于检测关键点,如以下示例所示:

该序列与到目前为止我们看到的其他算法完全相同。 让我们看看设置了哪些参数:

  • MaxFeatures参数只是应该检索的最大关键点数。 请注意,检测到的关键点数量可能比此数量少很多,但永远不会更高。

  • ScaleFactor或金字塔抽取比率,与我们先前算法中看到的八度参数有些相似,用于确定金字塔每个级别的尺度值,这些尺度将用于检测关键点并从不同尺度提取同一张图片的描述符。 这就是在 ORB 中实现尺度不变性的方式。

  • NLevels是金字塔的等级数。

  • PatchSize是 ORB 算法使用的补丁的大小。 有关此内容的详细信息,请确保参考以下参考文献,但是对于简短说明,补丁大小决定了要提取描述的关键点周围的区域。 请注意,PatchSizeEdgeThreshold参数需要大约相同的值,在前面的示例中也将其设置为相同的值。

  • EdgeThreshold是在关键点检测期间将忽略的以像素为单位的边框。

  • WTA_K或 ORB 算法内部使用的 WTA 哈希的 K 值是一个参数,用于确定将用于在 ORB 描述符中创建每个元素的点数。 我们将在本章稍后看到更多有关此的内容。

  • 可以设置为以下值之一的

    ScoreType
    

    决定 ORB 算法使用的关键点检测方法:

    • ORB::HARRIS_SCORE用于哈里斯角点检测算法
    • ORB::FAST_SCORE用于 FAST 关键点检测算法
  • FastThreshold只是 ORB 在关键点检测算法中使用的阈值。

提取和匹配描述符

计算机视觉中的描述符是一种描述关键点的方式,该关键点完全依赖于用于提取关键点的特定算法,并且与关键点(在KeyPoint类中定义的)不同,描述符没有共同的结构 ,除了每个描述符都代表一个关键点这一事实外。 OpenCV 中的描述符存储在Mat类中,其中生成的描述符Mat类中的每一行都引用关键点的描述符。 正如我们在上一节中了解到的,我们可以使用任何FeatureDetector子类的detect函数从图像上基本上检测出一组关键点。 同样,我们可以使用任何DescriptorExtractor子类的compute函数从关键点提取描述符。

由于特征检测器和描述符提取器在 OpenCV 中的组织方式(这都是Feature2D子类,正如我们在本章前面了解的那样),令人惊讶的是,将它们结合使用非常容易。 在本节中看到,我们将使用完全相同的类(或更确切地说,也提供描述符提取方法的类)从我们在上一节中使用各种类发现的关键点中提取特征描述符,以在场景图像中找到对象。 重要的是要注意,并非所有提取的关键点都与所有描述符兼容,并且并非所有算法(在这种情况下为Feature2D子类)都提供detect函数和compute函数。 不过,这样做的人还提供了detectAndCompute函数,可以一次性完成关键点检测和特征提取,并且比分别调用这两个函数要快。 让我们从第一个示例案例开始,以便使所有这些变得更加清晰。 这也是匹配两个单独图像的特征所需的所有步骤的示例,这些图像可用于检测,比较等:

  1. 首先,我们将使用 AKAZE 算法(使用上一节中学习的AKAZE类)从以下图像中检测关键点:

我们可以使用以下代码从两个图像中提取关键点:

      using namespace cv; using namespace std; Mat image1 = imread("image1.jpg"); Mat image2 = imread("image2.jpg"); Ptr<AKAZE> akaze = AKAZE::create(); // set AKAZE params ... vector<KeyPoint> keypoints1, keypoints2; akaze->detect(image1, keypoints1); akaze->detect(image2, keypoints2); 
  1. 现在我们有了两个图像的特征(或关键点),我们可以使用相同的AKAZE类实例从这些关键点提取描述符。 这是完成的过程:
        Mat descriptor1, descriptor2; akaze->compute(image1, keypoints1, descriptor1); akaze->compute(image2, keypoints2, descriptor2); 
  1. 现在,我们需要匹配两个图像,这是两个图像中关键点的描述符。 为了能够执行描述符匹配操作,我们需要在 OpenCV 中使用一个名为DescriptorMatcher(非常方便)的类。 需要特别注意的是,此匹配器类需要设置为正确的类型,否则,将不会得到任何结果,或者甚至可能在运行时在应用中遇到错误。 如果在本示例中使用 AKAZE 算法来检测关键点并提取描述符,则可以在DescriptorMatcher中使用FLANNBASED类型。 这是完成的过程:
        descMather = DescriptorMatcher::create( DescriptorMatcher::FLANNBASED); 

请注意,您可以将以下值之一传递给DescriptorMatcher的创建函数,并且这完全取决于您用来提取描述符的算法,显然,因为将对描述符执行匹配。 您始终可以参考每种算法的文档,以了解可用于任何特定描述符类型的算法,例如AKAZEKAZE之类的算法具有浮点类型描述符,因此可以将FLANNBASED与他们一起使用; 但是,具有String类型的描述符(例如ORB)将需要与描述符的汉明距离匹配的匹配方法。 以下是可用于匹配的现有方法:

  • FLANNBASED
  • BRUTEFORCE
  • BRUTEFORCE_L1
  • BRUTEFORCE_HAMMING
  • BRUTEFORCE_HAMMINGLUT
  • BRUTEFORCE_SL2

当然,最坏的情况是,当您不确定不确定时,尝试为每种特定的描述符类型找到正确的匹配算法时,只需简单地尝试每个。

  1. 现在,我们需要调用DescriptorMatchermatch函数,以尝试将第一张图像(或需要检测的对象)中找到的关键点与第二张图像(或可能包含我们的对象的场景)中的关键点进行匹配。 match函数将需要一个DMatch向量,并将所有匹配结果填充其中。 这是完成的过程:
        vector<DMatch> matches; descMather->match(descriptor1, descriptor2, matches); 

DMatch类是简单类,仅用作保存匹配结果数据的结构:

在这里插入图片描述

  1. 在深入研究如何解释匹配操作的结果之前,我们将学习如何使用drawMatches函数。 与drawKeypoints函数相似,drawMatches可用于自动创建适合显示的输出结果。 这是如何做:
     drawMatches(inputImage,keypoints1,secondImage,keypoints2,matches,outputImage,Scalar(0, 255, 0), // green for matchedScalar::all(-1), // unmatched color (default)vector<char>(), // empty maskDrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

在前面的代码中,dispImg显然是可以显示的Mat类。 这是结果图像:
在这里插入图片描述

现在,颜色更适合我们在这里使用的颜色。 还应注意一些很不正常的不正确匹配,可以通过修改 KAZE 算法的参数甚至使用其他算法来解决。 现在让我们看看如何解释匹配结果。

  1. 解释匹配结果完全取决于用例。 例如,如果我们要匹配两个具有相同大小和相同内容类型的图像(例如人脸,相同类型的对象,指纹等),则我们可能需要考虑距离值高于某个阈值的,匹配的关键点数量。 或者,就像在当前示例中一样,我们可能希望使用匹配来检测场景中的对象。 这样做的一种常见方法是尝试找出匹配关键点之间的单应性变化。 为此,我们需要执行以下三个操作:

    • 首先,我们需要过滤出匹配结果,以消除较弱的匹配,换句话说,仅保留良好的匹配; 同样,这完全取决于您的场景和对象,但是通常,通过几次尝试和错误,您可以找到最佳阈值
    • 接下来,我们需要使用findHomography函数来获得好关键点之间的单应性变化
    • 最后,我们需要使用perspectiveTransform将对象边界框(矩形)转换为场景

这篇关于OpenCV3 和 Qt5 计算机视觉 学习笔记 - 特征和描述符的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/900496

相关文章

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能