本文是一个较完整的Blob分析思路解说,是用OpenCvSharp联合c#编写的。
思路如下:
1、对图像进行二值化
2、设定ROI
3、腐蚀、膨胀(可选)
4、边缘提取(有区分是否需填充孔洞)
5、进行筛选
本来是要提供图片的,但发现上传不方便就算了,可以去我文库看完整版:file:///C:/Users/Administrator/Desktop/%E5%9B%BE%E7%89%871.png
部分源代码解说:
InputImage来自输入的要处理的图片。
gray_min、gray_max二值化的上下阈值
1、对图像进行二值化(根据二值化的上下阈值来判定二值化方式)
Mat binaryImage = new Mat(InputImage.Size(), MatType.CV_8UC1, Scalar.Black);
if (gray_min != 0 && gray_max != 255)
{
binaryImage = InputImage.Threshold(gray_min, 255, ThresholdTypes.Binary);
Mat binaryImage2 = InputImage.Threshold(gray_max, 255, ThresholdTypes.BinaryInv);
Mat binaryImage3 = new Mat(InputImage.Size(), MatType.CV_8UC1, Scalar.Black);
binaryImage.CopyTo(binaryImage3, binaryImage2);
binaryImage = binaryImage3.Clone();
binaryImage2.Dispose();
binaryImage3.Dispose();
}
else
{
if (gray_min == 0) binaryImage = InputImage.Threshold(gray_max, 255, ThresholdTypes.BinaryInv);
if (gray_max == 255) binaryImage = InputImage.Threshold(gray_min, 255, ThresholdTypes.Binary);
}
Mat binaryImage1 = new Mat(InputImage.Size(), MatType.CV_8UC1, Scalar.Black); 2、设定ROI(Mask就是ROI感兴趣区域)
binaryImage.CopyTo(binaryImage1, Mask);
3、腐蚀、膨胀(可选)
if (erosion_dilation == "腐蚀")
{
Cv2.Erode(binaryImage1, binaryImage1, element);
}
else
{
Cv2.Dilate(binaryImage1, binaryImage1, element);
}
4、边缘提取(有区分是否需填充孔洞)
if (fill_up_flag == true)
{
Cv2.FindContours(binaryImage1, out contours, hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
}
else
{
Cv2.FindContours(binaryImage1, out contours, hierarchy, RetrievalModes.CComp, ContourApproximationModes.ApproxSimple);
}
5、进行筛选
Mat uu = hierarchy.GetMat();
// 筛选那些面积小的
List<MyCenter> centers = new List<MyCenter>();
List<RotatedRect> regions = new List<RotatedRect>();
var detectorParams = new SimpleBlobDetector.Params
{
MinDistBetweenBlobs = 10, // 10 pixels between blobs
MinRepeatability = 1,
MinThreshold = 0,
MaxThreshold = 128,
ThresholdStep = 2,
FilterByArea = area_bool,
MinArea = area_min, // 10 pixels squared
MaxArea = area_max,
//圆度
FilterByCircularity = circularity_bool,
MinCircularity = circularity_min,
MaxCircularity = circularity_max,
// 凸包形状分析 - 过滤凹包
FilterByConvexity = false,
//FilterByConvexity = true,
MinConvexity = 0.001f,
MaxConvexity = 10,
FilterByInertia = false,
//FilterByInertia = true,
MinInertiaRatio = 0.001f,
FilterByColor = false,
//FilterByColor = true,
BlobColor = 255 // to extract light blobs
};
MyCenter center;
int j = 0;
Vec4i value;
Mat cnt = new Mat();
foreach (Mat cnt0 in contours)
{
value = uu.Get<Vec4i>(0, j);
j++;
//int i = hierarchy;
// 根据轮廓面积进行筛选
if (value[3] != -1)
{
cnt = contours[value[3]];
}
else
{
cnt = cnt0;
}
center.confidence = 1;
Moments moms = Cv2.Moments(cnt);
if (detectorParams.FilterByArea) //斑点面积的限制
{
double area = moms.M00; //零阶矩即为二值图像的面积
//如果面积超出了设定的范围,则不再考虑该斑点
if (area < detectorParams.MinArea || area >= detectorParams.MaxArea)
continue;
}
if (detectorParams.FilterByCircularity) //斑点圆度的限制
{
double area = moms.M00; //得到斑点的面积
//计算斑点的周长
double perimeter = Cv2.ArcLength(cnt, true);
//由公式3得到斑点的圆度
double ratio = 4 * Cv2.PI * area / (perimeter * perimeter);
//如果圆度超出了设定的范围,则不再考虑该斑点
if (ratio < detectorParams.MinCircularity || ratio >= detectorParams.MaxCircularity)
continue;
}
if (detectorParams.FilterByInertia) //斑点惯性率的限制
{
//计算公式13中最右侧等式中的开根号的值
double denominator = Math.Sqrt(Math.Pow(2 * moms.Mu11, 2) + Math.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 < detectorParams.MinInertiaRatio || ratio >= detectorParams.MaxInertiaRatio)
continue;
//斑点中心的权值定义为惯性率的平方
center.confidence = ratio * ratio;
}
if (detectorParams.FilterByConvexity) //斑点凸度的限制
{
Mat hull = new Mat(); //定义凸壳变量
//调用convexHull函数,得到该斑点的凸壳
Cv2.ConvexHull(cnt, hull);
//分别得到斑点和凸壳的面积,contourArea函数本质上也是求图像的零阶矩
double area = Cv2.ContourArea(cnt);
double hullArea = Cv2.ContourArea(hull);
double ratio = area / hullArea; //公式5,计算斑点的凸度
//如果凸度超出了设定的范围,则不再考虑该斑点
if (ratio < detectorParams.MinConvexity || ratio >= detectorParams.MaxConvexity)
continue;
}
//根据公式7,计算斑点的质心
center.location = new Point2d(moms.M10 / moms.M00, moms.M01 / moms.M00);
// 根据轮廓近似进行筛选,作用很小
double epsilon = 0.001 * Cv2.ArcLength(cnt, true);
Mat approx = new Mat();
Cv2.ApproxPolyDP(cnt, approx, epsilon, true);
// 根据最小矩形进行筛选,该矩形可能有方向
RotatedRect rect = Cv2.MinAreaRect(approx);
if (width_bool)
{
int width = rect.BoundingRect().Width;
if (width < width_min || width > width_max) continue;
}
if (height_bool)
{
int height = rect.BoundingRect().Height;
if (height < height_min || height > height_max) continue;
}
//根据质心来判断颜色不合理,但是我觉得这个不合理就把它禁用了
centers.Add(center);
outcontours.Add(cnt0);
}
|