原理部分参考链接:点击跳转https://blog.csdn.net/zhaocj/article/details/44886475
下面给出一个实际应用中调试出的参数值大小的例子
#include
#include
int main(int argc, char **argv) {
//[0]读入图片并检测
cv::Mat srcImg1 = cv::imread("Led。jpg", 0);
if (!srcImg1.data) { std::cout << "enter picture is wrong" << std::endl; return -1; }
cv::Mat srcImgTemp = srcImg1.clone();
//[1]创建结构体 变量params 用于存储各参数值
cv::SimpleBlobDetector::Params params;
params.minThreshold = 0; //二值化的起始阈值,即公式1的T1
params.thresholdStep = 20; //采用接近真实应用的20阈值
params.filterByInertia = true; //斑点圆度的限制变量,默认是不限制
params.filterByColor = true; //斑点颜色的限制变量
params.blobColor = 255; //表示只提取白色斑点
params.filterByArea = true; //斑点面积的限制变量
params.minArea = 30; //斑点的最小面积
//最小的斑点距离,不同二值图像的斑点间距离小于该值时,被认为是同一个位置的斑点,否则是不同位置上的斑点
params.minDistBetweenBlobs = 50;//7m处取最小距离应为50。
//[2]定义斑点检测类的对象 detector 并用参数params初始化
cv::Ptr<cv::SimpleBlobDetector> detector = cv::SimpleBlobDetector::create(params);
std::vector<cv::KeyPoint> keyPoints1;
detector->detect(srcImgTemp, keyPoints1);//采用SimpleBlobDetector方法检测关键点存与数组keyPoint_1中
//[3]将斑点的中心坐标存储在Circle_Center中
std::vector<cv::Point2f> circleCenter;
for (int i = 0; i < keyPoints1.size(); i++) {
circleCenter.push_back(keyPoints1[i].pt);
}
//test function to output keypoints_1
//输出区块的中心点
for (int i = 0; i < circleCenter.size(); i++) {
std::cout << "circle_center["<<i<<"]"<<circleCenter[i]<< std::endl;
}
//圈出区块的中心点显示在图片中
cv::drawKeypoints(srcImgTemp, keyPoints1,srcImgTemp,
cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DEFAULT);
cv::imshow("KeyPoint",srcImgTemp);
cv::waitKey(0);
return 0;
}
#include
#include
int main(int argc, char** argv)
{
//[0]读取图片
cv::Mat img = cv::imread("blob.jpg",0);
//[1]定义结构体Params
cv::SimpleBlobDetector::Params params;//定义params结构体并赋初值
params.minThreshold = 40;
params.maxThreshold = 160;
params.thresholdStep = 5;
params.minArea = 100;
params.minConvexity = .05f;
params.minInertiaRatio = .05f;
params.maxArea = 8000;
//[2]用指针模板创建指针detector 并用设定好的参数初始化
cv::Ptr<cv::SimpleBlobDetector> detector =cv::SimpleBlobDetector::create(params);
std::vector<cv::KeyPoint> key_points; //创建关键点数组
//[3]调用SimpleBlobDetector类的检测函数detect()
detector->detect(img, key_points);//调用detect()函数进行检测
cv::Mat output_img;//输出图
cv::drawKeypoints(img, key_points, output_img, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);//圈出监测到啊的斑点
//[4]显示
cv::namedWindow("SimpleBlobDetector");
cv::imshow("SimpleBlobDetector", output_img);
cv::waitKey(0);
return 0;
}
SimpleBlobDetector::Params::Params()
{
thresholdStep = 10; //二值化的阈值步长,即公式1的t
minThreshold = 50; //二值化的起始阈值,即公式1的T1
maxThreshold = 220; //二值化的终止阈值,即公式1的T2
//重复的最小次数,只有属于灰度图像斑点的那些二值图像斑点数量大于该值时,该灰度图像斑点才被认为是特征点
minRepeatability = 2;
//最小的斑点距离,不同二值图像的斑点间距离小于该值时,被认为是同一个位置的斑点,否则是不同位置上的斑点
minDistBetweenBlobs = 10;
filterByColor = true; //斑点颜色的限制变量
blobColor = 0; //表示只提取黑色斑点;如果该变量为255,表示只提取白色斑点
filterByArea = true; //斑点面积的限制变量
minArea = 25; //斑点的最小面积
maxArea = 5000; //斑点的最大面积
filterByCircularity = false; //斑点圆度的限制变量,默认是不限制
minCircularity = 0.8f; //斑点的最小圆度
//斑点的最大圆度,所能表示的float类型的最大值
maxCircularity = std::numeric_limits<float>::max();
filterByInertia = true; //斑点惯性率的限制变量
//minInertiaRatio = 0.6;
minInertiaRatio = 0.1f; //斑点的最小惯性率
maxInertiaRatio = std::numeric_limits<float>::max(); //斑点的最大惯性率
filterByConvexity = true; //斑点凸度的限制变量
//minConvexity = 0.8;
minConvexity = 0.95f; //斑点的最小凸度
maxConvexity = std::numeric_limits<float>::max(); //斑点的最大凸度
}
检测二值图像斑点的函数findBlods()
//image为输入的灰度图像
//binaryImage为二值图像
//centers表示该二值图像的斑点
void SimpleBlobDetector::findBlobs(const cv::Mat &image, const cv::Mat &binaryImage, vector<Center> ¢ers) const
{
(void)image;
centers.clear(); //斑点变量清零
vector < vector<Point> > contours; //定义二值图像的斑点的边界像素变量
Mat tmpBinaryImage = binaryImage.clone(); //复制二值图像
//调用findContours函数,找到当前二值图像的所有斑点的边界
findContours(tmpBinaryImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
#ifdef DEBUG_BLOB_DETECTOR
// Mat keypointsImage;
// cvtColor( binaryImage, keypointsImage, CV_GRAY2RGB );
//
// Mat contoursImage;
// cvtColor( binaryImage, contoursImage, CV_GRAY2RGB );
// drawContours( contoursImage, contours, -1, Scalar(0,255,0) );
// imshow("contours", contoursImage );
#endif
//遍历当前二值图像的所有斑点
for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++)
{
//结构类型Center代表着斑点,它包括斑点的中心位置,半径和权值
Center center; //斑点变量
//初始化斑点中心的置信度,也就是该斑点的权值
center.confidence = 1;
//调用moments函数,得到当前斑点的矩
Moments moms = moments(Mat(contours[contourIdx]));
if (params.filterByArea) //斑点面积的限制
{
double area = moms.m00; //零阶矩即为二值图像的面积
//如果面积超出了设定的范围,则不再考虑该斑点
if (area < params.minArea || area >= params.maxArea)
continue;
}
if (params.filterByCircularity) //斑点圆度的限制
{
double area = moms.m00; //得到斑点的面积
//计算斑点的周长
double perimeter = arcLength(Mat(contours[contourIdx]), true);
//由公式3得到斑点的圆度
double ratio = 4 * CV_PI * area / (perimeter * perimeter);
//如果圆度超出了设定的范围,则不再考虑该斑点
if (ratio < params.minCircularity || ratio >= params.maxCircularity)
continue;
}
if (params.filterByInertia) //斑点惯性率的限制
{
//计算公式13中最右侧等式中的开根号的值
double denominator = sqrt(pow(2 * moms.mu11, 2) + pow(moms.mu20 - moms.mu02, 2));
const double eps = 1e-2; //定义一个极小值
double ratio;
if (denominator > eps)
{
//cosmin和sinmin用于计算图像协方差矩阵中较小的那个特征值λ2
double cosmin = (moms.mu20 - moms.mu02) / denominator;
double sinmin = 2 * moms.mu11 / denominator;
//cosmin和sinmin用于计算图像协方差矩阵中较大的那个特征值λ1
double cosmax = -cosmin;
double sinmax = -sinmin;
//imin为λ2乘以零阶中心矩μ00
double imin = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmin - moms.mu11 * sinmin;
//imax为λ1乘以零阶中心矩μ00
double imax = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmax - moms.mu11 * sinmax;
ratio = imin / imax; //得到斑点的惯性率
}
else
{
ratio = 1; //直接设置为1,即为圆
}
//如果惯性率超出了设定的范围,则不再考虑该斑点
if (ratio < params.minInertiaRatio || ratio >= params.maxInertiaRatio)
continue;
//斑点中心的权值定义为惯性率的平方
center.confidence = ratio * ratio;
}
if (params.filterByConvexity) //斑点凸度的限制
{
vector < Point > hull; //定义凸壳变量
//调用convexHull函数,得到该斑点的凸壳
convexHull(Mat(contours[contourIdx]), hull);
//分别得到斑点和凸壳的面积,contourArea函数本质上也是求图像的零阶矩
double area = contourArea(Mat(contours[contourIdx]));
double hullArea = contourArea(Mat(hull));
double ratio = area / hullArea; //公式5,计算斑点的凸度
//如果凸度超出了设定的范围,则不再考虑该斑点
if (ratio < params.minConvexity || ratio >= params.maxConvexity)
continue;
}
//根据公式7,计算斑点的质心
center.location = Point2d(moms.m10 / moms.m00, moms.m01 / moms.m00);
if (params.filterByColor) //斑点颜色的限制
{
//判断一下斑点的颜色是否正确
if (binaryImage.at<uchar> (cvRound(center.location.y), cvRound(center.location.x)) != params.blobColor)
continue;
}
//compute blob radius
{
vector<double> dists; //定义距离队列
//遍历该斑点边界上的所有像素
for (size_t pointIdx = 0; pointIdx < contours[contourIdx].size(); pointIdx++)
{
Point2d pt = contours[contourIdx][pointIdx]; //得到边界像素坐标
//计算该点坐标与斑点中心的距离,并放入距离队列中
dists.push_back(norm(center.location - pt));
}
std::sort(dists.begin(), dists.end()); //距离队列排序
//计算斑点的半径,它等于距离队列中中间两个距离的平均值
center.radius = (dists[(dists.size() - 1) / 2] + dists[dists.size() / 2]) / 2.;
}
centers.push_back(center); //把center变量压入centers队列中
#ifdef DEBUG_BLOB_DETECTOR
// circle( keypointsImage, center.location, 1, Scalar(0,0,255), 1 );
#endif
}
#ifdef DEBUG_BLOB_DETECTOR
// imshow("bk", keypointsImage );
// waitKey();
#endif
}
原文链接:https://blog.csdn.net/zhaocj/article/details/44886475
检测特征点的函数detectImpl
void SimpleBlobDetector::detectImpl(const cv::Mat& image, std::vector<cv::KeyPoint>& keypoints, const cv::Mat&) const
{
//TODO: support mask
keypoints.clear(); //特征点变量清零
Mat grayscaleImage;
//把彩色图像转换为二值图像
if (image.channels() == 3)
cvtColor(image, grayscaleImage, CV_BGR2GRAY);
else
grayscaleImage = image;
//二维数组centers表示所有得到的斑点,第一维数据表示的是灰度图像斑点,第二维数据表示的是属于该灰度图像斑点的所有二值图像斑点
//结构类型Center代表着斑点,它包括斑点的中心位置,半径和权值
vector < vector<Center> > centers;
//遍历所有阈值,进行二值化处理
for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep)
{
Mat binarizedImage;
//调用threshold函数,把灰度图像grayscaleImage转换为二值图像binarizedImage
threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY);
#ifdef DEBUG_BLOB_DETECTOR
// Mat keypointsImage;
// cvtColor( binarizedImage, keypointsImage, CV_GRAY2RGB );
#endif
//变量curCenters表示该二值图像内的所有斑点
vector < Center > curCenters;
//调用findBlobs函数,对二值图像binarizedImage检测斑点,得到curCenters
findBlobs(grayscaleImage, binarizedImage, curCenters);
//newCenters表示在当前二值图像内检测到的不属于已有灰度图像斑点的那些二值图像斑点
vector < vector<Center> > newCenters;
//遍历该二值图像内的所有斑点
for (size_t i = 0; i < curCenters.size(); i++)
{
#ifdef DEBUG_BLOB_DETECTOR
// circle(keypointsImage, curCenters[i].location, curCenters[i].radius, Scalar(0,0,255),-1);
#endif
// isNew表示的是当前二值图像斑点是否为新出现的斑点
bool isNew = true;
//遍历已有的所有灰度图像斑点,判断该二值图像斑点是否为新的灰度图像斑点
for (size_t j = 0; j < centers.size(); j++)
{
//属于该灰度图像斑点的中间位置的那个二值图像斑点代表该灰度图像斑点,并把它的中心坐标与当前二值图像斑点的中心坐标比较,计算它们的距离dist
double dist = norm(centers[j][ centers[j].size() / 2 ].location - curCenters[i].location);
//如果距离大于所设的阈值,并且距离都大于上述那两个二值图像斑点的半径,则表示该二值图像的斑点是新的斑点
isNew = dist >= params.minDistBetweenBlobs && dist >= centers[j][ centers[j].size() / 2 ].radius && dist >= curCenters[i].radius;
//如果不是新的斑点,则需要把它添加到属于它的当前(即第j个)灰度图像的斑点中,因为通过上面的距离比较可知,当前二值图像斑点属于当前灰度图像斑点
if (!isNew)
{
//把当前二值图像斑点存入当前(即第j个)灰度图像斑点数组的最后位置
centers[j].push_back(curCenters[i]);
//得到构成该灰度图像斑点的所有二值图像斑点的数量
size_t k = centers[j].size() - 1;
//按照半径由小至大的顺序,把新得到的当前二值图像斑点放入当前灰度图像斑点数组的适当位置处,由于二值化阈值是按照从小到大的顺序设置,所以二值图像斑点本身就是按照面积的大小顺序被检测到的,因此此处的排序处理要相对简单一些
while( k > 0 && centers[j][k].radius < centers[j][k-1].radius )
{
centers[j][k] = centers[j][k-1];
k--;
}
centers[j][k] = curCenters[i];
//由于当前二值图像斑点已经找到了属于它的灰度图像斑点,因此退出for循环,无需再遍历已有的灰度图像斑点
break;
}
}
if (isNew) //当前二值图像斑点是新的斑点
{
//把当前斑点存入newCenters数组内
newCenters.push_back(vector<Center> (1, curCenters[i]));
//centers.push_back(vector (1, curCenters[i]));
}
}
//把当前二值图像内的所有newCenters复制到centers内
std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers));
#ifdef DEBUG_BLOB_DETECTOR
// imshow("binarized", keypointsImage );
//waitKey();
#endif
} //所有二值图像斑点检测完毕
//遍历所有灰度图像斑点,得到特征点信息
for (size_t i = 0; i < centers.size(); i++)
{
//如果属于当前灰度图像斑点的二值图像斑点的数量小于所设阈值,则该灰度图像斑点不是特征点
if (centers[i].size() < params.minRepeatability)
continue;
Point2d sumPoint(0, 0);
double normalizer = 0;
//遍历属于当前灰度图像斑点的所有二值图像斑点
for (size_t j = 0; j < centers[i].size(); j++)
{
sumPoint += centers[i][j].confidence * centers[i][j].location; //公式2的分子
normalizer += centers[i][j].confidence; //公式2的分母
}
sumPoint *= (1. / normalizer); //公式2,得到特征点的坐标位置
//保存该特征点的坐标位置和尺寸大小
KeyPoint kpt(sumPoint, (float)(centers[i][centers[i].size() / 2].radius));
keypoints.push_back(kpt); //保存该特征点
}
#ifdef DEBUG_BLOB_DETECTOR
namedWindow("keypoints", CV_WINDOW_NORMAL);
Mat outImg = image.clone();
for(size_t i=0; i<keypoints.size(); i++)
{
circle(outImg, keypoints[i].pt, keypoints[i].size, Scalar(255, 0, 255), -1);
}
//drawKeypoints(image, keypoints, outImg);
imshow("keypoints", outImg);
waitKey();
#endif
}
detect() 函数分析
void SimpleBlobDetectorImpl::detect(InputArray image, std::vector<cv::KeyPoint>& keypoints, InputArray)
{
//TODO: support mask
keypoints.clear();
Mat grayscaleImage;
if (image.channels() == 3)
cvtColor(image, grayscaleImage, COLOR_BGR2GRAY);
else
grayscaleImage = image.getMat();
if (grayscaleImage.type() != CV_8UC1) {
CV_Error(Error::StsUnsupportedFormat, "Blob detector only supports 8-bit images!");
}
std::vector < std::vector<Center> > centers;
for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep)
{
Mat binarizedImage;
//threshold 使得
//1. grayscaleImage中
//2. grayscaleImage中>=thresh的像素,其在binarizedImagek的对应位置像素为255
threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY);
std::vector < Center > curCenters;
findBlobs(grayscaleImage, binarizedImage, curCenters);
std::vector < std::vector<Center> > newCenters;
for (size_t i = 0; i < curCenters.size(); i++)
{
bool isNew = true;
for (size_t j = 0; j < centers.size(); j++)
{
double dist = norm(centers[j][ centers[j].size() / 2 ].location - curCenters[i].location);
isNew = dist >= params.minDistBetweenBlobs && dist >= centers[j][ centers[j].size() / 2 ].radius && dist >= curCenters[i].radius;
if (!isNew)
{
centers[j].push_back(curCenters[i]);
size_t k = centers[j].size() - 1;
while( k > 0 && centers[j][k].radius < centers[j][k-1].radius )
{
centers[j][k] = centers[j][k-1];
k--;
}
centers[j][k] = curCenters[i];
break;
}
}
if (isNew)
newCenters.push_back(std::vector<Center> (1, curCenters[i]));
}
std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers));
}
for (size_t i = 0; i < centers.size(); i++)
{
//remove 不够稳定的blob,即重复次数少于params.minRepeatability
if (centers[i].size() < params.minRepeatability)
continue;
Point2d sumPoint(0, 0);
double normalizer = 0;
for (size_t j = 0; j < centers[i].size(); j++)
{
sumPoint += centers[i][j].confidence * centers[i][j].location;
normalizer += centers[i][j].confidence;
}
sumPoint *= (1. / normalizer);
KeyPoint kpt(sumPoint, (float)(centers[i][centers[i].size() / 2].radius) * 2.0f);
keypoints.push_back(kpt);
}
}