Opencv C# 重叠 粘连 Overlap 轮廓分割 (不知道不知道)_opencv检测重叠图形
先上效果图
一种基于凹陷检测重叠轮廓分割的方法
这两个星期压力大的一批,心脏都给干得乱跳了,现在高血压+心率不齐+贫血。兄弟们保重身体啊。
简单说下逻辑:
- 前处理:的噼里啪啦我就不说了,根据样品来(灰度,滤波,二值化,形态学...)
- 提取轮廓并计算凸包
- 填充凸包后的轮廓 减去 原始轮廓得到凹陷区域
- 计算凹陷区域的面积,利用最大的两个凹陷区域计算出两个凹陷区域的最近两点
- 两点一条线作为分割线
步骤说起来很简单,做起来准备上天。
要想做到好的效果就需要有过滤参数,我做了基本的四个参数
- 最小轮廓长度:用来过滤小颗粒
- 深度阈值:凸包凹陷的阈值 用来过滤凹陷浅的轮廓
- 迭代次数:我们上面只是拿了单个轮廓两个最大凹陷区域进行分割,只适合两个规则形状的重叠,那三个呢 四个呢 无数个呢(管他那么多就是干)
- 线宽:分割线的宽度 最好要为2,别问 问就是坑
下面看看不同参数的效果:
迭代次数:可以简单理解我要分为几个颗粒
深度阈值:越小 小凹陷就越多
效果就这样了,这是基于形状的,但是都说基于距离变换+分水岭的好。先贴上代码吧白嫖兄弟们
!!!这是我封装的方法不一定适合你 参数自己传进来就行。最好别用,不一定好用。打开思路很重要..........
比如:根据长轴作为分界线 对应点是不是就可以不用那么多迭代???更好的?骨架作为分界线???迭代次数应该写在轮廓遍历里面???当然是的,但是我脑子不想思考了才来写个文章。
public ProcessingResult ProcessImage(Mat src, Dictionary parameters, Mat? originalMat = null){ if (!src.IsValidMat()) return new ProcessingResult(); try { int depthThreshold = parameters.GetValueOrDefault(\"depthThreshold\", 100)?.ToString()?.ToInt(100) ?? 100; int lineThickness = parameters.GetValueOrDefault(\"lineThickness\", 2)?.ToString()?.ToInt(2) ?? 2; int miniArcLength = parameters.GetValueOrDefault(\"miniArcLength\", 20)?.ToString()?.ToInt(20) ?? 20; int iterations = parameters.GetValueOrDefault(\"iterations\", 1)?.ToString()?.ToInt(1) ?? 1; Mat binary = new Mat(); if (src.Channels() > 1) { var gray = src.CvtColor(ColorConversionCodes.BGR2GRAY); double otsuThresh = Cv2.Threshold(src, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu); } else { binary = src; } Mat resultImage = binary.CvtColor(ColorConversionCodes.GRAY2BGR); // 彩色图方便显示 for (int iteration = 0; iteration < iterations; iteration++) { var contours = Cv2.FindContoursAsArray(binary, RetrievalModes.External, ContourApproximationModes.ApproxSimple); foreach (var contour in contours) { try { if (contour.Length < 5) continue; double arcLen = Cv2.ArcLength(contour, true); if (arcLen < miniArcLength) continue; int[] hullIndices = Cv2.ConvexHullIndices(contour, true); if (hullIndices.Length a.Item3 / 256f > depthThreshold)) continue; Point[] hull = new Point[hullIndices.Length]; for (int i = 0; i new Point(p.X - offset.X, p.Y - offset.Y)).ToArray(); } var hullLocal = TranslateContour(hull, rect.TopLeft); var contourLocal = TranslateContour(contour, rect.TopLeft); Cv2.DrawContours(mask, new List() { hullLocal }, 0, Scalar.White, -1); Cv2.DrawContours(mask, new List() { contourLocal }, 0, Scalar.Black, -1); var maskContours = Cv2.FindContoursAsArray(mask, RetrievalModes.External, ContourApproximationModes.ApproxSimple); if (maskContours.Length rect.Width / 2) continue; // 注意minDistancePoints中是mask局部坐标,转回resultImage全局坐标: Cv2.Line(binary, minDistancePoints[0] + rect.TopLeft, minDistancePoints[1] + rect.TopLeft, Scalar.Black, lineThickness); Cv2.Line(resultImage, minDistancePoints[0] + rect.TopLeft, minDistancePoints[1] + rect.TopLeft, Scalar.OrangeRed, lineThickness); } catch (Exception) { continue; } } } return new ProcessingResult(resultImage); } catch (Exception ex) { Console.WriteLine(ex); return new ProcessingResult(); }}private Point[][] GetTop2MaxAreaContours(Point[][] contours){ if (contours == null || contours.Length == 0) return new Point[0][]; // 按轮廓面积降序排序,取前2个 var top2 = contours.OrderByDescending(contour => Cv2.ContourArea(contour)).ToArray(); return top2;}private Point[] GetMinDistancePoint(Point[] contour, Point[] contour1){ if (contour == null || contour1 == null || contour.Length == 0 || contour1.Length == 0) return new Point[0]; Point minP1 = new Point(); Point minP2 = new Point(); double minDist = double.MaxValue; foreach (var p1 in contour) { foreach (var p2 in contour1) { double dist = Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y)); if (dist < minDist) { minDist = dist; minP1 = p1; minP2 = p2; } } } return new Point[] { minP1, minP2 };}
上面是基于凸包凹陷的,那都说分水岭+距离变换好!!!但是,好是有前提的 对一同样大小颗粒的分割效果是很好的。但是对大小不一效果不太行,但是我也做了优化 下面给出大体逻辑。
一种基于距离变幻+分水岭 检测重叠轮廓分割的方法
- 前处理:的噼里啪啦我就不说了,根据样品来(灰度,滤波,二值化,形态学...)
- 进行距离变换 DistanceTransform+Normalize
- 获取前景标记 Cv2.Threshold(distTrans8u, distTrans8u, foregroundThreshold * 255, 255, ThresholdTypes.Binary);
- 创建标记图像 Mat markers = Mat.Zeros(binary.Size(), MatType.CV_32S);
- 标记背景 using var sureBg = new Mat();
Cv2.Dilate(binary, sureBg, new Mat(), iterations: 3);- 应用分水岭算法 Cv2.Watershed(originalMat, markers);
- 前面几步都是烂大街的 随便一搜都有的代码 不清楚的直接搜 距离变换+分水岭
- 得到轮廓并绘制在一张与原图大小相等的黑图上并把边界涂色
result = Mat.Zeros(originalMat.Size(), originalMat.Type()); var boundaries = new Mat(); Cv2.Compare(markers, new Scalar(-1), boundaries, CmpType.EQ); result.SetTo(new Scalar(255, 255, 255), boundaries); result.Row(0).SetTo(new Scalar(0, 0, 0)); result.Row(result.Rows - 1).SetTo(new Scalar(0, 0, 0)); result.Col(0).SetTo(new Scalar(0, 0, 0)); result.Col(result.Cols - 1).SetTo(new Scalar(0, 0, 0)); boundaries.Dispose();
上一步得到了所谓分水岭的轮廓,但是很有可能把你的小轮廓给干掉了 或者说正常的轮廓,那么我们需要合并:
这是分水岭前后的图片,会发现少了很多。其实也不多就那几个。![]()
1.原图减去分水岭得到的二值图 再做形态学处理得到 图4![]()
2.合并分水岭二值图与图四至于效果我觉得一般 逻辑嘛也觉得一般 我就是菜鸡。
打个总结,牛马不如骡子。