如何在带支撑件的圆柱图像中准确检测并测量圆形半径?
问题描述
我正在开发一款测量圆形半径的应用,将圆柱放置在两个塑料支撑件上。对图像进行阈值化处理后,支撑件会被识别为圆柱的一部分。我尝试使用轮廓检测和最小外接圆来查找最大和第二大对象,但由于支撑件的干扰,外圆始终检测错误。我使用的是C#和EmguCV,形态学操作会损坏圆形边缘导致测量不准确,请问如何正确检测并测量圆形?Python和OpenCV的解决方案也可。
现有代码(C#/EmguCV)
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); CvInvoke.FindContours(binaryimg, contours, null, RetrType.Tree, ChainApproxMethod.ChainApproxNone); VectorOfPoint largestContour = null; VectorOfPoint secondLargestContour = null; double maxArea = 0; double secondMaxArea = 0; if (contours.Size < 2) { Console.WriteLine("Not enough contours found to detect both OD and ID."); } for (int i = 0; i < contours.Size; i++) { using (VectorOfPoint contour = contours[i]) { double area = CvInvoke.ContourArea(contour); if (area > maxArea) { secondMaxArea = maxArea; secondLargestContour = largestContour; maxArea = area; largestContour = contour; } else if (area > secondMaxArea) { secondMaxArea = area; secondLargestContour = contour; } } } if (largestContour != null && secondLargestContour != null) { CircleF outerCircle = CvInvoke.MinEnclosingCircle(largestContour); CircleF innerCircle = CvInvoke.MinEnclosingCircle(secondLargestContour); }
图像展示
原始图像

当前错误输出

解决方案
核心思路:放弃单纯的面积排序,通过**圆形度(Circularity)**筛选轮廓,结合精准拟合或霍夫圆检测,避开支撑件干扰。
方案一:C#/EmguCV 优化版
步骤说明
- 优化阈值化:用Otsu自动阈值减少支撑件误分割
- 筛选圆形轮廓:通过圆形度公式(
4 * π * 面积 / (周长²))筛选接近完美圆形的轮廓 - 拟合外接圆:对筛选后的轮廓拟合最小外接圆,按半径排序得到内外圆
代码示例
// 1. 替换原阈值步骤,用Otsu自动阈值优化分割 Mat grayImg = new Mat(); CvInvoke.CvtColor(originalImg, grayImg, ColorConversion.Bgr2Gray); Mat binaryImg = new Mat(); CvInvoke.Threshold(grayImg, binaryImg, 0, 255, ThresholdType.BinaryInv | ThresholdType.Otsu); // 2. 查找轮廓并筛选圆形特征 VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); CvInvoke.FindContours(binaryImg, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple); List<CircleF> validCircles = new List<CircleF>(); foreach (var contour in contours) { double area = CvInvoke.ContourArea(contour); if (area < 100) continue; // 过滤小噪声 double perimeter = CvInvoke.ArcLength(contour, true); if (perimeter == 0) continue; // 计算圆形度,值越接近1越接近完美圆形 double circularity = 4 * Math.PI * area / (perimeter * perimeter); if (circularity > 0.8) // 阈值可根据实际图像微调 { CircleF circle = CvInvoke.MinEnclosingCircle(contour); validCircles.Add(circle); } } // 3. 按半径排序得到内外圆 if (validCircles.Count >= 2) { validCircles.Sort((a, b) => b.Radius.CompareTo(a.Radius)); CircleF outerCircle = validCircles[0]; CircleF innerCircle = validCircles[1]; Console.WriteLine($"外圆半径:{outerCircle.Radius:F2},内圆半径:{innerCircle.Radius:F2}"); } else { Console.WriteLine("未检测到足够的有效圆形轮廓"); }
方案二:Python/OpenCV 实现
步骤说明
- 灰度化+Otsu阈值处理
- 轮廓检测+圆形度筛选
- 可选霍夫圆检测辅助验证
代码示例
import cv2 import numpy as np # 读取图像 img = cv2.imread("original_image.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 1. Otsu自动阈值化 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 2. 筛选圆形轮廓 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) valid_circles = [] for cnt in contours: area = cv2.contourArea(cnt) if area < 100: continue perimeter = cv2.arcLength(cnt, True) if perimeter == 0: continue # 计算圆形度 circularity = 4 * np.pi * area / (perimeter ** 2) if circularity > 0.8: (x, y), radius = cv2.minEnclosingCircle(cnt) valid_circles.append((x, y, radius)) # 3. 按半径排序输出结果 if len(valid_circles) >= 2: valid_circles.sort(key=lambda c: c[2], reverse=True) outer_x, outer_y, outer_r = valid_circles[0] inner_x, inner_y, inner_r = valid_circles[1] print(f"外圆半径:{outer_r:.2f},内圆半径:{inner_r:.2f}") # 可视化结果 cv2.circle(img, (int(outer_x), int(outer_y)), int(outer_r), (0, 255, 0), 2) cv2.circle(img, (int(inner_x), int(inner_y)), int(inner_r), (0, 0, 255), 2) cv2.imshow("Result", img) cv2.waitKey(0) else: print("未检测到足够的有效圆形轮廓")
可选优化:霍夫圆检测
若轮廓筛选效果不佳,可直接用霍夫圆检测(适合对比度较好的图像):
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.2, minDist=50, param1=50, param2=30, minRadius=50, maxRadius=200) if circles is not None: circles = np.uint16(np.around(circles)) for i in circles[0, :]: cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2) cv2.imshow("Hough Circles", img) cv2.waitKey(0)
内容的提问来源于stack exchange,提问作者Interceptor
相关产品推荐
相关产品推荐

