图像特征点检测包括角点和斑点,今天来说说斑点,斑点是指二维图像中和周围颜色有颜色差异和灰度差异的区域,因为斑点代表的是一个区域,所以其相对于单纯的角点,具有更好的稳定性和更好的抗干扰能力.斑点通常是指与周围有着颜色和灰度差别的区域。在实际地图中,往往存在着大量这样的斑点,如一颗树是一个斑点,一块草地是一个斑点,一栋房子也可以是一个斑点。由于斑点代表的是一个区域,相比单纯的角点,它的稳定性要好,抗噪声能力要强,所以它在图像配准上扮演了很重要的角色。同时有时图像中的斑点也是我们关心的区域,比如在医学与生物领域,我们需要从一些X光照片或细胞显微照片中提取一些具有特殊意义的斑点的位置或数量。
视觉领域的斑点检测的主要思路是检测出图像中比周围像素灰度打或者比周围区域灰度值小的区域,一般来说,有两种基本方法
1.基于求导的微分方法,这成为微分检测器
2.基于局部极值的分水岭算法,OPENCV中提供了simpleBlobDetector特征检测器来实现这种基本的斑点检测算法.
使用高斯拉普拉斯算子检测图像斑点是一种比较常见的办法,对一个二维的高斯函数G(x,y,),
它的拉普拉斯变换为:
进过规范化的拉普拉斯变换以后,
结果在二维图像上呈现为一个圆对称函数,我们可以用这个函数来检测图像的斑点,并且可以通过改动的值来检测不同尺寸的二维斑点.图像如下:
对上面这段话的直观理解是:一个图像和一个二维函数进行卷积运算,实际上就是求取图像和这个函数的相似性,同理,图像与高斯拉普拉斯函数进行卷积,就是求取图像和高斯拉普拉斯函数的相似性,当图像中的斑点尺寸与高斯拉普拉斯函数的形状趋于一致的时候,图像对应位置的拉普拉斯响应达到最大.
从概率的角度解释为:假设原图像是一个与位置有关的随机变量X的密度函数,而LOG为随机变量Y的密度函数,则随机变量X+Y的密度分布函数即为两个函数的卷积形式(这一部分的理论,可以参见本博客概率与统计相关文章)。如果想让X+Y能取到最大值,则X与Y能保持步调一致最好,即X上升时,Y也上升,X最大时,Y也最大。
那么LOG算子是怎么被构想出来的呢?
实际上,lapacian算子本身可以检测图像的极值点,设其为L算子,但是因为L算子并不能消除干扰,图像M中的噪声会影响求取到的结果,所以对图像f(x,y)进行名为G(x,y,)的高斯滤波去除干扰,去除图像中的噪点得到图像L,
然后对图像的拉普拉斯图像则为:
而实际上有下面等式:
所以,我们可以先求高斯核的拉普拉斯算子,再对图像进行卷积。也就是一开始描述的步骤
通过上面的那种算法,我们可以得到斑点,但是没有多尺度的斑点,当高斯协方差一定的时候,我们只能检测对应的半径的斑点,具体的半径通过对标准化的高斯拉普拉斯算子进行求导得到,
当时,,高斯拉普拉斯响应值达到最大。同理,如果图像中的圆形斑点黑白反向,那么,它的高斯拉普拉斯响应值在时达到最小。将高斯拉普拉斯响应达到峰值时的尺度值,称为特征尺度。
那么在多尺度的情况下,同时在空间和尺度上达到最大值(或最小值)的点就是我们所期望的斑点。对于二维图像I(x,y),计算图像在不同尺度下的离散拉普拉斯响应值,然后检查位置空间中的每个点;如果该点的拉普拉斯响应值都大于或小于其他26个立方空间领域(9+8+9)的值,那么该点就是被检测到的图像斑点.
Mat Feat::getHOGKernel(Size& ksize, double sigma)
{
Mat kernel(ksize, CV_64F);
Point centPoint = Point((ksize.width -1)/2, ((ksize.height -1)/2));
// first calculate Gaussian
for (int i=0; i < kernel.rows; i++)
{
double* pData = kernel.ptr(i);
for (int j = 0; j < kernel.cols; j++)
{
double param = -((i - centPoint.y) * (i - centPoint.y) + (j - centPoint.x) * (j - centPoint.x)) / (2*sigma*sigma);
pData[j] = exp(param);
}
}
double maxValue;
minMaxLoc(kernel, NULL, &maxValue);
for (int i=0; i < kernel.rows; i++)
{
double* pData = kernel.ptr(i);
for (int j = 0; j < kernel.cols; j++)
{
if (pData[j] < EPS* maxValue)
{
pData[j] = 0;
}
}
}
double sumKernel = sum(kernel)[0];
if (sumKernel != 0)
{
kernel = kernel / sumKernel;
}
// now calculate Laplacian
for (int i=0; i < kernel.rows; i++)
{
double* pData = kernel.ptr(i);
for (int j = 0; j < kernel.cols; j++)
{
double addition = ((i - centPoint.y) * (i - centPoint.y) + (j - centPoint.x) * (j - centPoint.x) - 2*sigma*sigma)/(sigma*sigma*sigma*sigma);
pData[j] *= addition;
}
}
// make the filter sum to zero
sumKernel = sum(kernel)[0];
kernel -= (sumKernel/(ksize.width * ksize.height));
return kernel;
}
这种检测方法分为以下几步
a.对一张图片,设定一个低阈值,设定一个高阈值,在设定一个阈值步进,然后从低阈值到高阈值按照阈值步进取一系列的阈值,即对[minThreshold,maxThreshold)区间,以thresholdStep为间隔,用每一个阈值对图像进行二值化,得到一系列图像;
b.对每张二值图片,使用findcontours查找这些图像的边,并计算每一个轮廓的中心;
c.根据b得到每一个图片的轮的中心点,全部放在一起。定义一个最小距离,在这个距离区域内的特征中心点[由theminDistBetweenBlobs控制多少才算接近]被归为一个group,对应一个bolb特征,得到特征点集合。
d.从c得到的那些点,估计最后的blob特征和相应半径,并以key points返回。对特征点进行相应的过滤,例如颜色过滤,面积过滤等
opencv中检测Blobs的类为SimpleBlobDetector,这个类在opencv中的定义如下:
class SimpleBlobDetector : public FeatureDetector
{
public:
struct Params
{
Params();
float thresholdStep;
float minThreshold;
float maxThreshold;
size_t minRepeatability;
float minDistBetweenBlobs;
bool filterByColor;
uchar blobColor;
bool filterByArea;
float minArea, maxArea;
bool filterByCircularity;
float minCircularity, maxCircularity;
bool filterByInertia;
float minInertiaRatio, maxInertiaRatio;
bool filterByConvexity;
float minConvexity, maxConvexity;
};
SimpleBlobDetector(const SimpleBlobDetector::Params ¶meters = SimpleBlobDetector::Params());
protected:
...
};
实例1:
//适用于opencv2
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat image = imread(argv[1]);
vector keyPoints;
SimpleBlobDetector::Params params;
SimpleBlobDetector blobDetect(params);
blobDetect.create("SimpleBlob");
blobDetect.detect(image, keyPoints);
cout << keyPoints.size() << endl;
drawKeypoints(image, keyPoints, image, Scalar(255,0,0));
namedWindow("blobs");
imshow("blobs", image);
waitKey();
return 0;
}
实例2 :
#include
#include
#include
using namespace std;
using namespace cv;
int main(){
Mat img = imread("blob.jpg",IMREAD_GRAYSCALE);
/*
SimpleBlobDetector::Params params;
//阈值控制
params.minThreshold = 10;
params.maxThreshold = 200;
//像素面积大小控制
params.filterByArea = true;
params.minArea = 1000;
//形状(凸)
params.filterByCircularity = false;
params.minCircularity = 0.7;
//形状(凹)
params.filterByConvexity = true;
params.minConvexity = 0.9;
//形状(园)
params.filterByInertia = false;
params.minInertiaRatio = 0.5;
*/
Ptr detector = SimpleBlobDetector::create();
vector keypoints;
detector->detect(img,keypoints);
Mat img_with_keypoints;
drawKeypoints(img,keypoints,img_with_keypoints,Scalar(0,0,255),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
imshow("keypoints",img_with_keypoints);
waitKey(0);
return 0;
}
结果:
from:https://blog.csdn.net/Good_Boyzq/article/details/72811687
from:https://blog.csdn.net/u012968002/article/details/42969107