当进行跟踪时或者其他类型的用到关键点及其描述符的分析时,通常需要做三件事情:第一个是根据一些关键点的定义搜索图像并查找该图像中的所有关键点;第二个是为发现的每个关键字创建一个描述符;第三个是通过将所找到的关键点的描述符与一些现有的描述符集进行比较,看看是否可以找到匹配项。
在跟踪应用程序中,最后一步涉及查找序列的一帧图像中的特征,并尝试将其与前一帧中的特征进行匹配。在目标检测应用程序中,人们通常会在一些(潜在的广泛)数据库中搜索“已知”特征的“已知”与各个目标或目标类相关联的特征。
对于这些层中的每一个,OpenCV提供了一个遵循“做东西的类”(函子)模型的泛化机制;对于这些阶段中的每一个都有一个抽象基类,它为从其派生的一系列对象定义一个公共接口;最后每个派生类都实现一个特定的算法。
1、cv :: KeyPoint对象
class cv::KeyPoint {
public:
cv::Point2f pt; // coordinates of the keypoint
float size; // diameter of the meaningful keypoint neighborhood
float angle; // computed orientation of the keypoint (-1 if none)
float response; // response for which the keypoints was selected
int octave; // octave (pyramid layer) keypoint was extracted from
int class_id; // object id, can be used to cluster keypoints by object
cv::KeyPoint(
cv::Point2f _pt,
float _size,
float _angle=-1,
float _response=0,
int _octave=0,
int _class_id=-1
);
cv::KeyPoint(
float x,
float y,
float _size,
float _angle=-1,
float _response=0,
int _octave=0,
int _class_id=-1
);
...
};
3种重载都包括了cv :: Point2f的信息,此外还包括其他一些能够说明“特征”的属性。
2、cv :: Feature2D
class cv::Feature2D : public cv::Algorithm {
public:
virtual void detect(
cv::InputArray image, // Image on which to detect
vector< cv::KeyPoint >& keypoints, // Array of found keypoints
cv::InputArray mask = cv::noArray()
) const;
virtual void detect(
cv::InputArrayOfArrays images, // Images on which to detect
vector<vector< cv::KeyPoint > >& keypoints, // keypoints for each image
cv::InputArrayOfArrays masks = cv::noArray ()
) const;
virtual void compute(
cv::InputArray image, // Image where keypoints are located
std::vector<cv::KeyPoint>& keypoints, // input/output vector of keypoints
cv::OutputArray descriptors ); // computed descriptors, M x N matrix,
// where M is the number of keypoints
// and N is the descriptor size
virtual void compute(
cv::InputArrayOfArrays image, // Images where keypoints are located
std::vector<std::vector<cv::KeyPoint> >& keypoints, //I/O vec of keypnts
cv::OutputArrayOfArrays descriptors ); // computed descriptors,
// vector of (Mi x N) matrices, where
// Mi is the number of keypoints in
// the i-th image and N is the
// descriptor size
virtual void detectAndCompute(
cv::InputArray image, // Image on which to detect
cv::InputArray mask, // Optional region of interest mask
std::vector<cv::KeyPoint>& keypoints, // found or provided keypoints
cv::OutputArray descriptors, // computed descriptors
bool useProvidedKeypoints=false ); // if true,
// the provided keypoints are used,
// otherwise they are detected
virtual int descriptorSize() const; // size of each descriptor in elements
virtual int descriptorType() const; // type of descriptor elements
virtual int defaultNorm() const; // the recommended norm to be used
// for comparing descriptors.
// Usually, it's NORM_HAMMING for
// binary descriptors and NORM_L2
// for all others.
virtual void read( const cv::FileNode& );
virtual void write( cv::FileStorage& ) const;
...
};
复杂的定义,注意Feature2D派生自alogorithm算法类。
a、如果它是一个单纯的关键点检测算法(如FAST),实际的实现可能只是执行cv :: Feature2D :: detect(),;
b、如果它是一个纯特征描述算法(如FREAK),实际的实现可能只是cv :: Feature2D :: compute();
c、在要求“完全解决”的算法的情况下,如SIFT,SURF,ORB,BRISK等,实际的实现就会是cv :: Feature2D :: detectAndCompute(),在这种情况下,detect()和compute()会被隐式地调用。
对于具体每一种算法,查找关键点的实际方法当然对于cv :: Feature2D的许多可用派生类中的每一个都是不同的。
值得注意的是,根据算法何时实现,许多关键点特征检测器和提取器都设计在一起成为一个对象。在这种情况下,方法cv::Feature2D::detectAndCompute() 被实现。每当某个算法提供了detectAndCompute()时,强烈建议直接使用它,而不是在后续调用detect(),然后再使用compute()。原因很明显,就是性能更好。
3、cv :: DMatch
寻找到的特征结果需要进行匹配,以方便下一步的计算。
class cv::DMatch {
public:
DMatch(); // sets this->distance
// to std::numeric_limits<float>::max()
DMatch( int _queryIdx, int _trainIdx, float _distance );
DMatch( int _queryIdx, int _trainIdx, int _imgIdx, float _distance );
int queryIdx; // query descriptor index
int trainIdx; // train descriptor index
int imgIdx; // train image index
float distance;
bool operator<( const DMatch &m ) const; // Comparison operator
// based on 'distance'
}
cv :: DMatch的数据成员是queryIdx,trainIdx,imgIdx和distance。前两个辨别与每个图像中的关键点列表匹配的关键点。 imgIdx用于辨别在图像和字典之间寻求匹配的情况下训练图像来自的特定图像。最后一个成员,distance,用来表示匹配的质量。
4、cv :: DescriptorMatcher(关键点匹配类)
一般有两种不同的情况:对于目标识别的情况,我们需要首先用描述符的字典训练匹配器,然后才能向匹配器呈现单个描述符列表,并告诉我们匹配器存储的哪个(如果有的话)关键点是与我们提供的列表中的那些相匹配;对于跟踪的情形,我们需要提供两个描述符列表,然后匹配器告诉我们它们之间的匹配位置。
cv:DescriptorMatcher类接口提供了三个函数match(),knnMatch()和 radiusMatch();对于每个函数,有两个不同的变形 - 一个用于识别(需要一个特征列表并使用经过训练的字典),另一个用于跟踪(需要两个特征列表)。
部分定义:
class
cv
:
:
DescriptorMatcher {
public:
virtual void add( InputArrayOfArrays descriptors ); // Add train descriptors
virtual void clear(); // Clear train descriptors
virtual bool empty() const; // true if no descriptors
void train(); // Train matcher
virtual bool isMaskSupported() const = 0; // true if supports masks
const vector<cv::Mat>& getTrainDescriptors() const; // Get train descriptors
// methods to match descriptors from one list vs. "trained" set (recognition)
//
void match(
InputArray queryDescriptors,
vector<cv::DMatch>& matches,
InputArrayOfArrays masks = noArray ()
);
void knnMatch(
InputArray queryDescriptors,
vector< vector<cv::DMatch> >& matches,
int k,
InputArrayOfArrays masks = noArray (),
bool compactResult = false
);
void radiusMatch(
InputArray queryDescriptors,
vector< vector<cv::DMatch> >& matches,
float maxDistance,
InputArrayOfArrays masks = noArray (),
bool compactResult = false
);
// methods to match descriptors from two lists (tracking)
//
// Find one best match for each query descriptor
void match(
InputArray queryDescriptors,
InputArray trainDescriptors,
vector<cv::DMatch>& matches,
InputArray mask = noArray ()
) const;
// Find k best matches for each query descriptor (in increasing
// order of distances)
void knnMatch(
InputArray queryDescriptors,
InputArray trainDescriptors,
vector< vector<cv::DMatch> >& matches,
int k,
InputArray mask = noArray(),
bool compactResult = false
) const;
// Find best matches for each query descriptor with distance less
// than maxDistance
void radiusMatch(
InputArray queryDescriptors,
InputArray trainDescriptors,
vector< vector<cv::DMatch> >& matches,
float maxDistance,
InputArray mask = noArray (),
bool compactResult = false
) const;
virtual void read( const FileNode& ); // Reads matcher from a file node
virtual void write( FileStorage& ) const; // Writes matcher to a file storage
virtual cv::Ptr<cv::DescriptorMatcher> clone(
bool emptyTrainData=false
) const = 0;
static cv::Ptr<cv::DescriptorMatcher> create(
const string& descriptorMatcherType
);
...
};
简单比较,match()方法需要通常的cv :: Mat格式的单个关键点描述符列表queryDescriptors。在这种情况下,请记住每行代表单个描述符,每列是该描述符向量表示的一个维度。
knnMatch() 函数,它需要与match() 相同的列表描述符。然而,在这种情况下,对于查询列表中的每个描述符,它将从字典中找到特定数量的最佳匹配。
第三种匹配方法是radiusMatch()。 与搜索k个最佳匹配的k-最近邻匹配不同,半径匹配返回了查询描述符特定距离内的所有匹配。
5、序列化
read()和write()方法分别对应cv :: FileNode和cv :: FileStorage格式的对象,并允许你从磁盘读取和写入匹配器;
clone() 和create() 方法允许你分别创建一个描述符的副本或者通过名称创建一个新的描述符;
感谢阅读至此,希望有所帮助。
目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com