本文要点总结(俩算法的联系与区别)
Harris角点检测与Shi-Tomasi角点检测都是经典的角点特征提取算法,
但两者在API的使用上有出入(详见文中代码或GitHub项目);
Harris角点检测的API,返回/输出的是一个与输入图像大小一致的Mat对象,
这个Mat对象的每一个坐标(i,j)都是对应输入图像对应坐标(i,j)的像素的响应值R,
要先将这个Mat对象归一化,
再循环每一个Mat数据元素,一 一 跟自己设置的阈值进行比较,
合格的再认为是角点并提取出来,
进行绘制和保存;
与Harris角点输出不同,shi-tomasi简单多了,
直接输出一个包含若干个(具体个数通过API形参设置)角点坐标的角点数组,(其数据类型是MatOfPoint)
省略了很多步骤;
遍历这个角点数组,
绘制出每个角点即可。
引子
前面两章笔记(图像操作、基本特征检测)
主要讲述了OpenCV中图像处理模块的主要知识与API使用;
本章的笔记记录OpenCV中另外一个重要模块——feature2d模块,
该模块的主要功能是检测图像的特征,
并根据特征进行对象匹配;
首先,关于图像的特征,
简单地说,特征就是边缘、角点、纹理等。
本章会笔记特征提取、检测与匹配相关的知识与API,
包括角点特征检测、特征点检测、特征描述子提取,
以及根据特征描述子去匹配、寻找特征对象。
本章知识会涉及较多的数学知识与公式,
我们可以阅读一些与特征提取相关的数学知识,
比如导数与微分、多项式与高斯公式曲线拟合,三角函数,矩阵的特征值与特征向量的简单计算等基础数学知识,
以更好地掌握本章知识以及各个API参数意义与用法。
0 角点的定义与作用
基本特征检测一章中,学习了关于边缘检测的知识,
在图像边缘中,有一些特殊的像素点值得我们特别关注,
那就是图像边缘的角点,
这些角点更能反映出图像中对象的整体特征,
基于角点周围的像素块生成特征描述子可以更好地表述图像特征数据。
本文首先笔记如何提取图像的角点特征。
1 Harris角点检测
关于角点特征提取最经典的算法之一就是Harris角点检测。
Harris角点检测的基本原理是对图像求导,对每个像素点生成二阶梯度图像,
只是在卷积核使用的时候需要使用高斯核,
得到图像X与Y方向的二阶矩,
基于它们就可以得到如下Hessian矩阵:
求得最大两个特征值 λ1 与 λ2,可以得到如下 角点响应值R:
其中,系数K常见的取值范围为0.02~0.04。
每个像素点有自己的一个响应值R,
也即有自己的一对特征值 λ1 与 λ2;
全局像素则有多个R值;
根据M计算可以得到特征值 λ1、λ2,它们的值与角点的关系如下图:
Harris角点检测的API:
cornerHarris(Mat src, Mat dst, int blockSize, int ksize, double k)
src:单通道的8位或者浮点数图像,用灰度图像;
dst:输出的每个像素点的响应值,是CV_32F类型,大小与输入图像一致。
blockSize:根据特征值与特征向量计算矩阵M的大小,常见取值为2。
ksize Sobel:算子梯度计算,常见取值为3。
k:系数大小,取值范围为0.02~0.04。
使用Harris角点检测函数计算得到图像角点的演示代码如下:
private void harrisCornerDemo(Mat src, Mat dst) {
// 定义阈值T//初始化各种Mat对象
int threshold = 100;
Mat gray = new Mat();
Mat response = new Mat();
Mat response_norm = new Mat();
// 角点检测
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);//转灰度!!!!!!
Imgproc.cornerHarris(gray, response, 2, 3, 0.04);
Core.normalize(response, response_norm, 0, 255, Core.NORM_MINMAX, CvType.CV_32F);//归一化
// 绘制角点
dst.create(src.size(), src.type());
src.copyTo(dst);
float[] data = new float[1];//!!!!!!!!!!
for(int j=0; j
{
for(int i=0; i
{
response_norm.get(j, i, data);
if((int)data[0] > 100)
{
Imgproc.circle(dst, new Point(i, j), 5, new Scalar(0, 0, 255),
2, 8, 0);
Log.i("Harris Corner", "find corner point……");
}
}
}
gray.release();
response.release();
}
response_norm归一化后的响应值Mat对象
data[0]是某个响应值;
>100认为其是一个较大的响应值,
响应值大于指定阈值T(这里是100),则对应的像素点被认为是角点;
float[] data = new float[1] //在这里,可能有人有疑问, 数组长度只有 1
get()方法,第三个参数要求是数组,
get多个像素时,传入一个多元素空数组,常规理解操作;
但当只要get一个像素,则需创建一个只有一个元素的数组!而非变量!
这种接口设计思想,
一个方法(如get())接口即可实现包含一到多个数据元素的形式参数的传入;
而没必要去准备/重载两个方法——
一个用来接收包含单个数据元素的变量型形参,
另一个用来接收包含多个数据元素的数组型形参;没必要这样了;
即无论是负责接收数据的形参是包含 单个数据元素 还是 多个数据元素 ,一律按 数组型形参 处理,把数据形参位定义成数组类型,接口统一设计,一套代码,一个接口即可,省时省力!
上述程序首先把彩色RGB图像转换为单通道灰度图像,
然后使用Harris角点检测函数完成各个像素点上角点响应值的计算,
最后使用阈值过滤绘制那些响应值R比较大的像素点(角点)。
注意,阈值T与绘制检测得到的角点数目相关,
T值越大,被过滤的响应像素点越多,留下来的就越可能是角点,反之亦然。
本章完整代码在文末GitHub里边的Feature2dMainActivity.java文件中,后续对此不再说明。
2 Shi-Tomasi角点检测
还有一种经常使用的角点检测方法称为Shi-Tomasi角点检测,
其与Harris角点检测类似,这种方法同样是基于梯度图像发展而来的,
它是1994年由两位作者Jianbo Shi与Carlo Tomasi一起提出来的,
他们当时所发表的论文名为<>,
这也是为什么在OpenCV中使用同名函数来表示Shi-Tomasi角点检测的原因。
Shi-Tomasi角点检测与Harris角点检测唯一(指的是方法逻辑,不包括API,API的输出还不同) 不同的地方在于计算角点响应R值时使用的是如下方法:
如果R大于指定阈值T,则对应的像素点被认为是角点;
假设λ1、λ2为坐标,
则对角点的描述就是当λ1、λ2都大于阈值T=λmin的右上角时,
角点响应值满足要求的区域,
如下图:
相关的API如下:
goodFeaturesToTrack(Mat image, MatOfPoint corners, int maxCorners, double qualityLevel, double minDistance, Mat mask, int blockSize, boolean useHarrisDetector, double k)
image:表示输入图像、类型为单通道的8位或浮点数,用灰度图像;
corners:输出得到角点数组,注意数据类型;
maxCorners:表示获取前N个最强响应R值的角点。
qualityLevel:其取值范围为0~1,这里取它与最大R值相乘,得到的值作为阈值T,低于它的都要被丢弃,
假设Rmax=1500,qualityLevel=0.01,则阈值T=15,小于15的角点都会被丢弃。
每个像素点有自己的一个响应值R,去全局像素最大的R为Rmax;
minDistance:最终返回的角点之间的最小距离,小于这个距离则的角点被丢弃。
mask:默认全部为零。
blockSize:计算矩阵M时需要的,常取值为3。
useHarrisDetector:是否使用Harris角点检测,true表示使用,若为false则使用Shi-Tomasi角点检测。
k:当使用Harris角点检测的时候才使用。
实现shi-tomasi角点检测的demo:
private void shiTomasicornerDemo(Mat src, Mat dst) {
// 变量定义
double k = 0.04;
int blockSize = 3;
double qualityLevel= 0.01;
boolean useHarrisCorner = false;
// 角点检测
Mat gray = new Mat();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
MatOfPoint corners = new MatOfPoint();
Imgproc.goodFeaturesToTrack(gray, corners, 100, qualityLevel, 10, new Mat(), blockSize, useHarrisCorner, k);
// 绘制角点
dst.create(src.size(), src.type());
src.copyTo(dst);
Point[] points = corners.toArray();
for(int i=0; i
Imgproc.circle(dst, points[i], 5, new Scalar(0, 0, 255), 2, 8, 0);
}
gray.release();
}
完整的代码可参考GitHub项目。
参考材料