检测角点 - sumpig/OpenCV GitHub Wiki


Harris 特征检测

角点是两条边缘线的接合点,是一种二维特征。Harris 特征检测是检测角点的经典方法。

若一个像素在一个预先定义的邻域里,计算其平均强度变化值,如果不止一个方向的变化值很高,就认为这个点是角点。

根据这个定义,Harris 测试的步骤为:首先获得平均强度值变化最大的方向,然后检查垂直方向上的平均强度变化值,看它是否也很大;如果是,就说明这是一个角点。


cv::cornerHarris

// 检测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);

GFTT

还可以对原始 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;
}


API说明

cv::threshold

对输入单通道矩阵逐像素进行固定阈值分割。典型应用是从灰度图像获取二值图像,或消除灰度值过大或过小的噪声。有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:过门限的值置零,其它值不变;


cv::minMaxLoc

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: 可有可无的掩模;


cv::dilate

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

主要用于两个图像之间进行逐像素的比较,并输出比较的结果。

cv::compare()
	bool cv::compare(
	cv::InputArray src1, // 输入数组1
	cv::InputArray src2, // 输入数组2
	cv::OutputArray dst, // 输出数组
	int cmpop // 比较操作子,见下表 
);

cv::bitwise_and

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());

⚠️ **GitHub.com Fallback** ⚠️