Blob是指图像中的一块连通区域,Blob分析就是对前景/背景分离后的二值图像,进行连通域提取和标记。知识点就是SimpleBlobDetector的使用,blob(斑点)筛选条件:斑点颜色、面积、圆度、惯性率、凸度。
void blobDetector()
{
Mat img = imread("d:\\11.jpg");
SimpleBlobDetector::Params params;
params.thresholdStep = 10; //二值化的阈值步长
params.minThreshold = 20;
params.maxThreshold = 200;
params.filterByArea = true;
params.minArea = 10;
params.maxArea = 80000;
params.filterByCircularity = true;
params.minCircularity = 0.5;
Ptr<SimpleBlobDetector> detector = SimpleBlobDetector::create(params);
vector<KeyPoint> key_points;
detector->detect(img, key_points);
Mat output_img;
drawKeypoints(img, key_points, output_img, Scalar(0, 0, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
namedWindow("SimpleBlobDetector", WINDOW_NORMAL);
imshow("SimpleBlobDetector", output_img);
waitKey(0);
}
- 阈值:通过使用以minThreshold开始的阈值对源图像进行阈值处理,将源图像转换为多个二进制图像。这些阈值以thresholdStep递增,直到maxThreshold。因此,第一个阈值为minThreshold,第二个阈值为minThreshold + thresholdStep,第三个阈值为minThreshold + 2 x thresholdStep,依此类推;
- 按大小:可以通过设置参数filterByArea = 1以及minArea和maxArea的适当值来基于大小过滤blob。例如。设置minArea = 100将滤除所有少于100个像素的斑点。
- 按圆度:这只是测量斑点距圆的距离。例如。正六边形的圆度比正方形高。要按圆度过滤,请设置filterByCircularity =1。然后为minCircularity和maxCircularity设置适当的值。圆度定义为(
以下给出 opencv 对候选 Blob 筛查的部分源码:
https://github.com/opencv/opencv/blob/4.2.0/modules/features2d/src/feature2d.cpp
https://github.com/opencv/opencv/blob/4.2.0/modules/features2d/src/blobdetector.cpp
//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
}
---
参考文献
OpenCV图像处理中“找圆技术”的使用