opencv学习笔记二:角点检测
角点检测被广泛应用于运动检测,图像匹配,视频跟踪,三维建模和目标识别等领域,也成为特征点检测。
角点通常被定义为两条边的角点,但也可以是图像中的一些数学特征点,如局部最大或最小灰度,某些梯度特征等。
我认为角点检测需要学习的不是简单的函数引用,因为现有的角点检测算法并不是十分强壮,而角点检测的方法是有很大的自定义空间的,并不非要使用Harris角点检测或者Shi-Tomasi角点检测,当然使用这两个方法很方便实用,但必要时我们也可以试着自己定义满足自己需要的角点,这就需要对角点的理论知识,数学知识有很大的理解。
当前角点检测算法可以归纳为以下三类:
- 基于灰度图像的角点检测
- 基于二值图像的角点检测
- 基于轮廓曲线的角点检测
基于灰度图的角点检测又可分为:
- 基于梯度
- 基于模板 :Kitchen-Rosenfeld角点检测算法,Harris,KLT角点检测算法,SUSAN角点检测
- 基于模板梯度组合
Harris角点检测:
引言(抄书)
兴趣点,也叫做关键点,特征点,是我们常常需要提取出来,并进行处理,识别的特征,如果能检测到足够多的这种点,同时他们的区分度喝高,并且可以精确定位的特征,那么这个方法就有实用价值。
图像的特征可以分为如下三类:
- 边缘
- 角点(感兴趣关键点)
- 斑点(Blobs)(感兴趣区域)
如果某一点在任意方向的一个微小变动都会引起灰度很大的变换,那么我们就把它称之为角点。
另外,角点的具体描述可以有如下几种:
- 一阶导数(即灰度的梯度)的局部最大所对应的像素点
- 两条及两条以上的边缘的交点
- 图像中梯度值和梯度方向的变化速率都很高的点
- 角点出的一阶导数最大,二阶导数为零,它指示了物体边缘变换不连续的方向
Hattis角点检测算法是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高。但由于采用的高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,而且角点提取有聚簇现象。
cornerHarris(gray_src, dst, //输入输出图像 输入为单通道8位图像
int blockSize, //计算&1 &2 时的矩阵大小
int ksize, //窗口大小
double k, //计算角度响应时的参数大小 ,一般为0.04-0.06
int BORDER_DEFAULT); //阈值t,用来过滤角度相应
综合示例:Harris角点检测与绘制
首先,定义主函数:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//定义一些辅助宏
#define WINDOW_NAME1 "【程序窗口1】"
#define WINDOW_NAME2 "【程序窗口2】"
//全局变量声明
Mat g_srcImage, g_srcImage1, g_grayImage;
int thresh = 30;
int max_thresh = 175;
void on_CornerHarris(int, void*);
int main()
{
//【1】读取原图并克隆保存
g_srcImage = imread("1.jpg", 1);
if (g_srcImage.empty()) {
printf("读取图片错误\n");
return -1;
}
imshow("原图", g_srcImage);
g_srcImage1 = g_srcImage.clone();
//【2】生成灰度图像
cvtColor(g_srcImage1, g_grayImage, COLOR_BGR2GRAY);
//【3】创建窗口和滚动条
namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
createTrackbar("阈值:", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris);
//【4】调用一次回调函数进行初始化
on_CornerHarris(0, 0);
waitKey();
return 0;
}
然后,定义回调函数:
//回调函数
void on_CornerHarris(int, void*)
{
Mat dstImage; //目标图
Mat normImage; //归一化后的图
Mat scaledImage; //线性变换后的八位无符号整型图
//初始化两张图
dstImage = Mat::zeros(g_srcImage.size(), CV_32FC1);
g_srcImage1 = g_srcImage.clone();
//进行角点检测
cornerHarris(g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT);
//归一化和线性变换:将图片二值化
normalize(dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
convertScaleAbs(normImage, scaledImage);
//画圆标记
for (int j = 0; j < normImage.rows; j++)
{
for (int i = 0; i < normImage.cols; i++)
{
if ((int)normImage.at<float>(j, i) > thresh + 80)
{
circle(g_srcImage1, Point(i, j), 5, Scalar(10, 10, 255), 2, 8, 0);
circle(scaledImage, Point(i, j), 5, Scalar(10, 10, 255), 2, 8, 0);
}
}
}
//显示
imshow(WINDOW_NAME1, g_srcImage1);
imshow(WINDOW_NAME2,scaledImage);
}
回调函数先对图像进行角点检测,再用normalize()函数将角点检测的图像归一化,这时图像像素值被归一化到0-1,再用convertScaleAbs()函数进行线性变换为8为无符号整型,及二值化,这时再显示出来的就是黑色背景下的角点
normalize()函数原型:
void cv::normalize(
InputArry src, #输入数组
InputOutputArray dst, #输出数组
double alpha=1, #1:用来规范值 2:规范范围,并且是下限
double beta=0, #规范范围,上限
int norm_type=NORM_L2, #归一化选择的数学公式类型
int dtype=-1, #当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输如不同,不同的地方游dtype决定
InputArray mark=noArry() #掩码。选择感兴趣区域,选定后只能对该区域进行操作
)
convertScaleAbs()函数原型:
该函数可以用来对图像进行增强操作
void cv::convertScaleAbs(
cv::InputArray src, // 输入数组
cv::OutputArray dst, // 输出数组
double alpha = 1.0, // 乘数因子
double beta = 0.0 // 偏移量
);
亚像素级角点检测
若我们进行图像处理的目的不是提取用于识别的特征点而是进行集合测量,这通常需要更高的精度,而函数goodFeaturesToTrack()只能提供简单的像素的坐标值,也就是说,有时候需要实数坐标值而不是整数坐标值
寻找亚像素角点:connerSubPix()函数
cornerSubPix(
InputArray image, //输入图像
InputOutputArray corners, //提供输入角点的初始坐标和精确的输出坐标
Size winSize, //搜索窗口大小
Size zeroZone, //死区大小
TermCriteria criteria //求角点的迭代过程的终止条件
)
使用示范:
//【1】亚像素角点检测的参数设置
Size winSize = Size(5, 5); //设置搜索窗口
Size zeroZone = Size(-1, -1); //设置死区大小
//设置角点迭代过程的终止条件
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
//【2】计算出亚像素角点位置
cornerSubPix(g_grayImage, corners, winSize, zeroZone, criteria);
//【3】输出角点信息
for (int i = 0; i < corners.size(); i++)
{
cout << " \t>>精确角点坐标[" << i << "] (" << corners[i].x << "," << corners[i].y << ")" << endl;
}
自定义角点检测函数:
自定义角点检测需要用到两个函数:
cornerMinEigenVal()函数:
计算梯度矩阵的最小特征值,用于角点检测
void cv::CornerMinEigenVal(
image,eigenval, //输入图像 ,保存最小特征的图像
int block_size, //邻域大小
int aperture_size = 3 //Sobel算子的核尺寸
)
minMaxLoc()函数:
查找数组和子数组的全局最小值和最大值
void cv::MinMaxLoc(
arr , //输入单通道数组
double* min_val, //返回最小值的指针
double* max_val,//返回最大值的指针
Point* min_loc,//指向返回最小值的位置指针
Point* max_loc,//指向返回最大值的位置指针
)