Opencv 关键点和描述符(二)—— 通用关键点和描述符
Opencv 关键点和描述符(三)—— 核心关键点检测方法
corners:包含大量本地信息的像素块,并能够在另一张图中被快速识别
keypoints:作为 corners 的扩展,它将像素块的信息进行编码从而使得更易辨识,至少在原则上唯一
descriptors:它是对 keypoints 进一步处理的结果。通常它具有更低的维度,从而使得图像块能够在另一幅不同的图像中被更快地识别。
为了实现上述图像的区分和辨识的功能,有很多特性可以使用。对于毫无变化的白墙上的点就不是好的特性,因此像素存在较大变化的点才可能成为特性点。不过由于光流的存在通常一条边通常也不足够构成一个特性。因此,最简单的特性就是角,角中主要的信息来自于两条不同方向边的交叉点中。
Opencv 提供了一个 cv::goodFeaturesToTrack() 函数来计算微分,并分析返回一系列的特征点,从而实现较好地跟踪。
void cv::goodFeaturesToTrack(
cv::InputArray image, // Input, CV_8UC1 or CV_32FC1
cv::OutputArray corners, // Output vector of corners
int maxCorners, // Keep this many corners
double qualityLevel, // (fraction) rel to best
double minDistance, // Discard corner this close
cv::InputArray mask = noArray(), // Ignore corners where mask=0
int blockSize = 3, // Neighborhood used
bool useHarrisDetector = false, // false='Shi Tomasi metric'
double k = 0.04 // Used for Harris metric
);
参数说明:
亚像素角
角的位置不再以像素的形式给出,而是通过分析估计一个更精确的位置,通常将不再为整数。
void cv::cornerSubPix(
cv::InputArray image, // Input image
cv::InputOutputArray corners, // Guesses in, and results out
cv::Size winSize, // Area is NXN; N=(winSize*2+1)
cv::Size zeroZone, // Size(-1,-1) to ignore
cv::TermCriteria criteria // When to stop refinement
);
参数说明:
其基本原理是亚像素角点到附近像素点的向量与选取的附近像素点梯度的内积(当选取的附近像素点在平缓区域或者处于边界出)都为零,即
光流问题涉及去找出有多少点从第一张视图移动到了第二张视图中。光流可以被用于物体移动估计或者相机移动估计中。而如果需要估计图像中每个像素点的速度和位置,被称为密集光流;而只估计其中某一些点的速度,则被称为稀疏光流。
稀疏光流的 Lucas-Kanade 方法
Lucas-Kanade 方法最开始提出被用来解决密集光流问题,但由于算法只依赖小范围内的信息,因此能够很方便地被迁移来做稀疏光流问题。同时层级化LK方法也很好地解决了目标运动过快超过局部区域的问题。
Lucas-Kanade 方法原理
LK 方法基于以下假设:
首先对于一维图像,由于时间和空间的微分都很小,因此下式成立。
同时考虑图像的亮度和采样率即使不能完全满足假设,在第一次得到近似值之后也可以在之后的处理中进一步迭代从而更加满足图像亮度和采样率的一致性,得到更精确的结果。
而对于二维图像,由于有两个速度需要估计,而只有一个等式,因此只能求得整体速度的大小而不能得到最后的具体的方向。
此时,我们可以选取特征点周围多个像素点分别建立相应的等式从而进行求解,比如 5 * 5 的窗
最后得到计算公式
当 是可逆矩阵,即存在两个较大的特征值,物体(纹理)在两个方向都有运动时,就可以对两个方向的速度进行求解。这也是为什么上面的 cv::goodFeaturesToTrack() 必须选取角作为特性点的原因。
同时为了解决速度过快,采样率不足等问题,通常采用层级化 LK 方法。它从最顶端开始对速度进行估计,并随着不断向下,速度的精度将不断提高。
cv::calcOpticalFlowPyrLK() 函数。基本的逻辑是提供图像和需跟踪的点,然后调用函数,检查状态位 status 判断哪些点被成功地跟踪,从而在 nextPts 中获取跟踪点新的位置。
void cv::calcOpticalFlowPyrLK(
cv::InputArray prevImg, // Prior image (t-1), CV_8UC1
cv::InputArray nextImg, // Next image (t), CV_8UC1
cv::InputArray prevPts, // Vector of 2d start points (CV_32F)
cv::InputOutputArray nextPts, // Results: 2d end points (CV_32F)
cv::OutputArray status, // For each point, found=1, else=0
cv::OutputArray err, // Error measure for found points
cv::Size winSize = Size(15, 15), // size of search window
int maxLevel = 3, // Pyramid layers to add
cv::TermCriteria criteria = TermCriteria( // How to end search
cv::TermCriteria::COUNT | cv::TermCriteria::EPS,
30,
0.01
),
int flags = 0, // use guesses, and/or eigenvalues
double minEigThreshold = 1e-4 // for spatial gradient matrix
);
参数说明:
cv::TermCriteria 介绍
struct cv::TermCriteria(
public:
enum {
COUNT = 1,
MAX_ITER = COUNT,
EPS = 2
};
TermCriteria();
TermCriteria(int _type, int_maxCount, double _epsilon);
int type, // one of the enum types above
int max_iter,
double epsilon
);
// Example 16-1. Pyramid L-K optical flow
//
#include
#include
#include
static const int MAX_CORNERS = 1000;
using std::cout;
using std::endl;
using std::vector;
void help( char** argv ) {
cout << "\nExample 16-1: Pyramid L-K optical flow example.\n" << endl;
cout << "Call: " < cornersA, cornersB;
const int MAX_CORNERS = 500;
cv::goodFeaturesToTrack(
imgA, // Image to track
cornersA, // Vector of detected corners (output)
MAX_CORNERS, // Keep up to this many corners
0.01, // Quality level (percent of maximum)
5, // Min distance between corners
cv::noArray(), // Mask
3, // Block size
false, // true: Harris, false: Shi-Tomasi
0.04 // method specific parameter
);
cv::cornerSubPix(
imgA, // Input image
cornersA, // Vector of corners (input and output)
cv::Size(win_size, win_size), // Half side length of search window
cv::Size(-1, -1), // Half side length of dead zone (-1=none)
cv::TermCriteria(
cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
20, // Maximum number of iterations
0.03 // Minimum change per iteration
)
);
// Call the Lucas Kanade algorithm
//
vector features_found;
cv::calcOpticalFlowPyrLK(
imgA, // Previous image
imgB, // Next image
cornersA, // Previous set of corners (from imgA)
cornersB, // Next set of corners (from imgB)
features_found, // Output vector, each is 1 for tracked
cv::noArray(), // Output vector, lists errors (optional)
cv::Size(win_size * 2 + 1, win_size * 2 + 1), // Search window size
5, // Maximum pyramid level to construct
cv::TermCriteria(
cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
20, // Maximum number of iterations
0.3 // Minimum change per iteration
)
);
// Now make some image of what we are looking at:
// Note that if you want to track cornersB further, i.e.
// pass them as input to the next calcOpticalFlowPyrLK,
// you would need to "compress" the vector, i.e., exclude points for which
// features_found[i] == false.
for (int i = 0; i < static_cast(cornersA.size()); ++i) {
if (!features_found[i]) {
continue;
}
line(
imgC, // Draw onto this image
cornersA[i], // Starting here
cornersB[i], // Ending here
cv::Scalar(0, 255, 0), // This color
1, // This many pixels wide
cv::LINE_AA // Draw line in this style
);
}
cv::imshow("ImageA", imgA);
cv::imshow("ImageB", imgB);
cv::imshow("LK Optical Flow Example", imgC);
cv::waitKey(0);
return 0;
}