在计算机视觉领域,兴趣点(也称关键点或特征点)的概念已经得到了广泛的应用,包括目标识别、图像配准、视觉跟踪、三维重建等。这个概念的原理是,从图像中选取某些特征点并对图像进行局部分析,而非观察整幅图像。
*检测图像中的角点
角点是很容易在图像中定位的局部特征,并且大量存在于人造物体中。角点的价值在于它是两条边缘线的接合点,是一种二维特征,可以被精确地定位(即使是子像素级精度)
Harris特征检测是检测角点地经典方法。
OpenCV中检测Harris角点地基本函数是cv::cornerHarris.调用该函数时输入一个图像,返回地结果是一个浮点型图像,其中每个像素点表示角点强度。
原图:
效果:
代码:
HarrisDetector类:
class HarrisDetecter
{
private:
cv::Mat cornerStrength; //32位浮点数型的角点强度图像
cv::Mat cornerTh; //32位浮点数型的阈值化角点图像
cv::Mat localMax; //局部最大值图像
int neighbourhood; //平滑导数的邻域尺寸
int aperture; //梯度计算的口径
double k; //Harris参数
double maxStrength; //阈值计算的最大强度
double threshold; //计算得到的阈值
int nonMaxSize; //非最大抑制的邻域尺寸
cv::Mat kernel; //非最大抑制内核
public:
HarrisDetecter() :neighbourhood(3), aperture(3), k(0.01), maxStrength(0.0), threshold(0.01), nonMaxSize(3)
{
setLocalMaxWindowSize(nonMaxSize);
}
void setLocalMaxWindowSize(int size)
{
nonMaxSize = size;
kernel.create(nonMaxSize, nonMaxSize, CV_8U);
}
void detect(const cv::Mat& image)
{
cv::cornerHarris(image, cornerStrength, neighbourhood, aperture, k);
//计算内部阈值
double minStrength;
cv::minMaxLoc(cornerStrength, &minStrength, &maxStrength);
//检测局部最大值
cv::Mat dilated;
cv::dilate(cornerStrength, dilated, cv::Mat());
cv::compare(cornerStrength, dilated, localMax, cv::CMP_EQ);
}
//用Harris值得到角点分布图
cv::Mat getCornerMap(double qualityLevel)
{
cv::Mat cornerMap;
threshold = qualityLevel*maxStrength;
cv::threshold(cornerStrength, cornerTh, threshold, 255, cv::THRESH_BINARY);
cornerTh.convertTo(cornerMap, CV_8U);
//非最大值抑制
cv::bitwise_and(cornerMap, localMax, cornerMap);
return cornerMap;
}
//用Harris值得到角点分布图
void getCorners(std::vector& points, double qualityLevel)
{
cv::Mat cornerMap = getCornerMap(qualityLevel);
getCorners(points, cornerMap);
}
void getCorners(std::vector& points, const cv::Mat& cornerMap)
{
for (int y = 0; y < cornerMap.rows; y++)
{
const uchar* rowPtr = cornerMap.ptr(y);
for (int x = 0; x < cornerMap.cols; x++)
{
if (rowPtr[x])
{
points.push_back(cv::Point(x, y));
}
}
}
}
void drawOnImage(cv::Mat& image, const std::vector& points,
cv::Scalar color = cv::Scalar(255, 255, 255), int radius = 3, int thickness = 1)
{
std::vector::const_iterator it = points.begin();
while (it != points.end())
{
cv::circle(image, *it, radius, color, thickness);
it++;
}
}
};
int main()
{
cv::Mat image = cv::imread("church.jpg");
cv::cvtColor(image, image, CV_BGR2GRAY);
cv::imshow("original image", image);
HarrisDetecter harris;
harris.detect(image);
std::vectorpts;
harris.getCorners(pts, 0.02);
harris.drawOnImage(image, pts);
cv::imshow("harris detected", image);
cvWaitKey();
}
错误记录:
在使用cv::cornerHarris()函数时报错,查阅官方文档:
其src为8位图像或浮点点图,故加载彩色图时image为CV_8U3
执行
cv::cvtColor(image, image, CV_BGR2GRAY);
语句后再次执行cv::cornerHarris()函数正确。
实现原理:
(因教材中公式有少量错误,故重新整理)
*适合跟踪的特征
有两个函数可以用来显式地计算Harris协方差矩阵地特征值(以及特征向量),即cv::cornerEignValAndVecs和cv::cornerMinEigenVal
还有一种对Harris地改进是针对特征点聚集地问题(兴趣点在图像中分布不均匀,聚集在高度纹理化地位置),改进的算法从Harris值最强的点开始,只允许一定距离之外的点成为兴趣点。,在OpenCV中调用函数
cv::goodFeaturesToTrack
实现这个算法,之所以采用这个函数名称,因为它检测到的特征非常适合作为视觉跟踪程序的起始集合。
cv::goodFeaturesToTrack与cv::coenerHarris效果的对比:
代码:
std::vector corners;
cv::goodFeaturesToTrack(image, corners,
500,//返回角点的最大数量
0.01,//质量等级
10);//角点间允许的最大距离
Harris算子对角点做出了规范的数学定义,该定义基于在两个相互垂直的方向上强度值的变化率。虽然这是一种很完美的定义,但是它需要计算图像的导数,而计算导数非常耗时。
FAST(加速分割测试获得特征,Features from Accelerated Segment Test)专门用来快速检测兴趣点,只要对比几个像素,就可以判断是否为关键点。
在OpenCV中,检测特征点有通用接口,因此调用任何特征点检测器都非常容易。
结果:
代码:
int main()
{
cv::Mat image = cv::imread("church.jpg");
std::vector keypoints;
cv::Ptr fast = cv::FastFeatureDetector::create(40);
fast->detect(image, keypoints);
cv::drawKeypoints(image, keypoints, image, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_OVER_OUTIMG);
cv::imshow("detected", image);
cvWaitKey();
}
在OpenCV3.3中,FastFeatureDetector为抽象类,不能实例化。
FAST检测器的使用方法为:
std::vector keypoints;
cv::Ptr fast = cv::FastFeatureDetector::create(40);
fast->detect(image, keypoints);
OpenCV提供了在图像上画关键点的通用函数:
cv::drawKeypoints()
实现原理:
①测试圆周上相隔90°的四个点,若至少三个点都比圆心更亮或者更暗,进入②;
②检测圆上的像素点,如果存在这样一个圆弧,它的连续长度超过周长的3/4,并且它上面所有的像素强度值都与圆心的强度值明显不同,就确认为一个关键点。
*适配的特征检测
如果要更好地控制被检测特征点地数量,可使用cv::FeatureDetector的一个专门的子类,即cv::DynamicAdaptedFeatureDetector.它可以指定被检测兴趣点的数量范围。
这时使用FAST特征检测器的方法如下:
cv::DynamicAdaptedFeatureDetector fastD(
new cv::FastAdjuster(40),//特征检测器
150,//特征的最小数量
200,//特征的最大数量
50);//最大迭代次数
fastD.detect(image,keypoints);//检测特征点
(在OpneCV3.3.1官方文档中没有找到这个类)
*网格适配特征检测
实际上在检测图像中的关键点时,经常出现这种情况,即很多兴趣点聚集在特定的纹理区域,cv::GridAdaptedFeatureDetector可以在图像上定义一个网格,网格中的每个单元格可以设置一个最大元素数量。这里的理念是把被检测的关键点以更好的方式扩展到整幅图像上。
(OpenCV3.3.1的官方文档中同样没有找到这个类)
*尺度不变特征检测
计算机视觉界尺度不变特征的理念是:不仅在任何尺度下拍摄的物体都能检测到一致的关键点,而且每个被检测的特征点都对应一个尺度因子。
理想情况下,对于两幅图像中不同尺度的同一个物体点,计算得到的两个尺度因子之间的比率应该等于图像尺度的比率。
SURF全称“加速稳健特征”(Speeded Up Robust Feature),它们不仅是尺度不变特征,而且是具有较高计算效率的特征。
实现:
cv::Ptrdetector = new cv::SURF(2000.0);//阈值
detector->detect(image,keypoints);
cv::drawKeypoints(image,keypoints,image,cv::Scalar(255,255,255),cv::DrawMatchedFlags::DRAW_RICH_KEYPOINTS);
使用DRAW_RICH_KEYPOINTS可得到关键点的圆,并且
圆的尺寸与每个特征计算得到的尺度成正比。为了使特征具有旋转不变性,SURF还让每个特征关联了一个方向,方向由院内的一条辐射线表示。
实现原理:
①对每个像素计算Hessian矩阵:
②根据矩阵的行列式的值,得到曲率的强度。该方法把角点定义为局部高曲率(即在多个方向上的变化幅度都很高)
Hessian矩阵的行列式值达到了局部最大值,那么就认为这是一个尺度不变性特征。
*SIFT特征检测算法
SUFR算法是SIFT算法的加速版,SIFT(尺度不变特征转换,Scale-Invariant Feature Transform)也采用图像空间和尺度空间的局部最大值,但它使用拉普拉斯滤波器响应而不是Hessian行列式值。SIFT算法检测的特征在空间和尺度上的定位更加精确,但是计算效率低。
调用方法:
cv::Ptrdetector = new cv::SIFT();//阈值
detector->detect(image,keypoints);
cv::drawKeypoints(image,keypoints,image,cv::Scalar(255,255,255),cv::DrawMatchedFlags::DRAW_RICH_KEYPOINTS);
*多尺度FAST特征的检测
1、BRISK(Binary Robust Invariant Scalable Keypoints,二元稳健恒定可扩展关键点)检测法,基于FAST特征检测法
代码:
int main()
{
cv::Mat image = cv::imread("church.jpg");
std::vector keypoints;
cv::Ptr detector = cv::BRISK::create();
detector->detect(image, keypoints);
cv::drawKeypoints(image, keypoints, image, cv::Scalar(255, 255, 255),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imshow("BRISK", image);
cvWaitKey();
}
*ORB特征检测算法
ORB代表定向FAST和旋转BRIEF(Oriented FAST and Rotated BRIEF)
这个缩写的第一层一丝表示关键点检测,第二层意思表示ORB算法提供的描述子。
效果:
int main()
{
cv::Mat image = cv::imread("church.jpg");
std::vector keypoints;
cv::Ptr detector = cv::ORB::create(200,1.2,8);
detector->detect(image, keypoints);
cv::drawKeypoints(image, keypoints, image, cv::Scalar(255, 255, 255),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imshow("BRISK", image);
cvWaitKey();
}