检测角点 - sumpig/OpenCV GitHub Wiki
角点是两条边缘线的接合点,是一种二维特征。Harris 特征检测是检测角点的经典方法。
若一个像素在一个预先定义的邻域里,计算其平均强度变化值,如果不止一个方向的变化值很高,就认为这个点是角点。
根据这个定义,Harris 测试的步骤为:首先获得平均强度值变化最大的方向,然后检查垂直方向上的平均强度变化值,看它是否也很大;如果是,就说明这是一个角点。
// 检测Harris 角点
cv::Mat cornerStrength;
cv::cornerHarris(image, // 输入图像
cornerStrength, // 角点强度的图像
3, // 邻域尺寸
3, // 口径尺寸
0.01); // Harris 参数
口径表示计算 Sobel 滤波器时用的口径。
Harris 参数根据经验, 0.05~0.5 通常是比较好的选择。
为了提升检测效果,需要增加了一个额外的非最大值抑制步骤,作用是排除掉紧邻的 Harris 角点。因此,Harris 角点不仅要有高于指定阈值的评分,还必须是局部范围内的最大值。为了检查这个条件,有一个小技巧,即对 Harris 评分的图像做膨胀运算:
cv::dilate(cornerStrength, dilated,cv::Mat());
膨胀运算会在邻域中把每个像素值替换成最大值,因此只有局部最大值的像素是不变的。用下面的相等测试可以验证这一点:
cv::compare(cornerStrength, dilated, localMax,cv::CMP_EQ);
因此只有在局部最大值的位置才为真(即非零)。然后排除掉所有非最大值的特征(用cv::bitwise 函数)。
检测 Harris 角点需要两个步骤。首先是计算每个像素的 Harris 值:
// 计算Harris 角点
void detect(const cv::Mat& image) {
cv::cornerHarris(image,
cornerStrength, // 32 位浮点数型的角点强度图像
neighborhood, // 邻域尺寸
aperture, // 口径尺寸
k); // Harris 参数
// 计算内部阈值
cv::minMaxLoc(cornerStrength, 0, &maxStrength); // 阈值计算的最大强度
// 检测局部最大值
cv::Mat dilated; // 临时图像
cv::dilate(cornerStrength, dilated, cv::Mat());
cv::compare(cornerStrength, dilated, localMax, cv::CMP_EQ);
}
然后,用指定的阈值获得特征点。因为 Harris 值的可选范围取决于选择的参数,所以阈值被作为质量等级,用最大 Harris 值的一个比例值表示:
// 用 Harris 值得到角点分布图
cv::Mat getCornerMap(double qualityLevel) {
cv::Mat cornerMap;
// 对角点强度阈值化
threshold = qualityLevel * maxStrength;
cv::threshold(cornerStrength, cornerTh, threshold, 255, cv::THRESH_BINARY);
// 转换成 8 位图像
cornerTh.convertTo(cornerMap, CV_8U);
// 非最大值抑制
cv::bitwise_and(cornerMap, localMax, cornerMap);
return cornerMap;
}
这个方法将返回一个被检测特征的二值角点分布图。因为 Harris 特征的检测过程分为两个方法,所以我们可以用不同的阈值来测试检测结果(直到获得适当数量的特征点),而不必重复进行耗时的计算过程。当然,你也可以从以 std::vector 形式表示的 cv::Point 实例中得到 Harris 特征:
// 用 Harris 值得到特征点
void getCorners(std::vector<cv::Point> &points, double qualityLevel) {
// 获得角点分布图
cv::Mat cornerMap= getCornerMap(qualityLevel);
// 获得角点
getCorners(points, cornerMap);
}
// 用角点分布图得到特征点
void getCorners(std::vector<cv::Point> &points, const cv::Mat& cornerMap) {
// 迭代遍历像素,得到所有特征
for( int y = 0; y < cornerMap.rows; y++ ) {
const uchar* rowPtr = cornerMap.ptr<uchar>(y);
for( int x = 0; x < cornerMap.cols; x++ ) {
// 如果它是一个特征点
if (rowPtr[x]) {
points.push_back(cv::Point(x,y));
}
}
}
}
现在可以用cv::circle 函数画出检测到的特征点,方法如下所示:
// 在特征点的位置画圆形
void drawOnImage(cv::Mat &image, const std::vector<cv::Point> &points, cv::Scalar color= cv::Scalar(255,255,255), int radius=3, int thickness=1) {
std::vector<cv::Point>::const_iterator it= points.begin();
// 针对所有角点
while (it!=points.end()) {
// 在每个角点位置画一个圆
cv::circle(image,*it,radius,color,thickness);
++it;
}
}
使用这个类检测Harris 特征点的方法如下所示:
// 创建Harris 检测器实例
HarrisDetector harris;
// 计算Harris 值
harris.detect(image);
// 检测Harris 角点
std::vector<cv::Point> pts;
harris.getCorners(pts,0.02);
// 画出Harris 角点
harris.drawOnImage(image,pts);
还可以对原始 Harris 角点检测算法做进一步的优化。OpenCV 的另一种角点检测方法扩展了 Harris 检测法,可以使角点在图像中的分布更加均匀。
在 OpenCV 中用 good-features-to-track(GFTT)实现这个算法。它限制两个兴趣点之间的最短距离,从 Harris 值最强的点开始(即具有最大的最低特征值),只允许一定距离之外的点成为兴趣点。它的使用方法如下所示:
// 计算适合跟踪的特征
std::vector<cv::KeyPoint> keypoints;
// GFTT 检测器
cv::Ptr<cv::GFTTDetector> ptrGFTT =
cv::GFTTDetector::create(
500, // 关键点的最大数量
0.01, // 质量等级
10); // 角点之间允许的最短距离
// 检测GFTT
ptrGFTT->detect(image, keypoints);
// 画图
std::vector<cv::KeyPoint>::const_iterator it= keypoints.begin();
while (it!=keypoints.end()) {
cv::circle(image,it->pt,3,cv::Scalar(255,255,255),1);
++it;
}
对输入单通道矩阵逐像素进行固定阈值分割。典型应用是从灰度图像获取二值图像,或消除灰度值过大或过小的噪声。有5种阈值分割类型,由参数thresholdType决定。
double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)
src: 输入矩阵对矩阵src中的每个元素应用固定阈值分割;
dst: 输出矩阵;
thresh: 阈值;
maxVal: 设置的最大值;
thresholdType: 阈值类型;
THRESH_BINARY:过门限的值设置为maxVal,其它值置零;
THRESH_TRUNC:过门限的值设置为门限值,其它值置不变;
THRESH_TRUNC:过门限的值设置为门限值,其它值置不变;
THRESH_TOZERO:过门限的值不变,其它值置零;
THRESH_TOZERO_INV:过门限的值置零,其它值不变;
void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray())
minVal: 最小值,可輸入NULL表示不需要;
maxVal : 最大值,可輸入NULL表示不需要;
minLoc: 最小值的位置,可输入NULL表示不需要,Point类型;
maxLoc: 最大值的位置,可输入NULL表示不需要,Point类型;
mask: 可有可无的掩模;
erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。
void dilate(
InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor=Point(-1,-1),
int iterations=1,
int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue()
);
kernel: 膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。
主要用于两个图像之间进行逐像素的比较,并输出比较的结果。
cv::compare()
bool cv::compare(
cv::InputArray src1, // 输入数组1
cv::InputArray src2, // 输入数组2
cv::OutputArray dst, // 输出数组
int cmpop // 比较操作子,见下表
);
bitwise_and 是对二进制数据进行“与”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,1&1=1,1&0=0,0&1=0,0&0=0
void bitwise_and(InputArray src1, InputArray src2,OutputArray dst, InputArray mask=noArray());