特征检测是检测图像中的特征信息,比如边缘,线段,角点位置等。在OpenCV中提供了各种特征检测函数和特征提取函数,其中包括有边缘检测(canny),角点检测等,如:Harris角点、ShiTomasi角点、亚像素级角点、SURF角点、Star关键点、FAST关键点、Lepetit关键点等等。下面将对特征检测模块中的检测函数的使用进行解释,主要来源于OpenCV的帮助文档(翻译)和网络上的资料摘编,并包含C++编程说明和图像处理的效果说明。
- 、void cv::Canny (InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize=3, bool L2gradient=false)
void cv::Canny (InputArray dx, InputArray dy, OutputArray edges,
double threshold1, double threshold2, bool L2gradient=false)
使用Canny算法查找图像中的边缘。
这个函数使用Canny算法查找输入图像的边缘并在输出图像中标记这些边缘图。threshold1和threshold2之间的最小值用于边缘的连接。最大值用于查找强边缘的起始线段。
参数
image | 8-位输入图像 |
edges | 输出的边缘图,单通道8位图像,与源图同尺寸 |
threshold1 | 迟滞过程的第一个阈值 |
threshold2 | 迟滞过程的第二个阈值 |
apertureSize | Sobel算子的滤镜尺寸 |
L2gradient | 标志,表示是用更精确的L2 norm = 来计算图像的梯度大小( L2gradient=true ), 还是用默认的 L1 norm =就足够了( L2gradient=false ) |
- 、void cv::cornerEigenValsAndVecs (InputArray src, OutputArray dst,
int blockSize, int ksize, int borderType=BORDER_DEFAULT)
计算图块的特征值和特征矢量,用于角点检测。
对每一个像素点p,函数cornerEigenValsAndVecs 考虑blockSize × blockSize邻域S(p) 。计算邻域上导数的协方差矩阵:
其中导数使用Sobel算子计算。
然后,函数查找M的特征矢量和特征值并存储成目标图像,如(λ1,λ2,x1,y1,x2,y2) 此处
- λ1,λ2 为M非排序的特征值
- x1,y1 是对应λ1的特征矢量
- x2,y2 是对应 λ2的特征矢量
这个函数的输出可用于强边和角的检测。
参数
src | 输入单通道8位或浮点图像 |
dst | 存储结果的目标图像。与源图有相同的尺寸,而类型为CV_32FC(6) |
blockSize | 邻域尺寸 |
ksize | Sobel算子的滤镜参数 |
borderType | 边框模式,用于外推图像以外的像素,参见 BorderTypes。不支持BORDER_WRAP 。 |
参见
cornerMinEigenVal, cornerHarris, preCornerDetect
- 、void cv::cornerHarris (InputArray src, OutputArray dst, int blockSize, int ksize, double k, int borderType=BORDER_DEFAULT)
Harris角点检测器。
这个函数在图像上运行Harris角检测器。类似于cornerMinEigenVal 和cornerEigenValsAndVecs ,对每一个点(x,y) ,计算blockSize×blockSize 邻域上的2×2 梯度协方差矩阵。然后计算下面的特征值:
图像中的角作为响应的局部最大值就能被找到。
参数
Src | 输入单通道8位或浮点图像。 |
Dst | 存储检测器响应的目标图像。其类型为CV_32FC1且与源图同尺寸。 |
blockSize | 邻域尺寸(详见cornerEigenValsAndVecs)。 |
Ksize | Sobel算子的滤镜参数。 |
K | Harris检测器的自由参数。见上面的公式。 |
borderType | 边框模式,用于外推图像以外的像素,参见 BorderTypes。不支持BORDER_WRAP 。 |
- 、void cv::cornerMinEigenVal (InputArray src, OutputArray dst,
int blockSize, int ksize=3, int borderType=BORDER_DEFAULT)
计算梯度矩阵的最小特征值用于角点检测。
这个函数类似于cornerEigenValsAndVecs,但它仅计算并存储导数协方差矩阵的最小特征值,即,在cornerEigenValsAndVecs函数的公式描述中的min(λ1,λ2)。
参数
src | 输入单通道8位或浮点图像。 |
dst | 存储最小特征值的目标图像。类型为CV_32FC1 并与源图像同尺寸。 |
blockSize | 邻域尺寸(详见cornerEigenValsAndVecs )。 |
ksize | Sobel算子的滤镜参数。 |
borderType | 边框模式,用于外推图像以外的像素,参见 BorderTypes。不支持BORDER_WRAP 。 |
- 、void cv::cornerSubPix (InputArray image, InputOutputArray corners,
Size winSize, Size zeroZone, TermCriteria criteria)
提炼角点位置。
这个函数迭代查找角点或径向鞍点的亚像素级精确位置,如下图所示。
图
亚像素精度角点定位是基于实际观察基础上的,如图,其中每一个落在邻域q内的从中心q到点p的矢量都正交于图像在p点的梯度,只受图像和噪声量的影响。考虑如下表达式:
此处 是在邻域q中pi点的图像梯度。q值是要查找的值,以使 ϵi 最小。设置ϵi为0建立等式:
其中梯度在邻域q(搜索窗口)内求和。令第一个梯度项为G,而第二个梯度项为b,则给出:
这个算法设置了邻域窗口的中心在新中心q,并且迭代直到中心驻留在一个设定的阈值内。
参数
image | 输入单通道8位或浮点图像 |
corners | 输入初始角点的坐标,输出为提炼过的坐标 |
winSize | 搜索窗口的半边长。例如,如果winSize=Size(5,5),则搜索窗口是 (5∗2+1)×(5∗2+1)=11×11 |
zeroZone | 搜索死区带的半尺寸参数,在这个区域上函数不做求和操作。这一般用于避免可能的自相关矩阵奇异点。(-1,-1) 表示没有这个尺寸。 |
criteria | 角提炼过程的终止准则。即,角位置提炼过程在criteria.maxCount次数后迭代停止,或在某次迭代后角位置移动小于criteria.epsilon则停止。 |
- 、Ptr<LineSegmentDetector> cv::createLineSegmentDetector (
int _refine=LSD_REFINE_STD,
double _scale=0.8,
double _sigma_scale=0.6,
double _quant=2.0,
double _ang_th=22.5,
double _log_eps=0,
double _density_th=0.7,
int _n_bins=1024)
建立指向LineSegmentDetector对象的指针并进行初始化。
LineSegmentDetector 算法使用标准值进行定义。只有高级使用者能够编辑并将其剪裁到自己的应用中。
参数
_refine | 查找要提炼线段所使用的的方法,见LineSegmentDetectorModes |
_scale | 用于查找直线的图像比例。范围(0..1]。 |
_sigma_scale | 高斯滤波的sigma值。sigma = _sigma_scale/_scale。 |
_quant | 限定在梯度范数上的量化误差。 |
_ang_th | 倾斜角度公差,度数单位。 |
_log_eps | 检测阈值:-log10(NFA) > log_eps。仅用于高级细化选择。 |
_density_th | 在封闭矩形中成直线区域点的最小密度。 |
_n_bins | 梯度模量伪排序中的箱数。 |
注意
由于原始代码的许可冲突,实现中已经将其删除
- 、void cv::goodFeaturesToTrack (InputArray image, OutputArray corners,
int maxCorners,
double qualityLevel,
double minDistance,
InputArray mask=noArray(),
int blockSize=3,
bool useHarrisDetector=false,
double k=0.04)
void cv::goodFeaturesToTrack (InputArray image, OutputArray corners,
int maxCorners,
double qualityLevel,
double minDistance,
InputArray mask,
int blockSize,
int gradientSize,
bool useHarrisDetector=false,
double k=0.04)
在图像中确定强角点位置,输出maxCorners个数的角点。这个函数在图像或图像的指定区域查找最显著的角点。函数使用cornerMinEigenVal 或 cornerHarris在源图像的每一个像素点计算角点的质量测度。
- 函数执行非最大抑制(保留3 x 3 邻域的局部最大)
- 排除最小特征值小于 的角点
- 保留下来的角点以降序方式存储其质量测度值
- 函数抛弃那些与强角点距离小于maxDistance的角点。
这个函数可用于初始化一个点轨迹对象
注意
如果调用这个函数使用A,B两个不同的参数qualityLevel值,且A > B,则返回的角点矢量对于qualityLevel=A 将排在qualityLevel=B的输出矢量前部。
参数
image | 输入8位或浮点32位,单通道图像 |
corners | 输出检测到的角点矢量 |
maxCorners | 要返回的最大角点数。如果有更多的角点,返回最强的那些角点。 maxCorners <= 0 表示不限制最大数,所有检测到的都返回 |
qualityLevel | 指定了最小可接受图像角点的质量。这个参数值与最佳质量测度值相乘表示最小特征值(见cornerMinEigenVal)或Harris函数的响应(见cornerHarris)。质量测度值小于这个乘积的角点被排除。例如,如果最佳角点的质量测度=1500,并qualityLevel=0.01,则所有质量测度小于15的角点都被排除 |
minDistance | 返回角点之间的最小几何距离(欧几里得距离) |
mask | 可选的感兴趣区域。如果这个图像非空(需要类型为CV_8UC1,且与源图同尺寸),它指定了检测角点的区域 |
blockSize | 在每一个像素邻域上计算导数协方差矩阵的平均块尺寸。见cornerEigenValsAndVecs |
useHarrisDetector | 标志参数,表示是使用Harris检测(见cornerHarris) 还是使用cornerMinEigenVal检测 |
k | Harris检测的开放参数 |
参见
cornerMinEigenVal, cornerHarris, calcOpticalFlowPyrLK, estimateRigidTransform,
- 、void cv::HoughCircles (InputArray image, OutputArray circles,
int method,
double dp,
double minDist,
double param1=100,
double param2=100,
int minRadius=0,
int maxRadius=0)
使用霍夫(Hough)变换查找灰度图像中的圆。
这个函数使用一种修正的霍夫变换查找灰度图像中的圆。例:
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat img, gray;
if( argc != 2 || !(img=imread(argv[1], 1)).data)
return -1;
cvtColor(img, gray, COLOR_BGR2GRAY);
// 平滑, 否则将检测到大量的伪圆
GaussianBlur( gray, gray, Size(9, 9), 2, 2 );
vector<Vec3f> circles;
HoughCircles(gray, circles, HOUGH_GRADIENT, 2, gray.rows/4, 200, 100 );
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// 画圆的中心
circle( img, center, 3, Scalar(0,255,0), -1, 8, 0 );
// 画圆的轮廓
circle( img, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
namedWindow( "circles", 1 );
imshow( "circles", img );
waitKey(0);
return 0;
}
注意
一般这个函数能够很好地检测到圆的中心。然而,它却可能在查找半径时失败。 可以使用指定半径范围( minRadius and maxRadius )的方法辅助函数的查找。或者,在使用HOUGH_GRADIENT 方法时设置maxRadius为负数,只返回圆的中心而不进行半径搜索,然后使用附加的过程查找正确的半径。
平滑图像还是有帮助的,除非图像已经软化。例如GaussianBlur() 使用7x7核,及sigma 为1.5x1.5,或类似的模糊都是有帮助的。
参数
image | 8位,单通道,灰度输入图像 |
circles | 输出所找到的圆的矢量。每个矢量包含3 或 4 元素的浮点矢量值 (x,y,radius)或 (x,y,radius,votes) |
method | 检测法,见HoughModes。可用的方法为 HOUGH_GRADIENT 和HOUGH_GRADIENT_ALT |
dp | 累加器分辨率与图像分辨率成反比。例如,如果dp=1,则累加器与输入图像有相同的分辨率。如果dp=2,则累加器有一半大小的宽高。对HOUGH_GRADIENT_ALT 方法,推荐使用dp=1.5,除非需要检测一些非常小的圆 |
minDist | 圆心到圆心之间的最小距离。如果这个参数太小,多个邻域的圆除了一个是真的意外,其他可能检测到为假圆。如果太大,可能丢失某些圆 |
param1 | 方法指定的第一个参数。在 HOUGH_GRADIENT 和HOUGH_GRADIENT_ALT时,它是两遍Canny边缘检测的高阈值(低阈值是其二倍小的值)。注,在 HOUGH_GRADIENT_ALT 时使用Scharr 算法计算图像导数,其阈值正常是高阈值,如300 对应正常曝光和对比度的图像 |
param2 | 方法指定的第二个参数。在HOUGH_GRADIENT时,它是检测阶段,圆心的累加器阈值。越小,假圆的检测越多。对应较大累加器值的圆首先被返回。在HOUGH_GRADIENT_ALT 算法时,这是圆的精度测度值。越靠近1,算法就选择越好形状的圆。最好的场合是0.9。如果想要更好地检测小圆,可以设定到0.85, 0.8 或更小。但是也要试着限制搜索范围[minRadius, maxRadius] 以避免过多的假圆 |
minRadius | 圆的最小半径 |
maxRadius | 圆的最大半径。如果<= 0,使用最大图像直径。如果<0,HOUGH_GRADIENT 返回没有找到半径的中心,HOUGH_GRADIENT_ALT 则总是计算圆的半径 |
参见
fitEllipse, minEnclosingCircle
- 、void cv::HoughLines (InputArray image, OutputArray lines,
double rho,
double theta,
int threshold,
double srn=0,
double stn=0,
double min_theta=0,
double max_theta=CV_PI)
使用标准的霍夫(Hough)变换在二值图像上查找直线。这个函数实现了直线检测的标准的或标准多尺度的霍夫变换算法。
参数
image | 8位,单通道二值源图像。函数可能修改这个图像。 |
lines | 输出的直线矢量。每一个直线由2或3个元素的矢量(ρ,θ)或 (ρ,θ,votes)表示。ρ 是自坐标原点(0,0) (图像的左上角)的距离。θ 是直线的旋转角度弧度值( 0∼垂直直线,π/2∼水平直线 )。votes 是累加值。 |
Rho | 像素累加的距离分辨率。 |
theta | 弧度累加的角度分辨率。 |
threshold | 累加器阈值参数。仅在线段有足够大的votes值( >threshold )时,才被返回。 |
Srn | 对多尺度霍夫变换,这个参数是距离分辨率rho的除数因子,且其精度累加分辨率为rho/srn 。如果二者srn=0 和stn=0 ,则使用经典的霍夫变换。否则,这两个参数都应该是正值。 |
Stn | 对多尺度霍夫变换,它是角度分辨率theta的除数因子。 |
min_theta | 对标准的和多尺度的霍夫变换,检查线段的最小角度。必需在0到max_theta之间。 |
max_theta | 对标准的和多尺度的霍夫变换,检查线段的最大角度。必须在min_theta 和CV_PI之间。 |
注:HoughLines返回的是直线(极坐标表示的(rho,theta))。如果画在图像上则是贯通全图的直线。
- 、void cv::HoughLinesP (InputArray image, OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength=0,
double maxLineGap=0)
使用概率霍夫变换查找二值图像上的直线段。这个函数实现了直线检测的概率霍夫变换算法。见下面的直线检测示例:
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src, dst, color_dst;
if( argc != 2 || !(src=imread(argv[1], 0)).data)
return -1;
Canny( src, dst, 50, 200, 3 );
cvtColor( dst, color_dst, COLOR_GRAY2BGR );
vector<Vec4i> lines;
HoughLinesP( dst, lines, 1, CV_PI/180, 80, 30, 10 );
for( size_t i = 0; i < lines.size(); i++ )
{
line( color_dst, Point(lines[i][0], lines[i][1]),
Point( lines[i][2], lines[i][3]), Scalar(0,0,255), 3, 8 );
}
namedWindow( "Source", 1 );
imshow( "Source", src );
namedWindow( "Detected Lines", 1 );
imshow( "Detected Lines", color_dst );
waitKey(0);
return 0;
}
这是一个函数参数已经调优后的示例图片:
图像
这是上面程序的输出,是在使用概率霍夫变换时的输出:
图像
参数
image | 8位,单通道二值源图像。这个图像可能会被函数所修改。 |
lines | 输出直线的数组。每一条线有4元素矢量表示(x1,y1,x2,y2),其中(x1,y1) 和(x2,y2) 是检测到的直线段端点。 |
rho | 像素累加的距离分辨率。 |
theta | 弧度累加的角度分辨率。 |
threshold | 累加器阈值参数。仅仅那些获得了足够大votes值(>threshold ) 的直线段才被返回。 |
minLineLength | 最小线段长度。拒绝短于这个值的线段。 |
maxLineGap | 连接同线段上点之间最大允许缝隙。 |
参见
LineSegmentDetector
注:这个函数返回的是直线段(有端点的直线)。
- 、void cv::HoughLinesPointSet (InputArray _point, OutputArray _lines,
int lines_max,
int threshold,
double min_rho,
double max_rho,
double rho_step,
double min_theta,
double max_theta,
double theta_step)
使用标准的霍夫变换查找点集中的直线段。这个函数使用修正后的霍夫变换查找点集中的直线。示例如下:
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat lines;
vector<Vec3d> line3d;
vector<Point2f> point;
const static float Points[20][2] = {
{ 0.0f,369.0f}, { 10.0f,364.0f}, { 20.0f,358.0f},
{ 30.0f,352.0f}, { 40.0f,346.0f}, { 50.0f,341.0f},
{ 60.0f,335.0f}, { 70.0f,329.0f}, { 80.0f,323.0f},
{ 90.0f,318.0f}, {100.0f,312.0f}, {110.0f,306.0f},
{120.0f,300.0f}, {130.0f,295.0f}, {140.0f,289.0f},
{150.0f,284.0f}, {160.0f,277.0f}, {170.0f,271.0f},
{180.0f,266.0f}, {190.0f,260.0f}};
for (int i = 0; i < 20; i++)
{
point.push_back(Point2f(Points[i][0],Points[i][1]));
}
double rhoMin = 0.0f;
double rhoMax = 360.0f;
double rhoStep = 1;
double thetaMin = 0.0f;
double thetaMax = CV_PI / 2.0f;
double thetaStep = CV_PI / 180.0f;
HoughLinesPointSet(point, lines, 20, 1,rhoMin, rhoMax, rhoStep, thetaMin, thetaMax, thetaStep);
lines.copyTo(line3d);
printf("votes:%d, rho:%.7f,heta:%.7f\n",(int)line3d.at(0).val[0],
line3d.at(0).val[1], line3d.at(0).val[2]);
}
参数
_point | 输入的点矢量,每一个矢量元素必须作为点矢量(x,y)编码。类型必需为CV_32FC2 或CV_32SC2。 |
_lines | 输出查找到的线段矢量。每一个矢量元素编码为vector<Vec3d> (votes,rho,theta)。'votes'值越大,霍夫直线的可靠性越高。 |
lines_max | 霍夫线段的最大计数。 |
threshold | 累加器阈值参数。仅仅有足够大的votes ( >threshold ) 那些线段被返回。 |
min_rho | 像素累加的最小距离值。 |
max_rho | 像素累加的最大距离值。 |
rho_step | 像素累加的距离分辨率。 |
min_theta | 弧度累加的最小角度。 |
max_theta | 弧度累加的最大角度。 |
theta_step | 弧度累加的角度分辨率。 |
注:这个函数返回的是直线,没有端点的极坐标直线。
- 、void cv::preCornerDetect (InputArray src, OutputArray dst, int ksize, int borderType=BORDER_DEFAULT)
计算角点检测的特征分布图。这个函数计算源图像复杂的空间导函数
此处 是图像的一阶导数, 是图像的二阶导数,而 是混合导数。角点可以在这个函数的局部最大点处找到,如下例所示:
Mat corners, dilated_corners;
preCornerDetect(image, corners, 3);
// 用3x3 矩形结构元素扩张
dilate(corners, dilated_corners, Mat(), 1);
Mat corner_mask = corners == dilated_corners;
参数
src | 源单通道8位浮点图像。 |
dst | 输出图像,具有CV_32F类型和与源图同尺寸。 |
ksize | Sobel滤镜尺寸。 |
borderType | 边框模式,用于外推图像以外的像素,参见 BorderTypes。不支持BORDER_WRAP 。 |
I、特征检测函数实例
计算机视觉中常用的图像特征包括:点、边缘、直线、曲线等。点特征是图像的基本特征,点特征属于局部特征,对遮挡有一定鲁棒性;通常图像中可以检测到成百上千的点特征,点特征有较好的辨识性,不同物体上的点容易区分,点特征提取速度很快。
角点是一个好的点特征,因为包含角点的小窗口沿任意方向移动,窗口的灰度都变化明显,所以它可以作为一个特征来进行区分和辨别。
所谓特征点需要有一定的不变性,即缩放不变性,旋转不变性,光照不变性,对比度不变性,遮挡不变性等。在图像分类、对比、拼接等应用中需要图像的不变性来确定多幅图像的共同属性。
以下介绍OpenCV的特征检测函数在特征抽取中的作用和示例分析。
1、边缘抽取函数
void cv::Canny (InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false);
在对输入图像采用canny边缘检测之前,首先要对图像进行滤波操作,以消除图像的噪声干扰。一般采用方框滤波或高斯滤波,在使用高斯滤波时,推荐参数为ksize=5,sigma=1.5。然后对结果图像使用canny检测。Canny利用sobel3x3算子在x,y方向上分别检测边缘属性(计算灰度梯度),然后相加(两个图像的合并),获得边缘图像,再对图像进行‘非最大值抑制’,以及强弱边缘点的连接判断获得单像素边缘图像。如图:
这是对同一张经过滤波的图像分别使用minthreshold=64,maxthreshold=120、150、200所获得的边缘图像,可以明显看出maxthreshold参数对于关键边缘特征的过滤效果。这个参数表明每一条边缘中的点集至少有一个点其灰度值大于maxthreshold值。
这是在maxthreshold=180时,minthreshold=64、100、150时的边缘图像。可以看出强特征点边缘并没有减少,仅仅是边缘中的灰度小于minthreshold的点被消除了。
在图像边缘操作中,适当地规定maxthreshold值,有助于提取图像边缘的强特征(关键特征),和排除干扰。
2、角点抽取函数
void cv::cornerEigenValsAndVecs (InputArray src, OutputArray dst, int blockSize, int ksize, int borderType=BORDER_DEFAULT)
这个函数计算图块的特征值和特征矢量,用于角点的检测。注,对于像素点p,考虑blockSize邻域块的关于p的协方差矩阵(由Sobel核计算),得出2x2矩阵对应点p。这个矩阵的特征值和特征矢量为函数的返回数据存储在dst中。根据dst中的特征值,通过指定像素点强度换算:
lambda_1=myHarris_dst.at<Vec6f>(j,i)[0];
lambda_2=myHarris_dst.at<Vec6f>(j,i)[1];
strg(j,i)=lambda_1 * lambda_2 - 0.04 * pow((lambda_1+lambda_2),2);
过滤非角点(或提取强角点)。一般特征值操作如下:
//计算图像grayimg的特征值和特征矢量dstimg
cornerEigenValsAndVecs(grayimg, dstimg, bSize, kSize, BORDER_DEFAULT);
//转换特征值
for( int j = 0; j < grayimg.rows; j++ ){
for( int i = 0; i < grayimg.cols; i++ ){
float lambda_1 = myHarris_dst.at<Vec6f>(j, i)[0];
float lambda_2 = myHarris_dst.at<Vec6f>(j, i)[1];
Mc.at<float>(j,i)=lambda_1*lambda_2-0.04*pow((lambda_1+lambda_2),2);
}
}
其中Mc(Mat类型)为图像像素点的特征值矩阵(强度)。然后用强度级来检测满足强度的点:
Mc(j,i)>mc.minVal+(mc.maxVal–mc.minVal)*qualityLevel/max_qualityLevel)
则为强角点像素(j,i)。qualityLevel(=50)和max_qualityLevel(=100)为设定常量。如图:
左图为灰度图,中图为对左图施加canny操作的边缘图,右图是对灰度图标记角点的源图,其中红点处为在灰度图上检测到的角点位置。
该函数计算所有像素点的特征值(角点属性特征值,能够反映像素点的角化特征强度)。然后对这个强度值进行阈值处理,获得具有一定强度的像素点,可认定这些点为图像中可能的角点。其中lambda_1和lambda_2的不同合并算法产生出了不同的角点算法。注意,角点像素周围的像素特征值也可能超过设定阈值,被认定为角点,因此函数返回的角点是有重叠的,这就需要使用距离属性判断邻域范围的最大值角点,即在指定的距离内只存在一个角点。使用距离属性过滤重叠角点有助于识别出真正的角点,并在随后的cornerSubPix函数中精确定位角点位置。其他的角点检测函数也需要使用距离属性过滤角点(使用邻域角特征最大值进行判断)。
距离过滤算法:
- 对角点检测函数大于阈值特征的点进行排序
- 检测给定角点i与i+j点的距离,小于指定值的则删除
- 剩余点为真实角点。
void cv::cornerHarris (InputArray src, OutputArray dst, int blockSize, int ksize, double k, int borderType=BORDER_DEFAULT)
这个函数是在图像上执行Harris算法的角检测器,与cornerEigenValsAndVecs类似,都是计算图块邻域上的特征值和特征矢量,只不过这里用到的是Harris算法。首先计算像素邻域的特征值(λ1 和λ2),然后根据Harris公式计算强度:
R =(λ1 *λ2)- k * exp((λ1 +λ2),2)
在函数的dst中返回的是图像对应像素点的R值。输入图像中的角点在输出图像中由局部最大值表示,因此在设定的阈值下,可获得返回角点的位置(R > threshold)。图像示例同上。
Harris算法采用R公式计算角点的强度。
void cv::cornerMinEigenVal (InputArray src, OutputArray dst, int blockSize, int ksize=3, int borderType=BORDER_DEFAULT)
这个函数计算图像上的特征值,并保留最小特征值,即取min(λ1,λ2)。此时消除了计算R的操作,直接用min(λ1,λ2)代替R。然后根据(R > threshold)检测可能的角点。注意:其中的threshold = minλ+(maxλ- minλ)*(minlevel/maxlevel)。在设定了minlevel和maxlevel后对结果图像dst使用minmaxLoc( Mc, &minλ, &maxλ, 0, 0, Mat());获得minλ和 &maxλ。
最小特征值算法根据特征值共同进退原理过滤角点的特征值,只要min(λ1,λ2)> threshold,可以肯定该像素点的角特征强度。
各角点检测函数的效果如图,cornerEigenValsAndVecs函数:
左图为没有距离过滤的角点,共612点,其中绝大多数重叠。右图为使用5像素邻域过滤重叠,只有79点。
cornerHarris函数:
左图为Harris检测,没有距离过滤,有1301个角点,中图为使用5邻域过滤,119角点,右图使用10邻域过滤,有79个角点。
cornerMinEigenVal函数:
左图为最小特征值检测,没有距离过滤,共1428个角点,中图为使用5邻域检测,有107个角点,右图为10邻域检测,有79个角点。
由上述示例可知,使用邻域重叠检测可排除绝大多数重叠角点。
void cv::cornerSubPix (InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
在已知角点坐标的情况下,精确查找并定位亚像素级的角点位置(对初始的整数角点坐标进行亚像素精度级的优化)。通过迭代处理达到理想精度(从整数角点坐标计算出小数精度的角点坐标)。进一步的精炼定位给定角点也可能会造成角点信息的损失,或偏移。只对非常强的角点特征值进行精炼有效,一般情况下,应该作用于goodFeaturesToTrack函数返回的强角点。如图:
左图为goodFeaturesToTrack函数检测的结果,右图为使用cornerSubPix函数精细化的结果,从图中可以看出cornerSubPix对角点进行了精细化调整。
使用其他角点检测函数,然后使用cornerSubPix进行精细化调整也是可行的。如图:
左图为cornerEigenValsAndVecs函数结果图,右图为cornerSubPix函数结果图,所有角点都基本调整到正确位置。
void cv::goodFeaturesToTrack (InputArray image, OutputArray corners,
int maxCorners,
double qualityLevel,
double minDistance,
InputArray mask=noArray(),
int blockSize=3,
bool useHarrisDetector=false,
double k=0.04)
这个函数在图像或图像的指定区域查找最显著的角点。使用minDistance来排除角点周围的干扰点(局部最大特征测度值)。maxCorners指定了需要返回的有效角点数,也可能返回少于指定数的角点个数。qualityLevel是检测像素点特征向量测度值的阈值,小于该测度值的像素点可以判定为非角点。useHarrisDetector指定使用Harris或者Shi Tomasi算法。K为harris算法需要的开放常量,一般为0.04。该函数的minDistance参数认为局部最大测度值点周围不能存在其他角点。通过反复迭代和排序确定最终的角点。如图:
左图使用最大角点数为100,距离邻域值为10。右图为最大角点数为79。
3、线段抽取函数
Ptr<LineSegmentDetector> cv::createLineSegmentDetector (
int _refine=LSD_REFINE_STD,
double _scale=0.8,
double _sigma_scale=0.6,
double _quant=2.0,
double _ang_th=22.5,
double _log_eps=0,
double _density_th=0.7,
int _n_bins=1024)
根据指定参数建立线段检测对象,返回指向这个对象的指针。通过指针调用对象的detect()函数检测图像中的线段。其中参数_refine指定检测线段使用的方法,有LSD_REFINE_STD
、LSD_REFINE_NONE和LSD_REFINE_ADV。其他参数一般使用默认值。
Ptr<FastLineDetector> cv::createFastLineDetector (
int _length_threshold = 10,
float _distance_threshold = 1.414213562f,
double _canny_th1 = 50.0,
double _canny_th2 = 50.0,
int _canny_aperture_size = 3,
bool _do_merge = false);
根据参数建立快速线段检测对象,并返回检测对象指针,通过指针调用对象的delect()函数检测图像中的的直线段。这是createLineSegmentDetector函数的改进型,由于代码版权的原因,opencv在3.0.0版本之后都是用这个函数替代createLineSegmentDetector。并在此后将createLineSegmentDetector代码移除opencv,以保持opencv代码的开源性。
参数:
int _length_threshold = 10 线段最小长度阈值
float _distance_threshold = 1.414213562f 线段最小距离阈值
double _canny_th1 = 50.0 canny算法灰度阈值
double _canny_th2 = 50.0
int _canny_aperture_size = 3 canny算法滤镜尺寸
bool _do_merge = false 是否合并线段
本函数使用canny算法抽取图像的边缘特征,在此基础上对图像的边缘线进行提炼,从而获得图像中的直线段。
左图使用createLineSegmentDetector函数,右图使用createFastLineDetector函数,作用相同的图像。
左图使用createLineSegmentDetector函数,右图使用createFastLineDetector函数,作用相同的图像。
从上两幅图像的效果可以看出,线段检测两函数各有优势,可根据检测目标的属性使用函数和设置参数。
- 综合属性函数
void cv::HoughCircles (InputArray image, OutputArray circles,
int method,
double dp,
double minDist,
double param1=100,
double param2=100,
int minRadius=0,
int maxRadius=0)
用霍夫变换检测图像中的圆,原理就是计算像素的梯度方向,并标记梯度方向上的点,圆心点的标记数是圆周边缘点数。根据阈值得出圆心位置以及圆周点列。可以使用圆周点列到中心的距离算法确定和区分同心圆的各个圆周点以及圆的参数(x,y,r)。
上图是函数HoughCircles在dp=1.minDist=20,param1=100,param2=50时的结果图。对于一般的图像进过高斯滤波后,即可得到其中的圆。有时候可能会得到一些假圆,这就需要其他方法进行排除。
void cv::HoughLines (InputArray image, OutputArray lines,
double rho,
double theta,
int threshold,
double srn=0,
double stn=0,
double min_theta=0,
double max_theta=CV_PI)
霍夫变换的原理是:在x-y坐标系中的直线上每一点导出的极坐标曲线(在极坐标中通过该点的所有直线在极坐标中绘出的曲线都相交于一点,直线的极点,一条直线对应一个极点)。计算图像中每像素所对应的极点,并排序过极点的像素数,根据阈值确定其像素列是否构成直线。
使用HoughLines函数返回的是直线属性(rho,theta),而不是图上的线段。因此所画直线都是贯穿全图的。
void cv::HoughLinesP (InputArray image, OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength=0,
double maxLineGap=0)
在HoughLines函数返回的基础上,计算(rho,theta,votes)即(ρ,θ,votes),其中votes为直线上的点列。从点列中逼近直线并给出可能的概率判断(阈值)。该函数返回的是直线段(x0,y0,x1,y1)而不是直线(ρ,θ)。
从图中可以看出返回的是线段,没有贯通线,真实反映图中的线段及其位置。
void cv::HoughLinesPointSet (InputArray _point, OutputArray _lines,
int lines_max,
int threshold,
double min_rho,
double max_rho,
double rho_step,
double min_theta,
double max_theta,
double theta_step)
对输入的点集计算霍夫直线。比如对于canny图像中的点,生成点集数组后作为输入,设置一定的参数后,函数给出满足条件的霍夫线(vector<Vec3d> (votes,rho,theta))。
Mat lines;
vector<Vec3d> line3d;
vector<Point2f> point;//从图中初始化点集push_back到point中
double rhoMin = 0.0f;
double rhoMax = 360.0f;
double rhoStep = 1;
double thetaMin = 0.0f;
double thetaMax = CV_PI / 2.0f;
double thetaStep = CV_PI / 180.0f;
HoughLinesPointSet(point, lines, 20, 1,rhoMin, rhoMax, rhoStep, thetaMin, thetaMax, thetaStep);
lines.copyTo(line3d);//从mat到线数组
同一图像(canny)通过三个lines函数HoughLines()、HoughLinesP()、HoughLinesPointSet()检测直线,得出不同结果。从图像中可以看出HoughLines()和HoughLinesP()函数能够得出比较直观的结果,HoughLinesPointSet()函数应该能检测出图像中的主要直线(识别主直线)。HoughLinesPointSet()在opencv3.4以上版本中出现。适当的step值和max值能很好地筛选结果直线。
void cv::preCornerDetect (InputArray src, OutputArray dst, int ksize, int borderType = BORDER_DEFAULT)
计算图像中每一像素点的特征值,形成特征值图像,在后续处理中可以使用阈值设定提取各阈值分段的角点位置。角点为特征值的局部最大值点。
在灰度图上检测的角点位置图。
在特征图(canny图)中检测的角点位置图。角点图中像素值为角点强度特征值。
6、特征检测的小结
图像的特征检测主要是对图像进行canny运算后,对图像边缘特征进行检测,首先是角点检测(角点强度特征值计算),然后是直线及线段检测,以及圆的检测。通过对图像的这些特征进行检测,能够基本得出图像中物体或目标的位置属性,再结合其他图像检测手段可以正确定位图像中的物理目标。在CT图像的三维重建中可以使用特征点信息构建图片系列的三维立体轮廓,使用opengl提供的工具建立起CT图片系列的三维模型。
注意:本文章内容前半部分来自opencv帮助资料,为本人所翻译,后半部分使用opencv函数编程所得的实际体验。源码来自opencv,经本人摘编和调试使用在程序中,基本来自opencv2.4.9,有些来自opencv3..0.0,opencv3.4.0和opencv4.3.0。由于各版本opencv采用的编程环境和C++版本关系,数据类型需要更改和重新定义,这里只是用了基本数据类型。