本文将详细介绍如何使用C#和OpenCvSharp来实现基于轮廓检测的对象计数。我们将通过多个实例来说明不同场景下的轮廓检测和计数方法。
首先需要安装必要的NuGet包:
BashInstall-Package OpenCvSharp4 Install-Package OpenCvSharp4.Windows
C#static void Main(string[] args)
{
CountClips("clip.png");
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
}
public static int CountClips(string imagePath)
{
using (var src = new Mat(imagePath))
using (var gray = new Mat())
using (var blur = new Mat())
using (var binary = new Mat())
{
// 转换为灰度图
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
// 高斯模糊
Cv2.GaussianBlur(gray, blur, new Size(5, 5), 0);
// 使用自适应阈值
Cv2.AdaptiveThreshold(
blur,
binary,
255,
AdaptiveThresholdTypes.GaussianC,
ThresholdTypes.Binary,
11, // 邻域大小
2 // 常数差值
);
// 查找轮廓,包括内部轮廓
Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(
binary,
out contours,
out hierarchy,
RetrievalModes.Tree, // 改用Tree模式
ContourApproximationModes.ApproxSimple);
// 过滤轮廓
int clipCount = 0;
for (int i = 0; i < contours.Length; i++)
{
// 计算轮廓周长
double perimeter = Cv2.ArcLength(contours[i], true);
Cv2.DrawContours(
src,
contours,
i,
Scalar.Red,
2);
clipCount++;
}
Cv2.ImShow("Result", src);
return clipCount;
}
}

PythonCv2.AdaptiveThreshold(
blur, // 输入图像(通常是灰度图或经过模糊处理的图像)
binary, // 输出图像(二值化结果)
255, // 最大值(像素被判定为白色时的值)
AdaptiveThresholdTypes.GaussianC, // 自适应方法类型
ThresholdTypes.Binary, // 阈值类型
11, // 邻域大小(块大小)
2 // 常数差值(C)
);
C#for (int i = 0; i < contours.Length; i++)
{
// 计算轮廓面积
double area = Cv2.ContourArea(contours[i]);
// 计算轮廓周长
double perimeter = Cv2.ArcLength(contours[i], true); //这个先不用
// 基于面积和周长比例筛选曲别针
if (area > 2000 && area < 7000) // 面积阈值需要根据实际图像调整
{
Cv2.DrawContours(
src,
contours,
i,
Scalar.Red,
2);
clipCount++;
}
}

C#static void Main(string[] args)
{
CountTriangles("a.png");
}
public static void CountTriangles(string imagePath)
{
using (var src = new Mat(imagePath))
using (var gray = new Mat())
using (var binary = new Mat())
{
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
Cv2.AdaptiveThreshold(gray, binary, 255,
AdaptiveThresholdTypes.GaussianC,
ThresholdTypes.BinaryInv,
11, 2);
Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(
binary,
out contours,
out hierarchy,
RetrievalModes.List,
ContourApproximationModes.ApproxSimple);
var triangles = new List<Triangle>();
Mat result = src.Clone();
foreach (var contour in contours)
{
double area = Cv2.ContourArea(contour);
if (area < 100) continue;
double perimeter = Cv2.ArcLength(contour, true);
Point[] approx = Cv2.ApproxPolyDP(contour, 0.03 * perimeter, true);
if (approx.Length == 3 && IsValidTriangle(approx))
{
var newTriangle = new Triangle(approx);
// 检查是否与现有三角形重叠
bool isOverlapping = false;
foreach (var existingTriangle in triangles)
{
if (IsOverlapping(newTriangle, existingTriangle))
{
isOverlapping = true;
break;
}
}
if (!isOverlapping)
{
triangles.Add(newTriangle);
}
}
}
// 绘制通过验证的三角形
for (int i = 0; i < triangles.Count; i++)
{
var triangle = triangles[i];
Cv2.DrawContours(result, new[] { triangle.Points }, -1, new Scalar(0, 0, 255), 2);
// 在三角形中心显示序号
Point center = triangle.Center;
Cv2.PutText(result, (i + 1).ToString(), center,
HersheyFonts.HersheySimplex, 0.8, new Scalar(0, 0, 255), 2);
}
Console.WriteLine($"检测到的三角形数量: {triangles.Count}");
Cv2.ImWrite("detected_triangles.jpg", result);
Cv2.ImShow("Detected Triangles", result);
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
}
}
private class Triangle
{
public Point[] Points { get; }
public Point Center { get; }
public double Area { get; }
public Triangle(Point[] points)
{
Points = points;
Center = new Point(
(points[0].X + points[1].X + points[2].X) / 3,
(points[0].Y + points[1].Y + points[2].Y) / 3
);
Area = Math.Abs((points[1].X - points[0].X) * (points[2].Y - points[0].Y) -
(points[2].X - points[0].X) * (points[1].Y - points[0].Y)) / 2;
}
}
private static bool IsOverlapping(Triangle t1, Triangle t2)
{
// 检查中心点距离
double centerDistance = Math.Sqrt(
Math.Pow(t1.Center.X - t2.Center.X, 2) +
Math.Pow(t1.Center.Y - t2.Center.Y, 2)
);
// 如果中心点距离小于某个阈值,认为是重叠的
double threshold = Math.Min(Math.Sqrt(t1.Area), Math.Sqrt(t2.Area)) * 0.5;
return centerDistance < threshold;
}
private static bool IsValidTriangle(Point[] points)
{
if (points.Length != 3) return false;
double side1 = Distance(points[0], points[1]);
double side2 = Distance(points[1], points[2]);
double side3 = Distance(points[2], points[0]);
if (side1 + side2 <= side3 || side2 + side3 <= side1 || side1 + side3 <= side2)
return false;
double minSide = Math.Min(Math.Min(side1, side2), side3);
double maxSide = Math.Max(Math.Max(side1, side2), side3);
double ratio = minSide / maxSide;
return ratio > 0.2;
}
private static double Distance(Point p1, Point p2)
{
return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
}

C#static void Main(string[] args)
{
CountCircles("southeast.jpg");
}
public static void CountCircles(string imagePath)
{
using (var src = new Mat(imagePath))
using (var gray = new Mat())
{
// 转换为灰度图
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
// 使用高斯模糊减少噪声
Cv2.GaussianBlur(gray, gray, new Size(9, 9), 2, 2);
// 使用Hough圆变换检测圆
CircleSegment[] circles = Cv2.HoughCircles(
gray,
HoughModes.Gradient,
dp: 1, // 累加器分辨率
minDist: 150, // 增加圆心之间的最小距离,避免重复检测
param1: 50, // 调整Canny边缘检测的阈值
param2: 25, // 降低累加器阈值,使检测更敏感
minRadius: 50, // 调整最小半径
maxRadius: 100 // 调整最大半径
);
Mat result = src.Clone();
var validCircles = new List<CircleSegment>();
// 过滤重叠的圆
foreach (var circle in circles)
{
bool isOverlapping = false;
foreach (var existingCircle in validCircles)
{
if (IsCirclesOverlapping(circle, existingCircle))
{
isOverlapping = true;
break;
}
}
if (!isOverlapping)
{
validCircles.Add(circle);
}
}
// 绘制检测到的圆
for (int i = 0; i < validCircles.Count; i++)
{
var circle = validCircles[i];
// 绘制圆形轮廓
Cv2.Circle(
result,
(int)circle.Center.X,
(int)circle.Center.Y,
(int)circle.Radius,
new Scalar(0, 0, 255), // 红色
2
);
// 绘制圆心
Cv2.Circle(
result,
(int)circle.Center.X,
(int)circle.Center.Y,
3,
new Scalar(0, 255, 0), // 绿色
-1
);
// 添加序号
Cv2.PutText(
result,
(i + 1).ToString(),
new Point(circle.Center.X - 10, circle.Center.Y - 10),
HersheyFonts.HersheySimplex,
0.8,
new Scalar(0, 0, 255),
2
);
}
Console.WriteLine($"检测到的圆形数量: {validCircles.Count}");
Cv2.ImWrite("detected_circles.jpg", result);
Cv2.ImShow("Detected Circles", result);
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
}
}
private static bool IsCirclesOverlapping(CircleSegment c1, CircleSegment c2)
{
// 计算两个圆心之间的距离
double centerDistance = Math.Sqrt(
Math.Pow(c1.Center.X - c2.Center.X, 2) +
Math.Pow(c1.Center.Y - c2.Center.Y, 2)
);
// 如果圆心距离小于两个圆半径之和的一定比例,认为是重叠的
double threshold = (c1.Radius + c2.Radius) * 0.5;
return centerDistance < threshold;
}

本文详细介绍了使用C#和OpenCvSharp实现基于轮廓检测的对象计数方法,从基础的轮廓检测到复杂的形状和颜色分析都有详细的示例代码。这些方法可以根据实际需求进行组合和优化,实现更复杂的图像分析任务。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!